问题

想要 ASP.NET Web API 执行模型验证,同时可以和 ASP.NET MVC 共享一些验证逻辑。

解决方案

ASP.NET Web API 与 ASP.NET MVC 支持一样的验证机制,都是通过System.ComponentModel.DataAnnoataions 的属性验证。使用框架提供的相关验证属性,已足够来用来验证模型。

想要更细粒度的验证,我们可以选择在我们的模型中实现 IValudateObject(来自于System.ComponentModel.DataAnnotations)。如果所有的属性都验证通过,ASP.NET Web API 将会调用接口的Validate 方法,在这里我们可以进行更进一步的进行实体验证。这是和 MVC 里面的行为一样,并且,我们甚至可以在 Web API 和 MVC 中使用同一个 DTO。

还有另一种方法,就是可以使用一个叫做 FluentValidation(NuGet 中可以下载FluentValidation)的第三方程序库,他可以构建更强大的验证场景。在这样的情况下,我们仍然需在我们的模型中实现 IValidateObject 接口,同时需要依赖于FluentValidation 验证器,而不是内嵌的验证逻辑。

小提示 ASP.NET Web API 的验证行为在跨宿主机上是相同的。

工作原理

为了从 HTTP 请求 Body 中读取的模型并执行验证,ASP.NET Web API 依赖于一个 IBodyModelValidator 的服务。接口的大致描述如清单 1-17 所示,然而,他是一个可替代的服务,正常情况下,默认实现(DefaultBodyModelValidator)足够我们使用,在HttpConfiguration 被设置为自启动。

清单 1-17. IBodyModelValidator 接口

1
2
3
4
5
public interface IBodyModelValidator
{
    bool Validate(object model, Type type, ModelMetadataProvider metadataProvider,
    HttpActionContext actionContext, string keyPrefix);
}

有一个叫做FormatrtParameterBinding 的服务,在 HTTP 请求 Body 绑定到 Action 参数的处理请求时,DefaultBodyModelValidator 的 Validate 方法会被调用。对于验证程序,他会递归验证整个对象图谱,验证每一个属性以及嵌套属性。Web API 通过使用DataAnnotationModelValidatorProviderr 来支持声明。如果我们的模型使用WCF 方式的 DataMemberAttribute 声明,那么,我们需要使用框架的 DataMemberValidatorProvider。

最后,我们的模型可以实现IValidatableObject 接口,这个接口只暴露了一个简单的方法如清单1-18所示。如果实现了接口,那就需要我们自己提供额外的验证逻辑。只要所有的属性验证通过,ASP.NET Wwb API 就会调用IValidateableObject接口的 Validate 方法,

清单1-18. IValidateableObject 接口的定义

1
2
3
4
public interface IValidateableObject
{
    IEnumerable<ValidationResult> Validate(ValidationContext validationContext);
}

验证结果是通过 ASP.NET Web API 的  ModelStateDictionary 形式表示,在这里 ModelState 也是可以用的。这个和 ASP.NET MVC 中的概念是完全一样的,但是使用的对象是不同的,因为 Web API 使用自己版本的System.Web.Http.Modelbinding。ModelStateDictionary 暴露了IsValid 属性,这个属性可以用来检查 Action 内Model 验证的状态。

声明的验证机制也很好的整合到了 ASP.NET Web API Help Page,可以提供对 API 语义上的描述。我们将会在7-11 的时候详细讨论他。

小提示 在 API 中最好的做法是使用不同的模型作为 Request 和Response 实体。例如,实体 ID 一般仅仅是 Response 模型需要的,如果 Request 中需要的话,是可以从 URI 中拿到的。

代码

清单 1-19 展示了一个模型有多种验证的情况:

RequiredAttribute,MaxLengthAttribute 和

RangeAttribute。接下来,我们就可以利用 ModelState 来验证 Controller 中的验证状态,同时响应适当的提示信息给调用端。

清单 1-19. 简单的 Web API 模型验证

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class Album
{
    public int Id { getset; }
    [Required(ErrorMessage = "{0} is required")]
    [MaxLength(30)]
    public string Artist { getset; }
    [Required(ErrorMessage = "{0} is required")]
    [MaxLength(40)]
    public string Title { getset; }
    [Range(0, 10, ErrorMessage = "{0} in the range of {1}-{2} is required.")]
    public int Rating { getset; }
}
public class AlbumController : ApiController
{
    public HttpResponseMessage Post(Album album)
    {
        if (!ModelState.IsValid)
        {
            throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.BadRequest,
            ModelState));
        }
        //omitted for brevity
    }
}

负责处理 ModelState 代码的一般验证可以很容易从 Controller 提取到成公共的部分,使其可以被很好的重用,不过这一部分我们将在 5-4 的时候再详细介绍。

现在,我们考虑一下这个场景,如果我们要在模型上增加增加两个额外的属性 Rating 和 Starred,同时扩展模型验证,验证的要求是这两个属性至少有一个是必填的。虽然,在两个属性之间纠缠的验证很难使用声明的方式来表示,但是,不要忘记 IValidateableObject 可以帮我们。我们可以使用接口中的 Validata 的方法去检查整个模型的状态,同时返回相应的 ValidationResult。我们要做的修改如清单 1-20 所示的代码。

清单 1-20. 修改 ASP.NET Web API 依赖于 IValidateableObject 的验证

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class Album : IValidatableObject
{
    public int Id { getset; }
  
    [Required(ErrorMessage = "{0} is required")]
    [MaxLength(30)]
    public string Artist { getset; }
  
    [Required(ErrorMessage = "{0} is required")]
    [MaxLength(40)]
    public string Title { getset; }
  
    public int? Rating { getset; }
    public bool? Starred { getset; }
  
    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
        if (!(Rating.HasValue && Rating > 0 && Rating < 10) || (Starred.HasValue && Starred.Value))
        {
            yield return new ValidationResult("You must set either the Rating in the 0-9 range orStarred flag.");
        }
    }
}

