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. asp.net mvc 模型验证组件——FluentValidation

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

  2. 模型验证组件——FluentValidation

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

  3. .NET Core中的验证组件FluentValidation的实战分享

    今天有人问我能不能出一篇FluentValidation的教程,刚好今天在实现我们的.NET Core实战项目之CMS的修改密码部分的功能中有用到FluentValidation,所以就以修改用户密码 ...

  4. 验证组件——FluentValidation

          FluentValidation FluentValidation是与ASP.NET DataAnnotataion Attribute验证实体不同的数据验证组件,提供了将实体与验证分离开 ...

  5. 验证组件FluentValidation的使用示例

    官方文档:https://fluentvalidation.net/start#complex-properties 示例Demo:https://github.com/DavideYang125/F ...

  6. FluentValidation 模型验证

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

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

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

  8. 【翻译】FluentValidation验证组件的使用

    由于本文是翻译,所以将原文原原本本的搬上来,大家看原文有什么不懂的也可以对照这里. 给出地址:https://fluentvalidation.net/ FluentValidation fluent ...

  9. .NET平台开源项目速览(10)FluentValidation验证组件深入使用(二)

    在上一篇文章:.NET平台开源项目速览(6)FluentValidation验证组件介绍与入门(一) 中,给大家初步介绍了一下FluentValidation验证组件的使用情况.文章从构建间的验证器开 ...

随机推荐

  1. 协程(Coroutine)并不是真正的多线程

    自:http://www.zhihu.com/question/23895384 说到Coroutine,我们必须提到两个更远的东西.在操作系统(os)级别,有进程(process)和线程(threa ...

  2. Get file extention in XSLT

      When working with data view web parts or data form web parts in SharePoint, you might want to use ...

  3. SharePoint中Rating相关的字段。

      From: https://sharepoint.stackexchange.com/questions/194197/how-to-manipulate-likeby-nooflikes-rat ...

  4. 详解Nginx + Tomcat 反向代理 如何在高效的在一台服务器部署多个站点

    转自:http://www.jb51.net/article/100111.htm 首先我们需要安装好Nginx.jdk.Tomcat,安装方法已经在 上一篇 说过了,本篇不再赘述. 下来看一下我们的 ...

  5. 比尔·盖茨写的最古老程序曝光:BAT程序员膜拜 #精选程序人生

    比尔·盖茨31岁,就成为世界首富.很多人好奇,作为世界第一大PC系统的创始人,抛弃世界首富的头衔,单单从程序员角度来讨论,比尔盖茨的代码水平如何? 其实,比尔·盖茨对写代码有一种狂热的喜好.上高中的时 ...

  6. .Net 泛型约束

    本文内容 使用泛型约束的原因 未绑定的类型参数 作为约束的类型参数 参考资料 当"设计模式"出现时,人们提"用接口编程":后来,有了泛型,人们提"用泛 ...

  7. Solidworks如何替换工程图参考零件

    不要在左侧树形图右击修改   而是要在右侧主视图上右击,替换模型   左侧浏览找到新的零件,然后打开   替换完成之后,会有一些尺寸变成黄色,只需要改动黄色部分即可,不需要每个尺寸重新标注    

  8. android中去掉ListView控件中的分割线

    通过设置android:divider="@null" ,可以去掉ListView控件中的分割线 也可以自定义分割线的颜色,比如: <ListView android:id= ...

  9. springmvc转换JSON数据

    1.引入jackson包 要想在springmvc框架下支持json的转换,需要引入jackson的包,在pom.xml中添加如下代码: <dependency> <groupId& ...

  10. 微信小程序 - 使用字体图标(阿里云库)

    实现 阿里图库:https://www.iconfont.cn/ CSS3:[attribute*=value] 选择器 步骤 1. 加入购物车 2. 下载代码解压,复制iconfont.css ap ...