欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

.NET 6開發(fā)TodoList應(yīng)用之使用AutoMapper實(shí)現(xiàn)GET請求

 更新時(shí)間:2021年12月27日 15:48:37   作者:CODE4NOTHING  
我們希望接受的請求和返回的值具有以下兩點(diǎn)需要遵循的原則:每個(gè)model被且只被一個(gè)API消費(fèi);每個(gè)model里僅僅包含API發(fā)起方希望包含的必要字段或?qū)傩浴utoMapper庫就是為了實(shí)現(xiàn)這個(gè)需求而存在的。本文將為大家介紹AutoMapper如何實(shí)現(xiàn)GET請求,需要的可以參考一下

需求

需求很簡單:實(shí)現(xiàn)GET請求獲取業(yè)務(wù)數(shù)據(jù)。在這個(gè)階段我們經(jīng)常使用的類庫是AutoMapper。

目標(biāo)

合理組織并使用AutoMapper,完成GET請求。

原理與思路

首先來簡單地介紹一下這這個(gè)類庫。

關(guān)于AutoMapper

在業(yè)務(wù)側(cè)代碼和數(shù)據(jù)庫實(shí)體打交道的過程中,一個(gè)必不可少的部分就是返回的數(shù)據(jù)類型轉(zhuǎn)換。對于不同的請求來說,希望得到的返回值是數(shù)據(jù)庫實(shí)體的一部分/組合/計(jì)算等情形。我們就經(jīng)常需要手寫用于數(shù)據(jù)對象轉(zhuǎn)換的代碼,但是轉(zhuǎn)換前后可能大部分情況下有著相同名稱的字段或?qū)傩?。這部分工作能避免手寫冗長的代碼嗎?可以。

我們希望接受的請求和返回的值(統(tǒng)一稱為model)具有以下兩點(diǎn)需要遵循的原則:

1.每個(gè)model被且只被一個(gè)API消費(fèi);

2.每個(gè)model里僅僅包含API發(fā)起方希望包含的必要字段或?qū)傩浴?/p>

AutoMapper庫就是為了實(shí)現(xiàn)這個(gè)需求而存在的,它的具體用法請參考官方文檔,尤其是關(guān)于Convention的部分,避免重復(fù)勞動(dòng)。

實(shí)現(xiàn)

所有需要使用AutoMapper的地方都集中在Application項(xiàng)目中。

引入AutoMapper

$ dotnet add src/TodoList.Application/TodoList.Application.csproj package AutoMapper.Extensions.Microsoft.DependencyInjection

然后在Application/Common/Mappings下添加配置,提供接口的原因是我們后面就可以在DTO里實(shí)現(xiàn)各自對應(yīng)的Mapping規(guī)則,方便查找。

IMapFrom.cs

using AutoMapper;

namespace TodoList.Application.Common.Mappings;

public interface IMapFrom<T>
{
    void Mapping(Profile profile) => profile.CreateMap(typeof(T), GetType());
}

MappingProfile.cs

using System.Reflection;
using AutoMapper;

namespace TodoList.Application.Common.Mappings;

public class MappingProfile : Profile
{
    public MappingProfile() => ApplyMappingsFromAssembly(Assembly.GetExecutingAssembly());

    private void ApplyMappingsFromAssembly(Assembly assembly)
    {
        var types = assembly.GetExportedTypes()
            .Where(t => t.GetInterfaces().Any(i =>
                i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IMapFrom<>)))
            .ToList();

        foreach (var type in types)
        {
            var instance = Activator.CreateInstance(type);

            var methodInfo = type.GetMethod("Mapping")
                             ?? type.GetInterface("IMapFrom`1")!.GetMethod("Mapping");

            methodInfo?.Invoke(instance, new object[] { this });
        }
    }
}

DependencyInjection.cs進(jìn)行依賴注入:

DependencyInjection.cs

// 省略其他...
services.AddAutoMapper(Assembly.GetExecutingAssembly());
services.AddMediatR(Assembly.GetExecutingAssembly());
return services;

實(shí)現(xiàn)GET請求

在本章中我們只實(shí)現(xiàn)TodoListQuery接口(GET),并且在結(jié)果中包含TodoItem集合,剩下的接口后面的文章中逐步涉及。

GET All TodoLists

Application/TodoLists/Queries/下新建一個(gè)目錄GetTodos用于存放創(chuàng)建一個(gè)TodoList相關(guān)的所有邏輯:

定義TodoListBriefDto對象:

TodoListBriefDto.cs

using TodoList.Application.Common.Mappings;

namespace TodoList.Application.TodoLists.Queries.GetTodos;

