Validation of integer in fluent validation when character is not number - c#

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");

Related

Fluent Validation output current record information

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.

FluentValidation Validate Method throwing an excepion

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.

C# Razor - Display value from list based on key

I'm trying to display some text from a list depending on the key:
<td>#x.OutcomeSummary</td>
<td>#Model.SummaryOutcomes.FirstOrDefault(c => c.Value == x.OutcomeSummary).Name</td>
<td>#Model.SummaryOutcomes.FirstOrDefault(x.OutcomeSummary).Name</td>
In this case x.OutcomeSummary is 7 and I would like for it to get the relevant text from SummaryOutcomes with a key of 7.
The second line gives me the following error: System.NullReferenceException: Object reference not set to an instance of an object.
And the third line gives me an error saying that the FirstOrDefault command has invalid arguments.
It imports the following model:
public class DogSummaryView
{
public IEnumerable<DogIncident> Incidents { get; set; }
public IEnumerable<Category> SummaryOutcomes { get; set; }
public IEnumerable<Category> DogBreeds { get; set; }
}
This is the category class:
public class Category
{
public string Value { get; set; }
public string Name { get; set; }
public bool InUse { get; set; }
}
And this is the controller:
public ActionResult Summary()
{
var vm = new DogSummaryView
{
Incidents = repository.GetAllRecords().OrderByDescending(x => x.DateRecieved),
SummaryOutcomes = repository.GetAllSummaryOutcomes()
};
return View("Summary", vm);
}
And finally here you can see that the list is populated and initialized:
Is there anyway of getting it so that instead of displaying 7, it displays the correct summary outcome?
Thanks
Thank you so so much to fourpastmidnight for his persistent help with this, and not only helping me to find a solution, but also helping me to understand just exactly where the problem lied. Here's an updated (working!) solution:
#foreach (var x in Model.Incidents)
{
var summaryOutput = "";
var firstOutcomeSummary = Model.SummaryOutcomes.FirstOrDefault(c => c.Value == x.OutcomeSummary);
if (firstOutcomeSummary != null) { summaryOutput = Model.SummaryOutcomes.FirstOrDefault(c => c.Value == x.OutcomeSummary).Name.ToString(); }
<tr>
<td>#Html.Raw(summaryOutput)</td>
</tr>
}
Ok, the problem is you're trying to compare a string to an int.
Change the second line as follows:
#Model.SummaryOutcomes.FirstOrDefault(c => c.Value == x.OutcomeSummary.ToString()).Name;
// You could also use '.Value'.
That should solve your problem.
UPDATE
Hmm, maybe x.OutcomeSummary.ToString() is resulting in the type name of the enumeration and not the integer value of the enumeration constant value.
Try updating the above line to the following:
#Model.SummaryOutcomes.FirstOrDefault(c => c.Value == ((int)x.OutcomeSummary).ToString()).Name;
UPDATE 2014-03-21
According to the OP's latest comment, try the following:
// If x.OutcomeSummary is the outcome summary name, then....
var firstOutcomeSummary = #Model.SummaryOutcomes.FirstOrDefault(c => c.Name == x.OutcomeSummary)
if (firstOutcomeSummary != null)
// Do something here.
// Else, if x.OutcomeSummary is the outcome summary value, e.g. "7", then...
var firstOutcomeSummary = #Model.SummaryOutcomes.FirstOrDefault(c => c.Value == x.OutcomeSummary)
if (firstOutcomeSummary != null)
// Do something here.
FirstOrDefault will do just that return the first element that matches the predicate or return default, i.e. null. Calling .Name on a null object will throw a NullReferenceException
In sum, the behavior you are describing will occur if there's no instance of Category in the enumeration SummaryOutcomes whose value is 7.

Integer validation against non-required attributes in MVC

