FluentValidation支持跟 ASP.NET Core 3.1 5.0 集成。启用后,MVC将使用FluentValidation验证模型。
要启用MVC集成,我们需要在Web项目中添加程序集引用FluentValidation.AspNetCore
。
在命令行中,您可以通过输入以下方式来添加引用:
dotnet add package FluentValidation.AspNetCore
在安装完成后,我们需要在Startup里面 加入using FluentValidation.AspNetCore
接下来我们需要在ConfigureServices方法里 AddMvc之后使用AddFluentValidation扩展方法。
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc(setup => {
//...mvc setup...
}).AddFluentValidation();
}
为了使ASP.NET发现我们的验证器,必须在ConfigureServices注册它们。 我们可以通过为每个验证器调用AddTransient方法来执行此操作:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc(setup =>
{
//...mvc setup...
}).AddFluentValidation();
services.AddTransient<IValidator<Person>, PersonValidator>();
//etc
}
我们还可以使用AddFromAssemblyContaining方法自动注册特定程序集中的所有验证器。 这将自动查找从AbstractValidator继承的所有公共,非抽象类型的验证器,并在容器中注册它们(不支持泛型)。
services.AddMvc()
.AddFluentValidation(fv => fv.RegisterValidatorsFromAssemblyContaining<PersonValidator>());
默认情况下,只有public的验证器会被注册,如果想包含internal的验证器。我们需要把可选参数includeInternalTypes设为true
RegisterValidatorsFromAssemblyContaining<PersonValidator>(includeInternalTypes: true))
.
排除某些不想注册的验证器
services.AddMvc()
.AddFluentValidation(fv => fv.RegisterValidatorsFromAssemblyContaining<PersonValidator>(discoveredType => discoveredType.ValidatorType != typeof(SomeValidatorToExclude)));
在如下的示例当中我们定义了一个Person类,还有Person验证器。 (我们需要在Startup 注册验证器。)
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
public string Email { get; set; }
public int Age { get; set; }
}
public class PersonValidator : AbstractValidator<Person>
{
public PersonValidator()
{
RuleFor(x => x.Id).NotNull();
RuleFor(x => x.Name).Length(0, 10);
RuleFor(x => x.Email).EmailAddress();
RuleFor(x => x.Age).InclusiveBetween(18, 60);
}
}
我们可以在People 控制器里面使用Person这个模型
public class PeopleController : Controller
{
public ActionResult Create()
{
return View();
}
[HttpPost]
public IActionResult Create(Person person)
{
if(! ModelState.IsValid)
{
// 重新 render
return View("Create", person);
}
Save(person); //保存到数据库,或者其它的逻辑
TempData["notice"] = "Person successfully created";
return RedirectToAction("Index");
}
}
相对应的razor类
@model Person
<div asp-validation-summary="ModelOnly"></div>
<form asp-action="Create">
Id: <input asp-for="Id" /> <span asp-validation-for="Id"></span>
<br />
Name: <input asp-for="Name" /> <span asp-validation-for="Name"></span>
<br />
Email: <input asp-for="Email" /> <span asp-validation-for="Email"></span>
<br />
Age: <input asp-for="Age" /> <span asp-validation-for="Age"></span>
<br /><br />
<input type="submit" value="submtit" />
</form>
现在,当我们提交表单时,MVC框架会自动用PersonValidator验证Person对象,并将验证结果添加到ModelState中。
默认情况下,在FluentValidation执行之后,Asp.net 自动的模型验证也会被执行。 这样这两个错误就会混在一起了。 如果我们想禁掉asp.net的默认验证的话,可以用下面的方式。
services.AddMvc().AddFluentValidation(fv =>
{
fv.RunDefaultMvcValidationAfterFluentValidationExecutes = false;
});
public IServiceProvider ConfigureServices(IServiceCollection services)
{
services.Configure<ApiBehaviorOptions>(options =>
{
options.InvalidModelStateResponseFactory = (actionContext) =>
{
//在这边可以把ModelState取出来并转成自己需要的对象
return new BadRequestObjectResult(actionContext.ModelState.ToResultVm());
};
});
}
当验证一个复杂对象的时候。默认情况下我们需要显式用SetValidator来验证子属性 看复杂属性
当我们在Asp.net mvc的程序里面使用FluentValidation的话。除了可以显式的设置验证器外。我们还可以可以通过配置ImplicitlyValidateChildProperties
为true来隐式的为所有的子属性开启验证。 Mvc的验证框架会自动尝试为每一个属性查找可用的验证器
services.AddMvc().AddFluentValidation(fv =>
{
fv.ImplicitlyValidateChildProperties = true;
});
注意。如果开启这个选项的话。我们不能在调用SetValidator
了。不然的话这个验证器会被执行两次。
默认情况下,我们必须创建特定的集合验证器或启用隐式子属性验证来验证集合类型的模型。
例如,除非定义了AbstractValidator<List<Person>>
继承的验证器,否则默认设置不会对以下模型进行验证。
public ActionResult DoSomething(List<Person> people) => Ok();
启用隐式子属性验证(参见上文)后,我们无需显式创建集合验证器类,因为集合中的每个person元素都会被自动验证。 但是,Person对象上的所有子属性也将被自动验证,这意味着您将无法再使用SetValidator。 如果您不希望这种行为,也可以选择仅对根集合元素启用隐式验证。 例如,如果您希望自动验证集合中的每个Person元素,而不是其子属性,则可以将ImplicitlyValidateRootCollectionElements设置为true:
services.AddMvc().AddFluentValidation(fv =>
{
fv.ImplicitlyValidateRootCollectionElements = true;
});
注意当ImplicitlyValidateChildProperties
为true的时候,该设置会被自动忽略掉。
FluentValidation是服务器端框架,不直接提供任何客户端验证。但是,它可以提供元数据,当将元数据应用于生成的HTML元素时,可以由客户端框架使用,就像ASP.NET的默认验证属性一样。
请注意,并非FluentValidation中定义的所有规则都可以与ASP.NET的客户端验证一起使用。例如,任何使用了(when unless
),自定义验证器或使用了Must
的规则都不会在客户端运行。规则集 中的任何规则也不行。
客户端仅支持下面的这些验证器
有时我们可能需要手动验证MVC项目中的对象。 在这种情况下,可以将验证结果复制到MVC的模型状态字典中:
public ActionResult DoSomething()
{
var customer = new Customer();
var validator = new CustomerValidator();
var results = validator.Validate(customer);
results.AddToModelState(ModelState, null);
return View();
}
AddToModelState是一个扩展方法,所以我们需要添加 using FluentValidation.AspNetCore
。
请注意,第二个参数是可选的模型名称,这将使ModelState中的属性名称带有前缀 例如,AddToModelState(ModelState, "Foo")
将生成Foo.Id和Foo.Name等的属性名称。 而不只是ID或名称)
使用集成验证不好的地方。是我们对验证的过程没有太多的控制权。
我们可以利用FluentValidation提供了特性CustomizeValidatorAttribute
来进行一些控制
public ActionResult Save([CustomizeValidator(RuleSet="MyRuleset")] Customer cust)
{
// ...
}
//等价于下面的方式
var validator = new CustomerValidator();
var customer = new Customer();
var result = validator.Validate(customer, options => options.IncludeRuleSet("MyRuleset"));
public ActionResult Save([CustomizeValidator(Properties="Surname,Forename")] Customer cust)
{
// ...
}
//等价于下面的方式
var validator = new CustomerValidator();
var customer = new Customer();
var result = validator.Validate(customer, options => options.IncludeProperties("Surname", "Forename"));
我们还可以使用CustomizeValidatorAttribute跳过对特定类型的验证。 这样我们可以进行手动验证(例如,执行异步验证, 注意 MVC的验证管道不支持异步验证)。
public ActionResult Save([CustomizeValidator(Skip=true)] Customer cust)
{
// ...
}
我们可以进一步使用拦截器来定义一些通用的验证过程。 拦截器必须实现FluentValidation.AspNetCore命名空间下的IValidatorInterceptor接口:
public interface IValidatorInterceptor
{
IValidationContext BeforeAspNetValidation(ActionContext actionContext, IValidationContext validationContext);
ValidationResult AfterAspNetValidation(ActionContext actionContext, IValidationContext validationContext, ValidationResult result);
}
BeforeAspNetValidation 会在验证器被调用之前执行, 在这边可以对validationContext做一些处理 AfterAspNetValidation会在验证器被调用之后执行,在这边我们可以对错误消息进行一些额外的处理,在它们被加入到ModelState之前.
除了直接在验证器类中实现此接口外,我们还可以在外部实现它,并通过在操作方法参数上使用CustomizeValidatorAttribute来指定拦截器:
public ActionResult Save([CustomizeValidator(Interceptor=typeof(MyCustomerInterceptor))] Customer cust)
{
//...
}
在上面的这种情况下,拦截器必须是实现IValidatorInterceptor的类,并且要有一个public无参数的构造函数。
我们也可以全局注册默认的IValidatorInterceptor。这样的话这个拦截器将用于所有验证器:
public void ConfigureServices(IServiceCollection services)
{
services
.AddMvc()
.AddFluentValidation(fv => fv.RegisterValidatorsFromAssemblyContaining<PersonValidator>());
// 注册一个默认的拦截器
services.AddTransient<IValidatorInterceptor, MyDefaultInterceptor>();
}
如果我们在ASP.NET MVC也使用了规则集, 我们会发现客户端消息不包括规则集. 我们可以通过使用特性 RuleSetForClientSideMessagesAttribute
:来指定它
[RuleSetForClientSideMessages("MyRuleset")]
public ActionResult Index()
{
return View(new PersonViewModel());
}
我们还可以在控制器中的方法里面使用SetRulesetForClientsideMessages扩展方法:
public ActionResult Index()
{
ControllerContext.SetRulesetForClientsideMessages("MyRuleset");
return View(new PersonViewModel());
}
鸡肋 功能 前面我们已经有开启了 fv.ImplicitlyValidateChildProperties = true;这个会自动加载所有的子属性了。 或者全部手动指定。
public class PersonValidator : AbstractValidator<Person>
{
public PersonValidator()
{
RuleFor(x => x.Address).InjectValidator();
}
}
与ASP.NET Razor Pages和PageModels一起使用的配置与上述MVC完全相同,但是有一个限制,我们没有办法为整个PageModel来定义验证器。 我们必须为这里面每一个我们暴露出来的属性单独定义验证器
同样我们可以使用 SetRulesetForClientsideMessages
把规则集的错误消息放到客户端
public IActionResult OnGet()
{
PageContext.SetRulesetForClientsideMessages("MyRuleset");
return Page();
}