// 實(shí)現(xiàn)IMapFrom<T>接口,因?yàn)榇薉to不涉及特殊字段的Mapping規(guī)則
// 并且屬性名稱與領(lǐng)域?qū)嶓w保持一致,根據(jù)Convention規(guī)則默認(rèn)可以完成Mapping,不需要額外實(shí)現(xiàn)
public class TodoListBriefDto : IMapFrom<Domain.Entities.TodoList>
{
    public Guid Id { get; set; }
    public string? Title { get; set; }
    public string? Colour { get; set; }
}

GetTodosQuery.cs

using AutoMapper;
using AutoMapper.QueryableExtensions;
using MediatR;
using Microsoft.EntityFrameworkCore;
using TodoList.Application.Common.Interfaces;

namespace TodoList.Application.TodoLists.Queries.GetTodos;

public class GetTodosQuery : IRequest<List<TodoListBriefDto>>
{
}

public class GetTodosQueryHandler : IRequestHandler<GetTodosQuery, List<TodoListBriefDto>>
{
    private readonly IRepository<Domain.Entities.TodoList> _repository;
    private readonly IMapper _mapper;

    public GetTodosQueryHandler(IRepository<Domain.Entities.TodoList> repository, IMapper mapper)
    {
        _repository = repository;
        _mapper = mapper;
    }

    public async Task<List<TodoListBriefDto>> Handle(GetTodosQuery request, CancellationToken cancellationToken)
    {
        return await _repository
            .GetAsQueryable()
            .AsNoTracking()
            .ProjectTo<TodoListBriefDto>(_mapper.ConfigurationProvider)
            .OrderBy(t => t.Title)
            .ToListAsync(cancellationToken);
    }
}

最后實(shí)現(xiàn)Controller層的邏輯:

TodoListController.cs

// 省略其他...
[HttpGet]
public async Task<ActionResult<List<TodoListBriefDto>>> Get()
{
    return await _mediator.Send(new GetTodosQuery());
}

GET Single TodoList

首先在Application/TodoItems/Queries/下新建目錄GetTodoItems用于存放獲取TodoItem相關(guān)的所有邏輯:

定義TodoItemDtoTodoListDto對象:

TodoItemDto.cs

using AutoMapper;
using TodoList.Application.Common.Mappings;
using TodoList.Domain.Entities;

namespace TodoList.Application.TodoItems.Queries.GetTodoItems;

// 實(shí)現(xiàn)IMapFrom<T>接口
public class TodoItemDto : IMapFrom<TodoItem>
{
    public Guid Id { get; set; }
    public Guid ListId { get; set; }
    public string? Title { get; set; }
    public bool Done { get; set; }
    public int Priority { get; set; }

    // 實(shí)現(xiàn)接口定義的Mapping方法,并提供除了Convention之外的特殊字段的轉(zhuǎn)換規(guī)則
    public void Mapping(Profile profile)
    {
        profile.CreateMap<TodoItem, TodoItemDto>()
            .ForMember(d => d.Priority, opt => opt.MapFrom(s => (int)s.Priority));
    }
}

TodoListDto.cs

using TodoList.Application.Common.Mappings;
using TodoList.Application.TodoItems.Queries.GetTodoItems;

namespace TodoList.Application.TodoLists.Queries.GetSingleTodo;

// 實(shí)現(xiàn)IMapFrom<T>接口,因?yàn)榇薉to不涉及特殊字段的Mapping規(guī)則
// 并且屬性名稱與領(lǐng)域?qū)嶓w保持一致,根據(jù)Convention規(guī)則默認(rèn)可以完成Mapping,不需要額外實(shí)現(xiàn)
public class TodoListDto : IMapFrom<Domain.Entities.TodoList>
{
    public Guid Id { get; set; }
    public string? Title { get; set; }
    public string? Colour { get; set; }

    public IList<TodoItemDto> Items { get; set; } = new List<TodoItemDto>();
}

創(chuàng)建一個(gè)根據(jù)ListId來獲取包含TodoItems子項(xiàng)的spec:

TodoListSpec.cs

using Microsoft.EntityFrameworkCore;
using TodoList.Application.Common;

namespace TodoList.Application.TodoLists.Specs;

public sealed class TodoListSpec : SpecificationBase<Domain.Entities.TodoList>
{
    public TodoListSpec(Guid id, bool includeItems = false) : base(t => t.Id == id)
    {
        if (includeItems)
        {
            AddInclude(t => t.Include(i => i.Items));
        }
    }
}

我們?nèi)匀粸檫@個(gè)查詢新建一個(gè)GetSingleTodo目錄,并實(shí)現(xiàn)GetSIngleTodoQuery

GetSingleTodoQuery.cs

using AutoMapper;
using AutoMapper.QueryableExtensions;
using MediatR;
using Microsoft.EntityFrameworkCore;
using TodoList.Application.Common.Interfaces;
using TodoList.Application.TodoLists.Specs;

namespace TodoList.Application.TodoLists.Queries.GetSingleTodo;

