8

【ASP.NET Core】绑定到 CancellationToken 对象 - 东邪独孤

 2 years ago
source link: https://www.cnblogs.com/tcjiaan/p/15972739.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

【ASP.NET Core】绑定到 CancellationToken 对象

负责管理 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>

把示例运行起来。

img

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

img

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

img


动不动就去访问 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 方式触发,就不用设置。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK