FluentValidation 是 .NET 下的模型验证组件,和 ASP.NET MVC 基于Attribute 声明式验证的不同处,其利用表达式语法链式编程,使得验证组件与实体分开。正如 FluentValidation 的 介绍:

A small validation library for .NET that uses a fluent interface and lambda expressions for building validation rules for your business objects.

使用后,只能用一句话来形容:真乃神器也!

项目地址:http://fluentvalidation.codeplex.com/

想体验 Lambda Expression 流畅的感觉吗,下面 let's go!

首先,你需要通过 NuGet 获取 FluentValidation、FluentValidation.MVC3 包,我当前使用的版本如下:

<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="FluentValidation" version="3.3.1.0" />
<package id="FluentValidation.MVC3" version="3.3.1.0" />
</packages>

快速入门

1. 建立模型类

为了演示,我这里建了一个 Person 类,并且假设有下面这些 Property(属性)。

 
/// <summary>
/// 个人
/// </summary>
public class Person
{
/// <summary>
/// 姓
/// </summary>
public string Surname { get; set; } /// <summary>
/// 名
/// </summary>
public string Forename { get; set; } /// <summary>
/// 公司
/// </summary>
public string Company { get; set; } /// <summary>
/// 地址
/// </summary>
public string Address { get; set; } /// <summary>
/// 邮政编码
/// </summary>
public string Postcode { get; set; } /// <summary>
/// 个人空间的地址的别名,比如:bruce-liu-cnblogs、cnblogs_bruce_liu
/// </summary>
public string UserZoneUrl { get; set; }
}
 

根据 FluentValidation 的使用方法,我们直接可以在 Person 类上面直接标记对应的 Validator,比如: [Validator(typeof(PersonValidator))]。但如果我们的模型层(Model Layer)不允许修改(假设),并且你像我一样喜欢干净的模型层,不想要标记太多业务型的 Attribute 时,我们就使用继承的方式来标记,在派生类上标记。下面我们建一个 Customer 类,继承自 Person 类,并且再增加 2 个 Property(属性),最后标记 Validator Attribute。

 
[Validator(typeof(CustomerValidator))]
public class Customer : Person
{
/// <summary>
/// 是否有折扣
/// </summary>
public bool HasDiscount { get; set; } /// <summary>
/// 折扣
/// </summary>
public float Discount { get; set; } }
 

2. 建立模型类相应的 FluentValidation 验证类

 
public class CustomerValidator : AbstractValidator<Customer>
{
public CustomerValidator()
{
// 在这里写验证规则,比如:
// Cascade(FluentValidation.CascadeMode.StopOnFirstFailure) 可以指定当前 CustomerValidator 的验证模式,可重写全局验证模式
RuleFor(customer => customer.Surname).Cascade(FluentValidation.CascadeMode.StopOnFirstFailure).NotEmpty().Length(3, int.MaxValue).WithLocalizedName(() => "姓").WithLocalizedMessage(() => "亲,{PropertyName}不能为空字符串,并且长度大于{0}!!!");
// 更多...
// 更多...
}
}
 

3. 在 Global.asax 里面的 Application_Start 中配置 FluentValidation

默认情况下,FluentValidation 使用的验证错误消息是英文的,且官方自带的语言包中没有中文,于是我自己就手动翻译,建立了一个资源文件 FluentValidationResource.resx,并且在 Global.asax 中配置。

 
protected void Application_Start()
{ ConfigureFluentValidation();
} protected void ConfigureFluentValidation()
{
// 设置 FluentValidation 默认的资源文件提供程序 - 中文资源
ValidatorOptions.ResourceProviderType = typeof(FluentValidationResource); /* 比如验证用户名 not null、not empty、length(2,int.MaxValue) 时,链式验证时,如果第一个验证失败,则停止验证 */
ValidatorOptions.CascadeMode = CascadeMode.StopOnFirstFailure; // ValidatorOptions.CascadeMode 默认值为:CascadeMode.Continue // 配置 FluentValidation 模型验证为默认的 ASP.NET MVC 模型验证
FluentValidationModelValidatorProvider.Configure();
}
 

