第 5 章 使用 Entity Framework Core

5.4 重构 Controller 和 Action

重构 AuthorController

构造函数重构

public IMapper Mapper { get; set; }
public IRepositoryWrapper RepositoryWrapper { get; set; } public AuthorController(IRepositoryWrapper repositoryWrapper, IMapper mapper)
{
RepositoryWrapper = repositoryWrapper;
Mapper = mapper;
}

IRepositoryWrapper 用于操作仓储类,IMapper 用于处理对象之间的映射关系

获取作者列表重构

[HttpGet]
public async Task<ActionResult<List<AuthorDto>>> GetAuthorsAsync()
{
var authors = (await RepositoryWrapper.Author.GetAllAsync()).OrderBy(author => author.Name);
var authorDtoList = Mapper.Map<IEnumerable<AuthorDto>>(authors); return authorDtoList.ToList();
}

在 RepositoryBase 类中使用的延迟执行会在程序运行到 Mapper.Map 时才实际去执行查询,获取单个资源的方法的重构思路类似

创建资源方法重构

[HttpPost]
public async Task<IActionResult> CreateAuthorAsync(AuthorForCreationDto authorForCreationDto)
{
var author = Mapper.Map<Author>(authorForCreationDto); RepositoryWrapper.Author.Create(author);
var result = await RepositoryWrapper.Author.SaveAsync();
if (!result)
{
throw new Exception("创建资源 author 失败");
} var authorCreated = Mapper.Map<AuthorDto>(author); // 返回201 Created 状态码,并在响应消息头中包含 Location 项,它的值是新创建资源的 URL
// 第一个参数是要调用 Action 的路由名称
// 第二个参数是包含要调用 Action 所需要参数的匿名对象
// 最后一个参数是代表添加成功后的资源本身
return CreatedAtRoute(nameof(GetAuthorsAsync), new { authorId = authorCreated.Id }, authorCreated);
}

当数据发生变化时,EF Core 会将实体对象的属性及其状态修改,只有在调用 DbContext 类的 Save 或 SaveAsync 方法后,所有的修改才会存储到数据库中

删除资源方法重构

[HttpDelete("{authorId}")]
public async Task<ActionResult> DeleteAuthorAsync(Guid authorId)
{
var author = await RepositoryWrapper.Author.GetByIdAsync(authorId);
if (author == null)
{
return NotFound();
} RepositoryWrapper.Author.Delete(author);
var result = await RepositoryWrapper.Author.SaveAsync();
if (!result)
{
throw new Exception("删除资源 author 失败");
} return NoContent();
}

重构 BookController

由于所有 Action 操作都基于一个存在的 Author 资源,因此每个 Action 中都会包含 IsExistAsync 逻辑,因此可以放在自定义过滤器中

namespace Library.API.Filters
{
public class CheckAuthorExistFilterAttribute : ActionFilterAttribute
{
public IRepositoryWrapper RepositoryWrapper { get; set; } public CheckAuthorExistFilterAttribute(IRepositoryWrapper repositoryWrapper)
{
RepositoryWrapper = repositoryWrapper;
} public override async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
var authorIdParameter = context.ActionArguments.Single(m => m.Key == "authorId");
Guid authorId = (Guid) authorIdParameter.Value; var isExist = await RepositoryWrapper.Author.IsExistAsync(authorId);
if (!isExist)
{
context.Result = new NotFoundResult();
} await base.OnActionExecutionAsync(context, next);
}
}
}

如果检查结果不存在,则结束本次请求,并返回 404 Not Found 状态码;反之,则继续完成 MVC 请求

接着,在 ConfigureServices 中注入

services.AddScoped<CheckAuthorExistFilterAttribute>();

注入之后可以在 BookController 中通过特性应用

[ServiceFilter(typeof(CheckAuthorExistFilterAttribute))]
public class BookController : ControllerBase

获取指定作者的所有图书,可以这么写

var books = await RepositoryWrapper.Book.GetByConditionAsync(book => book.Id == authorId);

但是更推荐在 IBookRepository 中定义专门的接口

Task<IEnumerable<Book>> GetBooksAsync(Guid authorId);

并在 BookRepository 中实现

public Task<IEnumerable<Book>> GetBooksAsync(Guid authorId)
{
return Task.FromResult(DbContext.Set<Book>().Where(book => book.AuthorId == authorId).AsEnumerable());
}

在 BookController 中重构 GetBooks

[HttpGet]
public async Task<ActionResult<List<BookDto>>> GetBooksAsync(Guid authorId)
{
var books = await RepositoryWrapper.Book.GetBooksAsync(authorId);
var bookDtoList = Mapper.Map<IEnumerable<BookDto>>(books); return bookDtoList.ToList();
}

