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 与实体框架配合使用 探讨了其中一些问题。
“过帐不足”:当客户端遗漏某些属性时,将发生过低过帐。 例如,假设客户端发送以下内容:
{"Id":4, "Name":"Gizmo"}
此处,客户端未指定 Price
或 Weight
的值。 JSON 格式化程序将默认值零分配给缺少的属性。
模型状态有效,因为零是这些属性的有效值。 这是否是问题取决于你的方案。 例如,在更新操作中,可能需要区分“零”和“未设置”。若要强制客户端设置值,请将 属性设置为可以为 null 并设置 Required 属性:
[Required]
public decimal? Price { get; set; }
“过度过帐”:客户端还可以发送比预期 更多的 数据。 例如:
{"Id":4, "Name":"Gizmo", "Color":"Blue"}
此处,JSON 包含模型中不存在 Product
的属性 (“Color”) 。 在这种情况下,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)
{
// ...
}
}