I've trying to validate a property on a model I have. This property is NOT required, and so if its invalid MVC seems to be ignoring it. I've even created a custom ValidationAttribute, but nothing works.
public class NumberWang : ValidationAttribute
{
public override bool IsValid(object value)
{
if (value == null)
return true;
int g;
if (int.TryParse(value.ToString(), out g))
{
if (g >= 0)
return true;
}
return false;
}
}
public class MyModel
{
[Range(0, 999999, ErrorMessage = "category_id must be a valid number")]
[NumberWang(ErrorMessage = "That's NumberWang!")]
public int? category_id { get; set; }
/* there are other properties of course, but I've omitted them for simplicity */
public void Validate()
{
Validator.TryValidateProperty(this.category_id,
new ValidationContext(this, null, null) { MemberName = "category_id" },
this.validation_results);
}
}
If I pass the value 'abc' as a category_id to this model, it validates just fine. What am I doing wrong?
I found an ugly workaround.
It seems that if category_id is a nullable int? and my value is not a valid number, a null value is passed, and the model doesn't see the invalid 'abc' value.
[Range(0, 999999, ErrorMessage = "category_id must be a valid number")]
public int? category_id { get; set; }
// when we pass a good number
MyAction?category_id=123
validation: successful
// when we pass a bad number
// validation ignores it. not what we want.
MyAction?category_id=abc
validation: successful
If I change category_id to a non-nullable int, it fails validation even when no value is passed.
[Range(0, 999999, ErrorMessage = "category_id must be a valid number")]
public int? category_id { get; set; }
// when we pass a good number
MyAction?category_id=123
validation: successful
// when we pass an bad number
MyAction?category_id=abc
validation: "category_id must be a valid number"
// BUT, when we don't pass any number at all ...
MyAction
validation: "category_id must be a valid number"
The Ugly Workaround
If I change category_id to a string, and then only convert it to an int when I need it, I can validate it properly, using only [Range]
[Range(0, 999999, ErrorMessage = "category_id must be a valid number")]
public string category_id { get; set; }
// when we pass a good number
MyAction?category_id=123
validation: successful
// when we pass a bad number
MyAction?category_id=abc
validation: "category_id must be a valid number"
// no number, no validation. hooray!
MyAction
validation: successful
It's ugly, but it works.
(Note: the custom attribute was not needed, so I removed it and just used [Range])
model:
// "(\s*[0-9]{0,6})" for 999999 max
// "(\s*[0-9]*)" for "infinite"
[RegularExpression(#"(\s*[0-9]{0,6})", ErrorMessage = "Field must be a natural number (max 999999)")]
public int? Quantity { get; set; }
view:
#Html.TextBoxFor(m => m.Quantity)
#Html.ValidationMessageFor(m => m.Quantity)
First your model should implement IValidatableObject, but the Validate method is going to be called only if the ModelState is valid. This link can help.
Are you receiving your model as a parameter in the action? Are you asking for the ModelState.IsValid? This should work fine with the Default model binder.
I found a workaround that I kind of like. The problem I ran into was a similar issue, where the value had to be greater than 0 and a number, so once I tried to cast the 9999999999 (invalid) string as a number it threw an exception and didn't show that the model state has an error message on the post. I hope this is on topic since it sounds like "Rules don't seem to apply correctly when I type in an invalid number"
Since my number had to be positive I intercepted the value into an anonymous type object (dynamic) and used it as an intercepting body between the model's user and the private int property. When I did that, the Data Annotations worked as expected.
public dynamic MyProperty
{
get { return _myProperty; }
set
{
try
{
/I have to convert the value to String because it comes in as String[], and if there is more than one value then it should be problematic, so you join it together with an invalid character and throw an error, or you take the first value/
_myProperty = = Convert.ToInt32(String.Join("a",value));
}
catch (Exception e)
{
_myProperty= -1;
}
}
}
private int _myProperty;

Reporting unknown arguments using CommandLineParser

Is there any way to make the Command Line Parser library report unknown arguments?
Given the following options class:
public class Options
{
[Option('i', "int-option", DefaultValue = 10, HelpText = "Set the int")]
public int IntOption { get; set; }
[ParserState]
public IParserState LastParserState { get; set; }
[HelpOption]
public string GetUsage()
{
return HelpText.AutoBuild(this,
HelpText.DefaultParsingErrorsHandler(this, current));
}
}
And the following program:
var options = new Options();
var parser = new Parser(settings =>
{
settings.HelpWriter = Console.Error;
settings.IgnoreUnknownArguments = false;
});
if (parser.ParseArgumentsStrict(args, options))
{
Console.WriteLine("Int value set: {0}", options.IntOption);
}
When calling the program with "MyProgram.exe --unknown"
I just get the default usage information, but no mention of what error made the parsing fail. I'd like to get some kind of indication to the user what went wrong.
Long story short: with the current implementation you can't get any info about the unknown options.
The long story:
If you put a brakepoint into your GetUsage method you will see that the LastParserState is not null but contains 0 element.
LastParserState is basically filled from the ArgumentParser.PostParsingState but the
the LongOptionParser (which in your case is involved because of the -- double dash) is not adding anything to the PostParsingState collection inside its parse method:
Source from Github:
var parts = argumentEnumerator.Current.Substring(2).Split(new[] { '=' }, 2);
var option = map[parts[0]];
if (option == null)
{
return _ignoreUnkwnownArguments ? PresentParserState.MoveOnNextElement :
PresentParserState.Failure;
}
So internally the parser doesn't store any info about what went wrong just record that fact.

Categories