重构 GetBook 方法与此类似

Task<Book> GetBookAsync(Guid authorId, Guid bookId);

public async Task<Book> GetBookAsync(Guid authorId, Guid bookId)
{
return await DbContext.Set<Book>()
.SingleOrDefaultAsync(book => book.AuthorId == authorId && book.Id == bookId);
} [HttpGet("{bookId}", Name = nameof(GetBookAsync))]
public async Task<ActionResult<BookDto>> GetBookAsync(Guid authorId, Guid bookId)
{
var book = await RepositoryWrapper.Book.GetBookAsync(authorId, bookId);
if (book == null)
{
return NotFound();
} var bookDto = Mapper.Map<BookDto>(book);
return bookDto;
}

当添加一个子级资源,将 BookForCreationDto 对象映射为 Book 后,还需要为其 AuthorId 属性设置值,否则创建失败

[HttpPost]
public async Task<IActionResult> AddBookAsync(Guid authorId, BookForCreationDto bookForCreationDto)
{
var book = Mapper.Map<Book>(bookForCreationDto);
book.AuthorId = authorId;
RepositoryWrapper.Book.Create(book);
if (!await RepositoryWrapper.Book.SaveAsync())
{
throw new Exception("创建资源 Book 失败");
} var bookDto = Mapper.Map<BookDto>(book);
return CreatedAtRoute(nameof(GetBookAsync), new {bookId = bookDto.Id}, bookDto);
}

对于更新子级资源或部分更新子级资源,处了检查父级、子级资源是否存在外,还应该使用 IMapper 接口中的 Map 方法的另一个重载

object Map(object source, object destination, Type sourceType, Type destinationType);

它能将源映射到一个已经存在的对象,重载是为了将 BookForUpdateDto 映射到已经从数据库中获取到的 Book 实体

[HttpPut("{bookId}")]
public async Task<IActionResult> UpdateBookAsync(Guid authorId, Guid bookId, BookForUpdateDto updateBook)
{
var book = await RepositoryWrapper.Book.GetBookAsync(authorId, bookId);
if (book == null)
{
return NotFound();
} Mapper.Map(updateBook, book, typeof(BookForUpdateDto), typeof(Book));
RepositoryWrapper.Book.Update(book);
if (!await RepositoryWrapper.Book.SaveAsync())
{
throw new Exception("更新资源 Book 失败");
} return NoContent();
}

部分更新的实现逻辑与此类似,不同的是获取需要部分更新的 Book 实体后,首先将它映射为 BookForUpdateDto 类型的对象,其次使用 JsonPatchDocument 的 ApplyTo 方法将更新信息应用到映射后的 BookForUpdateDto 对象,接着再将它映射到 Book 实体得到更新后的值

[HttpPatch("{bookId}")]
public async Task<IActionResult> PartiallyUpdateBookAsync(Guid authorId, Guid bookId, JsonPatchDocument<BookForUpdateDto> patchDocument)
{
var book = await RepositoryWrapper.Book.GetBookAsync(authorId, bookId);
if (book == null)
{
return NotFound();
} var bookUpdateDto = Mapper.Map<BookForUpdateDto>(book);
patchDocument.ApplyTo(bookUpdateDto, ModelState);
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
} Mapper.Map(bookUpdateDto, book, typeof(BookForUpdateDto), typeof(Book)); RepositoryWrapper.Book.Update(book);
if (!await RepositoryWrapper.Book.SaveAsync())
{
throw new Exception("更新资源 Book 失败");
} return NoContent();
}

本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。

