在.NET Core 使用 AutoMapper 輕鬆轉換類別

前言

在撰寫專案時,經常會碰到資料類別對映的處理,例如ViewModel和實際Entity之間的對應,或是層與層之間的轉換。常見的處理方式如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public IActionResult Index()
{
var result = employeeList.Select(x => new EmployeeViewModel
{
Id = x.Id,
Name = x.Name,
Phone = x.Phone
});

return View(result);
}

//或是
public IActionResult Index()
{
List<EmployeeViewModel> result = new List<EmployeeViewModel>();
foreach (var employee in employeeList)
{
result.Add(new EmployeeViewModel()
{
Id = employee.Id,
Name=employee.Name,
Phone=employee.Phone
});
}

return View(result);
}

如果欄位變多的話,程式碼會變得很冗長,這時候就可以使用今天的主角 AutoMapper 來做這樣的處理。

AutoMapper介紹

套件Github首頁:https://github.com/AutoMapper/AutoMapper
文件頁:https://docs.automapper.org/en/stable/index.html

AutoMapper 是一個類別對映轉換的套件,可以快速且簡單實現類別對映的處理,並減少繁瑣的程式碼。

新增.NET Core MVC專案

這邊使用.NET Core MVC專案來方便展示

測試環境設定

新增Model,ViewModel , Controller 和 View,這邊直接產生資料並展示在View上

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Employee
{
public int Id { get; set; }
public string Name { get; set; }
public string Phone { get; set; }
public int Age { get; set; }
}

public class EmployeeViewModel
{
public int Id { get; set; }
public string Name { get; set; }
public string Phone { get; set; }
}


Controller:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
namespace AutomapperCore.Controllers
{
public class EmployeeController : Controller
{
private List<Employee> _employeeList;
public EmployeeController()
{
_employeeList = new List<Employee>() {
new Employee{ Id=1, Name="Amy", Phone="0911334455", Age=25 },
new Employee{ Id=2, Name="Tom", Phone="0912554433", Age=28 },
new Employee{ Id=3, Name="Andy", Phone="0912112299", Age=30 },
};
}
public IActionResult Index()
{
var result = employeeList.Select(x => new EmployeeViewModel
{
Id = x.Id,
Name = x.Name,
Phone = x.Phone
});

return View(result);
}
}
}

View:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
@model IEnumerable<AutomapperCore.Models.EmployeeViewModel>

<table class="table">
<thead>
<tr>
<th>
@Html.DisplayNameFor(model => model.Id)
</th>
<th>
@Html.DisplayNameFor(model => model.Name)
</th>
<th>
@Html.DisplayNameFor(model => model.Phone)
</th>
</tr>
</thead>
<tbody>
@foreach (var item in Model)
{
<tr>
<td>
@Html.DisplayFor(modelItem => item.Id)
</td>
<td>
@Html.DisplayFor(modelItem => item.Name)
</td>
<td>
@Html.DisplayFor(modelItem => item.Phone)
</td>
</tr>
}
</tbody>
</table>

使用AutoMapper

透過 NuGet 安裝 AutoMapper 到專案中

基本使用:

1
2
3
4
5
6
7
8
9
10
public IActionResult Index()
{
var config = new MapperConfiguration(cfg => cfg.CreateMap<Employee, EmployeeViewModel>()); // 設定Model間的對映(左:來源類型,右:目標類型)
config.AssertConfigurationIsValid(); //驗證類型映射(如果映射失敗會報錯)
var mapper = config.CreateMapper(); // 建立 Mapper
var result = mapper.Map<IEnumerable<EmployeeViewModel>>(employeeList);

return View(result);
}

這邊說明一下config.AssertConfigurationIsValid()的功能:
如果我們對映時發生異常(EX:欄位名稱不一致時或是映射未定義等等…),加入這行會報錯誤訊息,讓我們來測試一下,將EmployeeViewModel的Name改成Name1,並更改對應的View。這時候產生的結果如下:


Name因為找不到對應欄位所以無法呈現資料,但我們無法直觀看出問題在哪。
這時候讓我們加入config.AssertConfigurationIsValid()後,產生的結果如下:


AutoMapper會明確告訴我們哪個欄位沒有對應到。其他錯誤情況請自行測試看看。

集中管理使用

如果有多組類別需要對映,我們可以建立一個profile檔案去繼承Automapper的profile,接著在建構式裡將需要的轉換關係都建立出來。就可以集中管理這些mapping。

Profile:

1
2
3
4
5
6
7
8
9
public class MappingProfile : Profile
{
public MappingProfile()
{
CreateMap<Employee, EmployeeViewModel>();

// ...使用 CreateMap<> 建立下一組
}
}

接著當我們建立 Mapper 時,就可以直接用 AddProfile 和建立好的 Profile 來直接讀入對映關係:

1
2
3
4
5
6
7
8
9
public IActionResult Index()
{
var config = new MapperConfiguration(cfg => cfg.AddProfile<MappingProfile>()); //讀取Profile並設定對映
config.AssertConfigurationIsValid();
var mapper = config.CreateMapper();
var result = mapper.Map<IEnumerable<EmployeeViewModel>>(employeeList);

return View(result);
}

在 MapperConfiguration 的時候也可以額外去做多次 AddProfile 和 CreateMap,因此可以按照實務上的運用去做分類和整理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public IActionResult Index()
{
//設定多組對映
var config = new MapperConfiguration(cfg =>
{
cfg.AddProfile<MappingProfile>();
cfg.CreateMap<Employee, EmployeeOtherViewModel>();
});
config.AssertConfigurationIsValid();
var mapper = config.CreateMapper();
var result = mapper.Map<IEnumerable<EmployeeOtherViewModel>>(employeeList);

return View(result);
}

導入DI

至 NuGet 安裝 AutoMapper DI 用的套件:AutoMapper.Extensions.Microsoft.DependencyInjection

因為該套件相依AutoMapper,會一起自動安裝AutoMapper,所以實際上可以只裝這個就好

在Startup.cs 註冊 Service,並加入驗證

1
2
3
4
5
6
7
8
9
10
11
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
services.AddAutoMapper(typeof(Startup));
}

public void Configure(IApplicationBuilder app, ... , IMapper mapper)
{
...
mapper.ConfigurationProvider.AssertConfigurationIsValid();
}

在Controller 注入取得Mapper

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
namespace AutomapperCore.Controllers
{
public class EmployeeController : Controller
{
private IMapper _mapper;
private List<Employee> _employeeList;
public EmployeeController(IMapper mapper)
{
this._mapper = mapper;

_employeeList = new List<Employee>() {
new Employee{ Id=1, Name="Amy", Phone="0911334455", Age=25 },
new Employee{ Id=2, Name="Tom", Phone="0912554433", Age=28 },
new Employee{ Id=3, Name="Andy", Phone="0912112299", Age=30 },
};
}
public IActionResult Index()
{
_mapper.ConfigurationProvider.AssertConfigurationIsValid();
var result = _mapper.Map<IEnumerable<EmployeeViewModel>>(_employeeList);
return View(result);
}
}
}

透過建構式注入 IMapper 即可自動取得所有Profile,並直接呼叫Map()方法對映,達成程式碼簡潔、低耦合的好處

延伸使用

(下列範例已將 EmployeeViewModel 的 Name 欄位改成 FullName)

反轉對映

當你的類別之間可能需要往回轉型,或是一時分不清是哪個Model要對應到哪個Model,那可以加上ReverseMap()實現雙向映射,以免轉來轉去出錯。

1
CreateMap<Employee, EmployeeViewModel>().ReverseMap();

客製化對映

在分層架構時會建立自己的Model,有時候可能實際上我們並不一定每個欄位的名稱都是一樣的、或者需要對Model進行特殊處理,那就可以用以下方法來處理:

1
CreateMap<Employee, EmployeeViewModel>().ForMember(des => des.FullName, opt => opt.MapFrom(src => src.Name));

如果是改變欄位資料或合併,那就可以用以下方法來處理:

1
CreateMap<Employee, EmployeeViewModel>().ForMember(des => des.FullName, opt => opt.MapFrom(src => $"{src.Id}{src.Name}"));

忽略對映

如果兩個類別之間大多數欄位雖然對映,但某幾個欄位是沒有對映的,來源資料並沒有某個目標資料需要的欄位,如果直接Map()對映,就會發生找不到對映欄位的錯誤。這時候就可以使用 Ignore 來忽略掉指定的欄位。

1
CreateMap<Employee, EmployeeViewModel>().ForMember(des => des.FullName, src => src.Ignore());

結語

這篇把 AutoMapper 運用在 .Net Core的基本用法和設定介紹完了,並利用.Net Core DI的特性集中管理,將這些類別轉換對映的設定都集中在一個地方

如果要運用在ASP.NET MVC 上可以參考網路較舊的教學文,但要注意新版的AutoMapper的設定與舊版有所差異,其他較進階的用法如多個物件對映至一個類別、複雜結構類別對映至扁平化類別,可以參考官方文件