FluentValidationResource 代码中的 Key-Value 如下(PS:由于不知道怎么贴 Resource 文件中的代码,我就用截图了):

翻译得不好,请多多包涵!从这里下载

4. 客户端调用

本来用控制台程序就可以调用的,由于笔者建立的项目是 ASP.NET MVC 项目,本文的重点也是 FluentValidation 在 ASP.NET MVC 中使用,于是就在 Action 里面验证了。在 HomeController 的 Index 方法里面的代码如下:

 
public ActionResult Index()
{
/* 下面的例子验证 FluentValidation 在 .net 中的使用,非特定与 ASP.NET MVC */ Customer customer = new Customer();
// 我们这里直接 new 了一个 Customer 类,看看模型验证能否通过 CustomerValidator validator = new CustomerValidator();
ValidationResult results = validator.Validate(customer);
// 或者抛出异常 validator.ValidateAndThrow(customer);
bool validationSucceeded = results.IsValid;
IList<ValidationFailure> failures = results.Errors; StringBuilder textAppender = new StringBuilder(); if (!results.IsValid)
{
foreach (var failureItem in failures)
{
textAppender.Append("<br/>==========================================<br/>");
textAppender.AppendFormat("引起失败的属性值为:{0}<br/>", failureItem.AttemptedValue);
textAppender.AppendFormat("被关联的失败状态为:{0}<br/>", failureItem.CustomState);
textAppender.AppendFormat("错误消息为:{0}<br/>", failureItem.ErrorMessage);
textAppender.AppendFormat("Property(属性)为:{0}<br/>", failureItem.PropertyName);
textAppender.Append("<br/>==========================================<br/>");
}
} ViewBag.Message = textAppender.ToString(); return View();
}
 

最后,运行就能看到效果!

进阶篇

1. 属性类(Property Class)的验证

既然是顾客,那么顾客就可能会有订单,我们建立一个 Order 类,把 Customer 类作为 Order 类的一个 Property(属性)。

 
/// <summary>
/// 订单
/// </summary>
[Validator(typeof(OrderValidator))]
public class Order
{
public Customer Customer { get; set; } /// <summary>
/// 价格
/// </summary>
public decimal Price { get; set; }
}
 

相应的,我们还需要建立一个验证类 OrderValidator。为了共用 CustomerValidator 类,我们需要在 OrderValidator 类的构造函数中,为 Order 类的 Customer 属性指定 Validator。

 
/// <summary>
/// 订单验证类
/// </summary>
public class OrderValidator : AbstractValidator<Order>
{
public OrderValidator()
{
RuleFor(order => order.Price).NotNull().GreaterThanOrEqualTo(0m).WithLocalizedName(() => "价格"); // 重用 CustomerValidator
RuleFor(order => order.Customer).SetValidator(new CustomerValidator());
}
}
 

在 ASP.NET MVC 中使用时,在 Action 方法的参数上,可以像使用 Bind Attribute 一样:

public ActionResult AddCustomer([Bind(Include = "Company", Exclude = "Address")]Customer customer)

使用 CustomizeValidator Attribute,来指定要验证的 Property(属性):

 
[HttpGet]
public ActionResult AddCustomer()
{
return View(new Customer());
} [HttpPost]
public ActionResult AddCustomer([CustomizeValidator(Properties="Surname,Forename")] Customer customer)
{
/*
在 Action 的参数上标记 CustomizeValidator 可以指定 Interceptor(拦截器)、Properties(要验证的属性,以逗号分隔)。
如果指定了 Properties (要验证的属性,以逗号分隔),请注意是否别的属性有客户端验证,导致客户端提交不了,而服务器端
又可以不用验证。
*/
if (!ModelState.IsValid)
{
return View(customer);
}
return Content("验证通过");
}
 

