ASP.NET Core MVC中的 [Required]與[BindRequired]


在開發ASP.NET Core MVC應用程序時,需要對控制器中的模型校驗數據有效性,元數據注釋(Data Annotations)是一個完美的解決方案。

元數據注釋最典型例子是確保API的調用者提供了某個屬性的值,在傳統的ASP.NET MVC中使用的是RequiredAttribute特性類。該屬性仍然可以在ASP.NET Core MVC中使用,但也提供了一個新的特性類BindRequiredAttribute

今天讓我們來看看它們之間的細微差別。

RequiredAttribute的典型用法

想象一下,您的模型是下面的BookOrder類。為了強制AuthorTitle屬性的值始終出現在傳入的請求中,並且值不能為空,我們使用RequiredAttribute修飾它們。

    public class BookOrder
    {
        [Required]
        public string Author { get; set; }
     
        [Required]
        public string Title { get; set; }
    }

這在傳統的ASP.NET MVC中是很常見方法。有了這個,您可以驗證ModelState是否存在錯誤,因為對這些屬性的驗證失敗會反映在這里。示例:

    [Route("bookorder")]
    public IActionResult PostBook([FromForm]BookOrder bookOrder)
    {
        if (!ModelState.IsValid)
        {
            return BadRequest(ModelState);
        }
     
        // 正常邏輯代碼
        return Content("OK");
    }

如果傳入的請求中缺少兩個必需的屬性(或者屬性的值為String.Empty),則響應將是一個400狀態碼,並將模型狀態錯誤序列化到響應中。

使用Postman進行測試,請求中只提供了Title屬性的值,而未提供Author屬性的值:

常有趣的問題,例如當我們添加類似數量的屬性時(類型為int)。在這種情況下,RequiredAttribute將無法正常工作,因為默認值為0,並且該屬性不可能為空。即使客戶端在不包括屬性值的情況下提交請求,模型實例的值都將為0。換句話說,RequiredAttribute不起作用。在我們的例子中,我們使用的是int,但是同樣適用於所有值的類型,例如DateTimeGuid

以前,在傳統的ASP.NET MVC中,解決這個問題的方法是使用一個可為空類型,如下所示:

    public class BookOrder
    {
        [Required]
        public string Author { get; set; }
     
        [Required]
        public string Title { get; set; }
     
        [Required]
        public int? Quantity { get; set; }
    }

雖然它解決了這個問題,但是這不是最優雅的方式。Quantity屬性不能為null,如果API的調用者未提供該屬性的值,則該值保持為null,配合RequiredAttribute特性可以達到我們的目的。

這種方法的缺點是,在正常邏輯代碼中,您需要直接訪問可為空的Value屬性。

    [Route("bookorder")]
    public IActionResult PostBook([FromForm]BookOrder bookOrder)
    {
        if (!ModelState.IsValid)
        {
            return BadRequest(ModelState);
        }
     
        // 正常邏輯代碼
        var requestedQuantity = bookOrder.Quantity.Value;
        
        return Content("OK");
    }

如果通過靜態分析工具來檢查代碼,因為它們找不不到空值檢測,所以會警告您可能出現空引用異常(即使在邏輯上,ModelState檢測是足夠的)。所以這並不是令人賞心悅目的方案。

如果在VS中安裝了Resharper工具,會顯示如下警告:

這就是BindRequiredAttribute產生的原因。它和RequiredAttribute的工作方式類似,它要求請求必須提示屬性的值 。

我們將模型改為如下結構,Quantity屬性為int類型,並使用[BindRequired]修飾:

    public class BookOrder
    {
        [Required]
        public string Author { get; set; }
     
        [Required]
        public string Title { get; set; }
     
        [BindRequired]
        public int Quantity { get; set; }
    }

使用Postman進行測試,在請求體不包括Quantity屬性的值:

測試結果為ModelState.IsValid返回的false,要求提供Quantity屬性的值。

RequiredAttribute與BindRequiredAttribute的差別

BindRequiredAttribute特性類只要求API的調用者提供該屬性的值,但不進行空值檢測,允許提供的值為空,例如我們將模型改為如下示例,所有屬性中都使用了BindRequiredAttribute特性類進行注釋:

    public class BookOrder
    {
        [BindRequired]
        public string Author { get; set; }
     
        [BindRequired]
        public string Title { get; set; }
     
        [BindRequired]
        public int Quantity { get; set; }
    }

使用Postman進行測試,在請求體包括這三個屬性名稱的參數,但是屬性的值均為空或默認值:

測試結果為正常,BindRequiredAttribute特性類只要求API的調用者提供該屬性的值,但不進行空值檢測,允許提供的值為空

RequiredAttribute 與 BindRequiredAttribute 混用問題

當然,在代碼中混合使用[Required][BindRequired]並不是一個最令人滿意的效果。值得慶幸的是,ASP.NET Core MVC為您提供了足夠的靈活性來改變RequiredAttribute的行為,強制它包含BindRequiredAttribute的行為。

您可以通過實現自己的IBindingMetadataProvider並在您的應用程序全局范圍內注冊來實現這一目標。

    public class RequiredBindingMetadataProvider : IBindingMetadataProvider
    {
        public void CreateBindingMetadata(BindingMetadataProviderContext context)
        {
            if (context.PropertyAttributes?.OfType<RequiredAttribute>().Any() ?? false) {
                context.BindingMetadata.IsBindingRequired = true;
            }
        }
    }

在全局范圍內注冊它:

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddMvc(o =>
        {
            o.ModelMetadataDetailsProviders.Add(new RequiredBindingMetadataProvider());
        });
    }

相關補充內容

1、ASP.NET Core MVC還提供了BindNeverAttribute特性類用於指定該屬性不進行模型綁定。例如:您擁有一個像IsAdmin這樣的屬性,這需要通過服務端來指定,而不是通過客戶提交的數據來指定;
2、如果客戶端提交的是Json格式的數據,BindRequiredAttributeBindNeverAttribute特性類就不會起任何作用,這是因為模型直接通過Json.Net反序列化來創建,MVC框架並不知道屬性的值從何而來。

上面補充的兩點,感興趣的同學可以自己進行測試。

總結

  • RequiredAttribute特性類的行為在傳統的ASP.NET MVC與ASP.NET Core MVC是一致的,校驗屬性的值不能為null
  • RequiredAttribute特性類對於值類型的屬性,就沒有任何效果了,所以在編碼的過程將屬性的類型改為可為空類型,不過編碼的過程就比較糾結了,需要訪問可為空類型的Value屬性,而且靜態代碼檢測工具會警告需要進行非空的校驗;
  • BindRequiredAttribute特性類可以解決值類型默認值的問題,強制要求客戶端提交請求時,必須包含屬性的值,不過屬性的值可以為空,不進行非空的校驗;
  • RequiredAttributeBindRequiredAttribute混合使用的情況下,我們也提供了優雅的方法,讓RequiredAttribute包含BindRequiredAttribute的行為。


免責聲明!

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



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