概述
NopCommerce源码架构详解-使用FluentValidation创建Model自定义Validator验证源码分析。
内容
一个健全的系统少不了大量的输入合法性验证,但是nop中的验证使用的是一个优秀的开源的验证框架。它的好处是,它可以采用lambda表达式的形式配置要验证的字段和错误信息。今天我们就来看看Nop中是怎么使用FluentValidation创建Model自定义Validator验证的。
FluentValidation的官方地址:https://github.com/JeremySkinner/FluentValidation
当然你也可以在VS中用nuget快速获取安装到你的项目中。
我们可以在Nop的解决方案中的三个项目中都能找到一个叫做Validators的文件夹。

里面就放的使用FluentValidation的API配置的验证器。这三个项目分别为Nop.Admin,Nop.Web,Nop.Web.Framework。
下面我们就找一个项目中Nop.Admin的一个Validator看看具体FluentValidation是怎么使用的。
using FluentValidation; using Nop.Admin.Models.Blogs; using Nop.Services.Localization; namespace Nop.Admin.Validators.Blogs { public class BlogPostValidator : AbstractValidator<BlogPostModel> { public BlogPostValidator(ILocalizationService localizationService) { RuleFor(x => x.Title) .NotEmpty() .WithMessage(localizationService.GetResource("Admin.ContentManagement.Blog.BlogPosts.Fields.Title.Required")); RuleFor(x => x.Body) .NotEmpty() .WithMessage(localizationService.GetResource("Admin.ContentManagement.Blog.BlogPosts.Fields.Body.Required")); } } }
上面这个BlogPostValidator是管理后台项目的博客文章验证,我们可以看到里面是通过RuleFor来定义验证规则的,里面一共定义有两条验证规则,分别是标题Title和内容Body不能为空。当验证不通过时就会输出用.WithMessage包含的错误提示信息。
要使上面配置的验证Validator生效,我们还需要更改Global.asax:
protected void Application_Start() { //省略其它代码... //fluent validation DataAnnotationsModelValidatorProvider.AddImplicitRequiredAttributeForValueTypes = false; ModelValidatorProviders.Providers.Add(new FluentValidationModelValidatorProvider(new NopValidatorFactory())); //省略其它代码... }
上面FluentValidationModelValidatorProvider构造函数参数传的是一个重写了GetValidator方法的NopValidatorFactory。
using System; using FluentValidation; using FluentValidation.Attributes; using Nop.Core.Infrastructure; namespace Nop.Web.Framework { public class NopValidatorFactory : AttributedValidatorFactory { //private readonly InstanceCache _cache = new InstanceCache(); public override IValidator GetValidator(Type type) { if (type != null) { var attribute = (ValidatorAttribute)Attribute.GetCustomAttribute(type, typeof(ValidatorAttribute)); if ((attribute != null) && (attribute.ValidatorType != null)) { //validators can depend on some customer specific settings (such as working language) //that's why we do not cache validators //var instance = _cache.GetOrCreateInstance(attribute.ValidatorType, // x => EngineContext.Current.ContainerManager.ResolveUnregistered(x)); var instance = EngineContext.Current.ContainerManager.ResolveUnregistered(attribute.ValidatorType); return instance as IValidator; } } return null; } } }
Nop除了写了很多Model的Validator,它还有一些属性验证。如验证信用卡号的CreditCardPropertyValidator:
using System; using System.linq; using FluentValidation.Validators; namespace Nop.Web.Framework.Validators { public class CreditCardPropertyValidator : PropertyValidator { public CreditCardPropertyValidator() : base("Credit card number is not valid") { } protected override bool IsValid(PropertyValidatorContext context) { var ccValue = context.PropertyValue as string; if (String.IsNullOrWhiteSpace(ccValue)) return false; ccValue = ccValue.Replace(" ", ""); ccValue = ccValue.Replace("-", ""); int checksum = 0; bool evenDigit = false; //http://www.beachnet.com/~hstiles/cardtype.html foreach (char digit in ccValue.Reverse()) { if (!Char.IsDigit(digit)) return false; int digitValue = (digit - '0') * (evenDigit ? 2 : 1); evenDigit = !evenDigit; while (digitValue > 0) { checksum += digitValue % 10; digitValue /= 10; } } return (checksum % 10) == 0; } } }
属性验证器继承于PropertyValidator,把验证逻辑写在方法IsValid中。接下来Nop写了一个IRuleBuilder的扩展方法IsCreditCard来使用这个验证信用卡号的Validator:
using FluentValidation; namespace Nop.Web.Framework.Validators { public static class MyValidatorExtensions { public static IRuleBuilderOptions<T, string> IsCreditCard<T>(this IRuleBuilder<T, string> ruleBuilder) { return ruleBuilder.SetValidator(new CreditCardPropertyValidator()); } } }