[水煮 ASP.NET Web API2 方法论](1-6)Model Validation的更多相关文章

  1. [水煮 ASP.NET Web API2 方法论](3-9)空气路由的设置

    阅读导航 问题 解决方案 工作原理 代码演示 在此解释一下,空气路由,是本人臆想出来,觉着更能表达 IgnoreRoute 的意图,如果看着辣眼睛^^,请见谅. 问题 我们在之定义过集中式路由,集中式 ...

  2. [水煮 ASP.NET Web API2 方法论](1-5)ASP.NET Web API Scaffolding(模板)

    问题 我们想快速启动一个 ASP.NET Web API 解决方案. 解决方案 APS.NET 模板一开始就支持 ASP.NET Web API.使用模板往我们的项目中添加 Controller,在我 ...

  3. [水煮 ASP.NET Web API2 方法论](3-8)怎样给指定路由配置处理器

    阅读导航 问题 解决方案 工作原理 代码演示 问题 如果仅仅针对指定的路由进行某些特定的消息处理,而不是应用于所有路由,我们应该怎么做呢? 解决方案 ASP.NET WEB API 的很多功能都内建了 ...

  4. [水煮 ASP.NET Web API2 方法论](3-7)默认 Action 请求方式以及 NonActionAttribute

    问题 在 Controller 中有一个 public 的方法,但是又不想将这个 publlic 方法暴露成为一个 API. 解决方案 ASP.NET Web API 中,正常是通过 HTTP 谓词来 ...

  5. [水煮 ASP.NET Web API2 方法论](3-6)万能路由

    问题 定义什么样的路由,可以不会受请求参数类型和数量的限制,而被全部捕获? 解决方案 在路由模板中,给参数添加一个"*"前缀,例如 {*param},只要请求的 URL 能够和路由 ...

  6. [水煮 ASP.NET Web API2 方法论](3-5)路由约束

    问题 怎么样限制路由中参数的值. 解决方案 ASP.NET WEB API 允许我们通过 IHttpRouteConstraint 接口设置路由约束.集中式路由和直接式路由都可以使用 IHttpRou ...

  7. [水煮 ASP.NET Web API2 方法论](3-4)设置路由可选项

    问题 怎么样创建一个路由,不管客户端传不传这个参数,都可以被成功匹配. 解决方案 ASP.NET WEB API 的集中式路由和属性路由都支持路由声明可选参数. 在用集中式路由中可以通过 RouteP ...

  8. [水煮 ASP.NET Web API2 方法论](3-3)路由默认值

    问题 如何为路由中参数设置默认值. 解决方案 不管使用属性路由还是集中式路由,ASP.NET WEB API 都可以很方便的为路由定义默认参数.在每次客户端请求的时候,如果客户端没有传这些参数,框架会 ...

  9. [水煮 ASP.NET Web API2 方法论](3-2)直接式路由/属性路由

    问题 怎么样可以使用更贴近资源(Controller,Action)的方式定义路由. 解决方案 可以使用属性路由直接在资源级别声明路由.只要简单的在 Action 上使用属性路由 RouteAttri ...

  10. [水煮 ASP.NET Web API2 方法论](3-1)集中式路由

    问题 怎样集中的定义路由 解决方案 通过调用 HttpRouteCollectionExtension 类中的 MapHttpRoute 扩展方法在 HttpRouteCollection 中定义路由 ...

随机推荐

  1. Bazinga 字符串HASH 这题不能裸HASH 要优化 毒瘤题

    Ladies and gentlemen, please sit up straight. Don't tilt your head. I'm serious. For nn given string ...

  2. 通过反射获取T.class代码片段

    说明 持久化框架MyBatis和Hibernate中我们多多少少都会自己取写工具类!但是我们一般都会处理结果集转换成持久化对象,但是我们都要使用类! 代码片段 abstract public clas ...

  3. JS把内容动态插入到DIV

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DT ...

  4. windows下codeblocks报错undefined reference to `WSAStartup@8'|

    Windows下C++Socket编程,调用WSAStartup函数报错:undefined reference to `WSAStartup@8'| 本人使用的是Codeblocks MinGW M ...

  5. MSSQL Export Excel

    输出Excel: -- To allow advanced options to be changed. GO -- To update the currently configured value ...

  6. Java进行http请求工具类代码(支持https)

    package com.guyezhai.modules.utils; import java.io.BufferedReader; import java.io.DataOutputStream; ...

  7. 利用forEach循环Dom元素…

    大家都知道forEach是循环数组用的,而且很方便,可以丢掉for循环了,但是它不能循环Dom元素.其实我们可以利用call来完成forEach循环Dom; 假设有这样的HTML结构: <ul ...

  8. Item 30 用enum代替int常量类型枚举,string常量类型枚举

    1.用枚举类型替代int枚举类型和string枚举类型 public class Show {   // Int枚举类型   // public static final int APPLE_FUJI ...

  9. 【NOIP】提高组2005 过河

    [算法]状态压缩型DP [题解] Q=tx+(t-1)y 对于Q≥t(t-1),x,y一定有解. 所以当两石子间距离long>t(t-1)时,令long=t(t-1),重新构造数组即可. [注意 ...

  10. sumblime快捷键

    原文地址:https://blog.csdn.net/shutfuckingup/article/details/23846603 Ctrl+D 选词 (反复按快捷键,即可继续向下同时选中下一个相同的 ...