我有一个与之通信的设备。它以各种整数表示(byte
,ushort
,uint
)返回一些位标志。
目前,为了在UI上显示它们,它们被映射到ViewModels:
// The ViewModel, annotated
[AutoMap(typeof(byte), TypeConverter = typeof(FlagConverter))]
public record FlagVM(bool One, bool Two)
{
// And its converter
public class FlagConverter : ITypeConverter<byte, FlagVM> {
public FlagVM Convert(byte src, FlagVM dst, ResolutionContext c)
=> new((src & 1) > 0, (src & 2) > 0);
}
使用AutoMapAttribute
,因为有50多个其他结构,AutoMapper
可以很容易地配置为整个汇编:
var mapper = new MapperConfiguration(cfg =>
cfg.AddMaps(this.GetType().Assembly)
).CreateMapper();
mapper.Map<FlagVM>((byte)2)
.Should().Be(new FlagVM(false, true)); //easy!
现在,问题来了:我还需要创建反向映射,返回到数字表示。很容易添加到转换器中:
public class FlagConverter
: ITypeConverter<byte, FlagVM>, ITypeConverter<FlagVM, byte> {
public FlagVM Convert(byte src, FlagVM dst, ResolutionContext c)
=> new(One:(src & 1) > 0, Two:(src & 2) > 0);
public byte Convert(FlagVM src, byte dst, ResolutionContext c)
=> (byte)((src.One ? 1 : 0) | (src.Two ? 2 : 0));
}
这一切都很好,除了现在我不能再使用AutoMapAttribute
了,因为简单地添加ReverseMap
不起作用:
// The TypeConverter is not applied to the reverse map
[AutoMap(typeof(byte), TypeConverter = typeof(FlagConverter), ReverseMap = true)]
我能得到双向映射的唯一方法是配置每一个(手动或反射)
var mapper = new MapperConfiguration(cfg =>
cfg.CreateMap<byte, FlagDto>().ConvertUsing<FlagConverter>();
cfg.CreateMap<FlagDto, byte>().ConvertUsing<FlagConverter>(); //reverse
// .. repeat 50+ times
// .. or use reflection to find all ITypeConverter<> implementations.
).CreateMapper();
// Forward map
mapper.Map<FlagVM>((byte)2).Should().Be(new FlagVM(false, true));
// Reverse map
mapper.Map<byte>(new FlagVM(false, true)).Should().Be(2);
是的,在一天结束的时候,AutoMapper会做反射来找到属性;但是整个程序是使用基于属性的映射来配置的,我更喜欢这些结构与其他代码库保持一致。
真的没有办法结合AutoMapAttribute
,ReverseMap
和TypeConverter
来创建双向地图吗?
注:.NET6, AutoMapper 11.0
正如在评论中所指出的,这不是可以用属性API做的事情。
我们最终通过简单地扫描整个程序集中的所有ITypeConverter<,>
实现并逐一注册它们来自动化流畅注册。
private static void CreateTypeConverterMaps(IMapperConfigurationExpression mapperCfg) {
var converters = typeof(AType).Assembly
.GetTypes()
.SelectMany(typ => typ
.GetInterfaces()
.Where(itf => itf.IsGenericType)
.Where(itf => itf.GetGenericTypeDefinition() == typeof(ITypeConverter<,>))
.Select(itf => new { Interface = itf, Class = typ }));
foreach (var conv in converters) {
// We know there's two TypeArgs, as we filter by <,> above
var srcType = conv.Interface.GenericTypeArguments[0];
var dstType = conv.Interface.GenericTypeArguments[1];
mapperCfg // Register the map+converter
.CreateMap(srcType, dstType)
.ConvertUsing(conv.Class);
}
}