《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) 。
《ASP.NET Core 与 RESTful API 开发实战》-- (第7章)-- 读书笔记(中)的更多相关文章
- 使用ASP.NET Core构建RESTful API的技术指南
译者荐语:利用周末的时间,本人拜读了长沙.NET技术社区翻译的技术标准<微软RESTFul API指南>,打算按照步骤写一个完整的教程,后来无意中看到了这篇文章,与我要写的主题有不少相似之 ...
- 4类Storage方案(AS开发实战第四章学习笔记)
4.1 共享参数SharedPreferences SharedPreferences按照key-value对的方式把数据保存在配置文件中,该配置文件符合XML规范,文件路径是/data/data/应 ...
- 菜单Menu(AS开发实战第四章学习笔记)
4.5 菜单Menu Android的菜单主要分两种,一种是选项菜单OptionMenu,通过按菜单键或点击事件触发,另一种是上下文菜单ContextMenu,通过长按事件触发.页面的布局文件放在re ...
- [Android]《Android艺术开发探索》第一章读书笔记
1. 典型情况下生命周期分析 (1)一般情况下,当当前Activity从不可见重新变为可见状态时,onRestart方法就会被调用. (2)当用户打开新的Activity或者切换到桌面的时候,回调如下 ...
- 温故知新,使用ASP.NET Core创建Web API,永远第一次
ASP.NET Core简介 ASP.NET Core是一个跨平台的高性能开源框架,用于生成启用云且连接Internet的新式应用. 使用ASP.NET Core,您可以: 生成Web应用和服务.物联 ...
- 快读《ASP.NET Core技术内幕与项目实战》WebApi3.1:WebApi最佳实践
本节内容,涉及到6.1-6.6(P155-182),以WebApi说明为主.主要NuGet包:无 一.创建WebApi的最佳实践,综合了RPC和Restful两种风格的特点 1 //定义Person类 ...
- 零基础ASP.NET Core WebAPI团队协作开发
零基础ASP.NET Core WebAPI团队协作开发 相信大家对“前后端分离”和“微服务”这两个词应该是耳熟能详了.网上也有很多介绍这方面的文章,写的都很好.我这里提这个是因为接下来我要分享的内容 ...
- ASP.NET Core WebApi构建API接口服务实战演练
一.ASP.NET Core WebApi课程介绍 人生苦短,我用.NET Core!提到Api接口,一般会想到以前用到的WebService和WCF服务,这三个技术都是用来创建服务接口,只不过Web ...
- 从 0 使用 SpringBoot MyBatis MySQL Redis Elasticsearch打造企业级 RESTful API 项目实战
大家好!这是一门付费视频课程.新课优惠价 699 元,折合每小时 9 元左右,需要朋友的联系爱学啊客服 QQ:3469271680:我们每课程是明码标价的,因为如果售价为现在的 2 倍,然后打 5 折 ...
- 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 ...
随机推荐
- 这才是你应该了解的Redis数据结构!
深入了解Redis数据结构 Redis,作为一种高性能的内存数据库,支持多种数据结构,从简单的字符串到复杂的哈希表.在这篇博文中,我们将深入探讨Redis的一些主要数据结构,并通过详细的例子展示它们的 ...
- C#树的实现
ddd /// <summary> /// 遍历,线索化等操作的接口 /// </summary> interface ITravelBinTree { void PreOrd ...
- 自动化测试复习巩固第一天,requests的用法
如何快速发送post请求 因为我用的python语言,所以大家需要在本地安装python语言和pycharm,如何安装请自行查找教程,这里不做过多赘述 这里需要提前下载安装好需要的第三方库reques ...
- java项目实践-jsp-filter-监听器-day19
目录 1. jsp 2. 过滤器 3. listener 监听器 1. jsp servle逻辑处理方便 html页面表现麻烦 jsp 页面表现方便 但是逻辑处理麻烦 JSP 是一种页面技术 JSP本 ...
- 碎碎念 | 20230326 · 与 SEU & 南传跆协共进晚餐
(碎碎念)今天晚上跟社团一起吃饭,南传的跆协来交流了.南传的人说 他们基本散养,没人正经自习 图书馆基本废弃,校园里有一个大舞台 每天表演,大家每天写剧本 / 演绎 / 拍摄 剪辑,天天喝庆功酒()然 ...
- 修改elasticsearch默认索引返回数
1. 背景 (1) 客户反映es查询只能返回10000个数据,而需求时返回1.9W个数据,因此需要设置对应索引的默认返回数index.max_result_window (2) 给客户部署的服务以do ...
- mvn 编译异常:Fatal error compiling: 无效的标记: -parameters
错误信息: [ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin::compile (defaul ...
- 08-Shell计算命令
1.expr命令 expr (evaluate expressions 的缩写),译为"表达式求值".Shell expr 是一个功能强大,并且比较复杂的命令,它除了可以实现整数计 ...
- 【SHELL】命令补全
# 指定文件 dodo_path=/home/skull/work/scripts/dodo echo "hello skull" ## COMP_WORDS 是一个 bash 内 ...
- [转帖]浏览器HTTP请求并发数和TCP连接的关系
https://cloud.tencent.com/developer/article/1518678 面试题目(头条): 网页中的图片资源为什么分放在不同的域名下? 浏览器与服务器建立一个TCP连接 ...