欢迎转载、使用、重新发布,但务必保留文章署名 郑子铭 (包含链接: http://www.cnblogs.com/MingsonZheng/ ),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。

如有任何疑问,请与我联系 (MingsonZheng@outlook.com) 。

《ASP.ENT Core 与 RESTful API 开发实战》-- (第5章)-- 读书笔记(下)的更多相关文章

  1. 使用ASP.NET Core构建RESTful API的技术指南

    译者荐语:利用周末的时间,本人拜读了长沙.NET技术社区翻译的技术标准<微软RESTFul API指南>,打算按照步骤写一个完整的教程,后来无意中看到了这篇文章,与我要写的主题有不少相似之 ...

  2. 4类Storage方案(AS开发实战第四章学习笔记)

    4.1 共享参数SharedPreferences SharedPreferences按照key-value对的方式把数据保存在配置文件中,该配置文件符合XML规范,文件路径是/data/data/应 ...

  3. 菜单Menu(AS开发实战第四章学习笔记)

    4.5 菜单Menu Android的菜单主要分两种,一种是选项菜单OptionMenu,通过按菜单键或点击事件触发,另一种是上下文菜单ContextMenu,通过长按事件触发.页面的布局文件放在re ...

  4. [Android]《Android艺术开发探索》第一章读书笔记

    1. 典型情况下生命周期分析 (1)一般情况下,当当前Activity从不可见重新变为可见状态时,onRestart方法就会被调用. (2)当用户打开新的Activity或者切换到桌面的时候,回调如下 ...

  5. 温故知新,使用ASP.NET Core创建Web API,永远第一次

    ASP.NET Core简介 ASP.NET Core是一个跨平台的高性能开源框架,用于生成启用云且连接Internet的新式应用. 使用ASP.NET Core,您可以: 生成Web应用和服务.物联 ...

  6. 快读《ASP.NET Core技术内幕与项目实战》WebApi3.1:WebApi最佳实践

    本节内容,涉及到6.1-6.6(P155-182),以WebApi说明为主.主要NuGet包:无 一.创建WebApi的最佳实践,综合了RPC和Restful两种风格的特点 1 //定义Person类 ...

  7. 零基础ASP.NET Core WebAPI团队协作开发

    零基础ASP.NET Core WebAPI团队协作开发 相信大家对“前后端分离”和“微服务”这两个词应该是耳熟能详了.网上也有很多介绍这方面的文章,写的都很好.我这里提这个是因为接下来我要分享的内容 ...

  8. ASP.NET Core WebApi构建API接口服务实战演练

    一.ASP.NET Core WebApi课程介绍 人生苦短,我用.NET Core!提到Api接口,一般会想到以前用到的WebService和WCF服务,这三个技术都是用来创建服务接口,只不过Web ...

  9. 从 0 使用 SpringBoot MyBatis MySQL Redis Elasticsearch打造企业级 RESTful API 项目实战

    大家好!这是一门付费视频课程.新课优惠价 699 元,折合每小时 9 元左右,需要朋友的联系爱学啊客服 QQ:3469271680:我们每课程是明码标价的,因为如果售价为现在的 2 倍,然后打 5 折 ...

  10. Asp.Net Core 5 REST API - Step by Step

    翻译自 Mohamad Lawand 2021年1月19日的文章 <Asp.Net Core 5 Rest API Step by Step> [1] 在本文中,我们将创建一个简单的 As ...

随机推荐

  1. element-ui实现部分引用

    1.首先安装 babel-plugin-component组件: 2.修改babel.js配置 1 module.exports = { 2 presets: [ 3 '@vue/app', 4 [' ...

  2. location对象的方法

    location.assign() 跟href一样,可以跳转页面(也称为重定向页面). location.replace() 替换当前页面,因为不记录历史,所以不能后退页面. location.rel ...

  3. 无向图求桥 UVA 796

    ***桥的概念:无向连通图中,如果删除某边后,图变 成不连通,则称该边为桥.*** ***一条边(u,v)是桥,当且仅当(u,v)为树枝边,且 满足dfn(u)<low(v)(前提是其没有重边) ...

  4. Windows 平台 UTF-8 编码转换为本机编码

    std::string from_utf8(const std::string& src) { int n = MultiByteToWideChar(CP_UTF8, 0, src.c_st ...

  5. 使用Amber计算单点能三步走

    技术背景 Amber是一个在分子动力学中非常常用的一个软件,可以用于进行分子动力学模拟计算,可以与一些软件配合进行增强采样.这里我们简单介绍一下如何使用Amber去计算一个分子构象的单点势能值,及其对 ...

  6. 【C++】类成员冒号初始化以及构造函数内赋值

    From:https://blog.csdn.net/zj510/article/details/8135556 通常我们对类成员进行"初始化"有两种方式: 1. 构造函数后面跟冒 ...

  7. 0xGame 2023【WEEK2】Crypto全解

    中间的那个人 题目信息 from secret import flag from Crypto.Util.number import * from Crypto.Cipher import AES f ...

  8. [转帖]深度硬核文:Nginx的301重定向处理过程分析

    https://zhuanlan.zhihu.com/p/84539204 本文首发于公众号:js-mindmap 一,序言 "晚上九点,办公室里烟雾缭绕,工作进度依然没有什么进展.王二胖打 ...

  9. [转帖]062、监控指标之PD

    PD相关 Grafana 监控 PD -> PD Dashboard 是否存在异常状态的TiKV Grafana 监控 PD -> Region health 大表清理后,出现了大量的空r ...

  10. [转帖]Oracle、SQL Server、MySQL数据类型对比

    Oracle.SQL Server.MySQL数据类型对比 - 知乎 (zhihu.com) 1,标准SQL数据类型 BINARY 每个字符占一个字节 任何类型的数据都可存储在这种类型的字段中.不需数 ...