【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 方式觸發,就不用設置。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM