一、前言
在實際專案開發程序中,我們使用到的各種 ORM 組件都可以很便捷的將我們獲取到的資料系結到對應的 List<T> 集合中,因為我們最終想要在頁面上展示的資料與資料庫物體類之間可能存在很大的差異,所以這里更常見的方法是去創建一些對應于頁面資料展示的 `視圖模型` 類,通過對獲取到的資料進行二次加工,從而滿足實際頁面顯示的需要,
因此,如何更便捷的去實作 資料庫持久化物件 與 視圖物件 間的物體映射,避免我們在代碼中去一次次的手工實作這一程序,就可以降低開發的作業量,而 AutoMapper 則是可以幫助我們便捷的實作物體轉換這一程序的利器,所以,本章我們就來學習如何在 ASP.NET Core 專案中通過使用 AutoMapper 去完成物體間的映射,
當然,如果你習慣于從視圖展現到持久化到資料庫都采用資料庫物體,那么本篇文章對你可能不會有任何的幫助,
代碼倉儲:https://github.com/Lanesra712/ingos-common/tree/master/sample/aspnetcore/aspnetcore-automapper-tutorial
二、Step by Step
AutoMapper 是一個 OOM(Object-Object-Mapping) 組件,從名字上就可以看出來,這一系列的組件主要是為了幫助我們實作物體間的相互轉換,從而避免我們每次都采用手工撰寫代碼的方式進行轉換,在沒有采用 OOM 組件之前,如果我們需要實作類似于一份資料在不同客戶端顯示不同的欄位,我們只能以手工的、逐個屬性賦值的方式實作資料在各個客戶端資料型別間的資料傳遞,而 OOM 組件則可以很方便的幫我們實作這一需求,
1、幾個概念
在上面我們有提到 資料庫持久化物件 和 視圖物件 這兩個概念,其實除了這兩個物件的概念之外,還存在一個 資料傳輸物件 的概念,這里我們來簡單闡述下這三種物件的概念,
資料庫持久化物件(Persistent Object):顧名思義,這個物件是用來將我們的資料持久化到資料庫,一般來說,持久化物件中的欄位會與資料庫中對應的 table 保持一致,
這里,如果你采用了 DDD 的思想去指導設計系統架構,其實最終落地到我們代碼中的其實是 領域物件(Domain Object),它與 資料庫持久化物件 最顯著的差異在于 領域物件 會包含當前業務領域的各種事件,而 資料庫持久化物件 僅是包含了資料庫中對應 table 的資料欄位資訊,
視圖物件(View Object):視圖物件 VO 是面向前端用戶頁面的,一般會包含呈現給用戶的某個頁面/組件中所包含的所有資料欄位資訊,
資料傳輸物件(Data Transfer Object):資料傳輸物件 DTO 一般用于前端展示層與后臺服務層之間的資料傳遞,以一種媒介的形式完成 資料庫持久化物件 與 視圖物件 之間的資料傳遞,
這里通過一個簡單的示意圖去解釋下這三種物件的具體使用場景,在這個示例的專案中,我省略了資料傳輸物件,將資料庫持久化物件直接轉換成頁面顯示的視圖物件,

2、組件加載
首先我們需要通過 Nuget 將 AutoMapper 加載到專案中,因為這個示例專案只包含一個 MVC 的專案,并沒有多余的分層,所以這里需要將兩個使用到的 dll 都添加到這個 MVC 專案中,
Install-Package AutoMapper
Install-Package AutoMapper.Extensions.Microsoft.DependencyInjection

這里我添加了 AutoMapper.Extensions.Microsoft.DependencyInjection 這個程式集,從這個程式集的名字就可以看出來,這個程式集主要是為了我們可以通過依賴注入的方式在專案中去使用 AutoMapper,
在 .NET Fx 的時代,我們使用 AutoMapper 時,可能就像下面的代碼一樣,更多的是通過 Mapper 的幾個靜態方法來實作物體間的映射,不過在 .NET Core 程式中,我們首選還是采用依賴注入的方式去完成物體間的映射,
// 構建物體映射規則 Mapper.Initialize(cfg => cfg.CreateMap<OrderModel, OrderDto>()); // 物體映射 var order = new OrderModel{}; OrderDto dto = Mapper.Map<OrderModel,OrderDto>(order);
3、使用案例
因為原本想要使用的示例專案是之前的 ingos-server 這個專案,由于目前自己有在學習 DDD 的知識,并且有在按照微軟的 eShopOnContainers 這個專案中基于 DDD 思想設計的框架,對自己的這個 ingos-server 專案進行 DDD 化的調整,嗯,其實就是照葫蘆畫瓢,所以目前整個專案被我改的亂七八糟的,不太適合作為示例專案了,所以這里新創建了一個比較單純的 ASP.NET Core MVC 專案來作為這篇文章的演示專案,
因為這個示例專案只是為了演示如何在 ASP.NET Core 專案中去使用 AutoMapper,所以這里并沒有進行分層,整個示例頁面的運行流程就是,PostController 中的 List Action 呼叫 PostAppService 類中的 GetPostLists 方法去獲取所有的文章資料,同時在這個方法中會進行物體映射,將我們從 PostDomain 中獲取到的 PO 物件轉換成頁面展示的 VO 物件,專案中每個檔案夾的作用見下圖所示,

