Showing posts with label fluentvalidation. Show all posts
Showing posts with label fluentvalidation. Show all posts

Monday, October 15, 2018

Fluent Validation validator results in an error before validation code is even added to it

Leave a Comment

I'm trying out Fluent Validation using the Contoso University project.

So I've added a validator attribute to an existing class:

[Validator(typeof(PersonValidator))] public abstract class Person {     public int ID { get; set; }      [Required]     [StringLength(50)]     [Display(Name = "Last Name")]     public string LastName { get; set; } } 

My PersonValidator doesn't do anything yet:

public class PersonValidator : AbstractValidator<Person> {     public PersonValidator()     {     } } 

But when I access the create page for a Student my debugger stops on the EditorFor line....

 @Html.EditorFor(model => model.LastName,        new { htmlAttributes = new { @class = "form-control" } }) 

….and I get an error:

Validation type names in unobtrusive client validation rules must be unique. The following validation type was seen more than once: required

I don't appear to have the same validation on the same element more than once, so why am I getting the error? Can Fluent Validation work alongside MVC's built in validation?

2 Answers

Answers 1

This can happen if you use FluentValidation with DataAnnotations. Try to do something like this in Application_Start

DataAnnotationsModelValidatorProvider.AddImplicitRequiredAttributeForValueTypes = false; FluentValidationModelValidatorProvider.Configure(provider => provider.AddImplicitRequiredValidator = false); var fluentValidationModelValidatorProvider = new FluentValidationModelValidatorProvider(new AttributedValidatorFactory()); ModelValidatorProviders.Providers.Add(fluentValidationModelValidatorProvider); 

Answers 2

As per this page, you can try removing the DataAnnotations validations.

Compatibility with ASP.NET’s built-in Validation By default, after FluentValidation is executed then any other validator providers will also have a chance to execute as well. 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:

services.AddMvc().AddFluentValidation(fv => {  fv.RunDefaultMvcValidationAfterFluentValidationExecutes = false; }); 

Note If you do set RunDefaultMvcValidationAfterFluentValidationExecutes to false then support for IValidatableObject will also be disabled.

Hope this helps!

Read More

Monday, July 10, 2017

Testing FluentValidation PropertyValidator

Leave a Comment

Is it possible to test a FluentValidation PropertyValidator in isolation?

I know I can test the Validator that's using the PropertyValidator for specific errors but I’d rather test true/false just on the property validator if possible.

Can this be done? If so, how?

3 Answers

Answers 1

I also wanted to test my true / false logic. It is a shame the IsValid method is protected. My work around was to create another IsValid method and have the protected IsValid call through to it.

public class MyValidator: PropertyValidator  {     public MyValidator(         string errorMessage = "default Message") : base(errorMessage)     {     }      protected override bool IsValid(PropertyValidatorContext context)     {         var stringToValidate = context.PropertyValue as String;         return IsValid(stringToValidate);     }      public bool IsValid(string stringToValidate)     {         if (stringToValidate == null)         {             return false;         }          //testing logic here         return true;     } } 

Answers 2

I know this has been a while, but I achieved this as follows:

Custom Validator:

public class MyValidator : PropertyValidator {     public MyValidator ()         : base("Value must be null or between 0 and 3.")     {     }      protected override bool IsValid(PropertyValidatorContext context)     {         if (context.PropertyValue == null)         {             return true;         }          var value = (decimal)context.PropertyValue;         return value >= 0m && value <= 3m;     } } 

Test Validator:

public class TestValidator : InlineValidator<TestObject> {     public TestValidator (params Action<TestValidator >[] actions)     {         foreach (var action in actions)         {             action(this);         }     } } 

Test Object:

public class TestObject {     public TestObject(decimal? val)     {         this.GenericDecimal = val;     }      public decimal? GenericDecimal { get; set; } } 

Test:

[Test] public void TestIt() {     var validator = new TestValidator(v => v.RuleFor(obj => obj.GenericDecimal).SetValidator( new MyValidator() ));      Assert.IsTrue(validator.Validate(new TestObject(null)).IsValid);         Assert.IsTrue(validator.Validate(new TestObject(0m)).IsValid);        Assert.IsTrue(validator.Validate(new TestObject(3m)).IsValid);        Assert.IsFalse(validator.Validate(new TestObject(-1m)).IsValid);        Assert.IsFalse(validator.Validate(new TestObject(3.01m)).IsValid);    } 

Answers 3

As for version 6.2 of FluentValidation it is possible to build the PropertyValidator.Validate() parameter due to making ValidatorSelectors globally configurable: https://github.com/JeremySkinner/FluentValidation/commit/95376c0519da1a06388be91a97fb5062fd4a162e

In the below example you see how I validate the 'puic' property of Track

Unit test:

    public void ExistsInCollectionValidatorTest()     {         var track = new Track()         {             puic = "p1"         };          var sut = new ExistsInCollectionValidator<Track>();          // Build PropertyValidator.Validate() parameter         var selector = ValidatorOptions.ValidatorSelectors.DefaultValidatorSelectorFactory();         var context = new ValidationContext(track, new PropertyChain(), selector);         var propertyValidatorContext = new PropertyValidatorContext(context, PropertyRule.Create<Track,string>(t => t.puic), "puic");          var results = sut.Validate(propertyValidatorContext);         // Assertion..     } 
Read More

Thursday, September 1, 2016

Fluent Validation doesn't validate the entire form the first time

Leave a Comment

So I'm using Fluent Validation on a form. When I click submit and have nothing entered, I get a validation error for Date of Birth. If I enter a DoB, then I get the validation for First Name.

Why is this happening? I can't figure out what I wired up wrong.

My form:

 @using (Html.BeginForm())     {         @Html.AntiForgeryToken()         @Html.HiddenFor(customer => customer.CustomerIncomeInfo.CustomerEmploymentInfoModel.EmployerModel.Id)              <!-- basic customer info -->         <fieldset>             <legend>Customer Info</legend>             @Html.ValidationSummary(false, "Please correct the errors and try again", new { @class = "text-danger" })             <div class="row">                 <div class="col-md-6">                     <dl class="dl-horizontal">                         <dt>@Html.LabelFor(model => model.FirstName)</dt>                         <dd>@Html.EditorFor(model => model.FirstName, new {@class = "form-control"})</dd>                      </dl>                 </div>                 <div class="col-md-6">                     <dl class="dl-horizontal">                         <dt>@Html.LabelFor(model => model.DateOfBirth)</dt>                         <dd>@Html.EditorFor(model => model.DateOfBirth, new {@class = "form-control"})</dd>                      </dl>                 </div>             </div>         </fieldset> } 

My Fluent Validation Code:

public CustomerValidator()         {             RuleFor(customer => customer.FirstName)                 .Length(3, 50)                 .NotEmpty()                 .WithMessage("Please enter a valid first name");              RuleFor(customer => customer.DateOfBirth).NotEmpty().WithMessage("Please enter a valid date of birth");           } 

My Model:

public class CustomerModel     {         public CustomerModel()         {         }          public Guid Id { get; set; }         [DisplayName("First Name")]         public string FirstName { get; set; }         [DisplayName("D.O.B.")]         public DateTime DateOfBirth { get; set; } } 

Registering the validator with Autofac:

builder.RegisterType<CustomerValidator>()                 .Keyed<IValidator>(typeof(IValidator<CustomerModel>))                 .As<IValidator>(); 

1 Answers

Answers 1

I am also usingFluent Validation in my project in my project it is working the same way as you are required.I have tried your code same as my code it is working fine please refer below code:

/// Test CODE  /// Model Class [Validator(typeof (CustomerModelValidator))] public class CustomerModel {     public CustomerModel() {}     public Guid Id {         get;         set;     }     [DisplayName("First Name")]     public string FirstName {         get;         set;     }     [DisplayName("D.O.B.")]     public DateTime DateOfBirth {         get;         set;     } } // Validator Class public class CustomerModelValidator: AbstractValidator < CustomerModel > {     public CustomerModelValidator() {         RuleFor(customer = > customer.FirstName)             .Length(3, 50)             .NotEmpty()             .WithMessage("Please enter a valid first name");         RuleFor(customer = > customer.DateOfBirth).NotEmpty().WithMessage("Please enter a valid date of birth");     } } 

Hope it helps you.

Read More

Thursday, June 23, 2016

FluentValidation Doesn't Work When Using WebApi [Route] Attribute

Leave a Comment

I successfully implemented FluentValidation in my WebApi project controller that only had one HttpGet method. When I added another HttpGet method, I added route attribute to both methods. i.e. [Route("Method1")] and [Route("Method2")].

Now the ModelState comes back as true regardless of whether I enter any data or not.

Here is my code.

WebApiConfig

public static class WebApiConfig {     public static void Register(HttpConfiguration config)     {          config.Filters.Add(new ValidateModelStateFilter());          //FluentValidation         FluentValidationModelValidatorProvider.Configure(config);          // Web API routes         config.MapHttpAttributeRoutes();          config.Routes.MapHttpRoute(             name: "DefaultApi",             routeTemplate: "{action}/{id}",             defaults: new { controller = "Menu", id = RouteParameter.Optional}         );       } } 

ValidateModelStateFilter

public class ValidateModelStateFilter : ActionFilterAttribute {     public override void OnActionExecuting(HttpActionContext actionContext)     {         if (!actionContext.ModelState.IsValid)         {             actionContext.Response = actionContext.Request.CreateErrorResponse(HttpStatusCode.BadRequest, actionContext.ModelState);         }     } } 

Controller

[HttpGet] [Route("Method1")] public IHttpActionResult ReadAllMenusByApplication([FromUri] ReadAllMenusByApplicationInput input) {         var result = new List<ApplicationMenu>();         ... } 

Input Object

using FluentValidation; using FluentValidation.Attributes;  namespace MenuService.Models { [Validator(typeof(ReadAllMenusByApplicationInputValidator))] public class ReadAllMenusByApplicationInput {     public ReadAllMenusByApplicationInput() {         this.ApplicationName = string.Empty;     }      /// <summary>     /// The MenuSystem name of the application     /// </summary>     public string ApplicationName { get; set; } }  public class ReadAllMenusByApplicationInputValidator : AbstractValidator<ReadAllMenusByApplicationInput> {     public ReadAllMenusByApplicationInputValidator()     {         RuleFor(x => x.ApplicationName).NotEmpty();     } } 

}

1 Answers

Answers 1

Using this article for reference

Custom Validation in ASP.NET Web API with FluentValidation

You seem to have most of what is done in the referenced article.

Check your configuration order.

public static class WebApiConfig {     public static void Register(HttpConfiguration config) {         // Web API configuration and services         config.Filters.Add(new ValidateModelStateFilter());          // Web API routes         config.MapHttpAttributeRoutes();          config.Routes.MapHttpRoute(             name: "DefaultApi",             routeTemplate: "{action}/{id}",             defaults: new { controller = "Menu", id = RouteParameter.Optional}         );          //FluentValidation         FluentValidationModelValidatorProvider.Configure(config);         } } 

FluentValidation automatically inserts its errors into the ModelState. You should include an error message.

public class ReadAllMenusByApplicationInputValidator : AbstractValidator<ReadAllMenusByApplicationInput> {     public ReadAllMenusByApplicationInputValidator() {         RuleFor(x => x.ApplicationName).NotEmpty()             .WithMessage("The Application Name cannot be blank.");     } } 

The article has some content that is outside of the scope of your question. mainly wrapping the responses but everything else should work for you.

Read More