« Fluent validation » : différence entre les versions
Apparence
(11 versions intermédiaires par le même utilisateur non affichées) | |||
Ligne 106 : | Ligne 106 : | ||
= [https://fluentvalidation.net/start#collections Validate elements of a collection] = | = [https://fluentvalidation.net/start#collections Validate elements of a collection] = | ||
{{warn | If your root object is a collection, be aware that FV doesn't handle {{boxx|IReadOnlyCollection}}}} | |||
<kode lang='cs'> | <kode lang='cs'> | ||
public sealed class MyClassValidator : AbstractValidator<MyClass> { | public sealed class MyClassValidator : AbstractValidator<MyClass> { | ||
Ligne 205 : | Ligne 206 : | ||
} | } | ||
</filebox> | </filebox> | ||
= [https://docs.fluentvalidation.net/en/latest/custom-validators.html Custom validator] = | |||
<kode lang='cs'> | |||
RuleFor(x => x.Elements) | |||
.Custom((list, context) => | |||
{ | |||
if(list.Count > 10) | |||
context.AddFailure("The list must contain 10 items or fewer"); | |||
} | |||
); | |||
</kode> | |||
= [https://docs.fluentvalidation.net/en/latest/cascade.html Cascade mode] = | = [https://docs.fluentvalidation.net/en/latest/cascade.html Cascade mode] = | ||
<kode lang='cs'> | <kode lang='cs'> | ||
// test first if Name is not null, then even if Name is null test if lenght is >= 3 | // test first if Name is not null, then even if Name is null test if lenght is >= 3 | ||
RuleFor(x => x.Name).NotNull().MinimumLength(3); | RuleFor(x => x.Name) | ||
.NotNull() | |||
.MinimumLength(3); | |||
// test first if Name is not null, then only if Name is not null test if lenght is >= 3 | // test first if Name is not null, then only if Name is not null test if lenght is >= 3 | ||
RuleFor(x => x.Name).Cascade(CascadeMode. | RuleFor(x => x.Name) | ||
.Cascade(CascadeMode.Stop) | |||
.NotNull() | |||
.MinimumLength(3); | |||
// change the default cascade mode | // change the default cascade mode | ||
public MyClassValidator() { | public MyClassValidator() { | ||
// First set the cascade mode | // First set the cascade mode | ||
RuleLevelCascadeMode = CascadeMode.Stop; | |||
RuleFor(x => x.Name).NotNull().MinimumLength(3); | RuleFor(x => x.Name).NotNull().MinimumLength(3); | ||
Ligne 272 : | Ligne 289 : | ||
</kode> | </kode> | ||
<filebox fn=' | <filebox fn='Program.cs'> | ||
// automatic validation | // automatic validation | ||
builder.Services | |||
.AddValidatorsFromAssemblyContaining<PersonValidator>() | |||
.AddFluentValidationAutoValidation(); | |||
// disable other validator providers like DataAnnotations | |||
// fv.RunDefaultMvcValidationAfterFluentValidationExecutes = false; | |||
// manual registration of the validators | |||
services.AddTransient<IValidator<Person>, PersonValidator>(); | |||
</filebox> | </filebox> | ||
Ligne 305 : | Ligne 318 : | ||
return BadRequest(new ValidationProblemDetails(ModelState)); | return BadRequest(new ValidationProblemDetails(ModelState)); | ||
} | } | ||
</filebox> | |||
== Exclude validators from loading from assembly == | |||
<filebox fn='Program.cs'> | |||
builder.Services | |||
.AddValidatorsFromAssemblyContaining<PersonValidator>(ServiceLifetime.Scoped, x => x.ValidatorType != typeof(OtherValidator)) // do not load OtherValidator | |||
.AddFluentValidationAutoValidation(); | |||
builder.Services | |||
.AddValidatorsFromAssemblyContaining<AssetModelRunValidator>( | |||
ServiceLifetime.Scoped, | |||
x => !x.ValidatorType.IsDefined(typeof(ExcludeValidatorAttribute), inherit: true)) | |||
.AddFluentValidationAutoValidation(); | |||
</filebox> | |||
<filebox fn='ExcludeValidatorAttribute.cs'> | |||
[AttributeUsage(AttributeTargets.Class)] | |||
public class ExcludeValidatorAttribute : Attribute { } | |||
</filebox> | |||
<filebox fn='OtherValidator.cs'> | |||
[ExcludeValidator] | |||
public class OtherValidator : AbstractValidator<Other> | |||
{ } | |||
</filebox> | </filebox> | ||
Dernière version du 18 janvier 2025 à 21:46
Liens
- Fluent Validation
- Built-in Validators
- Fluent Validation on git
- Dynamic Validation with FluentValidation in WPF/MVVM
Exemple
var mc = new MyClass();
var validator = new MyClassValidator();
ValidationResult results = validator.Validate(mc);
if (!results.IsValid)
{
foreach (var failure in results.Errors)
{
Console.WriteLine("Property " + failure.PropertyName + " failed validation. Error was: " +
failure.ErrorMessage);
}
string allMessages = results.ToString();
Console.WriteLine(allMessages);
}
public sealed class MyClassValidator : AbstractValidator<MyClass>
{
public MyClassValidator()
{
this.RuleFor(x => x.Name)
.NotNull()
.Must(name => name != null)
.Must(IsValid)
.WithMessage("{PropertyName} is invalid."); // change error message. Default is: 'Name' must not be empty.
}
private static bool IsValid(string value)
{
return value != null;
}
}
public sealed class MyClass
{
public int Id { get; set; }
public string Name { get; set; }
}
|
Output
{
"type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
"title": "One or more validation errors occurred.",
"status": 400,
"traceId": "|84df05e2-41e0d4841bb61293.",
"errors": {
"Name": [
"Name is invalid."
]
}
}
|
Built-in Validators
Enum Validator
Checks whether a numeric value is valid to be in that enum.
RuleFor(x => x.ErrorLevel).IsInEnum();
|
Set validator for complex properties
public sealed class MyClassValidator : AbstractValidator<MyClass> {
public MyClassValidator() {
RuleFor(mc => mc.MySubClass).SetValidator(new MySubClassValidator());
}
}
public sealed class MySubClassValidator : AbstractValidator<MySubClass> {
public MySubClassValidator() {
RuleFor(msc => msc.Name).NotNull();
}
}
public sealed class MyClass
{
public int Id { get; set; }
public MySubClass MySubClass { get; set; }
}
public sealed class MySubClass
{
public int Id { get; set; }
public string Name { get; set; }
}
|
Validation on multiple properties
this.RuleFor(x => new { x.Property1, x.Property2 })
.Must(x => x.Property1.Count > 0 || x.Property2 .Count > 0)
.OverridePropertyName(x => x.Property1);
|
Validate elements of a collection
![]() |
If your root object is a collection, be aware that FV doesn't handle IReadOnlyCollection |
public sealed class MyClassValidator : AbstractValidator<MyClass> {
public MyClassValidator() {
// use RuleForEach to apply rules on each element
RuleForEach(x => x.Items)
.NotEmpty()
// use ChildRules for complex object
.ChildRules(items =>
{
items.RuleFor(x => x.Name).NotNull();
});
RuleForEach(mc => mc.Items)
.SetValidator(new ItemValidator());
// use ForEach inside a RuleFor to apply rules on each element
RuleFor(x => x.Ids)
.NotEmpty()
.ForEach(idRule => idRule.Must(x => x > 0));
RuleFor(x => x.Items)
.NotEmpty()
.ForEach(itemRule => itemRule.ChildRules(items =>
{
items.RuleFor(x => x.Name).NotNull();
}));
}
}
public sealed class MyClass {
public List<int> Ids { get; } = new List<int>();
public List<Item> Items { get; } = new List<Item>();
}
public sealed class Item {
public string Name { get; set; }
}
|
Conditions
When(x => x.Property > 0, () => {
RuleFor(x => x.Name).NotNull();
}).Otherwise(() => {
RuleFor(x => x.Name).MinimumLength(3);
});
RuleFor(x => x.Name)
.NotNull()
.When(x => x.Property > 0);
// code of length 4 or null
RuleFor(x => x.Code)
.Length(4)
.Unless(x => string.IsNullOrEmpty(x.Code));
|
Overriding the error message
this.RuleFor(x => x.Name)
.WithMessage("Error message on {PropertyName}.");
|
Placeholder | |
---|---|
{PropertyName} | The name of the property being validated |
{PropertyValue} | The value of the property being validated, these include the predicate validator (‘Must’ validator), the email and the regex validators. |
Built-in Validators and their Placeholders
Custom message placeholders
ItemValidator.cs |
public ItemValidator(IItemService itemService)
{
this.itemService = itemService;
RuleFor(x => x).MustAsync(HasNoSimilarItemAsync)
.OverridePropertyName("SimilarItem")
.WithMessage("A similar item already exists. Name: {SimilarItemName} - Date: {SimilarItemDate:dd/MM/yyyy}");
}
private async Task<bool> HasNoSimilarItemAsync(
Item rootObject,
Item item,
PropertyValidatorContext context,
CancellationToken token)
{
var similarItems = await itemService.FindSimilarItemsAsync(item);
if (similarItems.Any())
{
context.MessageFormatter.AppendArgument("SimilarItemName", similarItems[0].Name)
.AppendArgument("SimilarItemDate", similarItems[0].Date);
return false;
}
return true;
}
|
Custom validator
RuleFor(x => x.Elements)
.Custom((list, context) =>
{
if(list.Count > 10)
context.AddFailure("The list must contain 10 items or fewer");
}
);
|
Cascade mode
// test first if Name is not null, then even if Name is null test if lenght is >= 3
RuleFor(x => x.Name)
.NotNull()
.MinimumLength(3);
// test first if Name is not null, then only if Name is not null test if lenght is >= 3
RuleFor(x => x.Name)
.Cascade(CascadeMode.Stop)
.NotNull()
.MinimumLength(3);
// change the default cascade mode
public MyClassValidator() {
// First set the cascade mode
RuleLevelCascadeMode = CascadeMode.Stop;
RuleFor(x => x.Name).NotNull().MinimumLength(3);
|
Async Validation
var validator = new MyClassValidator();
var validationResult = await validator.ValidateAsync(myClass);
public sealed class MyClassValidator : AbstractValidator<MyClass>
{
SomeExternalWebApiClient client;
public MyClassValidator(SomeExternalWebApiClient client)
{
this.client = client;
this.RuleFor(x => x.Name)
.MustAsync(async (name, cancellation) => {
bool nameExists = await client.NameExists(name);
return !nameExists;
}).WithMessage("Name is not valid.");
}
private static bool IsValid(string value)
{
return value != null;
}
}
|
Throwing Exceptions
try
{
validator.ValidateAndThrow(mc);
}
catch (ValidationException e)
{
foreach (var failure in e.Errors)
{
Console.WriteLine(failure.ErrorMessage);
}
}
|
ASP.NET Core integration
![]() |
You should not use asynchronous rules when using ASP.NET automatic validation as ASP.NET’s validation pipeline is not asynchronous. |
# install the nuget package
dotnet add package FluentValidation.AspNetCore
|
Program.cs |
// automatic validation
builder.Services
.AddValidatorsFromAssemblyContaining<PersonValidator>()
.AddFluentValidationAutoValidation();
// disable other validator providers like DataAnnotations
// fv.RunDefaultMvcValidationAfterFluentValidationExecutes = false;
// manual registration of the validators
services.AddTransient<IValidator<Person>, PersonValidator>();
|
PersonController.cs |
public class PersonController : Controller {
[HttpPost]
public IActionResult Create(Person person) {
if(!ModelState.IsValid) {
return BadRequest(ModelState);
}
// manual async validation
var personValidator = new PersonValidator();
var validationResult = await personValidator.ValidateAsync(person);
validationResult.AddToModelState(ModelState, null);
if (!ModelState.IsValid)
{
return BadRequest(new ValidationProblemDetails(ModelState));
}
|
Exclude validators from loading from assembly
Program.cs |
builder.Services
.AddValidatorsFromAssemblyContaining<PersonValidator>(ServiceLifetime.Scoped, x => x.ValidatorType != typeof(OtherValidator)) // do not load OtherValidator
.AddFluentValidationAutoValidation();
builder.Services
.AddValidatorsFromAssemblyContaining<AssetModelRunValidator>(
ServiceLifetime.Scoped,
x => !x.ValidatorType.IsDefined(typeof(ExcludeValidatorAttribute), inherit: true))
.AddFluentValidationAutoValidation();
|
ExcludeValidatorAttribute.cs |
[AttributeUsage(AttributeTargets.Class)]
public class ExcludeValidatorAttribute : Attribute { }
|
OtherValidator.cs |
[ExcludeValidator]
public class OtherValidator : AbstractValidator<Other>
{ }
|
Use filter to check the model validity
Ainsi il n'est plus nécessaire de tester ModelState.IsValid dans chaque Controller.
App_Start\WebApiConfig.cs |
public static void Register(HttpConfiguration config)
{
config.Filters.Add(new ValidateModelStateFilter());
|
ValidateModelStateFilter.cs |
public class ValidateModelStateFilter : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
if (!actionContext.ModelState.IsValid)
{
actionContext.Response =
actionContext.Request.CreateErrorResponse(HttpStatusCode.BadRequest, actionContext.ModelState);
|
Unit tests
MyValidatorTest.cs |
public class MyValidatorTest
{
private readonly MyValidatorTest validator = new MyValidatorTest();
[Fact]
public void ValidationOfProp3_AskProp1AndProp2_Error()
{
// Arrange
var query = new MyQuery
{
Prop1 = "value1",
Prop2 = "value2"
};
// Act
var result = this.validator.TestValidate(query);
// Assert
result.ShouldHaveValidationErrorFor(x => x.Prop3, query)
.WithErrorCode(nameof(ValidationMessageResources.MyErrorMessage))
.WithErrorMessage(ValidationMessageResources.MyErrorMessage);
}
|
WebApi 2 integration
![]() |
Integration with ASP.NET WebApi 2 is no longer supported as of FluentValidation 9. Use ASP.NET Core. link |
Installer le package NuGet FluentValidation.WebApi
Validator attribute
App_Start\WebApiConfig.cs |
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// ...
FluentValidationModelValidatorProvider.Configure(config);
|
ItemController.cs |
[HttpPost]
[Route("")]
[ResponseType(typeof(Item))]
public IHttpActionResult Post(Item item)
{
// if the item is null, the validator is not executed
if (item == null)
{
return this.BadRequest("Item cannot be null.");
}
// if there are validation errors
if (!ModelState.IsValid)
{
return this.BadRequest(ModelState);
}
|
Item.cs |
// link the class with its validator
[Validator(typeof(ItemValidator))]
public sealed class Item { }
|
Dependency injection with Autofac
App_Start\WebApiConfig.cs |
public static void Register(HttpConfiguration config)
{
var builder = new ContainerBuilder();
// register all the validator
AssemblyScanner.FindValidatorsInAssembly(typeof(WebApiConfig).Assembly)
.ForEach(result =>
{
builder.RegisterType(result.ValidatorType).As(result.InterfaceType);
});
// register the ValidatorFactory because its ctor needs an IComponentContext
builder.RegisterType<AutofacValidatorFactory>();
IContainer container = builder.Build();
FluentValidationModelValidatorProvider.Configure(config,
provider => { provider.ValidatorFactory = container.Resolve<AutofacValidatorFactory>(); });
|
AutofacValidatorFactory.cs |
internal class AutofacValidatorFactory : ValidatorFactoryBase
{
private readonly IComponentContext context;
public AutofacValidatorFactory(IComponentContext context)
{
this.context = context;
}
public override IValidator CreateInstance(Type validatorType)
{
if (this.context.TryResolve(validatorType, out var instance))
{
var validator = instance as IValidator;
return validator;
}
return null;
}
}
|