由此可见,FluentValidation 真是用心良苦,这都想到了,不容易啊!

扩展篇

1. 完善 CustomerValidator

接下来,我们继续 完善 CustomerValidator ,增加更多的验证规则。

 
public class CustomerValidator : AbstractValidator<Customer>
{
public CustomerValidator()
{
// CascadeMode = CascadeMode.StopOnFirstFailure; 可以指定当前 CustomerValidator 的验证模式,可重写全局验证模式
RuleFor(customer => customer.Surname).Cascade(FluentValidation.CascadeMode.StopOnFirstFailure).NotEmpty().Length(3, int.MaxValue).WithLocalizedName(() => "姓").WithLocalizedMessage(() => "亲,{PropertyName}不能为空字符串,并且长度大于{0}!!!");
// 注意:调用 Cascade(FluentValidation.CascadeMode.StopOnFirstFailure) 表示当一个验证条件失败后,不再继续验证 RuleFor(customer => customer.Forename).NotEmpty().WithLocalizedName(() => "名").WithLocalizedMessage(() => "{PropertyName} 一定要不为空,Do you know ?");
RuleFor(customer => customer.Company).NotNull().WithLocalizedName(() => "公司名称").WithMessage(string.Format("{{PropertyName}} 不能 \"{0}\",下次记住哦,{1}!", "为空", "呵呵"));
RuleFor(customer => customer.Discount).NotEqual(0).WithLocalizedName(() => "折扣").When(customer => customer.HasDiscount);
RuleFor(customer => customer.Address).Length(20, 250).WithLocalizedName(() => "地址").Matches("^[a-zA-Z]+$").WithLocalizedMessage(() => "地址的长度必须在 20 到 250 个字符之间,并且只能是英文字符!");
RuleFor(customer => customer.Postcode).Must(BeAValidPostcode).WithLocalizedName(() => "邮政编码").WithMessage("请指定一个合法的邮政编码");
// 注意:如果用了 Must 验证方法,则没有客户端验证。 Custom((customer, validationContext) =>
{
bool flag1 = customer.HasDiscount;
bool flag2 = !validationContext.IsChildContext;
return flag1 && flag2 && customer.Discount > 0 ? null : new ValidationFailure("Discount", "折扣错误", customer.Discount);
});
} /// <summary>
/// 检查是否是合法的邮政编码
/// </summary>
/// <param name="postcode"></param>
/// <returns></returns>
private bool BeAValidPostcode(string postcode)
{
if (!string.IsNullOrEmpty(postcode) && postcode.Length == 6)
{
return true;
}
return false;
}
}
 

当我想要给 Customer.UserZoneUrl(个人空间的地址的别名) 写验证规则的时候,我发现它的验证规则可以提取出来,方便下次有类似的功能需要用到。那能不能像调用 NotNull() 、NoEmpty() 方法那样,调用我们写的 EntryName() 呢?答案:当然可以!

这样调用怎么样?

RuleFor(customer => customer.UserZoneUrl).EntryName();

其中 EntryName() 是一个扩展方法。

 
using FluentValidation;

public static class FluentValidatorExtensions
{
public static IRuleBuilderOptions<T, string> EntryName<T>(this IRuleBuilder<T, string> ruleBuilder)
{
return ruleBuilder.SetValidator(new EntryNameValidator());
}
}
 

我们看到,调用 EntryName 扩展方法其实是调用另外一个 Validator - EntryNameValidator。

 
public class EntryNameValidator : PropertyValidator, IRegularExpressionValidator
{
private readonly Regex regex;
const string expression = @"^[a-zA-Z0-9][\w-_]{1,149}$"; public EntryNameValidator()
: base(() => ExtensionResource.EntryName_Error)
{
regex = new Regex(expression, RegexOptions.IgnoreCase);
} protected override bool IsValid(PropertyValidatorContext context)
{
if (context.PropertyValue == null) return true; if (!regex.IsMatch((string)context.PropertyValue))
{
return false;
} return true;
} public string Expression
{
get { return expression; }
}
}
 

