Automapper:将参数传递给 Map 方法
Automapper: passing parameter to Map method
我在一个项目中使用 Automapper,我需要动态确定目标对象的一个字段。
在我的配置中有类似的东西:
cfg.CreateMap<Message, MessageDto>()
// ...
.ForMember(dest => dest.Timestamp, opt => opt.MapFrom(src => src.SentTime.AddMinutes(someValue)))
//...
;
配置代码中的someValue
是我需要在运行时传递给mapper的参数,不是源对象的字段
有办法实现吗?像这样:
Mapper.Map<MessageDto>(msg, someValue));
您无法完全按照自己的意愿行事,但您可以通过在调用 Map 时指定映射选项来获得非常接近的结果。忽略配置中的 属性:
cfg.CreateMap<Message, MessageDto>()
.ForMember(dest => dest.Timestamp, opt => opt.Ignore());
然后在调用地图时传入选项:
int someValue = 5;
var dto = Mapper.Map<Message, MessageDto>(message, opt =>
opt.AfterMap((src, dest) => dest.TimeStamp = src.SendTime.AddMinutes(someValue)));
请注意,您需要使用 Mapper.Map<TSrc, TDest>
重载才能使用此语法。
使用 Map 方法时的另一个可能选项是使用 Items 字典。示例:
int someValue = 5;
var dto = Mapper.Map<Message>(message,
opts => opts.Items["Timestamp"] = message.SentTime.AddMinutes(someValue));
它的代码少了一点,并且具有动态指定字段的优点。
您完全可以使用自定义 ITypeConverter<TSource, TDestination>
实现完全按照您的意愿行事。
- 调用
Map
时,您可以使用第二个回调参数使用自定义参数配置转换上下文。
- 在您的客户类型转换器的
Convert
方法中,您可以从作为第三个参数传递的上下文中恢复您的参数。
完整的解决方案:
namespace BegToDiffer
{
using AutoMapper;
using System;
/// <summary>
/// "Destiantion" type.
/// </summary>
public class MessageDto
{
public DateTime SentTime { get; set; }
}
/// <summary>
/// "Source" type.
/// </summary>
public class Message
{
public DateTime Timestamp { get; set; }
}
/// <summary>
/// Extension methods to make things very explicit.
/// </summary>
static class MessageConversionExtensions
{
// Key used to acccess time offset parameter within context.
static readonly string TimeOffsetContextKey = "TimeOffset";
/// <summary>
/// Recovers the custom time offset parameter from the conversion context.
/// </summary>
/// <param name="context">conversion context</param>
/// <returns>Time offset</returns>
public static TimeSpan GetTimeOffset(this ResolutionContext context)
{
if (context.Items.TryGetValue(TimeOffsetContextKey, out var timeOffset))
{
return (TimeSpan)timeOffset;
}
throw new InvalidOperationException("Time offset not set.");
}
/// <summary>
/// Configures the conversion context with a time offset parameter.
/// </summary>
/// <param name="options"></param>
/// <param name="timeOffset"></param>
public static IMappingOperationOptions SetTimeOffset(this IMappingOperationOptions options, TimeSpan timeOffset)
{
options.Items[TimeOffsetContextKey] = timeOffset;
// return options to support fluent chaining.
return options;
}
}
/// <summary>
/// Custom type converter.
/// </summary>
class MessageConverter : ITypeConverter<Message, MessageDto>
{
public MessageDto Convert(Message source, MessageDto destination, ResolutionContext context)
{
if (destination == null)
{
destination = new MessageDto();
}
destination.SentTime = source.Timestamp.Add(context.GetTimeOffset());
return destination;
}
}
public class Program
{
public static void Main()
{
// Create a mapper configured with our custom type converter.
var mapper = new MapperConfiguration(cfg =>
cfg.CreateMap<Message, MessageDto>().ConvertUsing(new MessageConverter()))
.CreateMapper();
// Setup example usage to reflect original question.
int someValue = 5;
var msg = new Message { Timestamp = DateTime.Now };
// Map using custom time offset parameter.
var dto = mapper.Map<MessageDto>(msg, options => options.SetTimeOffset(TimeSpan.FromMinutes(someValue)));
// The proof is in the pudding:
Console.WriteLine("msg.Timestamp = {0}, dto.SentTime = {1}", msg.Timestamp, dto.SentTime);
}
}
}
我有通用扩展方法版本:
public static class AutoMapperExtensions
{
public static TDestination Map<TSource, TDestination>(this IMapper mapper, TSource value,
params (string, object)[] additionalMap)
{
return mapper.Map<TSource, TDestination>(value,
opt => opt.AfterMap(
(src, dest) => additionalMap.ForEach(am =>
{
var (propertyName, value) = am;
var property = typeof(TDestination).GetProperty(propertyName);
property.SetValue(dest, value, null);
})));
}
}
在使用之前您必须忽略其他属性:
CreateMap<User, AuthenticateResponse>().ForMember(ar => ar.Token, opt => opt.Ignore());
使用:
private readonly IMapper _mapper;
...
return _mapper.Map<User, AuthenticateResponse>(user, (nameof(AuthenticateResponse.Token), token));
您还需要 IEnumerable 扩展:
public static void ForEach<T>(this IEnumerable<T> source, Action<T> action)
{
foreach (var item in source)
{
action(item);
}
}
或者您可以将 additionalMap.ForEach 更改为 foreach (..){..}
我在一个项目中使用 Automapper,我需要动态确定目标对象的一个字段。
在我的配置中有类似的东西:
cfg.CreateMap<Message, MessageDto>()
// ...
.ForMember(dest => dest.Timestamp, opt => opt.MapFrom(src => src.SentTime.AddMinutes(someValue)))
//...
;
配置代码中的someValue
是我需要在运行时传递给mapper的参数,不是源对象的字段
有办法实现吗?像这样:
Mapper.Map<MessageDto>(msg, someValue));
您无法完全按照自己的意愿行事,但您可以通过在调用 Map 时指定映射选项来获得非常接近的结果。忽略配置中的 属性:
cfg.CreateMap<Message, MessageDto>()
.ForMember(dest => dest.Timestamp, opt => opt.Ignore());
然后在调用地图时传入选项:
int someValue = 5;
var dto = Mapper.Map<Message, MessageDto>(message, opt =>
opt.AfterMap((src, dest) => dest.TimeStamp = src.SendTime.AddMinutes(someValue)));
请注意,您需要使用 Mapper.Map<TSrc, TDest>
重载才能使用此语法。
使用 Map 方法时的另一个可能选项是使用 Items 字典。示例:
int someValue = 5;
var dto = Mapper.Map<Message>(message,
opts => opts.Items["Timestamp"] = message.SentTime.AddMinutes(someValue));
它的代码少了一点,并且具有动态指定字段的优点。
您完全可以使用自定义 ITypeConverter<TSource, TDestination>
实现完全按照您的意愿行事。
- 调用
Map
时,您可以使用第二个回调参数使用自定义参数配置转换上下文。 - 在您的客户类型转换器的
Convert
方法中,您可以从作为第三个参数传递的上下文中恢复您的参数。
完整的解决方案:
namespace BegToDiffer
{
using AutoMapper;
using System;
/// <summary>
/// "Destiantion" type.
/// </summary>
public class MessageDto
{
public DateTime SentTime { get; set; }
}
/// <summary>
/// "Source" type.
/// </summary>
public class Message
{
public DateTime Timestamp { get; set; }
}
/// <summary>
/// Extension methods to make things very explicit.
/// </summary>
static class MessageConversionExtensions
{
// Key used to acccess time offset parameter within context.
static readonly string TimeOffsetContextKey = "TimeOffset";
/// <summary>
/// Recovers the custom time offset parameter from the conversion context.
/// </summary>
/// <param name="context">conversion context</param>
/// <returns>Time offset</returns>
public static TimeSpan GetTimeOffset(this ResolutionContext context)
{
if (context.Items.TryGetValue(TimeOffsetContextKey, out var timeOffset))
{
return (TimeSpan)timeOffset;
}
throw new InvalidOperationException("Time offset not set.");
}
/// <summary>
/// Configures the conversion context with a time offset parameter.
/// </summary>
/// <param name="options"></param>
/// <param name="timeOffset"></param>
public static IMappingOperationOptions SetTimeOffset(this IMappingOperationOptions options, TimeSpan timeOffset)
{
options.Items[TimeOffsetContextKey] = timeOffset;
// return options to support fluent chaining.
return options;
}
}
/// <summary>
/// Custom type converter.
/// </summary>
class MessageConverter : ITypeConverter<Message, MessageDto>
{
public MessageDto Convert(Message source, MessageDto destination, ResolutionContext context)
{
if (destination == null)
{
destination = new MessageDto();
}
destination.SentTime = source.Timestamp.Add(context.GetTimeOffset());
return destination;
}
}
public class Program
{
public static void Main()
{
// Create a mapper configured with our custom type converter.
var mapper = new MapperConfiguration(cfg =>
cfg.CreateMap<Message, MessageDto>().ConvertUsing(new MessageConverter()))
.CreateMapper();
// Setup example usage to reflect original question.
int someValue = 5;
var msg = new Message { Timestamp = DateTime.Now };
// Map using custom time offset parameter.
var dto = mapper.Map<MessageDto>(msg, options => options.SetTimeOffset(TimeSpan.FromMinutes(someValue)));
// The proof is in the pudding:
Console.WriteLine("msg.Timestamp = {0}, dto.SentTime = {1}", msg.Timestamp, dto.SentTime);
}
}
}
我有通用扩展方法版本:
public static class AutoMapperExtensions
{
public static TDestination Map<TSource, TDestination>(this IMapper mapper, TSource value,
params (string, object)[] additionalMap)
{
return mapper.Map<TSource, TDestination>(value,
opt => opt.AfterMap(
(src, dest) => additionalMap.ForEach(am =>
{
var (propertyName, value) = am;
var property = typeof(TDestination).GetProperty(propertyName);
property.SetValue(dest, value, null);
})));
}
}
在使用之前您必须忽略其他属性:
CreateMap<User, AuthenticateResponse>().ForMember(ar => ar.Token, opt => opt.Ignore());
使用:
private readonly IMapper _mapper;
...
return _mapper.Map<User, AuthenticateResponse>(user, (nameof(AuthenticateResponse.Token), token));
您还需要 IEnumerable 扩展:
public static void ForEach<T>(this IEnumerable<T> source, Action<T> action)
{
foreach (var item in source)
{
action(item);
}
}
或者您可以将 additionalMap.ForEach 更改为 foreach (..){..}