« Fluent validation » : différence entre les versions
De Banane Atomic
Aller à la navigationAller à la recherche
Ligne 118 : | Ligne 118 : | ||
= [https://fluentvalidation.net/start#conditions Conditions] = | = [https://fluentvalidation.net/start#conditions Conditions] = | ||
<kode lang='cs'> | <kode lang='cs'> | ||
When( | When(x => x.Property > 0, () => { | ||
RuleFor( | RuleFor(x => x.Name).NotNull(); | ||
}).Otherwise(() => { | }).Otherwise(() => { | ||
RuleFor( | RuleFor(x => x.Name).MinimumLength(3); | ||
}); | }); | ||
RuleFor(x => x.Name) | |||
.NotNull() | |||
.When(x => x.Property > 0); | |||
</kode> | </kode> | ||
Version du 2 mars 2021 à 23:47
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("Name 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; } } |
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; } } |
Validate elements of a collection
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); |
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
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.StopOnFirstFailure).NotNull().MinimumLength(3); // change the default cascade mode public MyClassValidator() { // First set the cascade mode CascadeMode = CascadeMode.StopOnFirstFailure; RuleFor(x => x.Name).NotNull().MinimumLength(3); |
Async Validation
var validator = new MyClassValidator(); var result = 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); } } |
WebApi 2 integration
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; } } |
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() { var query = new MyQuery { Prop1 = "value1", Prop2 = "value2" }; this.validator.ShouldHaveValidationErrorFor(x => x.Prop3, query) .WithSeverity(Severity.Error) .WithErrorMessage(ValidationMessageResources.MyErrorMessage); } |