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


    第 4 章 资源操作

    4.5 创建资源

    由于创建资源的 Id 会在服务端生成,因此在创建资源时,不建议使用与获取数据时相同的 DTO,而要单独创建一个新的 DTO 类,并通过数据注解特性对相应 的属性做限制

    namespace Library.API.Models
    {
        public class AuthorForCreationDto
        {
            [Required(ErrorMessage = "必须提供姓名")]
            [MaxLength(20, ErrorMessage = "姓名的最大长度为20个字符")]
            public string Name { get; set; }
            
            public int Age { get; set; }
            
            [EmailAddress(ErrorMessage = "邮箱的格式不正确")]
            public string Email { get; set; }
        }
    }
    

    在 IAuthorRepository 添加用于资源添加的方法

    void AddAuthor(AuthorDto author);
    

    并在 AuthorMockRepository 中实现

    public void AddAuthor(AuthorDto author)
    {
        author.Id = Guid.NewGuid();
        LibraryMockData.Current.Authors.Add(author);
    }
    

    接着在 AuthorController 中添加用于创建 Author 的 Action

    [HttpPost]
    public IActionResult CreateAuthor(AuthorForCreationDto authorForCreationDto)
    {
        var authorDto = new AuthorDto
        {
            Name = authorForCreationDto.Name,
            Age = authorForCreationDto.Age,
            Email = authorForCreationDto.Email
        };
    
        AuthorRepository.AddAuthor(authorDto);
    
        // 返回201 Created 状态码,并在响应消息头中包含 Location 项,它的值是新创建资源的 URL
        // 第一个参数是要调用 Action 的路由名称
        // 第二个参数是包含要调用 Action 所需要参数的匿名对象
        // 最后一个参数是代表添加成功后的资源本身
        return CreatedAtRoute(nameof(GetAuthor), new {authorId = authorDto.Id}, authorDto);
    }
    

    由于 CreatedAtRoute 方法要生成指向 GetAuthor 方法的 URL,因此还需要为这个 Action 定义一个路由名称

    [HttpGet("{authorId}", Name = nameof(GetAuthor))]
    public ActionResult<AuthorDto> GetAuthor(Guid authorId)
    

    创建子级资源,创建 DTO,在 IBookRepository 中接口添加方法,在 BookMockRepository 中实现类实现接口方法

    namespace Library.API.Models
    {
        public class BookForCreationDto
        {
            public string Title { get; set; }
            public string Description { get; set; }
            public int Pages { get; set; }
        }
    }
    
    void AddBook(BookDto book);
    
    public void AddBook(BookDto book)
    {
        LibraryMockData.Current.Books.Add(book);
    }
    

    在 BookController 添加 Action

    [HttpPost]
    public IActionResult AddBook(Guid authorId, BookForCreationDto bookForCreationDto)
    {
        if (!AuthorRepository.IsAuthorExists(authorId))
        {
            return NotFound();
        }
    
        var newBook = new BookDto
        {
            Id = Guid.NewGuid(),
            Title = bookForCreationDto.Title,
            Description = bookForCreationDto.Description,
            Pages = bookForCreationDto.Pages,
            AuthorId = authorId
        };
    
        BookRepository.AddBook(newBook);
    
        return CreatedAtRoute(nameof(GetBook), new {authorId = authorId, bookId = newBook.Id}, newBook);
    }
    

    同样为 GetBook 方法指定路由名称

    [HttpGet("{bookId}", Name = nameof(GetBook))]
    public ActionResult<BookDto> GetBook(Guid authorId, Guid bookId)
    

    4.6 删除资源

    在 IBookRepository 中接口添加方法,在 BookMockRepository 中实现类实现接口方法

    void DeleteBook(BookDto book);
    
    public void DeleteBook(BookDto book)
    {
        LibraryMockData.Current.Books.Remove(book);
    }
    

    在 BookController 添加 Action

    [HttpDelete("{bookID}")]
    public IActionResult DeleteBook(Guid authorId, Guid bookId)
    {
        if (!AuthorRepository.IsAuthorExists(authorId))
        {
            return NotFound();
        }
    
        var book = BookRepository.GetBookForAuthor(authorId, bookId);
        if (book == null)
        {
            return NotFound();
        }
    
        BookRepository.DeleteBook(book);
        return NoContent();
    }
    

    删除父与子,当删除一个父级资源,所有相关子级资源也一同删除

    在 IAuthorRepository 中接口添加方法,在 AuthorMockRepository 中实现类实现接口方法

    void DeleteAuthor(AuthorDto author);
    
    public void DeleteAuthor(AuthorDto author)
    {
        LibraryMockData.Current.Books.RemoveAll(book => book.AuthorId == author.Id);
        LibraryMockData.Current.Authors.Remove(author);
    }
    

    4.7 更新资源

    从 HTTP 方法的角度来看,更新资源有两种情况:

    • 整体更新,PUT 方法完成
    • 部分更新,PATCH 方法完成

    整体更新,创建 DTO,在 IBookRepository 中接口添加方法,在 BookMockRepository 中实现类实现接口方法

    namespace Library.API.Models
    {
        public class BookForUpdateDto
        {
            public string Title { get; set; }
            public string Description { get; set; }
            public int Pages { get; set; }
        }
    }
    
    void UpdateBook(Guid authorId, Guid bookId, BookForUpdateDto book);
    
    public void UpdateBook(Guid authorId, Guid bookId, BookForUpdateDto book)
    {
        var originalBook = GetBookForAuthor(authorId, bookId);
    
        originalBook.Title = book.Title;
        originalBook.Pages = book.Pages;
        originalBook.Description = book.Description;
    }
    

    在 BookController 添加 Action

    [HttpPut("{bookId}")]
    public IActionResult UpdateBook(Guid authorId, Guid bookId, BookForUpdateDto updateBook)
    {
        if (!AuthorRepository.IsAuthorExists(authorId))
        {
            return NotFound();
        }
    
        var book = BookRepository.GetBookForAuthor(authorId, bookId);
        if (book == null)
        {
            return NotFound();
        }
    
        BookRepository.UpdateBook(authorId, bookId, updateBook);
        return NoContent();
    }
    

    部分更新,PATCH 方法的请求正文使用的是 JSON Patch 文档格式

    文档由一个数组构成,数组中的每个元素代表一个更改项,每一项包括3项:

    • op:操作类型
    • path:对象的属性名
    • value:对象的值

    op 的值包括以下6种:

    • add
    • remove
    • replace
    • copy
    • move
    • test

    因此以下内容会更新图书资源的 Title 属性,并清空 Description 属性

    [
      {
        "op": "replace",
        "path": "/title",
        "value": "Book 1 - Updated"
      },
      {
        "op": "remove",
        "path": "/description" 
      }
    ]
    

    在 BookController 添加 Action

    [HttpPatch("{bookId}")]
    public IActionResult PartiallyUpdateBook(Guid authorId, Guid bookId, JsonPatchDocument<BookForUpdateDto> patchDocument)
    {
        if (!AuthorRepository.IsAuthorExists(authorId))
        {
            return NotFound();
        }
    
        var book = BookRepository.GetBookForAuthor(authorId, bookId);
        if (book == null)
        {
            return NotFound();
        }
    
        var bookToPatch = new BookForUpdateDto
        {
            Title = book.Title,
            Description = book.Description,
            Pages = book.Pages
        };
    
        patchDocument.ApplyTo(bookToPatch, ModelState);
        if (!ModelState.IsValid)
        {
            return BadRequest(ModelState);
        }
    
        BookRepository.UpdateBook(authorId, bookId, bookToPatch);
        return NoContent();
    }
    

    第三个参数类型为 JsonPatchDocument,它的值会从请求信息的正文中获取

    ApplyTo 方法将相应的修改操作应用到新建的对象上,并将可能出现的错误记录到 ModelStateDictionary 中,使用该方法需要添加引用

    dotnet add package Microsoft.AspNetCore.Mvc.NewtonsoftJson
    

    4.8 内容协商

    RESTful API 应该根据客户端的需要返回不同格式的数据

    客户端指明格式是在其请求消息的消息头中添加 Accept 项,它的值是一个 MIME 类型,如 application/xml

    如果支持返回此格式数据则直接返回,不支持则返回 406 NotAcceptable 状态码

    以上过程称为内容协商

    ASP.NET Core MVC 中,对于不支持的 Accept 类型返回 406 NotAcceptable 这一配置项默认为 false,因此它会返回默认格式,可以在 ConfigureService 方法中添加 MVC 服务时配置

    services.AddMvc(configure =>
    {
        configure.ReturnHttpNotAcceptable = true;
    });
    

    Formatter 是 ASP.NET Core 中用于处理数据输出或输入格式的组件,它分为两类:输出 Formatter 和输入 Formatter

    前者满足 HTTP 请求消息头的 Accept 项,后者匹配 HTTP 请求消息头的 Content-Type 项

    要使服务器能够返回 XML 格式的数据,只要将能够输出 XML 格式数据的 Formatter 添加到输出 Formatter 集合中即可

    services.AddMvc(configure =>
    {
        configure.ReturnHttpNotAcceptable = true;
        configure.OutputFormatters.Add(new XmlSerializerOutputFormatter());
    });
    

    services.AddMvc() 方法返回 IMvcBuilder 接口,可以直接调用扩展方法将 XML 格式数据输入输出都添加进来

    services.AddMvc(configure =>
    {
        configure.ReturnHttpNotAcceptable = true;
        //configure.OutputFormatters.Add(new XmlSerializerOutputFormatter());
    }).AddXmlSerializerFormatters();
    

    对于特殊格式数据,需要创建自定义 Formatter,继承自 TextOutputFormatter 类或 TextInputFormatter 类

    知识共享许可协议

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

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

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

  • 相关阅读:
    TextView文字排版问题:
    Cent OS 6 主机名设置
    windows server 时间同步
    DELL服务器SAS 5 I_R 完全配置手册
    SAS 5/iR Adapter 驱动下载
    U盘加载硬盘控制卡驱动安装Windows 2003 指南
    邮件客户端导入邮件通讯录地址薄
    Symantec System Recovery
    windows server 备份与还原
    Acronis 备份使用
  • 原文地址:https://www.cnblogs.com/MingsonZheng/p/13205885.html
Copyright © 2020-2023  润新知