共用方式為


ASP.NET Web API 中的模型驗證 (機器翻譯)

本文介紹如何標註模型、使用標註進行資料驗證以及處理 Web API 中的驗證錯誤。 當用戶端將資料傳送到您的 Web API 時,您通常希望在進行任何處理之前驗證資料。

資料標註

在 ASP.NET Web API 中,您可以使用 System.ComponentModel.DataAnnotations 命名空間中的屬性來設定模型屬性的驗證規則。 請考慮下列模型:

using System.ComponentModel.DataAnnotations;

namespace MyApi.Models
{
    public class Product
    {
        public int Id { get; set; }
        [Required]
        public string Name { get; set; }
        public decimal Price { get; set; }
        [Range(0, 999)]
        public double Weight { get; set; }
    }
}

如果您在 ASP.NET MVC 中使用過模型驗證,這應該看起來很熟悉。 Required 屬性表示 Name 屬性不能為 Null。 Range 屬性規定 Weight 必須介於 0 到 999 之間。

假設用戶端傳送具有以下 JSON 表示形式的 POST 請求:

{ "Id":4, "Price":2.99, "Weight":5 }

您可以看到用戶端沒有包含標記為必填的 Name 屬性。 當 Web API 將 JSON 轉換為 Product 執行個體時,它會根據驗證屬性對 Product 進行驗證。 在控制器動作中,您可以檢查模型是否有效:

using MyApi.Models;
using System.Net;
using System.Net.Http;
using System.Web.Http;

namespace MyApi.Controllers
{
    public class ProductsController : ApiController
    {
        public HttpResponseMessage Post(Product product)
        {
            if (ModelState.IsValid)
            {
                // Do something with the product (not shown).

                return new HttpResponseMessage(HttpStatusCode.OK);
            }
            else
            {
                return Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState);
            }
        }
    }
}

模型驗證並不能保證用戶端資料的安全。 應用程式的其他層可能需要額外的驗證。 (例如,資料層可能會強制執行外鍵條件約束。)「將 Web API 與 Entity Framework 結合使用」教學課程中探討了其中一些問題。

「遞交不足」:當客戶遺漏某些屬性時,就會發生遞交不足的情況。 例如,假設用戶端傳送以下內容:

{"Id":4, "Name":"Gizmo"}

在這裡,用戶端沒有指定 PriceWeight 的值。 JSON 格式器為缺少的屬性指派預設值零。

程式碼片段的螢幕擷取畫面,其中包含 Product Store dot Models dot Product 的下拉式清單選項。

模型狀態有效,因為零是這些屬性的有效值。 這是否是問題取決於您的情境。 例如,在更新操作中,您可能想要區分「零」和「未設定」。若要強制用戶端設定值,請將屬性設為可為空,並設定 Required 屬性:

[Required]
public decimal? Price { get; set; }

過度遞交:用戶端傳送的資料也可能比您預期更多。 例如:

{"Id":4, "Name":"Gizmo", "Color":"Blue"}

在這裡,JSON 包含 Product 模型中不存在的屬性 (「顏色」)。 在這種情況下,JSON 格式器會忽略該值。 (XML 格式器執行相同的動作。) 如果您的模型具有您打算唯讀的屬性,則過度遞交會導致問題。 例如:

public class UserProfile
{
    public string Name { get; set; }
    public Uri Blog { get; set; }
    public bool IsAdmin { get; set; }  // uh-oh!
}

您不希望使用者更新 IsAdmin 屬性並將自己提升為管理員! 最安全的策略是使用與允許用戶端傳送的內容完全匹配的模型類別:

public class UserProfileDTO
{
    public string Name { get; set; }
    public Uri Blog { get; set; }
    // Leave out "IsAdmin"
}

注意

Brad Wilson 的部落格文章「ASP.NET MVC 中的輸入驗證與模型驗證」對遞交不足和過度遞交進行了很好的討論。 儘管這篇文章是關於 ASP.NET MVC 2 的,但這些問題仍然與 Web API 相關。

處理驗證錯誤

當驗證失敗時,Web API 不會自動向用戶端傳回錯誤。 由控制器的動作來檢查模型狀態並做出適當的反應。

您也可以建立一個動作篩選器來在呼叫控制器動作之前檢查模型狀態。 下列程式碼顯示一個範例:

using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http.Controllers;
using System.Web.Http.Filters;
using System.Web.Http.ModelBinding;

namespace MyApi.Filters
{
    public class ValidateModelAttribute : ActionFilterAttribute
    {
        public override void OnActionExecuting(HttpActionContext actionContext)
        {
            if (actionContext.ModelState.IsValid == false)
            {
                actionContext.Response = actionContext.Request.CreateErrorResponse(
                    HttpStatusCode.BadRequest, actionContext.ModelState);
            }
        }
    }
}

如果模型驗證失敗,此篩選器將傳回包含驗證錯誤的 HTTP 回應。 在這種情況下,不會呼叫控制器動作。

HTTP/1.1 400 Bad Request
Content-Type: application/json; charset=utf-8
Date: Tue, 16 Jul 2013 21:02:29 GMT
Content-Length: 331

{
  "Message": "The request is invalid.",
  "ModelState": {
    "product": [
      "Required property 'Name' not found in JSON. Path '', line 1, position 17."
    ],
    "product.Name": [
      "The Name field is required."
    ],
    "product.Weight": [
      "The field Weight must be between 0 and 999."
    ]
  }
}

若要將此篩選器套用至所有 Web API 控制器,請在設定期間將篩選器的執行個體新增至 HttpConfiguration.Filters 集合:

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        config.Filters.Add(new ValidateModelAttribute());

        // ...
    }
}

另一種選擇是將篩選器設定為單一控制器或控制器動作的屬性:

public class ProductsController : ApiController
{
    [ValidateModel]
    public HttpResponseMessage Post(Product product)
    {
        // ...
    }
}