如何将【ASP.NET Core】应用与 CancellationToken 对象进行有效绑定?

2026-05-22 19:071阅读0评论SEO资讯
  • 内容介绍
  • 文章标签
  • 相关推荐

本文共计1499个文字,预计阅读时间需要6分钟。

负责管理HTTP请求的上下文对象HttpContext,具有一个名为RequestAborted的属性。该属性类型为CancellationToken,用于表示客户端请求是否已取消。如果属性值为true,则表示请求已取消。

负责管理 HTTP 请求上下文的 HttpContext 对象有一个名为 RequestAborted 的属性。据其名思其义,就是可用来表示客户端请求是否已取消。

果然,它的类型是 CancellationToken,这家伙是结构类型,为啥强调是结构呢——因为是值类型啊。在访问 HTTP 的整个上下文传递过程,直接赋值会复制多个实例。所以,类库内部在传递此属性值时会用 object 类型的变量来引用它的值,嗯,对的,就是“装箱”。以引用类型的方式操作它,可以避免对象的复制而造成数据不统一。

具体可以看看 CancellationTokenModelBinder 类的源代码(命名空间:Microsoft.AspNetCore.Mvc.ModelBinding.Binders)。

public class CancellationTokenModelBinder : IModelBinder { /// <inheritdoc /> public Task BindModelAsync(ModelBindingContext bindingContext) { if (bindingContext == null) { throw new ArgumentNullException(nameof(bindingContext)); } // We need to force boxing now, so we can insert the same reference to the boxed CancellationToken // in both the ValidationState and ModelBindingResult. // // DO NOT simplify this code by removing the cast. var model = (object)bindingContext.HttpContext.RequestAborted; bindingContext.ValidationState.Add(model, new ValidationStateEntry() { SuppressValidation = true }); bindingContext.Result = ModelBindingResult.Success(model); return Task.CompletedTask; } }

以上内容,大伙伴们应该能看懂的。看不懂咋办?没事的,不要自卑,不必跳河,看不懂但知道怎么用就行。

重点看这一句:

var model = (object)bindingContext.HttpContext.RequestAborted;

把它强制转换为 object 类型再赋值,确保赋值后 CancellationToken 实例没有被复制。

如果在提交 HTTP 后,以及在服务器处理完毕返回消息给客户端之前,如果客户端关闭(取消)了连接(比如,关掉浏览器,单击“取消”请求,网络断了,路由器着火了等情况),那么,透过 HttpContext.RequestAborted 属性我们在服务器代码中就获得相关信息。说直接一点,就是 IsCancellationRequested 会返回 true。

老周暂不讲模型绑定的事,先看看这个 RequestAborted 属性如何使用。

来个示例。从前,有个 controller 名叫 Happy,它有两个儿子(action),老大叫 Index,老二叫 ChouJiang(抽奖)。Happy 家里开了个彩票店,老大 Index 负责门面,喜迎南北客;老二负责业务,包括把开奖结果告诉客人。

public class HappyController : Controller { // 用来随机生成幸运数字 private static readonly Random rand = new((int)DateTime.Now.ToBinary()); // 记录日志,可通过依赖注入解决实例化问题 private readonly ILogger logger; /// <summary> /// 构造函数 /// </summary> /// <param name="logfac">从依赖注入获取</param> public HappyController(ILoggerFactory logfac) { logger = logfac.CreateLogger("Demo Log"); } public IActionResult Index() { // 门面功夫,开门迎客 return View("~/views/TestView1.cshtml"); } public async Task<IActionResult> ChouJiang() { // 抽奖模拟中 int x = 5; int result = 0; // 抽五次,选最后一个幸运数字 while(x > 0) { // 如果连接挂了,直接拜拜 if(HttpContext.RequestAborted.IsCancellationRequested) { logger.LogInformation("请求已取消"); return NoContent(); } await Task.Delay(500); //模拟延时 x--; result = rand.Next(0, 1000);//生成随机数 } // 开大奖了 return Content($"<script>alert('幸运数字:{result}')</script>", "text/html", Encoding.UTF8); } }

此例中的核心是判断 HttpContext.RequestAborted.IsCancellationRequested 是否为 true。如果是,那么这一轮抽奖活动结束。

下面 Razor 代码是 Happy 彩票店的门面装修效果,请隔壁老王设计的。

@{ ViewBag.Title = "演示-1"; } <p>点击下面链接,开启虎年幸运大奖</p> <a target="_blank" asp-action="ChouJiang" asp-controller="Happy">抽奖</a>

把示例运行起来。

点击页面上的链接,如果你有足够的耐心,等其完成抽奖,会看到幸运数字。

如果你觉得没意思,在点击链接后,点击浏览器上的“X”,取消操作,会看到日志输出,表示连接断了/请求取消了。


动不动就去访问 HttpContext.RequestAborted.IsCancellationRequested 也不怎么方便,至少没有方便面方便。所以,咱们要做一进升级——使用模型绑定。

要求是:

