昨天在討論完 ViewModel 與 Entity 之後,我們發現當下完查詢指令,將 Entity 中的屬性對應到 ViewModel 中相對應的屬性,如果要對應的屬性一多,用手動的方式一個一個的 Coding 的話,確實是一項苦力的工作,我們寫程式的目的是要幫助企業減省人力,除去做些規則且又重複的工作。當然在寫程式的過程當中,也會希望有工具(如果有現成的,就用現成的,沒有現成的就自己開發)能幫我們做些規則而又重複的工作。為了讓我們的工作能夠輕鬆愉快,還是求問一下 Google 好了,看來這一篇有提到 AutoMapper 這項工具,看來不錯用,今天花點時間就來體驗看看吧!
安裝 NuGet 套件
將 AutoMapper 套件安裝進來,請留意,因為是要在 ASP.NET Core 中使用,所以請記得選擇 AutoMapper.Extensions.Microsoft.DependencyInjection
套件:
註冊 AutoMapper
安裝完 AutoMapper 套件之後,接著在 Demae.Api 專案的 Startup.cs 檔案中加入 services.AddAutoMapper(typeof(Startup));
註冊 AutoMapper 服務,如下所述:
public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
........
........
services.AddAutoMapper(typeof(Startup));
........
........
}
接著在 AddressController 的建構函式中加入 IMapper mapper
如下所示:
public class AddressesController : Controller
{
private readonly DemaeContext _context;
private IMapper _mapper;
public AddressesController(DemaeContext context, IMapper mapper)
{
_context = context;
_mapper = mapper;
}
.....
.....
.....
.....
}
接著修改程式如下所示,使用 _mapper.Map<IEnumerable<AddressModel>>(addresses) 或是 _mapper.Map<AddressModel>(address) 將查詢到的 Entity 對應到 ViewModel 中:
[HttpGet]
public IEnumerable<AddressModel> GetAddresses()
{
var addresses = _context.Addresses;
return _mapper.Map<IEnumerable<AddressModel>>(addresses);
}
[HttpGet("{id}")]
public async Task<IActionResult> GetAddress([FromRoute] int id)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var address =
await _context.Addresses.SingleOrDefaultAsync(m => m.Id == id);
if (address == null)
{
return NotFound();
}
return Ok(_mapper.Map<AddressModel>(address));
}
接著以 Postman 試著執行,卻發生如下圖所示的錯誤訊息:「Missing type map configuration」!看來 AutoMapper 不知道哪兩個物件是要互相對應的。
AutoMapper Profile
有關 AutoMapper Profile 請先參考這一篇(往後會有相關文章介紹),底下先針對解決目前的問題,說明做法。請先在 Demae.Core 專案加入 AutoMapper 套件:
接著在 Demae.Core 專案的 Models 資料夾中加入 AddressMapProfile 類別(可隨意命名,建議取個有意義的名稱)並加入程式碼如下:
namespace Demae.Core.Models
{
public class AddressMapProfile : Profile
{
public AddressMapProfile()
{
CreateMap<Address, AddressModel>();
}
}
}
類別繼承 AutoMapper.Profile 類別,並在建構函式中加入 CreateMap<Address, AddressModel>(); 設定 Address 為對應來源,而 AddressModel 為目標。
接著試著以 Postman 查詢,雖然沒有發生錯誤也有結果產出,但是除了 id 有值之外其他的屬性都是 null 值,看來 Entity 與 ViewModel 的屬性間有一定的命名規則必需遵行,相同的名稱可以毫無疑問地對應上,名稱不同就不知如何對應了。
修改 ViewModel 的屬性名稱如下:
public class AddressModel
{
public int Id { get; set; }
public string AreaCityName{ get; set; }
public string AreaName { get; set; }
public string Address { get; set; }
}
因為 Area 與 City 資料需要的資訊,所以在查詢時也要一起包含進來:
[HttpGet]
public IEnumerable<AddressModel> GetAddresses()
{
var addresses = _context.Addresses
.Include(e=>e.Area)
.ThenInclude(e=>e.City);
return _mapper.Map<IEnumerable<AddressModel>>(addresses);
}
[HttpGet("{id}")]
public async Task<IActionResult> GetAddress([FromRoute] int id)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var address =
await _context.Addresses
.Include(e => e.Area)
.ThenInclude(e => e.City)
.SingleOrDefaultAsync(m => m.Id == id);
if (address == null)
{
return NotFound();
}
return Ok(_mapper.Map<AddressModel>(address));
}
接著再試著以 Postman 查詢看看,縣市與鄉鎮區的資料有了,但是 address 還是 null 值,因為 address 是由多個屬性組合而成,所以不是只有單純的用命名規則就可對應得上,必需再加以設定。
AutoMapper Custom Mapping
修改一下 AutoMapper Profile 明確地指定目標類別 Address 屬性是由來源類別的哪些屬性所組合成的:
namespace Demae.Core.Models
{
public class AddressMapProfile : Profile
{
public AddressMapProfile()
{
CreateMap<Address,AddressModel>()
.ForMember(c => c.Address, opt => opt.MapFrom(a => a.Area.City.Name + a.Area.Name + a.Line));
}
}
}
再試著執行,成功了:
好吧!今天就學到這裡,明天就繼續努了了。