这里我们的 EntryNameValidator 除了继承自 PropertyValidator,还实现了 IRegularExpressionValidator 接口。为什么要实现 IRegularExpressionValidator 接口 呢?是因为可以共享由 FluentValidation 带来的好处,比如:客户端验证等等。

其中 ExtensionResource 是一个资源文件,我用来扩展 FluentValidation 时使用的资源文件。

2. 复杂验证

下面我们再建立一个 Pet(宠物)类,为 Customer 类增加一个 public List<Pet> Pets { get; set; } 属性。

 
/// <summary>
/// 顾客类
/// </summary>
[Validator(typeof(CustomerValidator))]
public class Customer : Person
{
/// <summary>
/// 是否有折扣
/// </summary>
public bool HasDiscount { get; set; } /// <summary>
/// 折扣
/// </summary>
public float Discount { get; set; } /// <summary>
/// 一个或多个宠物
/// </summary>
public List<Pet> Pets { get; set; } } /// <summary>
/// 宠物类
/// </summary>
public class Pet
{
public string Name { get; set; }
}
 

那 FluentValidation 对集合的验证,该如何验证呢?下面我们要求顾客的宠物不能超过 10 个。你一定想到了用下面的代码实现:

 
Custom(customer =>
{
return customer.Pets.Count >= 10
? new ValidationFailure("Pets", "不能操作 10 个元素")
: null;
});
 

或者我们写一个自定义的 Property(属性)验证器 ListMustContainFewerThanTenItemsValidator<T>,让它继承自 PropertyValidator

 
public class ListMustContainFewerThanTenItemsValidator<T> : PropertyValidator
{
public ListMustContainFewerThanTenItemsValidator()
: base("属性 {PropertyName} 不能超过 10 个元素!")
{
// 注意:这里的错误消息也可以用资源文件
} protected override bool IsValid(PropertyValidatorContext context)
{
var list = context.PropertyValue as IList<T>;
if (list != null && list.Count >= 10)
{
return false;
}
return true;
}
}
 

应用这个属性验证器就很容易了,在 Customer 的构造函数中:

RuleFor(customer => customer.Pets).SetValidator(new ListMustContainFewerThanTenItemsValidator<Pet>());

再或者为了公用,写一个扩展方法,扩展 IRuleBuilder<T, IList<TElement>> 类

 
/// <summary>
/// 定义扩展方法,是为了方便调用。
/// </summary>
public static class MyValidatorExtensions
{
public static IRuleBuilderOptions<T, IList<TElement>> MustContainFewerThanTenItems<T, TElement>(this IRuleBuilder<T, IList<TElement>> ruleBuilder)
{
return ruleBuilder.SetValidator(new ListMustContainFewerThanTenItemsValidator<TElement>());
}
}
 

调用也像上面调用 EntryName() 一样,直接调用:

RuleFor(customer => customer.Pets).MustContainFewerThanTenItems();

3. 与 IoC 容器(Autofac、Unity、StructureMap等)集成

下面以 Autofac 为例进行演示

1. 创建自己的 ValidatorFactory

比如我这里创建为 AutofacValidatorFactory,继承自 FluentValidation.ValidatorFactoryBase,而 ValidatorFactoryBase 本身是实现了 IValidatorFactory 的。IValidatorFactory 的代码如下:

 
// 摘要:
// Gets validators for a particular type.
public interface IValidatorFactory
{
// 摘要:
// Gets the validator for the specified type.
IValidator<T> GetValidator<T>();
//
// 摘要:
// Gets the validator for the specified type.
IValidator GetValidator(Type type);
}
 

