A Professional ASP.NET Core API - FluentValidation
FluentValidation is a A .NET library for building strongly-typed validation rules. It uses lambda expressions for building validation rules for your business objects.
If you want to do simple validation in asp.net mvc application then data annotations validation is good but in case if you want to implement complex validation then you need to use FluentValidation.
In the following we will see how it can be added to a project and how it works.
publicvoidConfigureServices(IServiceCollection services) { services .AddControllers() // HERE .AddFluentValidation() ; }
In order for ASP.NET to discover your validators, they must be registered with the services collection. You can either do this by calling the AddTransient method for each of your validators:
1 2 3 4 5 6 7 8 9 10
publicvoidConfigureServices(IServiceCollection services) { services .AddControllers() // HERE .AddFluentValidation() ;
Now, If you call the Create API with above JSON data you will see the below result
1 2 3 4 5 6 7 8 9 10 11
{ "type": "https://tools.ietf.org/html/rfc7231#section-6.5.1", "title": "One or more validation errors occurred.", "status": 400, "traceId": "|4e6b48f8-4d5461460c3f9b04.", "errors": { "Name": [ "'Name' must be between 6 and 16 characters. You entered 5 characters." ] } }
Automatic Registration
You can also use the below methods to automatically register all validators within a particular assembly. This will automatically find any public, non-abstract types that inherit from AbstractValidator and register them with the container (open generics are not supported).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
publicvoidConfigureServices(IServiceCollection services) { services .AddControllers() .AddFluentValidation(options => { // HERE options.RegisterValidatorsFromAssembly(Assembly.GetExecutingAssembly()); // OR options.RegisterValidatorsFromAssemblyContaining<Startup>(); // OR options.RegisterValidatorsFromAssemblyContaining<PersonValidator>(); // OR options.RegisterValidatorsFromAssemblyContaining<PersonValidator>(lifetime:ServiceLifetime.Singleton); // OR options.RegisterValidatorsFromAssemblyContaining<PersonValidator>(discoveredType => discoveredType.ValidatorType != typeof(SomeValidatorToExclude)); }); }
Compatibility with ASP.NET’s built-in Validation
By default, after FluentValidation is executed, any other validator providers will also have a chance to execute. This means you can mix FluentValidation with DataAnnotations attributes (or any other ASP.NET ModelValidatorProvider implementation).
If you want to disable this behaviour so that FluentValidation is the only validation library that executes, you can set the RunDefaultMvcValidationAfterFluentValidationExecutes to false in your application startup routine:
NotNull ("NotNull"): to check the property is null.
NotEmpty ("NotEmpty"): to check the property is null, empty or has whitespace.
NotEqual ("NotEqual"): to check the specified property is not equal to a particular value.
Equal Validator ("Equal"): to check the value of the specified property is equal to a particular value.
Length Validator ("Length"): to check the length of a particular string property is within the specified range.
MaxLength Validator ("MaximumLength"): to check the length of a particular string property is no longer than the * specified value.
MinLength Validator ("MinimumLength"): to check the length of a particular string property is longer than the * specified value.
Less Than Validator ("LessThan"): to check the length of the specified property is less than a particular value
LessThanOrEqual Validator ("LessThanOrEqualTo"): to check the value of the specified property is less than or * equal to a particular value.
Greater Than Validator ("GreaterThan"): to check the value of the specified property is greater than a particular * value.
Regular Expression Validator ("Matches"): to check the value of the specified property matches the given regular * expression.
Email Validator Validator ("EmailAddress"): to check the value of the specified property is a valid email address.
Implicit vs Explicit Child Property Validation
When validating complex object graphs, by default, you must explicitly specify any child validators for complex properties by using SetValidator.
When running an ASP.NET MVC application, you can also optionally enable implicit validation for child properties. When this is enabled, instead of having to specify child validators using SetValidator, MVC’s validation infrastructure will recursively attempt to automatically find validators for each property. This can be done by setting ImplicitlyValidateChildProperties to true:
Sometimes you may want to manually validate an object in a MVC project. In this case, the validation results can be copied to MVC’s modelstate dictionary:
[HttpPost] publicasync Task<IActionResult> Create() { TesterValidator validator = new TesterValidator(); List<string> ValidationMessages = new List<string>(); var tester = new Tester { FirstName = "", Email = "bla!" }; var validationResult = validator.Validate(tester); var response = new ResponseModel(); if (!validationResult.IsValid) { response.IsValid = false; foreach (ValidationFailure failure in validationResult.Errors) { ValidationMessages.Add(failure.ErrorMessage); } response.ValidationMessages = ValidationMessages; } return Ok(response); }
Custom messages
We can extend the above example to include a more useful error message. At the moment, our custom validator always returns the message “The list contains too many items” if validation fails. Instead, let’s change the message so it returns “’Pets’ must contain fewer than 10 items.” This can be done by using custom message placeholders. FluentValidation supports several message placeholders by default including {PropertyName} and {PropertyValue} (see this list for more), but we can also add our own.
1 2 3 4 5 6 7 8
publicclassPersonValidator : AbstractValidator<Person> { publicPersonValidator() { RuleFor(x => x.Id).NotNull().WithMessage("{PropertyName} should be not null. NEVER!");; RuleFor(x => x.Name).Length(6, 16); RuleFor(x => x.Email).EmailAddress(); RuleFor(x => x.Age).InclusiveBetween(18, 60); } }
Localization
You can add IStringLocalizer<T> to the ctor of a validator
// person.en-US.json { "Name": "'{0}' must be at least 5 characters length.", "Address": "'Address' must be at least 10 characters length.", "EmailAddress": "'EmailAddress' is not valid.", "Age": "'Age' must be between 20 and 60.", }
// person.de.json { "Name": "'{0}' muss mindestens 5 Zeichen lang sein.", "Address": "'Address' muss mindestens 10 Zeichen lang sein.", "EmailAddress": "'EmailAddress' ist ungültig.", "Age": "'Age' muss zwischen 20 und 60 liegen.", }
// person.fa-IR.json { "Country": "کشور وارد شده معتبر نیست.", "Name": "{0} نباید کمتر از 5 کاراکتر باشد.", "Address": "آدرس نباید کمتر از 10 کاراکتر باشد.", "EmailAddress": "ایمیل وارد شده معتبر نیست.", "Age": "سن باید بین 20 تا 60 باشد.", }
Swagger integration
Use FluentValidation rules instead of ComponentModel attributes to define swagger schema.
publicvoidConfigureServices(IServiceCollection services) { // HttpContextServiceProviderValidatorFactory requires access to HttpContext services.AddHttpContextAccessor();
services .AddControllers() // Adds fluent validators to Asp.net .AddFluentValidation(c => { c.RegisterValidatorsFromAssemblyContaining<Startup>(); //HERE // Optionally set validator factory if you have problems with scope resolve inside validators. c.ValidatorFactoryType = typeof(HttpContextServiceProviderValidatorFactory); })
services.AddSwaggerGen(c => { c.SwaggerDoc("v1", new OpenApiInfo { Title = "My API", Version = "v1" });
// HERE // Adds fluent validation rules to swagger c.AddFluentValidationRules(); });
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline. publicvoidConfigure(IApplicationBuilder app, IHostingEnvironment env) { app.UseRouting();