public class GetSingleTodoQuery : IRequest<TodoListDto?>
{
    public Guid ListId { get; set; }
}

public class ExportTodosQueryHandler : IRequestHandler<GetSingleTodoQuery, TodoListDto?>
{
    private readonly IRepository<Domain.Entities.TodoList> _repository;
    private readonly IMapper _mapper;

    public ExportTodosQueryHandler(IRepository<Domain.Entities.TodoList> repository, IMapper mapper)
    {
        _repository = repository;
        _mapper = mapper;
    }

    public async Task<TodoListDto?> Handle(GetSingleTodoQuery request, CancellationToken cancellationToken)
    {
        var spec = new TodoListSpec(request.ListId, true);
        return await _repository
            .GetAsQueryable(spec)
            .AsNoTracking()
            .ProjectTo<TodoListDto>(_mapper.ConfigurationProvider)
            .FirstOrDefaultAsync(cancellationToken);
    }
}

添加Controller邏輯,這里的Name是為了完成之前遺留的201返回的問題,后文會(huì)有使用。

TodoListController.cs

// 省略其他...
[HttpGet("{id:Guid}", Name = "TodListById")]
public async Task<ActionResult<TodoListDto>> GetSingleTodoList(Guid id)
{
    return await _mediator.Send(new GetSingleTodoQuery
    {
        ListId = id
    }) ?? throw new InvalidOperationException();
}

驗(yàn)證

運(yùn)行Api項(xiàng)目

獲取所有TodoList列表

請求

響應(yīng)

獲取單個(gè)TodoList詳情

請求

響應(yīng)

填一個(gè)POST文章里的坑

使用.NET 6開發(fā)TodoList應(yīng)用(6)——使用MediatR實(shí)現(xiàn)POST請求中我們留了一個(gè)問題,即創(chuàng)建TodoList后的返回值當(dāng)時(shí)我們是臨時(shí)使用Id返回的,推薦的做法是下面這樣:

// 省略其他...
[HttpPost]
public async Task<IActionResult> Create([FromBody] CreateTodoListCommand command)
{
    var createdTodoList = await _mediator.Send(command);
    // 創(chuàng)建成功返回201
    return CreatedAtRoute("TodListById", new { id = createdTodoList.Id }, createdTodoList);
}

請求

返回

Content部分

以及Header部分

我們主要觀察返回的HTTPStatusCode是201,并且在Header中l(wèi)ocation字段表明了創(chuàng)建資源的位置。

總結(jié)

其他和查詢請求相關(guān)的例子我就不多舉了,通過兩個(gè)簡單的例子想說明如何組織CQRS模式下的代碼邏輯。我們可以直觀地看出,CQRS操作是通過IRequest和IRequestHandler實(shí)現(xiàn)的,其中IRequest部分直接和API接口的請求參數(shù)直接或間接相關(guān)聯(lián),將請求參數(shù)通過注入的_mediator對象進(jìn)行處理。

同時(shí)我們在實(shí)現(xiàn)兩個(gè)查詢接口的過程中也可以發(fā)現(xiàn),查詢語句中的Select部分現(xiàn)在已經(jīng)被AutoMapper的相關(guān)功能替代掉了,所以在調(diào)用Repository時(shí),可能并不經(jīng)常用到SelectXXXX相關(guān)的具有數(shù)據(jù)類型轉(zhuǎn)換的接口,更多的還是使用返回IQueryable對象的接口。這和我在使用.NET 6開發(fā)TodoList應(yīng)用之實(shí)現(xiàn)Repository模式中實(shí)踐的有一點(diǎn)出入,在那篇文章中,我之所以把Repository的抽象層次做的很高的原因是,我希望順便把類似的類庫實(shí)現(xiàn)思路也梳理一下。就像評論中有朋友提出的那樣,其實(shí)更多的場合下,因?yàn)闀?huì)配合系統(tǒng)里其他組件的使用,比如這里的AutoMapper,那么對于Repository的實(shí)際需求就變成了只需要給我一個(gè)IQueryable對象即可。這也是我在那篇文章中試圖強(qiáng)調(diào)的那樣:關(guān)于Repository,每個(gè)人的理解和實(shí)現(xiàn)都有差別,因?yàn)槿Q于抽象程度和應(yīng)用場合。

這一篇文章處理了關(guān)于GET的請求,有一個(gè)小的知識點(diǎn)沒有講到:后臺(tái)分頁返回,這部分內(nèi)容會(huì)在后面專門再回到查詢的場景里來說。然后又留了一個(gè)小坑下一篇文章來說:全局異常處理和統(tǒng)一返回類型。?

以上就是.NET 6開發(fā)TodoList應(yīng)用之使用AutoMapper實(shí)現(xiàn)GET請求的詳細(xì)內(nèi)容,更多關(guān)于.NET 6 AutoMapper實(shí)現(xiàn)GET請求的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

最新評論