Introduction
It's a common to map a similar object to another object. It's also tedious and repeating since generally both objects (classes) may have similar/same properties mapped to each other. Think on a typical application servicemethod below:
将相似的对象映射到另一个对象是很常见的。这也是乏味和重复的,因为一般两个对象(类)可能具有相似的/相同的属性映射到彼此。想象在一个典型的应用服务的方法:
public class UserAppService : ApplicationService
{
private readonly IRepository<User> _userRepository; public UserAppService(IRepository<User> userRepository)
{
_userRepository = userRepository;
} public void CreateUser(CreateUserInput input)
{
var user = new User
{
Name = input.Name,
Surname = input.Surname,
EmailAddress = input.EmailAddress,
Password = input.Password
}; _userRepository.Insert(user);
}
}
CreateUserInput is a simple DTO and User is a simple entity here. We manually created a User entity from given input. User entity will have much more properties in a real world application and manually creating it will become tedious and error-prone. Also, we should change the mapping code when we want to add new properties to User and CreateUserInput.
createuserinput是一个简单的DTO和用户这是一个简单的实体。我们从给定输入手动创建了一个用户实体。用户实体在实际应用程序中具有更多的属性,手工创建它将变得单调乏味,容易出错。同时,我们应该改变映射的代码时,我们要对用户和createuserinput添加新的属性。
We can use a library to make this mapping automatically. AutoMapper is one of the best libraries for object to object mapping. ASP.NET Boilerplate defines IObjectMapper interface to abstract it and implements this interface using AutoMapper in Abp.AutoMapper package.
我们可以使用一个库来自动绘制这个映射。AutoMapper是一个对象到对象的映射最好的库。ASP.NET的模板定义iobjectmapper接口抽象和实现这个接口使用AutoMapper Abp.AutoMapper包。
IObjectMapper Interface
IObjectMapper is a simple abstraction that has Map methods to map an object to another. We can write the sample code above as like below:
public class UserAppService : ApplicationService
{
private readonly IRepository<User> _userRepository;
private readonly IObjectMapper _objectMapper; public UserAppService(IRepository<User> userRepository, IObjectMapper objectMapper)
{
_userRepository = userRepository;
_objectMapper = objectMapper;
} public void CreateUser(CreateUserInput input)
{
var user = _objectMapper.Map<User>(input);
_userRepository.Insert(user);
}
}
Map is a simple method gets the source object and creates a new destination object with the type declared as the generic parameter (User in this sample). Map method has an overload to map an object to an existing object. Assume that we already have a User entity and want to update it's properties by an object:
map是获取源对象并创建一个新的目标对象的简单方法,该对象声明为泛型参数(本示例中的用户)。映射方法具有将对象映射到现有对象的重载。假设我们已经有一个用户实体,并且想通过对象更新它的属性:
public void UpdateUser(UpdateUserInput input)
{
var user = _userRepository.Get(input.Id);
_objectMapper.Map(input, user);
}
AutoMapper Integration
Abp.AutoMapper nuget package (module) implements IObjectMapper and provides additional features.
Installation
First, install Abp.AutoMapper nuget to your project:
Install-Package Abp.AutoMapper
Then add a dependency for AbpAutoMapperModule to your module definition class:
[DependsOn(typeof(AbpAutoMapperModule))]
public class MyModule : AbpModule
{
...
}
Then you can safely inject and use IObjectMapper in your code. You can also use AutoMapper's own API when you need.
Creating Mappings
AutoMapper requires to define mappings between classes (by default) before using the mapping. You can see it's own documentation for details on mapping. ASP.NET Boilerplate makes it a bit easier and modular.
AutoMapper需要定义之间的映射类(默认)在使用映射。关于映射的详细信息,您可以看到它自己的文档。ASP.NET样板使它更简单和模块化。
Auto Mapping Attributes(自动映射特性)
Most of time you only want to directly (and conventionally) map classes. In that case, you can use AutoMap, AutoMapFrom and AutoMapTo attributes. For instance, as we want to map CreateUserInput class to User class in the sample above, we can use AutoMapTo attribute as shown below:
[AutoMapTo(typeof(User))]
public class CreateUserInput
{
public string Name { get; set; } public string Surname { get; set; } public string EmailAddress { get; set; } public string Password { get; set; }
}
AutoMap attribute maps two classes in both direction. But in this sample, we only need to map from CreateUserInput to User, so we used AutoMapTo.
Custom Mapping(自定义映射)
Simple mapping may not be suitable in some cases. For instance, property names of two classes may be a little different or you may want to ignore some properties during the mappping. In such cases you should directly use AutoMapper's API to define the mapping. Abp.AutoMapper package defines API to make this custom mapping stuff more modular.
在某些情况下,简单映射可能不合适。例如,两类属性名称可能有所不同,或者你可能想忽略的一些性质在映射。在这种情况下,你应该直接使用AutoMapper的API来定义映射。abp.automapper包定义API进行自定义映射的东西更加模块化。
Assume that we want to ignore Password on mapping and User has Email property for email address. We can define mapping as shown below:
假设我们希望忽略映射中的密码,用户拥有电子邮件地址的电子邮件属性。我们可以定义映射如下图所示:
[DependsOn(typeof(AbpAutoMapperModule))]
public class MyModule : AbpModule
{
public override void PreInitialize()
{
Configuration.Modules.AbpAutoMapper().Configurators.Add(config =>
{
config.CreateMap<CreateUserInput, User>()
.ForMember(u => u.Password, options => options.Ignore())
.ForMember(u => u.Email, options => options.MapFrom(input => input.EmailAddress));
});
}
}
AutoMapper has much more options and abilities for object to object mapping. See it's documentation for more.
MapTo Extension Methods(MapTo 扩展方法)
It's suggested to inject and use IObjectMapper interface as defined before. This makes our project independent from AutoMapper as much as possible. It also makes unit testing easier since we can replace (mock) the mapping in unit tests.
Abp.AutoMapper module also defines MapTo extension methods which can be used on any object to map it to another object without injecting IObjectMapper. Example usage:
public class UserAppService : ApplicationService
{
private readonly IRepository<User> _userRepository; public UserAppService(IRepository<User> userRepository)
{
_userRepository = userRepository;
} public void CreateUser(CreateUserInput input)
{
var user = input.MapTo<User>();
_userRepository.Insert(user);
} public void UpdateUser(UpdateUserInput input)
{
var user = _userRepository.Get(input.Id);
input.MapTo(user);
}
}
MapTo extension methods are defined in Abp.AutoMapper namespace, so you first import this namespaces into your code file.
Since MapTo extension methods are statics, they use AutoMapper's static instance (Mapper.Instance). This is simple and fine for the application code, but can have problems in unit tests since static configuration and mapper is shared among different tests and they may effect each other.
由于扩展方法是静态的,他们使用AutoMapper的静态实例(制图。实例)。这对于应用程序代码来说是简单而精细的,但是单元测试可能会有问题,因为静态配置和映射器在不同的测试*享,它们可能相互影响。
Unit Tests
We want to isolate tests from each others. To do that, we should design our project with the following rules:
我们希望从彼此之间分离测试。要做到这一点,我们应该按照以下规则来设计我们的项目:
1. Always use IObjectMapper, not use MapTo extension methods.
2. Configure Abp.AutoMapper module to use local Mapper instance (registered as singleton to dependency injection) rather than the static one (Abp.AutoMapper uses the static Mapper.Instance by default to allow to use MapTo extension methods defined above):
Configuration.Modules.AbpAutoMapper().UseStaticMapper = false;
Pre Defined Mappings(预先定义的映射)
LocalizableString -> string
Abp.AutoMapper module defines a mapping to convert LocalizableString (or ILocalizableString) objects to string objects. It makes the conversion using ILocalizationManager, so localizable properties are automatically localized during the mapping process of any class.
abp.automapper模块定义转换localizablestring映射(或ilocalizablestring)对象的字符串对象。它使转换使用ilocalizationmanager,所以可本地化的属性自动定位的任何类别的映射过程。
Injecting IMapper
You may need to directly use AutoMapper's IMapper object instead of IObjectMapper abstraction. In that case, just inject IMapper in your classes and use it. Abp.AutoMapper package registers IMapper to dependency injectionas singleton.