這里的示例專案是演示當我們從資料庫獲取到需要的資料后,如何完成從 PO 到 VO 的物體映射,PostModel(PO)和 PostViewModel(VO)的類定義如下所示,
public class PostModel { public Guid Id { get; set; } public long SerialNo { get; set; } public string Title { get; set; } public string Author { get; set; } public string Image { get; set; } public short CategoryCode { get; set; } public bool IsDraft { get; set; } public string Content { get; set; } public DateTime ReleaseDate { get; set; } public virtual IList<CommentModel> Comments { get; set; } } public class PostViewModel { public Guid Id { get; set; } public long SerialNo { get; set; } public string Title { get; set; } public string Author { get; set; } public short CategoryCode { get; set; } public string Category => CategoryCode == 1001 ? ".NET" : "雜談"; public string ReleaseDate { get; set; } public short CommentCounts { get; set; } public virtual int Count { get; set; } }
首先我們需要創建一個物體映射的配置類,需要繼承于 AutoMapper 的 Profile 類,在無參建構式中,我們就可以通過 CreateMap 方法去創建兩個物體間的映射關系,
public class PostProfile : Profile { /// <summary> /// ctor /// </summary> public PostProfile() { // 配置 mapping 規則 // CreateMap<PostModel, PostViewModel>(); } }

通過泛型的 CreateMap 方法就可以完成我們從 PostModel(PO) 到 PostViewModel(VO) 的物體映射,當然,因為 AutoMapper 默認是通過匹配欄位名稱和型別進行自動匹配,所以如果你進行轉換的兩個類的中的某些欄位名稱不一樣,這里我們就需要進行手動的撰寫轉換規則,
就像在這個需要進行物體映射的示例代碼中,PostViewModel 中的 CommentCounts 欄位是根據 PostModel 中 CommentModel 集合的資料個數進行賦值的,所以這里我們就需要對這個欄位的轉換規則進行修改,
在 AutoMapper 中,我們可以通過 ForMember 方法對映射規則做進一步的加工,這里我們需要指明 PostViewModel 的 CommentCounts 欄位的值是通過對 PostModel 中的 Comments 資訊進行求和從而獲取到的,最終實作的轉換代碼如下所示,
public class PostProfile : Profile { /// <summary> /// ctor /// </summary> public PostProfile() { // 配置 mapping 規則 // CreateMap<PostModel, PostViewModel>() .ForMember(destination => destination.CommentCounts, source => source.MapFrom(i => i.Comments.Count())); } }
ForMember 方法不僅可以進行指定不同名稱的欄位進行轉換,也可以通過撰寫規則實作欄位型別的轉換,例如這里 PO 中的 ReleaseDate 欄位其實是 DateTime 型別的,我們需要通過撰寫規則將該欄位對應到 VO 中 string 型別的 ReleaseDate 欄位上,最終的實作代碼如下所示,
public class PostProfile : Profile { /// <summary> /// ctor /// </summary> public PostProfile() { // Config mapping rules // CreateMap<PostModel, PostViewModel>() .ForMember(destination => destination.CommentCounts, source => source.MapFrom(i => i.Comments.Count())) .ForMember(destination => destination.ReleaseDate, source => source.ConvertUsing(new DateTimeConverter())); } } public class DateTimeConverter : IValueConverter<DateTime, string> { public string Convert(DateTime source, ResolutionContext context) => source.ToString("yyyy-MM-dd HH:mm:ss"); }
這里很多人可能習慣將所有的物體映射規則都放到同一個 Profile 檔案里面,因為這里采用是單體架構的專案,所以整個專案中會存在不同的模塊,所以這里我是按照每個模塊去創建對應的 Profile 檔案,實際在 ingos-server 這個專案中的使用方式見下圖所示,

當我們創建好對應的映射規則后,因為我們是采用依賴注入的方式進行使用,所以這里我們就需要將我們的匹配規則注入到 IServiceCollection 中,從之前加載的程式集的 github readme 描述中可以看到,我們需要將配置好的 Profile 類通過 AddAutoMapper 這個擴展方法進行注入,
因為我們在實際專案中可能存在多個自定義的 Profile 檔案,而我們肯定是需要將這些自定義規則都注入到 IServiceCollection 中,所以我在 AddAutoMapper 這個方法的基礎上創建了一個 AddAutoMapperProfiles 方法去注入我們的物體映射規則,
通過 AutoMapper 的說明我們可以看出來,所有的自定義的 Profile 類都是需要繼承于 AutoMapper 的 Profile 基類,所以這里我是采用反射的方式,通過獲取到程式集中所有繼承于 Profile 類的類檔案進行批量的注入到 IServiceCollection 中,具體的實作代碼如下所示,
/// <summary> /// Automapper 映射規則配置擴展方法 /// </summary> public static class AutoMapperExtension { public static IServiceCollection AddAutoMapperProfiles(this IServiceCollection services) { // 從 appsettings.json 中獲取包含配置規則的程式集資訊 string assemblies = ConfigurationManager.GetConfig("Assembly:Mapper"); if (!string.IsNullOrEmpty(assemblies)) { var profiles = new List<Type>(); // 獲取繼承的 Profile 型別資訊 var parentType = typeof(Profile); foreach (var item in assemblies.Split(new char[] { '|' }, StringSplitOptions.RemoveEmptyEntries)) { // 獲取所有繼承于 Profile 的類 // var types = Assembly.Load(item).GetTypes() .Where(i => i.BaseType != null && i.BaseType.Name == parentType.Name); if (types.Count() != 0 || types.Any()) profiles.AddRange(types); } // 添加映射規則 if (profiles.Count() != 0 || profiles.Any()) services.AddAutoMapper(profiles.ToArray()); } return services; } }
因為我是將需要加載的程式集資訊放到組態檔中的,所以這里我們只需要將包含 Profile 規則的程式集添加到對應的配置項下面就可以了,此時如果包含多個程式集,則需要使用 `|` 進行分隔,
{
"Assembly": {
"Mapper": "aspnetcore-automapper-tutorial"
}
}
當我們將所有的物體映射規則注入到 IServiceCollection 中,就可以在代碼中使用這些物體映射規則,和其它通過依賴注入的介面使用方式相同,我們只需要在使用到的地方注入 IMapper 介面,然后通過 Map 方法就可以完成物體間的映射,使用的代碼如下,
public class PostAppService : IPostAppService
{
#region Initialize
/// <summary>
///
/// </summary>
private readonly IPostDomain _post;
/// <summary>
///
/// </summary>
private readonly IMapper _mapper;
/// <summary>
/// ctor
/// </summary>
/// <param name="post"></param>
/// <param name="mapper"></param>
public PostAppService(IPostDomain post, IMapper mapper)
{
_post = post ?? throw new ArgumentNullException(nameof(post));
_mapper = mapper ?? throw new ArgumentNullException(nameof(mapper));
}
#endregion Initialize
/// <summary>
/// 獲取所有的文章資訊
/// </summary>
/// <returns></returns>
public IList<PostViewModel> GetPostLists()
{
var datas = _post.GetPostLists();
return _mapper.Map<IList<PostModel>, IList<PostViewModel>>(datas);
}
}
至此我們就實作了在 ASP.NET Core 專案中使用 AutoMapper,實作后的結果如下圖所示,

三、總結
本篇文章主要是演示下如何在 ASP.NET Core 專案中去使用 AutoMapper 來實作物體間的映射,因為之前只是在 .NET Fx 專案中有使用過這個組件,并沒有在 .NET Core 專案中使用,所以這次趁著國慶節假期就來嘗試如何在 .NET Core 專案中使用,整個組件使用起來其實是很簡單的,但是使用后卻可以給我們在實際的專案開發中省很多的事,所以就把自己的使用方法分享出來,如果對你有些許的幫助的話,不勝榮幸~~~
轉載請註明出處,本文鏈接:https://www.uj5u.com/net/108767.html
標籤:.NET Core
