前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >《ASP.NET Core 与 RESTful API 开发实战》-- (第7章)-- 读书笔记(中)

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

作者头像
郑子铭
发布2021-01-13 15:44:45
5640
发布2021-01-13 15:44:45
举报
文章被收录于专栏:DotNet NB && CloudNative

第 7 章 高级主题

7.2 并发

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

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

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

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

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

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

代码语言:javascript
复制
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 两个方法应用该特性

代码语言:javascript
复制
[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

代码语言:javascript
复制
Install-Package Microsoft.AspNetCore.Mvc.Versioning

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

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

接下来,添加一个 PersonController

代码语言:javascript
复制
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 设置

代码语言:javascript
复制
options.ApiVersionReader = new QueryStringApiVersionReader("ver");

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

代码语言:javascript
复制
[Route("api/v{version:apiVersion}/students")]

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

即可访问相应的版本接口

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

代码语言:javascript
复制
options.ApiVersionReader = new HeaderApiVersionReader("api-version");

在消息头添加 api-version 项

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

代码语言:javascript
复制
options.ApiVersionReader = new MediaTypeApiVersionReader();

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

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

代码语言:javascript
复制
options.ApiVersionReader = ApiVersionReader.Combine(
                    new MediaTypeApiVersionReader(),
                    new QueryStringApiVersionReader("api-version"));

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

代码语言:javascript
复制
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

代码语言:javascript
复制
[ApiVersion("1.0", Deprecated = true)]

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

代码语言:javascript
复制
options.Conventions.Controller<Controllers.V1.PersonController>()
                    .HasApiVersion(new ApiVersion(1,0));

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

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

代码语言:javascript
复制
protected ApiVersion RequestApiVersion => HttpContext.GetRequestedApiVersion();

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-07-18,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 DotNet NB 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 第 7 章 高级主题
    • 7.2 并发
      • 7.3 版本
      相关产品与服务
      Serverless HTTP 服务
      Serverless HTTP 服务基于腾讯云 API 网关 和 Web Cloud Function(以下简称“Web Function”)建站云函数(云函数的一种类型)的产品能力,可以支持各种类型的 HTTP 服务开发,实现了 Serverless 与 Web 服务最优雅的结合。用户可以快速构建 Web 原生框架,把本地的 Express、Koa、Nextjs、Nuxtjs 等框架项目快速迁移到云端,同时也支持 Wordpress、Discuz Q 等现有应用模版一键快速创建。
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档