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

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

  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. JavaScriptif while for switch流程控制 JS函数 内置对象

    1,if else语句 2,if else if else语句 3,switch语句 4,for循环 5,while循环 6,三元运算 7,JS中的函数 8,JS中的匿名函数 9,JS中的立即执行函数 ...

  2. 小白学正则表达式之 regexp

    1. 正则表达式介绍 正则表达式是程序文本处理常用的模板模式,按照解析正则表达式的引擎可将正则表达式分为 POSIX 基础正则表达式(Basic regular expression,BRE) 引擎和 ...

  3. 介绍这个库:C# Blazor中显示Markdown文件

    1 讲目的 前几天上线了一个在线Icon转换工具,为了让大家使用放心,改了点代码,在转换下载Icon图标后立即删除临时文件,并在工具下面贴上了工具的开发步骤和代码,大家看这样改是否合适,见Issue ...

  4. Laravel组件化开发学习笔记

    组件化开发就是基于组件来进行迭代开发,而不是从零开始开发 1.语法基础 组件开发的基础语法是命名空间. 可以使用魔法常量__NAMESPACE__可以直接获取当前命名空间的名称的字符串. 例如: &l ...

  5. 问题--链表指针传参,修改next指针只传值

    1.问题--链表指针传参,修改next指针只传值 Link_creat_head(&head, p_new);//将新节点加入链表 在这当中head头指针传的是地址,而p_new传的是值,这二 ...

  6. PS去除PDF中的WPS 编辑试用水印

    1. 问题 自己的学习资料.pdf被添加上了相关水印,导致阅读起来很不舒服 2. 解决方式(适用于白底黑字的pdf资料,去除水印) 2.1 初始图片如下: 2.2 打开PS,选择菜单栏中选择一项中的色 ...

  7. C:\Keil_v5\ARM\ARMCC\include\stdint.h contains an incorrect path.

    1.问题 在使用Keil uvison5打开例程代码进行学习时,发现部分.h文件无法读取 2.解决方法 1.找到如图的设置按钮(小锤子) 2.根据自己所用的是C/C++还是ARM选择(我这里是C/C+ ...

  8. [转帖]TPC-C 、TPC-H和TPC-DS区别

    https://zhuanlan.zhihu.com/p/339886289 针对数据库不同的使用场景TPC组织发布了多项测试标准. TPC-C: TPC Benchmark C于1992年7月获得批 ...

  9. 【转帖】linux 软连接的使用

    https://www.cnblogs.com/sueyyyy/p/10985443.html 软连接是linux中一个常用命令,它的功能是为某一个文件在另外一个位置建立一个同不的链接. 具体用法是: ...

  10. [转帖]⭐万字长篇超详细的图解Tomcat中间件方方面面储备知识⭐

    https://developer.aliyun.com/article/885079?spm=a2c6h.24874632.expert-profile.321.7c46cfe9h5DxWK 202 ...