ValidatorFactoryBase 的代码如下:

 
public abstract class ValidatorFactoryBase : IValidatorFactory
{
protected ValidatorFactoryBase(); public abstract IValidator CreateInstance(Type validatorType);
public IValidator<T> GetValidator<T>();
public IValidator GetValidator(Type type);
}
 

我们看到 ValidatorFactoryBase 其实是把 IValidatorFactory 接口的 2 个方法给实现了,但核心部分还是抽象出来了,那我们的 AutofacValidatorFactory 需要根据 Autofac 的使用方法进行编码,代码如下:

 
public class AutofacValidatorFactory : ValidatorFactoryBase
{
private readonly IContainer _container; public AutofacValidatorFactory(IContainer container)
{
_container = container;
} /// <summary>
/// 尝试创建实例,返回值为 NULL 表示不应用 FluentValidation 来做 MVC 的模型验证
/// </summary>
/// <param name="validatorType"></param>
/// <returns></returns>
public override IValidator CreateInstance(Type validatorType)
{
object instance;
if (_container.TryResolve(validatorType, out instance))
{
return instance as IValidator;
}
return null;
}
}
 

2. 在 Application_Start 中注册 Autofac

 
protected void Application_Start()
{
RegisterAutofac();
} protected void RegisterAutofac()
{
// 注册 IoC
ContainerBuilder builder = new ContainerBuilder();
builder.RegisterNewsManagement();
// 创建 container
IContainer _container = builder.Build();
// 在 NewsManagement 模型下设置 container
_container.SetAsNewsManagementResolver(); ModelValidatorProviders.Providers.Add(new FluentValidationModelValidatorProvider(new AutofacValidatorFactory(_container)));
DataAnnotationsModelValidatorProvider.AddImplicitRequiredAttributeForValueTypes = false;
}
 

其中上面那 2 个方法(RegisterNewsManagement、SetAsNewsManagementResolver)是扩展方法,代码如下:

 
public static class AutofacExtensions
{
public static void RegisterNewsManagement(this ContainerBuilder builder)
{
builder.RegisterType<NewsCategoryValidator>().As<IValidator<NewsCategoryModel>>();
builder.RegisterType<NewsValidator>().As<IValidator<NewsModel>>();
builder.RegisterControllers(typeof(MvcApplication).Assembly);
} public static void SetAsNewsManagementResolver(this IContainer contaner)
{
DependencyResolver.SetResolver(new AutofacDependencyResolver(contaner));
}
}
 

至此,我们的模型上面就可以注释掉对应的 Attribute 了。

 
/// <summary>
/// 文章表模型
/// </summary> //[Validator(typeof(NewsValidator))]
public class NewsModel : NewsEntity
{ }

