我想使用AutoMapper并在运行时提供一些值。
例如,我有DTO和ViewModel。
DTO中不存在该属性之一,并且不能使用Converters / Resolvers / Transformers直接对其进行映射;
namespace Lab.So.Sample
{
public class UserDto
{
public int UserId { get; set; }
public string UserCode { get; set; }
}
public class UserGroupDto
{
public List<UserDto> Users { get; set; }
}
public class UserViewModel
{
public int UserId { get; set; }
public string FullName { get; set; }
}
public class UserGroup
{
public List<UserViewModel> Users { get; set; }
}
class Program
{
static void Main(string[] args)
{
var src = new Fixture().Create<UserGroupDto>();
Mapper.Initialize(cfg =>
{
cfg.CreateMissingTypeMaps = true;
cfg.ValidateInlineMaps = false;
});
// How to hook mapping of nested object User->UserViewModel to provide value of FullName in runtime
var dst = Mapper.Map<UserGroupDto, UserGroup>(src);
}
}
}
我有一些解决方法,但就我而言,这并不友好:
class Program
{
internal class UserViewModelFullNameResolver : IValueResolver<UserDto, UserViewModel, string>
{
public string Resolve(UserDto source, UserViewModel destination, string destMember, ResolutionContext context)
{
var names = context.Items["ctx.Names"] as IDictionary<int, string>;
if (names == null || !names.TryGetValue(source.UserId, out var fullName))
{
return null;
}
return fullName;
}
}
static void Main(string[] args)
{
var src = new Fixture().Create<UserGroupDto>();
Mapper.Initialize(cfg =>
{
cfg.CreateMissingTypeMaps = true;
cfg.ValidateInlineMaps = false;
cfg.CreateMap<UserDto, UserViewModel>()
.ForMember(d => d.FullName, opt => opt.MapFrom<UserViewModelFullNameResolver>());
});
var names = new Dictionary<int, string>
{
{ 10, "FullName-10" },
{ 20, "FullName-20" },
};
var dst = Mapper.Map<UserGroupDto, UserGroup>(src, opt=>opt.Items["ctx.Names"] = names);
}
}
在这种解决方法中,最不方便的是opt.Items中的键名一致; 如果由于意外而导致拼写错误,则很难进行调查和修复;
我看起来像这样:
var dst = Mapper.Map<UserGroupDto, UserGroup>(src, opt=>opt.Use(new UserViewModelFullNameResolver());
换句话说,它在运行时为每种唯一情况定义了一个解析器实例;
如果我能够定义钩子来映射对象图中的特定类型,我也将接受:
var dst = Mapper.Map<UserGroupDto, UserGroup>(src, opt=>opt.Hook<UserDto,UserViewModel>((s,d)=> { /* any logic to read external data */ });
用法示例:
var srcA = readDataA();
var srcB = readDataB();
var dst = Mapper.Map<UserGroupDto, UserGroup>(
src,
opt=>opt.Hook<UserDto,UserViewModel>(
(s,d)=>
{
d.FullName = srcA + srcB;
});
请提出一些建议,如果源在目的地中没有嵌套对象的所有必需数据,则将有助于读取数据以完成映射。
答案 0 :(得分:0)
我能够使用现有的API归档目标:
public static class InlineMappingExtensions
{
public static void EnableInlineMapping(this IMapperConfigurationExpression cfg)
{
cfg.ForAllMaps((t, i) =>
{
i.AfterMap((s, d, ctx) =>
{
ctx.ApplyInlineMap(s, d);
}
);
});
}
public static IMappingOperationOptions OnMap<TSrc, TDst>(this IMappingOperationOptions opts,
Action<TSrc, TDst> resolve)
{
var srcTypeName = typeof(TSrc);
var dstTypeName = typeof(TDst);
var ctxKey = $"OnMap_{srcTypeName}_{dstTypeName}";
opts.Items.Add(ctxKey, resolve);
return opts;
}
private static void ApplyInlineMap(this ResolutionContext opts, object src, object dst)
{
if (src == null)
{
return;
}
if (dst == null)
{
return;
}
var srcTypeName = src.GetType();
var dstTypeName = dst.GetType();
var ctxKey = $"OnMap_{srcTypeName}_{dstTypeName}";
if (!opts.Items.TryGetValue(ctxKey, out var inlineMap))
{
return;
}
var act = inlineMap as Delegate;
act?.DynamicInvoke(src, dst);
}
}
要启用它:
Mapper.Initialize(cfg =>
{
cfg.CreateMissingTypeMaps = true;
cfg.ValidateInlineMaps = false;
cfg.EnableInlineMapping();
});
用法示例:
// read data from external sources
var names = new Dictionary<int, string>
{
{ 10, "FullName-10" },
{ 20, "FullName-20" },
};
Action<UserDto, UserViewModel> mapA = (s, d) =>
{
if (names.TryGetValue(s.UserId, out var name))
{
d.FullName = name;
}
};
Action<UserGroupDto, UserGroup> mapB = (s, d) =>
{
if (DateTime.Now.Ticks > 0)
{
d.Users = null;
}
};
var dst = Mapper.Map<UserGroupDto, UserGroup>(src, opt => opt.OnMap(mapA).OnMap(mapB));