ABP框架学习之——AutoMapper。
网上关于abp框架的学习的文章很多。本文大部分也是摘自其他作者的文章。
官方api文档:https://aspnetboilerplate.com/api-docs/html/N_Abp_AutoMapper.htm
简单说一下
- 什么是AutoMapper?
AutoMapper是一个对象和对象间的映射器。对象与对象的映射是通过转变一种类型的输入对象为一种不同类型的输出对象工作的。让AutoMapper有意思的地方在于它提供了一些将类型A映射到类型B这种无聊的事情的有趣惯例。只要类型B遵守AutoMapper已经建立的惯例,大多数情况下,映射两种类型零配置就可以了。 - 为什么使用AutoMapper?
映射代码是无聊的。测试映射代码更无聊。AutoMapper提供了一些简单配置,还有一些简单的映射测试。真正的问题可能是“为什么使用对象-对象的映射呢”?映射可能发生在一个应用的许多地方,但大多数情况下都发生在层与层之间的边界,比如UI/Domain层之间,或者Service/Domain层之间。关注一层通常和关注另一层发生冲突,因此对象-对象间的映射来隔离模型model,这样就只会影响每一层关注的类型。 - 如何使用AutoMapper?
定义
Mapper.CreateMap<<CreateNoteDto, Note>>();
使用
Note note = Mapper.Map<Note>(CreateNoteDto);
映射前后操作
偶尔有时候,在映射发生之前或之后,你可能需要执行一些自定义的逻辑。这可能是很少见的事情,因为在AutoMapper之外处理这些事情是更明显的。你可以创建一个映射前后的全局操作:
Mapper.CreateMap<Source, Dest>()
.BeforeMap((src, dest) => src.Value = src.Value + 10)
.AfterMap((src, dest) => dest.Name = "John");
条件映射
在属性映射之前,AutoMapper允许将必须满足的条件添加到属性上。
//创建映射,映射条件是源类型的Age属性在区间(0,149)范围内
Mapper.CreateMap<Aliens, Person>().ForMember(dest => dest.Age, opt => opt.Condition(src => src.Age > 0 && src.Age < 149));
配置
初始化是配置AutoMapper受人欢迎的模式,每个应用域应该配置一次:
//初始化配置文件
Mapper.Initialize(cfg =>
{
cfg.CreateMap<Aliens, Person>();
cfg.AddProfile<AliensPersonProfile>();//添加一个配置文件
});
自定义一个继承了Profile类的类,然后重写Configure方法,在该方法中放一些映射的配置。这个在我们建立的.net core项目中会看见
public class NoteMapProfile : Profile
{
public NoteMapProfile()
{
CreateMap<CreateNoteDto, Note>();
}
}
自定义类型转换
有时,需要完全控制一个类型到另一个类型的转换。一个类型一点都不像另一个类型,而且转换函数已经存在了,在这种情况下,你想要从一个“宽松”的类型转换成一个更强壮的类型,例如一个string的源类型到一个int32的目标类型。
这里有两个类Source和Destination,要把前者映射到后者,代码如下:
public class Source
{
public string Value1 { get; set; }
public string Value2 { get; set; }
public string Value3 { get; set; }
}
public class Destination
{
public int Value1 { get; set; }
public DateTime Value2 { get; set; }
public Type Value3 { get; set; }
}
public class CustomTypeConverter : ITypeConverter<Source, Destination>
{
public Destination Convert(ResolutionContext context)
{
Source src = context.SourceValue as Source;
var dest = new Destination
{
Value1 = System.Convert.ToInt32(src.Value1),
Value2 = System.Convert.ToDateTime(src.Value2),
Value3 = context.SourceType
};
return dest;
}
}
Mapper.CreateMap<Source, Destination>().ConvertUsing<CustomTypeConverter>();
自定义值解析
虽然AutoMapper覆盖了相当一部分目标成员的映射场景,但是还有 1-5%的目标值需要解析处理一下。很多时候,自定义的值解析是可以放在领域层的领域逻辑。然而,如果该逻辑只是和映射操作有关的话,那它就会应为一些不必要的行为使得源类型很凌乱。这种场合,AutoMapper允许我们为目标成员配置自定义的值解析器。
我们可以实现抽象类ValueResolver<TSource, TDestination>来实现自定义值解析。例如:
/// <summary>
/// 文章信息
/// </summary>
public class Note
{
/// <summary>
/// 是否发布
/// </summary>
public bool IsPublic { get; set; }
}
/// <summary>
/// 用于列表展示
/// </summary>
public class NoteDto
{
/// <summary>
/// 是否发布
/// </summary>
public string IsPublic { get; set; }
}
public class NoteToNoteDtoResolver : IValueResolver<Note, NoteDto, string>
{
public string Resolve(Note source, NoteDto destination, string destMember, ResolutionContext context)
{
return source.IsPublic ? "已发布" : "未发布";
}
}
CreateMap<Note, NoteDto>().ForMember(x=>x.IsPublic,opt=> {
opt.ResolveUsing<NoteToNoteDtoResolver>();
});
测试结果
因为我们只提供了自定义的解析器类型给AutoMapper,所以映射引擎会使用反射创建该值解析器的实例。如果我们不想要AutoMapper使用反射创建实例,我们要么直接提供一个实例,要么使用ConstruceBy方法来提供一个自定义的构造函数方法。在映射操作期间,AutoMapper不使用反射,直接执行此回调函数:
Mapper.CreateMap<Source, Destination>().ForMember(dest => dest.Total, opt =>
{
opt.ResolveUsing<MyValueResolver>().ConstructedBy(()=>new MyValueResolver());
});