• 《ASP.ENT Core 与 RESTful API 开发实战》-- (第5章)-- 读书笔记(下)


    第 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) 。

  • 相关阅读:
    mongodb 记录
    php保存文件
    调用AngularJS的API
    angular修改数据
    大小写转换
    使用Properties类动态加载配置文件里的内容
    org.apache.commons.cli.Options
    Google guava和Apache commons
    orc格式文件
    shell的awk命令使用
  • 原文地址:https://www.cnblogs.com/MingsonZheng/p/13228124.html
Copyright © 2020-2023  润新知