呈上篇,我們已經將驗證透過IPipelineBehavior進行簡化了,以下是目前的程式碼

[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
    private readonly IMediator _mediator;

    public WeatherForecastController(IMediator mediator)
    {
        _mediator = mediator;
    }

    [HttpPost]
    public async Task<IActionResult> CreateWeatherForecast(
				[FromBody] CreateWeatherForecastRequest request)
    {
        var command = new CreateWeatherForecastCommand
        {
            Nation = request.Nation,
            City = request.City,
            Date = request.Date,
            TemperatureC = request.TemperatureC,
            Summary = request.Summary,
        };

        var result = await _mediator.Send(command);

        return result ? Ok() : BadRequest();
    }

    [HttpPut("{id:int}")]
    public async Task<IActionResult> UpdateWeatherForecast(int id, 
				[FromBody] UpdateWeatherForecastRequest request)
    {
        if (id != request.WeatherForecastId) return BadRequest();

        var command = new UpdateWeatherForecastCommand
        {
            WeatherForecastId = request.WeatherForecastId,
            Nation = request.Nation,
            City = request.City,
            Date = request.Date,
            TemperatureC = request.TemperatureC,
            Summary = request.Summary,
        };

        var result = await _mediator.Send(command);

        return result ? Ok() : BadRequest();
    }
}

其中我們可以看到CreateWeatherForecastRequestCreateWeatherForecastCommand,兩個類別高度重複

// CreateWeatherForecastRequest.cs 類別
public class CreateWeatherForecastRequest
{
    public string Nation { get; set; }
    public string City { get; set; }
    public DateTime Date { get; set; }
    public int TemperatureC { get; set; }
    public string Summary { get; set; }
}
// CreateWeatherForecastCommand.cs 類別
public class CreateWeatherForecastCommand : ICommand<bool>
{
    public string Nation { get; set; } = string.Empty;
    public string City { get; set; } = string.Empty;
    public DateTime Date { get; set; }
    public int TemperatureC { get; set; }
    public string? Summary { get; set; }
}

有部分範例會直接使用 Command 取代 Request,但這會造成高層元件依賴低層。當然在簡單的專案上,是否需要完整切割,還是看個人與團隊的共識,減少類別與簡化實作也是一個選擇。

而本篇介紹另一個套件,AutoMapper,該套件已經存在數十年之久,也有相當多的專案採用與文章介紹,前幾篇有提到 AutoMapper 和 MediatR 均是出自 Jimmy Bogard 這位大神的手中,深遠的影響著大大小小的專案,該作者的Blog,也提出相當多,值得學習的觀點,包括 Repository 層是否實作的爭議。

由於相關介紹文章已經很多了,這邊只快速帶過,介紹導入步驟。

環境

官方告知從版本 13.0 開始,AddAutoMapper成為核心包的一部分,並且 DI 套件將停止使用。然而寫這篇的時候,最新版本為 12.0.1,所以我們還是安裝了AutoMapper.Extensions.Microsoft.DependencyInjection,才能使用AddAutoMapper

安裝 AutoMapper

2023-10-13_15-54-30.png

新增 Profile 實例

個人習慣會開一個 Mappers 資料夾,然後設定一個AutoMappers空類別,內部依照類別+Profile命名。

2023-10-13_11-59-18.png

public class AutoMappers
{
}

public class WeatherForecastProfile : Profile
{
    public WeatherForecastProfile()
    {
        CreateMap<CreateWeatherForecastRequest, CreateWeatherForecastCommand>();
        CreateMap<UpdateWeatherForecastRequest, UpdateWeatherForecastCommand>();
    }
}

// 其他類別的 Mapper Instances
//public class LocationProfile : Profile
//{
//    public LocationProfile()
//    {
//        CreateMap<TSourece,TDestination> ...
//    }
//}

IServiceCollection註冊

builder.Services.AddAutoMapper(typeof(AutoMappers));

Controller引用IMapper