问题

想要 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. ACE线程管理机制-面向对象的线程类ACE_Task

    转载于:http://www.cnblogs.com/TianFang/archive/2006/12/05/583231.html 我们在前一章中使用ACE_Thread包装时,你一定已经注意到了一 ...

  2. Codeforces Round #344 (Div. 2) A

    A. Interview time limit per test 1 second memory limit per test 256 megabytes input standard input o ...

  3. Debug模式下加载文件,运行程序异常的慢

    今天在进行单元测试的时候,debug模式下加载速度很慢,但是run模式下速度很快. 原因:在debug模式下,断点位置不当,解决办法 移除编译器中的所有断点.

  4. [USACO09DEC] Cow Toll Paths

    https://www.luogu.org/problem/show?pid=2966 题目描述 Like everyone else, FJ is always thinking up ways t ...

  5. 基于html5的动画库,非svg和canvas

    基于html5的动画库,非svg和canvas https://greensock.com/docs/#/HTML5/GSAP/TweenLite/

  6. 打印菱形(c语言)

    #define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #include <stdlib.h> int main() { // 定 ...

  7. 6、MySQL索引种类

    1.普通索引 这是最基本的索引,它没有任何限制,比如上文中为title字段创建的索引就是一个普通索引,MyIASM中默认的BTREE类型的索引,也是我们大多数情况下用到的索引. –直接创建索引 CRE ...

  8. bzoj 3453 数论

    首先我们知道对于f(x)来说,它是一个k次的多项式,那么f(x)的通项公式可以表示成一个k+1次的式子,且因为f(x)没有常数项,所以我们设这个式子为 f(x)=Σ(a[i]*x^i) (1<= ...

  9. js_读【javascript面向对象编程指南】笔记

    写在前面: 工欲善其事,必先利其器.编程的器,是前人总结的经验,常言道站在巨人的肩膀上开发,往往比自己另辟蹊径容易的多.经验藏于书,故有书中自有颜如玉,书中自有黄金屋,我也一度认为读书要花费很多时间, ...

  10. Python 源码学习之内存管理 -- (转)

    Python 的内存管理架构(Objects/obmalloc.c): _____ ______ ______ ________ [ int ] [ dict ] [ list ] ... [ str ...