.NET 6開發(fā)TodoList應(yīng)用之使用AutoMapper實現(xiàn)GET請求
需求
需求很簡單:實現(xiàn)GET請求獲取業(yè)務(wù)數(shù)據(jù)。在這個階段我們經(jīng)常使用的類庫是AutoMapper。
目標(biāo)
合理組織并使用AutoMapper,完成GET請求。
原理與思路
首先來簡單地介紹一下這這個類庫。
關(guān)于AutoMapper
在業(yè)務(wù)側(cè)代碼和數(shù)據(jù)庫實體打交道的過程中,一個必不可少的部分就是返回的數(shù)據(jù)類型轉(zhuǎn)換。對于不同的請求來說,希望得到的返回值是數(shù)據(jù)庫實體的一部分/組合/計算等情形。我們就經(jīng)常需要手寫用于數(shù)據(jù)對象轉(zhuǎn)換的代碼,但是轉(zhuǎn)換前后可能大部分情況下有著相同名稱的字段或?qū)傩?。這部分工作能避免手寫冗長的代碼嗎?可以。
我們希望接受的請求和返回的值(統(tǒng)一稱為model)具有以下兩點需要遵循的原則:
1.每個model被且只被一個API消費;
2.每個model里僅僅包含API發(fā)起方希望包含的必要字段或?qū)傩浴?/p>
AutoMapper庫就是為了實現(xiàn)這個需求而存在的,它的具體用法請參考官方文檔,尤其是關(guān)于Convention的部分,避免重復(fù)勞動。
實現(xiàn)
所有需要使用AutoMapper
的地方都集中在Application
項目中。
引入AutoMapper
$ dotnet add src/TodoList.Application/TodoList.Application.csproj package AutoMapper.Extensions.Microsoft.DependencyInjection
然后在Application/Common/Mappings
下添加配置,提供接口的原因是我們后面就可以在DTO里實現(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
進行依賴注入:
DependencyInjection.cs
// 省略其他... services.AddAutoMapper(Assembly.GetExecutingAssembly()); services.AddMediatR(Assembly.GetExecutingAssembly()); return services;
實現(xiàn)GET請求
在本章中我們只實現(xiàn)TodoList
的Query
接口(GET),并且在結(jié)果中包含TodoItem
集合,剩下的接口后面的文章中逐步涉及。
GET All TodoLists
在Application/TodoLists/Queries/
下新建一個目錄GetTodos
用于存放創(chuàng)建一個TodoList
相關(guān)的所有邏輯:
定義TodoListBriefDto
對象:
TodoListBriefDto.cs
using TodoList.Application.Common.Mappings; namespace TodoList.Application.TodoLists.Queries.GetTodos; // 實現(xiàn)IMapFrom<T>接口,因為此Dto不涉及特殊字段的Mapping規(guī)則 // 并且屬性名稱與領(lǐng)域?qū)嶓w保持一致,根據(jù)Convention規(guī)則默認(rèn)可以完成Mapping,不需要額外實現(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); } }
最后實現(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)的所有邏輯:
定義TodoItemDto
和TodoListDto
對象:
TodoItemDto.cs
using AutoMapper; using TodoList.Application.Common.Mappings; using TodoList.Domain.Entities; namespace TodoList.Application.TodoItems.Queries.GetTodoItems; // 實現(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; } // 實現(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; // 實現(xiàn)IMapFrom<T>接口,因為此Dto不涉及特殊字段的Mapping規(guī)則 // 并且屬性名稱與領(lǐng)域?qū)嶓w保持一致,根據(jù)Convention規(guī)則默認(rèn)可以完成Mapping,不需要額外實現(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)建一個根據(jù)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)); } } }
我們?nèi)匀粸檫@個查詢新建一個GetSingleTodo
目錄,并實現(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返回的問題,后文會有使用。
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列表
請求
響應(yīng)
獲取單個TodoList詳情
請求
響應(yīng)
填一個POST文章里的坑
在使用.NET 6開發(fā)TodoList應(yīng)用(6)——使用MediatR實現(xiàn)POST請求中我們留了一個問題,即創(chuàng)建TodoList后的返回值當(dāng)時我們是臨時使用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)的例子我就不多舉了,通過兩個簡單的例子想說明如何組織CQRS模式下的代碼邏輯。我們可以直觀地看出,CQRS操作是通過IRequest和IRequestHandler實現(xiàn)的,其中IRequest部分直接和API接口的請求參數(shù)直接或間接相關(guān)聯(lián),將請求參數(shù)通過注入的_mediator對象進行處理。
同時我們在實現(xiàn)兩個查詢接口的過程中也可以發(fā)現(xiàn),查詢語句中的Select部分現(xiàn)在已經(jīng)被AutoMapper的相關(guān)功能替代掉了,所以在調(diào)用Repository時,可能并不經(jīng)常用到SelectXXXX相關(guān)的具有數(shù)據(jù)類型轉(zhuǎn)換的接口,更多的還是使用返回IQueryable對象的接口。這和我在使用.NET 6開發(fā)TodoList應(yīng)用之實現(xiàn)Repository模式中實踐的有一點出入,在那篇文章中,我之所以把Repository的抽象層次做的很高的原因是,我希望順便把類似的類庫實現(xiàn)思路也梳理一下。就像評論中有朋友提出的那樣,其實更多的場合下,因為會配合系統(tǒng)里其他組件的使用,比如這里的AutoMapper,那么對于Repository的實際需求就變成了只需要給我一個IQueryable對象即可。這也是我在那篇文章中試圖強調(diào)的那樣:關(guān)于Repository,每個人的理解和實現(xiàn)都有差別,因為取決于抽象程度和應(yīng)用場合。
這一篇文章處理了關(guān)于GET的請求,有一個小的知識點沒有講到:后臺分頁返回,這部分內(nèi)容會在后面專門再回到查詢的場景里來說。然后又留了一個小坑下一篇文章來說:全局異常處理和統(tǒng)一返回類型。?
以上就是.NET 6開發(fā)TodoList應(yīng)用之使用AutoMapper實現(xiàn)GET請求的詳細內(nèi)容,更多關(guān)于.NET 6 AutoMapper實現(xiàn)GET請求的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
asp.net 中將表單提交到另一頁 Code-Behind(代碼和html在不同的頁面)
To send Server control values from a different Web Forms page2009-04-04在asp.net中實現(xiàn)datagrid checkbox 全選的方法
在asp.net中實現(xiàn)datagrid checkbox 全選的方法...2006-12-12ASP.NET MVC5網(wǎng)站開發(fā)之展示層架構(gòu)(五)
這篇文章主要為大家詳細介紹了ASP.NET MVC5網(wǎng)站開發(fā)之展示層架構(gòu),文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2016-08-08asp.net ajaxControlToolkit FilteredTextBoxExtender的簡單用法
最近寫的東西驗證比較多,尤其是數(shù)字驗證,無意中發(fā)現(xiàn)這個控件,有點兒意思。記錄一下2008-11-11把aspx頁面?zhèn)窝b成靜態(tài)html格式的實現(xiàn)代碼
把aspx頁面?zhèn)窝b成靜態(tài)html格式的實現(xiàn)代碼,主要是利于搜索引擎的收錄。2011-10-10asp.net Reporting Service在Web Application中的應(yīng)用
由于我們這個項目中使用微軟的報表服務(wù)(Reporting Services)作為報表輸出工具,本人也對它進行一點點研究,雖沒有入木三分,但這點知識至少可以在大部分Reporting Service的場景中應(yīng)用。2008-11-11在?ASP.NET?Core?中使用?HTTP?標(biāo)頭傳播詳情
這篇文章主要介紹了在?ASP.NET?Core?中使用?HTTP?標(biāo)頭傳播詳情,文章通過,我們創(chuàng)建?ServerA、ServiceB?兩個?Web?API?項目展開內(nèi)容,需要的朋友可以參考一下2022-04-04asp.net文件上傳解決方案(圖片上傳、單文件上傳、多文件上傳、檢查文件類型)
這篇文章主要介紹了asp.net文件上傳解決方案,包括:圖片上傳、單文件上傳、多文件上傳、檢查文件類型等案例,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2015-09-09基于.net core微服務(wù)的另一種實現(xiàn)方法
這篇文章主要給大家介紹了基于.net core微服務(wù)的另一種實現(xiàn)方法,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2018-07-07