FluentValidation 模型验证的更多相关文章

  1. 模型验证组件 FluentValidation

    FluentValidation 是 .NET 下的模型验证组件,和 ASP.NET MVC 基于Attribute 声明式验证的不同处,其利用表达式语法链式编程,使得验证组件与实体分开.正如 Flu ...

  2. 模型验证组件——FluentValidation

    之前在博客园有幸从网友那里得知一个C#的模型验证组件(哈 不知道这样表述正确不),组件的功能比较简单,主要是实现了对Model的验证,例如验证用户名是否为空,密码长度是不是多余6个字符,当然还有其他更 ...

  3. 【翻译】asp.net core中使用FluentValidation来进行模型验证

    asp.net core中使用FluentValidation FluentValidation 可以集成到asp.net core中.一旦启用,MVC会在通过模型绑定将参数传入控制器的方法上时使用F ...

  4. asp.net mvc 模型验证组件——FluentValidation

    asp.net mvc 模型验证组件——FluentValidation 示例 using FluentValidation; public class CustomerValidator: Abst ...

  5. Newbe.ObjectVisitor 0.4.4 发布,模型验证器上线

    Newbe.Claptrap 0.4.4 发布,模型验证器上线. 更新内容 完全基于表达式树的模型验证器 本版本,我们带来了基于表达式树实现的模型验证器.并实现了很多内置的验证方法. 我们罗列了与 F ...

  6. 客官,.NETCore无代码侵入的模型验证了解下

    背景 .NETCore下的模型验证相信绝大部分的.NET开发者或多或少的都用过,微软官方提供的模型验证相关的类位于System.ComponentModel.DataAnnotations命令空间下, ...

  7. webapi - 模型验证

    本次要和大家分享的是webapi的模型验证,讲解的内容可能不单单是做验证,但都是围绕模型来说明的:首先来吐槽下,今天下午老板为自己买了套新办公家具,看起来挺好说明老板有钱,不好的是我们干技术的又成了搬 ...

  8. ASP.NET Core 中文文档 第四章 MVC(2.2)模型验证

    原文:Model Validation 作者:Rachel Appel 翻译:娄宇(Lyrics) 校对:孟帅洋(书缘) 在这篇文章中: 章节: 介绍模型验证 验证 Attribute 模型状态 处理 ...

  9. MVC遇上bootstrap后的ajax表单模型验证

    MVC遇上bootstrap后的ajax表单验证 使用bootstrap后他由他自带的样式has-error,想要使用它就会比较麻烦,往常使用jqueyr.validate的话只有使用他自己的样式了, ...

随机推荐

  1. SQL Server 创建角色和账号

    /*--示例说明         示例在数据库pubs中创建一个拥有表jobs的所有权限.拥有表titles的SELECT权限的角色r_test     随后创建了一个登录l_test,然后在数据库p ...

  2. Python基础学习----公共方法及运算符

    # 公共方法:在python高级数据类型通用的方法 # 常见的:max() min() len() del() # 列表 list=[1,2,3] print(len(list)) print(min ...

  3. c++的c风格字符串函数的实现

    要注意使用断言判断传入的字符串非空. #include <cassert> //求字符串长度 size_t StrLen(const char *str) { assert(str != ...

  4. BZOJ3212: Pku3468 A Simple Problem with Integers(线段树)

    3212: Pku3468 A Simple Problem with Integers Time Limit: 1 Sec  Memory Limit: 128 MBSubmit: 2530  So ...

  5. 20155317 2016-2017-2 《Java程序设计》第8周学习总结

    20155317 2016-2017-2 <Java程序设计>第8周学习总结 教材学习内容总结 1.java.util.logging包提供了日志功能相关类与接口. 2.使用日志的起点是L ...

  6. 20155315 2016-2017-2 《Java程序设计》第八周学习总结

    教材学习内容总结 第14章 NIO与NIO2 1.认识NIO NIO使用频道(Channel)来衔接数据节点,在处理数据时,NIO可以让你设定缓冲区(Buffer)容量,在缓冲区中对感兴趣的数据区块进 ...

  7. 好的框架需要好的 API 设计 —— API 设计的六个原则

    说到框架设计,打心底都会觉得很大很宽泛,而 API 设计是框架设计中的重要组成部分.相比于有很多大佬都认可的面向对象的六大原则.23 种常见的设计模式来说,API 设计确实缺少行业公认的原则或者说设计 ...

  8. TCP滑动窗口与回退N针协议

    [转]TCP 滑动窗口协议/1比特滑动窗口协议/后退n协议/选择重传协议 2014-1-5阅读884 评论0 本文转自 http://www.cnblogs.com/ulihj/archive/201 ...

  9. 【java基础】java字符串之StringBuffer和StringBuilder

    [一]简述区别 package com.sxf.test.string; public class StringBufferStringBuilderTest { public static void ...

  10. 系列文章--oracle简单入门教程

    oracle入门很简单:八.oracle数据表 1.创建oracle数据表创建oracle数据表的语法如下: create table命令用于创建一个oracle数据表:括号内列出了数据表应当包含的列 ...