• 《ASP.NET Core 与 RESTful API 开发实战》-- (第7章)-- 读书笔记(中)


    第 7 章 高级主题

    7.2 并发

    当两个用户获取同一个资源后,再同时修改该资源,就会导致并发问题

    常见实现并发的方法有以下两种:

    • 保守式并发控制,每次修改资源,都锁定资源
    • 开放式并发控制,每次修改资源,将获取资源时得到的资源散列值一并提交给服务器,判断是否有效,有效则意味着资源未被修改

    由于 HTTP 无状态,对于 RESTful API 应用程序来说,只能使用开放式并发控制,可以使用上一节提到的 ETag 来实现

    接下来为图书资源更新与部分更新实现并发控制

    对于 PUT 或 PATCH 请求,必须检查客户端的请求消息头是否包含 If-Match 消息头,可以通过过滤器判断

    namespace Library.API.Filters
    {
        public class CheckIfMatchHeaderFilterAttribute : ActionFilterAttribute
        {
            public override Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
            {
                if (!context.HttpContext.Request.Headers.ContainsKey(HeaderNames.IfMatch))
                {
                    context.Result = new BadRequestObjectResult(new ApiError
                    {
                        Message = "必须提供 If-Match 消息头"
                    });
                }
    
                return base.OnActionExecutionAsync(context, next);
            }
        }
    }
    

    接着在 BookController 的 UpdateBookAsync 和 PartiallyUpdateBookAsync 两个方法应用该特性

    [HttpPut("{bookId}")]
    [CheckIfMatchHeaderFilter]
    public async Task<IActionResult> UpdateBookAsync(Guid authorId, Guid bookId, BookForUpdateDto updateBook)
    {
        var book = await RepositoryWrapper.Book.GetBookAsync(authorId, bookId);
        if (book == null)
        {
            return NotFound();
        }
    
        // 资源已被修改,返回412
        var entityHash = HashFactory.GetHash(book);
        if (Request.Headers.TryGetValue(HeaderNames.IfMatch, out var requestETag) && requestETag != entityHash)
        {
            return StatusCode(StatusCodes.Status412PreconditionFailed);
        }
    
        Mapper.Map(updateBook, book, typeof(BookForUpdateDto), typeof(Book));
        RepositoryWrapper.Book.Update(book);
        if (!await RepositoryWrapper.Book.SaveAsync())
        {
            throw new Exception("更新资源 Book 失败");
        }
    
        // 资源未被修改,更新散列值
        var entityNewHash = HashFactory.GetHash(book);
        Response.Headers[HeaderNames.ETag] = entityNewHash;
    
        return NoContent();
    }
    

    PartiallyUpdateBookAsync 逻辑同上

    7.3 版本

    指定版本的方法有两种:

    • 使用 [ApiVersion] 特性
    • 使用版本约定特性

    ASP.NET Core MVC 默认的方式是使用查询字符串,参数名为 api-version

    添加nuget

    Install-Package Microsoft.AspNetCore.Mvc.Versioning
    

    然后添加 API 版本服务,在 ConfigureServices 中

    services.AddApiVersioning(options =>
    {
        // 指明当客户端未提供版本时是否使用默认版本,默认为false
        options.AssumeDefaultVersionWhenUnspecified = true;
        // 指明了默认版本
        options.DefaultApiVersion = new ApiVersion(1, 0);
        // 指明是否在HTTP响应消息头中包含api-supported-versions和api-deprecated-versions这两项
        options.ReportApiVersions = true;
    });
    

    接下来,添加一个 PersonController

    using Microsoft.AspNetCore.Mvc;
    
    namespace Library.API.Controllers.V1
    {
        [Microsoft.AspNetCore.Components.Route("api/person")]
        [ApiVersion("1.0")]
        public class PersonController : ControllerBase
        {
            [HttpGet]
            public ActionResult<string> Get() => "Result from v1";
        }
    }
    
    namespace Library.API.Controllers.V2
    {
        [Microsoft.AspNetCore.Components.Route("api/person")]
        [ApiVersion("2.0")]
        public class PersonController : ControllerBase
        {
            [HttpGet]
            public ActionResult<string> Get() => "Result from v2";
        }
    }
    

    运行程序,访问 http://localhost:5001/api/person

    结果返回 Result from v1,因为默认版本1.0

    访问 http://localhost:5001/api/person?api-version=2.0

    结果返回 Result from v2

    参数名 api-version 可改为自定义参数名,通过 ApiVersionReader 设置

    options.ApiVersionReader = new QueryStringApiVersionReader("ver");
    

    使用 URL 路径形式来访问指定版本 API,需要为 Controller 修改路由

    [Route("api/v{version:apiVersion}/students")]
    

    运行程序,访问 http://localhost:5001/api/v2/students

    即可访问相应的版本接口

    使用自定义 HTTP 消息头访问指定 API

    options.ApiVersionReader = new HeaderApiVersionReader("api-version");
    

    在消息头添加 api-version 项

    还可以通过媒体类型来获取 API

    options.ApiVersionReader = new MediaTypeApiVersionReader();
    

    在请求消息头添加 Content-Type,它的值为 application/json;v=2

    如果要同时支持多种方式,则可以使用 Combine

    options.ApiVersionReader = ApiVersionReader.Combine(
                        new MediaTypeApiVersionReader(),
                        new QueryStringApiVersionReader("api-version"));
    

    除了 Controller 级别的版本外,还可以创建 Action 级别的版本

    namespace Library.API.Controllers
    {
        [Route("api/news")]
        [ApiVersion("1.0")]
        [ApiVersion("2.0")]
        public class NewController : ControllerBase
        {
            [HttpGet]
            public ActionResult<string> Get() => "Result from v1";
    
            [HttpGet, MapToApiVersion("2.0")]
            public ActionResult<string> GetV2() => "Result from v2";
        }
    }
    

    先前的版本不需要时,可以将 Deprecated 属性设置为 true

    [ApiVersion("1.0", Deprecated = true)]
    

    除了特性外,ASP.NET Core MVC 还支持使用约定的方式来指定

    options.Conventions.Controller<Controllers.V1.PersonController>()
                        .HasApiVersion(new ApiVersion(1,0));
    

    相比特性,这种方式的优点是能够集中地管理应用程序所有 API 的版本信息,还可以灵活、动态地为 API 配置版本

    在程序中获取客户端请求版本信息

    protected ApiVersion RequestApiVersion => HttpContext.GetRequestedApiVersion();
    

    知识共享许可协议

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

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

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

  • 相关阅读:
    Navicat for MySQL破解版安装
    LACP学习笔记
    MUX VLAN
    Beyond Compare用于文件比较还是蛮好的选择,特别是我们程序袁用于比较两个项目的时候,最初使用的是Beyond Compare3一直用着挺好的,几年前更新了版本4,用着用着就提示试用期30天已过期,于是我尝试如下步骤:
    思科交换机如何进行备份与还原?
    vSphere ESXi 6.7 注册码(有效)
    VMware ESXi 6.7密码正确不能登录
    Esxi 6.5 6.7的root密码经过一段时间就不可用的解决方法
    Windows Server 2012 R2 安装密钥
    ubuntu 16 添加多个IP
  • 原文地址:https://www.cnblogs.com/MingsonZheng/p/13334252.html
Copyright © 2020-2023  润新知