  1. 绑定的对象类型是 CancellationToken
  2. 绑定目标可以是 action 方法参数,也可以是 Controller 的属性(MVC),或 Model Page 的属性(Razor Pages)。

于是,上面的抽奖代码可以这样改:

public async Task<IActionResult> ChouJiang(CancellationToken ct) { // …… while(x > 0) { // 如果连接挂了,直接拜拜 if(ct.IsCancellationRequested) { logger.LogInformation("请求已取消"); return NoContent(); } await Task.Delay(500); //模拟延时 x--; result = rand.Next(0, 1000);//生成随机数 } // …… }


也可以在 Controller 中定义属性来绑定。把本例进行修改。

// 这是属性 [BindProperty(SupportsGet = true)] public CancellationToken CancelTK { get; set; } public async Task<IActionResult> ChouJiang() { // …… while(x > 0) { // 如果连接挂了,直接拜拜 if(CancelTK.IsCancellationRequested) { //…… } await Task.Delay(500); //模拟延时 x--; result = rand.Next(0, 1000);//生成随机数 } // …… }

如果用属性来绑定,那么在属性上应用 BindProperty 特性是必须的。这里要把 SupportsGet 设置为 true,因为老周这个例子中,视图是点击链接后调用抽奖代码的,是以 HTTP-GET 方式请求的,而默认情况是 BindProperty 在 GET 方式时不进行绑定。所以,为了能顺利绑定,就得把 SupportsGet 改为 true;如果你用的是 POST 方式触发,就不用设置。

本文共计1499个文字,预计阅读时间需要6分钟。

负责管理HTTP请求的上下文对象HttpContext,具有一个名为RequestAborted的属性。该属性类型为CancellationToken,用于表示客户端请求是否已取消。如果属性值为true,则表示请求已取消。

负责管理 HTTP 请求上下文的 HttpContext 对象有一个名为 RequestAborted 的属性。据其名思其义,就是可用来表示客户端请求是否已取消。

果然,它的类型是 CancellationToken,这家伙是结构类型,为啥强调是结构呢——因为是值类型啊。在访问 HTTP 的整个上下文传递过程,直接赋值会复制多个实例。所以,类库内部在传递此属性值时会用 object 类型的变量来引用它的值,嗯,对的,就是“装箱”。以引用类型的方式操作它,可以避免对象的复制而造成数据不统一。

具体可以看看 CancellationTokenModelBinder 类的源代码(命名空间:Microsoft.AspNetCore.Mvc.ModelBinding.Binders)。

public class CancellationTokenModelBinder : IModelBinder { /// <inheritdoc /> public Task BindModelAsync(ModelBindingContext bindingContext) { if (bindingContext == null) { throw new ArgumentNullException(nameof(bindingContext)); } // We need to force boxing now, so we can insert the same reference to the boxed CancellationToken // in both the ValidationState and ModelBindingResult. // // DO NOT simplify this code by removing the cast. var model = (object)bindingContext.HttpContext.RequestAborted; bindingContext.ValidationState.Add(model, new ValidationStateEntry() { SuppressValidation = true }); bindingContext.Result = ModelBindingResult.Success(model); return Task.CompletedTask; } }

以上内容,大伙伴们应该能看懂的。看不懂咋办?没事的,不要自卑,不必跳河,看不懂但知道怎么用就行。

重点看这一句:

var model = (object)bindingContext.HttpContext.RequestAborted;

把它强制转换为 object 类型再赋值,确保赋值后 CancellationToken 实例没有被复制。

如果在提交 HTTP 后,以及在服务器处理完毕返回消息给客户端之前,如果客户端关闭(取消)了连接(比如,关掉浏览器,单击“取消”请求,网络断了,路由器着火了等情况),那么,透过 HttpContext.RequestAborted 属性我们在服务器代码中就获得相关信息。说直接一点,就是 IsCancellationRequested 会返回 true。

老周暂不讲模型绑定的事,先看看这个 RequestAborted 属性如何使用。

来个示例。从前,有个 controller 名叫 Happy,它有两个儿子(action),老大叫 Index,老二叫 ChouJiang(抽奖)。Happy 家里开了个彩票店,老大 Index 负责门面,喜迎南北客;老二负责业务,包括把开奖结果告诉客人。

public class HappyController : Controller { // 用来随机生成幸运数字 private static readonly Random rand = new((int)DateTime.Now.ToBinary()); // 记录日志,可通过依赖注入解决实例化问题 private readonly ILogger logger; /// <summary> /// 构造函数 /// </summary> /// <param name="logfac">从依赖注入获取</param> public HappyController(ILoggerFactory logfac) { logger = logfac.CreateLogger("Demo Log"); } public IActionResult Index() { // 门面功夫,开门迎客 return View("~/views/TestView1.cshtml"); } public async Task<IActionResult> ChouJiang() { // 抽奖模拟中 int x = 5; int result = 0; // 抽五次,选最后一个幸运数字 while(x > 0) { // 如果连接挂了,直接拜拜 if(HttpContext.RequestAborted.IsCancellationRequested) { logger.LogInformation("请求已取消"); return NoContent(); } await Task.Delay(500); //模拟延时 x--; result = rand.Next(0, 1000);//生成随机数 } // 开大奖了 return Content($"<script>alert('幸运数字:{result}')</script>", "text/html", Encoding.UTF8); } }

此例中的核心是判断 HttpContext.RequestAborted.IsCancellationRequested 是否为 true。如果是,那么这一轮抽奖活动结束。

下面 Razor 代码是 Happy 彩票店的门面装修效果,请隔壁老王设计的。

@{ ViewBag.Title = "演示-1"; } <p>点击下面链接,开启虎年幸运大奖</p> <a target="_blank" asp-action="ChouJiang" asp-controller="Happy">抽奖</a>

把示例运行起来。

点击页面上的链接,如果你有足够的耐心,等其完成抽奖,会看到幸运数字。

如果你觉得没意思,在点击链接后,点击浏览器上的“X”,取消操作,会看到日志输出,表示连接断了/请求取消了。


动不动就去访问 HttpContext.RequestAborted.IsCancellationRequested 也不怎么方便,至少没有方便面方便。所以,咱们要做一进升级——使用模型绑定。

要求是:

  1. 绑定的对象类型是 CancellationToken
  2. 绑定目标可以是 action 方法参数,也可以是 Controller 的属性(MVC),或 Model Page 的属性(Razor Pages)。

于是,上面的抽奖代码可以这样改:

public async Task<IActionResult> ChouJiang(CancellationToken ct) { // …… while(x > 0) { // 如果连接挂了,直接拜拜 if(ct.IsCancellationRequested) { logger.LogInformation("请求已取消"); return NoContent(); } await Task.Delay(500); //模拟延时 x--; result = rand.Next(0, 1000);//生成随机数 } // …… }


也可以在 Controller 中定义属性来绑定。把本例进行修改。

// 这是属性 [BindProperty(SupportsGet = true)] public CancellationToken CancelTK { get; set; } public async Task<IActionResult> ChouJiang() { // …… while(x > 0) { // 如果连接挂了,直接拜拜 if(CancelTK.IsCancellationRequested) { //…… } await Task.Delay(500); //模拟延时 x--; result = rand.Next(0, 1000);//生成随机数 } // …… }

如果用属性来绑定,那么在属性上应用 BindProperty 特性是必须的。这里要把 SupportsGet 设置为 true,因为老周这个例子中,视图是点击链接后调用抽奖代码的,是以 HTTP-GET 方式请求的,而默认情况是 BindProperty 在 GET 方式时不进行绑定。所以,为了能顺利绑定,就得把 SupportsGet 改为 true;如果你用的是 POST 方式触发,就不用设置。