[TOC]
During the process of writing this blog, I found that my understanding of many foundational theories was too weak, which may lead to incorrect or inaccurate explanations of many professional terms. I recommend readers to refer to official documentation or other books for better understanding.
This article mainly explains how to configure and use object mapping in ABP, with a significant portion related to the AutoMapper framework. I suggest readers learn this framework in advance; you can refer to another blog post by me: Introduction to AutoMapper.
Basics
DTO and Entity
Entity
An entity is a concept from Domain Driven Design (DDD). An entity typically maps to the intrinsic properties of certain objects, with the most common usage being tables in relational databases.
In ABP, entities are located in the domain layer, and the entity class needs to implement the IEntity<TKey>
interface or inherit from the Entity<TKey>
base class, as shown below:
public class Book : Entity<Guid>
{
public string Name { get; set; }
public float Price { get; set; }
}
DTO
A Data Transfer Object (DTO) serves as a data model for a data transfer process, used to transmit data between the application layer and the presentation layer.
In ABP, DTOs are located in the application service layer, specifically in the AbpBase.Application
project of the example source code in this series.
Typically, when the presentation layer or other types of clients call application services, they pass DTOs as parameters. The application services use domain objects (entities) to execute certain specific business logic and return a DTO (which is not the same as the incoming DTO) to the presentation layer, thus completely isolating the presentation layer from the domain layer.
The DTO class may have a high similarity to the fields/properties of the entity class, and creating DTO classes for each method of each service can be tedious and time-consuming.
An example of an ABP DTO class is as follows:
public class ProductDto : EntityDto<Guid>
{
public string Name { get; set; }
//...
}
Troublesome Mapping
As mentioned earlier, the domain layer and the application service layer need to be isolated. For example, the following pseudocode:
class HomeController
{
AddService _service;
[HttpPost]
public int AddEquip(EquipDto dto)
{
return _service.Add(dto).Id;
}
}
class AddService
{
DataContext _context;
EquipDto Add(EquipDto dto)
{
Equip equip = new Equip()
{
Name = dto.Name;
};
_context.Equip.Add(equip);
_context.SaveChange();
dto.Id = equip.Id;
return dto;
}
}
class EquipDto
{
int Id;
string Name;
}
----------
class Equip
{
int Id;
string Name;
}
In this case, we need to manually map the fields of the DTO class and the entity class each time. When an entity has dozens of fields, the written code can become very lengthy and it is easy to overlook certain fields, ultimately leading to bugs.
Everyone knows that AutoMapper can solve this problem.
AutoMapper Integration
The Volo.Abp.AutoMapper
module in ABP encapsulates or integrates AutoMapper, so we can use this module to define object mappings for ABP applications.
Regarding the use of AutoMapper and how to configure Profiles, I have written separately in Introduction to AutoMapper, please click the link to learn about the use of AutoMapper.
We can create a new file named AbpBaseApplicationAutoMapperProfile.cs
in the AbpBase.Application
project, which will implement Profiles and define mappings. It is advisable to centralize domain service mappings into this file, or create a Profiles
folder to store some Profile classes within it.
Its content is as follows:
public class AbpBaseApplicationAutoMapperProfile:Profile
{
public AbpBaseApplicationAutoMapperProfile()
{
// base.CreateMap<MyEntity, MyDto>();
}
}
Once defined, you need to configure AutoMapper dependency injection. You can add the following code in the ConfigureServices
method of the AbpBaseApplicationModule
:
Configure<AbpAutoMapperOptions>(options =>
{
// Register mappings on a per-module basis
options.AddMaps<AbpBaseApplicationModule>();
//// Register mapping on a single Profile basis
// options.AddProfile<AbpBaseApplicationAutoMapperProfile>();
});
During debugging, we are concerned that when changing code in the project, new fields may be inadvertently omitted from the mapping configuration, or for other reasons. In AutoMapper, we can check mappings using configuration.AssertConfigurationIsValid();
. In ABP, we can enable checks using the validate: true
parameter.
Configure<AbpAutoMapperOptions>(options =>
{
// Register mappings on a per-module basis
options.AddMaps<AbpBaseApplicationModule>(validate: true);
//// Register mapping on a single Profile basis
// options.AddProfile<AbpBaseApplicationAutoMapperProfile>(validate: true);
});
IObjectMapper/ObjectMapper
In the AbpBase.Application
project, add a NuGet package by searching for Volo.Abp.ObjectMapping
and downloading the corresponding stable version.
There are two IObjectMapper
interfaces: one for AutoMapper and the generic interface from Volo.Abp.ObjectMapping
.
The AutoMapper's IObjectMapper
is not user-friendly, so it is advisable not to use it; prefer the IObjectMapper<TModule>
from Volo.Abp.ObjectMapping
.
The ObjectMapper
is part of AutoMapper, and we can directly inject ObjectMapper
in controllers and other places, and then use the ObjectMapper instance to map objects.
The ObjectMapper
only has the .Map()
method that is convenient to use.
private readonly ObjectMapper<T1,T2> _mapper;
public TestController(ObjectMapper<T1,T2> mapper)
{
_mapper = mapper;
// ... usage example
_ = mapper.Map<T1>();
}
You can also use the IObjectMapper
interface through dependency injection.
However, since ObjectMapper
is a generic class, injecting it for each type of DTO can be cumbersome, so this approach can be abandoned.
On the other hand, the generic IObjectMapper<TModule>
is an abstraction. Using IObjectMapper<TModule>
for dependency injection allows you to replace it with another object mapping framework in the future without modifying the existing code. Additionally, IObjectMapper<TModule>
is more user-friendly.
Usage example:
private readonly IObjectMapper<AbpBaseApplicationModule> _mapper;
public TestController(IObjectMapper<AbpBaseApplicationModule> mapper)
{
_mapper = mapper;
// ... usage example
_ = mapper.Map<...>();
}
Object Extensions
The ABP framework provides an Entity Extension System that allows you to add extra properties to existing objects without modifying related classes. This sentence is quoted from the official ABP documentation.
To support object extension mapping, configuration needs to be enabled:
public class MyProfile : Profile
{
public MyProfile()
{
CreateMap<User, UserDto>()
.MapExtraProperties();
}
}
Due to time constraints, I will only clarify the content of the official documentation here. Readers need to continue to refer to the official documentation to fully understand object extensions.
ObjectExtensionManager
is an object mapping class that allows you to explicitly extend some additional properties for classes; this type is defined in Volo.Abp.ObjectMapping
.
ObjectExtensionManager
is a type, but we cannot instantiate it directly or use dependency injection. We can only retrieve the new type through the ObjectExtensionManager.Instance
property. We need not concern ourselves with the design patterns used or any caching reasons behind this design.
ObjectExtensionManager
has two properties with the following descriptions:
AddOrUpdate
: The main method for defining or updating extra properties of an object.AddOrUpdateProperty
: A convenient method for defining a single additional property.
AddOrUpdateProperty
is used to define a single property, while AddOrUpdate
is a container that can include multiple AddOrUpdateProperty
.
Example code for AddOrUpdateProperty
is as follows:
ObjectExtensionManager.Instance
.AddOrUpdateProperty<TestA, string>("Name");
// Added a G property to the TestA class
Example code for AddOrUpdate
is as follows:
ObjectExtensionManager.Instance
.AddOrUpdate<TestA>(options =>
{
options.AddOrUpdateProperty<string>("Name");
options.AddOrUpdateProperty<bool>("Nice");
}
);
Of course, we can also define an additional property for multiple types simultaneously:
ObjectExtensionManager.Instance
.AddOrUpdateProperty<string>(
new[]
{
typeof(TestA),
typeof(TestB),
typeof(TestC)
},
"Name"
);
If you need to define multiple properties, you can use AddOrUpdate
:
ObjectExtensionManager.Instance
.AddOrUpdate(options =>
{
options.AddOrUpdateProperty<string>("Name");
}, new[]{
typeof(TestA),
typeof(TestB)
});
Additionally, it can set default values, increase validation rules, etc. These will not be elaborated here; interested readers can click the link to view the official documentation.
https://docs.abp.io/zh-Hans/abp/latest/Object-Extensions#validation
文章评论