Ok, I am using Fluent Validation for one of my classes what I want to know is. How does one determine what record is at fault i.e say for example the following
Is being classed as a number that the customer can refer to how would I Change my string below using Fluent Validation to output the current record it's working on Document No to the customer.
public string DocumentNo { get; set; }
Is it just as simple as appending it to the string?
Code:
public class SupplierTransactionsValidation : AbstractValidator<SageStandardImportInvoces>
{
public SupplierTransactionsValidation()
{
RuleFor(x => x.AnalysisCode1) // code repeated
.NotEqual("None").WithMessage("Please enter a value for AnalysisCode1")
.Length(0, 3);
RuleFor(x => x.AnalysisCode2) // code repeated
.NotEqual("None").WithMessage("Please enter a value for AnalysisCode2")
.Length(0, 3);
RuleFor(x => x.AnalysisCode3) // code repeated
.NotEqual("None").WithMessage("Please enter a value for AnalysisCode3")
.Length(0, 3);
}
}
If I understand your issue correctly, you could create a private method that gets the name of the property to be validated by casting the body of the expression to a MemberExpression:
public class SupplierTransactionsValidation : AbstractValidator<SageStandardImportInvoces>
{
public SupplierTransactionsValidation()
{
BuildRule(x => x.AnalysisCode1);
BuildRule(x => x.AnalysisCode2);
BuildRule(x => x.AnalysisCode3);
}
private IRuleBuilderOptions<SageStandardImportInvoces, string>
BuildRule(System.Linq.Expressions.Expression<Func<SageStandardImportInvoces, string>> expression)
{
return RuleFor(expression)
.NotEqual("None")
.WithMessage($"Please enter a value for {(expression.Body as System.Linq.Expressions.MemberExpression)?.Member.Name}")
.Length(0, 3);
}
}
This way you don't have to repeat your logic.
Related
I'm using fluent validation in order to validate certain model.
public class CarModelValidator : AbstractValidator<CarModel>
{
public CarModelValidator ()
{
RuleFor(x => x.Name).NotEmpty();
// RuleFor(x => x.NrOfDoors)....
}
}
is it possible to validate CarModel and it's property NrOfDoors, and if its less than 2 to set the value to be 2. Or fluent validation is not meant for that? Is it just for validate the model and informing the user and not for setting the values?
Best practice is that validation via any class library or tools do only the validation task and It is based on Separation_of_concerns pattern.
But if you need to make sure NrOfDoors property has maximum value by 2 you can use full property like this:
public class CarModel
{
private int nrOfDoors;
public int NrOfDoors
{
get { return nrOfDoors; }
set
{
if (value > 2)
nrOfDoors = 2;
else
nrOfDoors = value;
}
}
}
I think RuleFor(x => x.NrOfDoors).GreaterThanOrEqualTo(2) should do the trick
I think the real answer is: It is not meant for that. Validation is an intermediate step to deny a value change and inform the user if the rule(s) would be broken.
I have installed fluent validation in my web api core project. I created one validator in which I have validations for one field.
My TrainingDto.cs looks like this:
public class TrainingDto
{
public int Id { get; set; }
public string TrainingName { get; set; }
public int? NumberOfTrainings { get; set; }
public decimal Price { get; set; }
}
My validator looks like this:
public class TrainingDtoValidator : AbstractValidator<TrainingDto>
{
public TrainingDtoValidator ()
{
RuleFor(x => x.NumberOfTrainings)
.NotEmpty()
.WithMessage("Number of trainings can't be empty.")
.Must((x, list, context) =>
{
if (x.NumberOfTrainings.ToString() != "")
{
context.MessageFormatter.AppendArgument("NumberOfTrainings", x.NumberOfTrainings);
return Int32.TryParse(x.NumberOfTrainings.ToString(), out int number);
}
return true;
})
.WithMessage("Number of trainings must be a number.");
}
}
The problem is with this second validation condition. When I type letter instead of number, I got exception message "Could not convert string to integer: a. Path 'numberOfTrainings', line 1, position 24." instead of this message "Number of trainings must be a number." I want to show on the screen my validation message instead of this ASP.NET exception message. Number in this field should be greather than 0. Is this possible? Any idea how to fix this?
Edit based on OP edit:
As NumberOfTrainings is a nullable int, I would write the rule (using your messages) for ensuring this is not null and has a value greater than 0 as follows:
RuleFor(x => x.NumberOfTrainings)
.NotNull().WithMessage("Number of trainings can't be empty.")
.GreaterThan(0).WithMessage("Number of trainings must be greater than 0.");
A condensed set of tests for this rule would be as follows:
var fixture = new Fixture();
var validator = new TrainingDtoValidator();
var dto1 = fixture.Build<TrainingDto>().Without(x => x.NumberOfTrainings).Create();
validator.ShouldHaveValidationErrorFor(x => x.NumberOfTrainings, dto1).WithErrorMessage("Number of trainings can't be empty.");
var dto2 = fixture.Build<TrainingDto>().With(x => x.NumberOfTrainings, -1).Create();
validator.ShouldHaveValidationErrorFor(x => x.NumberOfTrainings, dto2).WithErrorMessage("Number of trainings must be greater than 0.");
var dto3 = fixture.Build<TrainingDto>().With(x => x.NumberOfTrainings, 0).Create();
validator.ShouldHaveValidationErrorFor(x => x.NumberOfTrainings, dto3).WithErrorMessage("Number of trainings must be greater than 0.");
var dto4 = fixture.Build<TrainingDto>().With(x => x.NumberOfTrainings, 1).Create();
validator.ShouldNotHaveValidationErrorFor(x => x.NumberOfTrainings, dto4);
Previous:
public int NumberOfTrainings { get; set; } as per this definition cannot be null and it cannot be 'not a number'. So the custom property validator
.Must((x, list, context) =>
{
if (x.NumberOfTrainings.ToString() != "")
{
context.MessageFormatter.AppendArgument("NumberOfTrainings", x.NumberOfTrainings);
return Int32.TryParse(x.NumberOfTrainings.ToString(), out int number);
}
return true;
})
.WithMessage("Number of trainings must be a number.");
which is converting the NumberOfTrainings to a string, then attempting to parse it to an Int32, is redundant. I'm not even sure NotEmpty will work as you expect, as a default int is 0 which I wouldn't call empty. Maybe that checks for int.MinValue or something. Normally I'd write an int rule using a greater/less than or a range validator.
Can you elaborate on what is a 'valid NumberOfTrainings'? possibly >= 0? Then a better answer can be provided.
LessThanOrEqualTo and GreaterThanOrEqualTo do not support but if you want, you can use like this way
RuleFor(I => I.Number).NotNull().WithMessage("your message")
.LessThanOrEqualTo(24).WithMessage("your message")
.GreaterThanOrEqualTo(0);
OR,
use new way InclusiveBetween
.InclusiveBetween(0, 24).WithMessage("your message");
Imagine you have a class like :
public enum Kind { Kind1, Kind2 }
public class MyForm
{
public string Kind { get; set; }
public ACustomClass1 Custom1 { get; set; }
public ACustomClass2 Custom2 { get; set; }
}
And you want to validate Custom1 with Custom1Validator when Kind == Kind1 (and Custom2 with Custom2Validator when Kind == Kind2, obviously)
What is the best way to proceed with version 8.6.0 ?
At the moment, I've done like this (but I find it is awkward):
public class MyFormValidator : AbstractValidator<MyForm>
{
public MyFormValidator (IStringLocalizer<Strings> localizer, Custom1Validator validator1, Custom2Validator validator2)
{
//validate Kind and then, in function of Kind, use correct validator
RuleFor(x => x).Custom((f, context) => {
if (!Enum.TryParse<Kind>(f.Kind, out var kind))
{
context.AddFailure(localizer["Invalid Kind"]);
return;
}
switch (kind)
{
case Kind.Kind1:
if (f.Custom1 == null)
{
context.AddFailure(localizer["Invalid Kind"]);
}
else if (! validator1.Validate(f.Custom1, out var firstError))
{
context.AddFailure(firstError);
}
break;
case Kind.Kind2:
if (f.Custom2 == null)
{
context.AddFailure(localizer["Invalid Kind"]);
}
else if (!validator2.Validate(f.Custom2, out var firstError))
{
context.AddFailure(firstError);
}
break;
}
});
}
}
Note that I am using asp.net core with dependency injection (this is why there is IStringLocalizer and I can not use SetValidator for Custom1 and Custom2)
What I'd like instead is something like
RuleFor(x => x.Kind).NotEmpty().IsEnumName(typeof(Kind)).withMessage(_ => localizer["Invalid Kind"]);
RuleFor(x => x.Custom1).NotEmptyWhen(f => f.Kind == Kind.Custom1.ToString()).withMessage(_ => localizer["Invalid Kind"])
RuleFor(x => x.Custom1).SetValidator(validator1); //would be executed only when custom1 is not null
//same for custom2
The problem is that I do not see how to do code the NotEmptyWhen method
Restructure?
By the looks of your posted code snippets, I presume that MyForm will never have a populated Custom1 and Custom2 property in the same request. So, instead of having a parent model that holds both payload kinds, I would encourage you to directly use the model that represents the payload being validated. Then you won't run into this nasty pattern of checking the kind wherever necessary.
One of your form endpoints accepts a Custom1, which has an associated Custom1Validator. Another one of your form endpoints accepts a Custom2, which has an associated Custom2Validator. They are decoupled. You can safely change one without affecting the other.
Use Fluent Validation Conditions (When/Unless)
If you're dead set on having one model responsible for representing the payload of multiple requests (please don't), you can use the When() method provided by the library. Take a look at their documentation on conditional rules.
Along with null error message, even the custom/must validation is ran and error failure is displayed.
I have set globally ValidatorOptions.CascadeMode = CascadeMode.StopOnFirstFailure;
Inside Custom can check if(motor = null) return; But is there any other way to restrict not to run custom or must validation in fluent validator.
Below is my PersonDetails model class :
Public Class PersonDetails {
public PersonName {get;set;}
}
Public Class PersonName {
public string Firstname {get;set;}
public string LastName {get;set;}
}
public class PersonApplicantValidator : AbstractValidator<PersonDetails>
{
RuleFor(x => x.PersonName ).NotNull().WithMessage("Mandatory field").Custom(
(personName, context) =>
{
if(personName.Firstname == null)
context.AddFailure("FirstName is mandatory");
});
}
When PersonName= null,
Actual : Mandatory field FirstName is mandatory
Expected : Mandatory field
How to stop on first failure and not run custom?
In order to make FirstName mandatory only when Name is empty you can use this:
RuleFor(x => x.FirstName)
.NotNull().When(x => string.IsNullOrEmpty(x.Name)).WithMessage("FirstName is mandatory");
I'm having some issues the using FluentValidation library.
I have a very small Model
`
[FluentValidation.Attributes.Validator(typeof(PersonValidator))]
public class PersonModel
{
public string Name { get; set; }
public Nullable<short> Type { get; set; }
}
`
Have a validator class
public class PersonValidator : AbstractValidator<PersonModel>
{
public PersonValidator()
{
RuleFor(x => x.Name)
.Length(1, 5)
.WithLocalizedMessage(() => BaseValidationResource.LengthValidationMessage, 1, 5);
}
}
And I have a controller
public ActionResult Index()
{
var model = new PersonModel();
model.Name = "John Doe";
var validator = new PersonValidator();
var results = validator.Validate(model);
var error = GetModelErrors();
return View(model);
}
So far so good, the issue is that when the progam is executing and it gets to the line with ; var results = validator.Validate(model); it throws a SystemFormatException.
Instead of throwing an exception, shouldn't the validate method just return an object containing a boolean field which indicates if the model is valid and a list of errors.
PS : I know that this particular validation can also be done using DataAnnotations but i want to use Fluentvalidation because its more flexible.
Thanks in advance for your help.
As #RIc pointed out was a string formatting issue on my Resource file.
On my validor i had this line
RuleFor(x => x.Name)
.Length(1, 5)
.WithLocalizedMessage(() => BaseValidationResource.LengthValidationMessage, 1, 5);
Which pointed to the resource file and passed 2 parameters. But on my resource file i was expecting 3 parameters (property name, min value, max value).
However i was using the wrong annotation. Below are the before and after version of the the resource file.