.NET|.NET 6开发TodoList应用之使用AutoMapper实现GET请求
目录
- 需求
- 目标
- 原理与思路
- 实现
- 引入AutoMapper
- 实现GET请求
- 验证
- 获取所有TodoList列表
- 获取单个TodoList详情
- 填一个POST文章里的坑
- 总结
需求 需求很简单:实现GET请求获取业务数据。在这个阶段我们经常使用的类库是AutoMapper。
目标 【.NET|.NET 6开发TodoList应用之使用AutoMapper实现GET请求】合理组织并使用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{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请求
在本章中我们只实现
TodoList
的Query
接口(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接口,因为此Dto不涉及特殊字段的Mapping规则// 并且属性名称与领域实体保持一致,根据Convention规则默认可以完成Mapping,不需要额外实现public class TodoListBriefDto : IMapFrom {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>{}public class GetTodosQueryHandler : IRequestHandler
>{private readonly IRepository _repository; private readonly IMapper _mapper; public GetTodosQueryHandler(IRepository repository, IMapper mapper){_repository = repository; _mapper = mapper; }public async Task > Handle(GetTodosQuery request, CancellationToken cancellationToken){return await _repository.GetAsQueryable().AsNoTracking().ProjectTo
(_mapper.ConfigurationProvider).OrderBy(t => t.Title).ToListAsync(cancellationToken); }}
最后实现
Controller
层的逻辑:TodoListController.cs
// 省略其他...[HttpGet]public async Task>> Get(){return await _mediator.Send(new GetTodosQuery()); }
GET Single TodoList
首先在
Application/TodoItems/Queries/
下新建目录GetTodoItems
用于存放获取TodoItem
相关的所有逻辑:定义
TodoItemDto
和TodoListDto
对象:TodoItemDto.cs
using AutoMapper; using TodoList.Application.Common.Mappings; using TodoList.Domain.Entities; namespace TodoList.Application.TodoItems.Queries.GetTodoItems; // 实现IMapFrom接口public class TodoItemDto : IMapFrom {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 ().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接口,因为此Dto不涉及特殊字段的Mapping规则// 并且属性名称与领域实体保持一致,根据Convention规则默认可以完成Mapping,不需要额外实现public class TodoListDto : IMapFrom {public Guid Id { get; set; }public string? Title { get; set; }public string? Colour { get; set; }public IList Items { get; set; } = new List (); }
创建一个根据ListId来获取包含TodoItems子项的spec:
TodoListSpec.cs
using Microsoft.EntityFrameworkCore; using TodoList.Application.Common; namespace TodoList.Application.TodoLists.Specs; public sealed class TodoListSpec : SpecificationBase{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{public Guid ListId { get; set; }}public class ExportTodosQueryHandler : IRequestHandler {private readonly IRepository _repository; private readonly IMapper _mapper; public ExportTodosQueryHandler(IRepository repository, IMapper mapper){_repository = repository; _mapper = mapper; }public async Task Handle(GetSingleTodoQuery request, CancellationToken cancellationToken){var spec = new TodoListSpec(request.ListId, true); return await _repository.GetAsQueryable(spec).AsNoTracking().ProjectTo (_mapper.ConfigurationProvider).FirstOrDefaultAsync(cancellationToken); }}
添加Controller逻辑,这里的Name是为了完成之前遗留的201返回的问题,后文会有使用。
TodoListController.cs
// 省略其他...[HttpGet("{id:Guid}", Name = "TodListById")]public async Task> GetSingleTodoList(Guid id){return await _mediator.Send(new GetSingleTodoQuery{ListId = id}) ?? throw new InvalidOperationException(); }
验证 运行
Api
项目获取所有TodoList列表
请求
![.NET|.NET 6开发TodoList应用之使用AutoMapper实现GET请求](https://img.it610.com/image/info11/26a1d48664974d3893478fb4acf3d207.jpg)
文章图片
响应
![.NET|.NET 6开发TodoList应用之使用AutoMapper实现GET请求](https://img.it610.com/image/info11/da6178521537497eba0c39dc59286583.jpg)
文章图片
获取单个TodoList详情
请求
![.NET|.NET 6开发TodoList应用之使用AutoMapper实现GET请求](https://img.it610.com/image/info11/36fc5b721e7d425886e21f9764f532f0.jpg)
文章图片
响应
![.NET|.NET 6开发TodoList应用之使用AutoMapper实现GET请求](https://img.it610.com/image/info11/4f983dce91854ebf827dbf7fe8e02070.jpg)
文章图片
填一个POST文章里的坑
在使用.NET 6开发TodoList应用(6)——使用MediatR实现POST请求中我们留了一个问题,即创建TodoList后的返回值当时我们是临时使用Id返回的,推荐的做法是下面这样:
// 省略其他...[HttpPost]public async TaskCreate([FromBody] CreateTodoListCommand command){var createdTodoList = await _mediator.Send(command); // 创建成功返回201return CreatedAtRoute("TodListById", new { id = createdTodoList.Id }, createdTodoList); }
请求
![.NET|.NET 6开发TodoList应用之使用AutoMapper实现GET请求](https://img.it610.com/image/info11/d8a94ef8f8f243edb8bc9b212c495ec7.jpg)
文章图片
返回
Content部分
![.NET|.NET 6开发TodoList应用之使用AutoMapper实现GET请求](https://img.it610.com/image/info11/fdc30ed9383748819c362bd90907a57d.jpg)
文章图片
以及Header部分
![.NET|.NET 6开发TodoList应用之使用AutoMapper实现GET请求](https://img.it610.com/image/info11/54118369c0364773a17ae644cdce11db.jpg)
文章图片
我们主要观察返回的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请求的资料请关注脚本之家其它相关文章!
推荐阅读
- 深入理解Go之generate
- 标签、语法规范、内联框架、超链接、CSS的编写位置、CSS语法、开发工具、块和内联、常用选择器、后代元素选择器、伪类、伪元素。
- 我的软件测试开发工程师书单
- echart|echart 双轴图开发
- NPDP拆书(三)(新产品开发战略(经营与创新战略))
- 芯灵思SinlinxA33开发板Linux内核定时器编程
- 常用git命令总结
- 藏族开发的修路人——记致富援乡的斯定那珠
- ASP.NET|ASP.NET Core应用开发思维导图
- VueX(Vuex|VueX(Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式)