欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

.NET 6开发TodoList应用之使用AutoMapper实现GET请求

程序员文章站 2022-03-08 17:23:48
目录需求目标原理与思路实现引入automapper实现get请求验证获取所有todolist列表获取单个todolist详情填一个post文章里的坑总结需求需求很简单:实现get请求获取业务数据。在这...

需求

需求很简单:实现get请求获取业务数据。在这个阶段我们经常使用的类库是automapper

目标

合理组织并使用automapper,完成get请求。

原理与思路

首先来简单地介绍一下这这个类库。

关于automapper

在业务侧代码和数据库实体打交道的过程中,一个必不可少的部分就是返回的数据类型转换。对于不同的请求来说,希望得到的返回值是数据库实体的一部分/组合/计算等情形。我们就经常需要手写用于数据对象转换的代码,但是转换前后可能大部分情况下有着相同名称的字段或属性。这部分工作能避免手写冗长的代码吗?可以。

我们希望接受的请求和返回的值(统一称为model)具有以下两点需要遵循的原则:

1.每个model被且只被一个api消费;

2.每个model里仅仅包含api发起方希望包含的必要字段或属性。

automapper库就是为了实现这个需求而存在的,它的具体用法请参考官方文档,尤其是关于convention的部分,避免重复劳动。

实现

所有需要使用automapper的地方都集中在application项目中。

引入automapper

$ dotnet add src/todolist.application/todolist.application.csproj package automapper.extensions.microsoft.dependencyinjection

然后在application/common/mappings下添加配置,提供接口的原因是我们后面就可以在dto里实现各自对应的mapping规则,方便查找。

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进行依赖注入:

dependencyinjection.cs

// 省略其他...
services.addautomapper(assembly.getexecutingassembly());
services.addmediatr(assembly.getexecutingassembly());
return services;

实现get请求

在本章中我们只实现todolistquery接口(get),并且在结果中包含todoitem集合,剩下的接口后面的文章中逐步涉及。

get all todolists

application/todolists/queries/下新建一个目录gettodos用于存放创建一个todolist相关的所有逻辑:

定义todolistbriefdto对象:

todolistbriefdto.cs

using todolist.application.common.mappings;

namespace todolist.application.todolists.queries.gettodos;

// 实现imapfrom<t>接口,因为此dto不涉及特殊字段的mapping规则
// 并且属性名称与领域实体保持一致,根据convention规则默认可以完成mapping,不需要额外实现
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);
    }
}

最后实现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相关的所有逻辑:

定义todoitemdtotodolistdto对象:

todoitemdto.cs

using automapper;
using todolist.application.common.mappings;
using todolist.domain.entities;

namespace todolist.application.todoitems.queries.gettodoitems;

// 实现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; }

    // 实现接口定义的mapping方法,并提供除了convention之外的特殊字段的转换规则
    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;

// 实现imapfrom<t>接口,因为此dto不涉及特殊字段的mapping规则
// 并且属性名称与领域实体保持一致,根据convention规则默认可以完成mapping,不需要额外实现
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>();
}

创建一个根据listid来获取包含todoitems子项的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));
        }
    }
}

我们仍然为这个查询新建一个getsingletodo目录,并实现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返回的问题,后文会有使用。

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();
}

验证

运行api项目

获取所有todolist列表

请求

.NET 6开发TodoList应用之使用AutoMapper实现GET请求

响应

.NET 6开发TodoList应用之使用AutoMapper实现GET请求

获取单个todolist详情

请求

.NET 6开发TodoList应用之使用AutoMapper实现GET请求

响应

.NET 6开发TodoList应用之使用AutoMapper实现GET请求

填一个post文章里的坑

使用.net 6开发todolist应用(6)——使用mediatr实现post请求中我们留了一个问题,即创建todolist后的返回值当时我们是临时使用id返回的,推荐的做法是下面这样:

// 省略其他...
[httppost]
public async task<iactionresult> create([frombody] createtodolistcommand command)
{
    var createdtodolist = await _mediator.send(command);
    // 创建成功返回201
    return createdatroute("todlistbyid", new { id = createdtodolist.id }, createdtodolist);
}

请求

.NET 6开发TodoList应用之使用AutoMapper实现GET请求

返回

content部分

.NET 6开发TodoList应用之使用AutoMapper实现GET请求

以及header部分

.NET 6开发TodoList应用之使用AutoMapper实现GET请求

我们主要观察返回的httpstatuscode是201,并且在header中location字段表明了创建资源的位置。

总结

其他和查询请求相关的例子我就不多举了,通过两个简单的例子想说明如何组织cqrs模式下的代码逻辑。我们可以直观地看出,cqrs操作是通过irequest和irequesthandler实现的,其中irequest部分直接和api接口的请求参数直接或间接相关联,将请求参数通过注入的_mediator对象进行处理。

同时我们在实现两个查询接口的过程中也可以发现,查询语句中的select部分现在已经被automapper的相关功能替代掉了,所以在调用repository时,可能并不经常用到selectxxxx相关的具有数据类型转换的接口,更多的还是使用返回iqueryable对象的接口。这和我在使用.net 6开发todolist应用之实现repository模式中实践的有一点出入,在那篇文章中,我之所以把repository的抽象层次做的很高的原因是,我希望顺便把类似的类库实现思路也梳理一下。就像评论中有朋友提出的那样,其实更多的场合下,因为会配合系统里其他组件的使用,比如这里的automapper,那么对于repository的实际需求就变成了只需要给我一个iqueryable对象即可。这也是我在那篇文章中试图强调的那样:关于repository,每个人的理解和实现都有差别,因为取决于抽象程度和应用场合。

这一篇文章处理了关于get的请求,有一个小的知识点没有讲到:后台分页返回,这部分内容会在后面专门再回到查询的场景里来说。然后又留了一个小坑下一篇文章来说:全局异常处理和统一返回类型。 

以上就是.net 6开发todolist应用之使用automapper实现get请求的详细内容,更多关于.net 6 automapper实现get请求的资料请关注其它相关文章!