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;
Related
I'm using .Net Core 3. I have a controller with [ApiController] attribute on it. So it returns BadRequest Error if Model Validation fails, automatically. But I have primitive values in my models like int, bool, and if I don't include these values in my request object, the ApiController does not produce an error in this case. But initializes these fields to default value. I want it to produce BadRequest Error. How can I do this?
I made the fields nullable and it worked but I don't want to make them nullable. Please help me. Thanks.
Edit:
public class AddEmployeeReqObject
{
[Required]
[JsonPropertyName("casino_id")]
[Display(Name = "casino_id")]
public long CasinoId { get; set; }
[Required]
[JsonPropertyName("name")]
[Display(Name = "name")]
public string Name { get; set; }
[Required]
[JsonPropertyName("yubico_key")]
[Display(Name = "yubico_key")]
public string YubicoKey { get; set; }
[Required]
[JsonPropertyName("is_manager")]
[Display(Name = "is_manager")]
public bool IsManager { get; set; }
}
Here IsManager and CasinoId are initialized by default with false and 0.
You should use BindRequiredAttribute instead of RequiredAttribute. RequiredAttribute works perfect for nullable-types, but for non-nullable it has no effect. For example for int the default value is 0 and the property can never be null. Even if the client submits a request where the property is completely missing, the model instance would have a value 0.
BindRequiredAttribute comes to action!
It works the same way as RequiredAttribute, except it mandates that the value comes from the request – so it not only rejects null values, but also “unbound” values.
For examples visit: https://www.strathweb.com/2017/12/required-and-bindrequired-in-asp-net-core-mvc/
I don’t know why you both set the default value for the fields, but
also want to judge it's default value as null and verify as an error.
While verifying ModelState.IsValid, you can determine whether the two fields of the model are the default values you set. If yes, then fail the verification and return BadRequest Error.
public async Task<IActionResult> Test(AddEmployeeReqObject obj)
{
if (ModelState.IsValid && obj.CasinoId != 0 && obj.IsManager)
{
return Ok();
}
else
{
return BadRequest();
}
}
int and bool variables cannot be null so to check them you have to:
make them nullable and test if they are null
or
set the bool field true (precheck the checkbox or sth) and set the int to a negative value
I have ASP.Net WebAPI based application. Below is my DTO.
public class CustomerTO
{
[Required(ErrorMessage="Name required")]
[StringLength(50, MinimumLength = 3, ErrorMessage = "Name invalid")]
public string Name { get; set; }
[Required(ErrorMessage="CountryId required")]
[Range(1,250,ErrorMessage="CountryId invalid")]
public int Country { get; set; }
}
My API Controller.
[HttpPost]
[AllowAnonymous]
public HttpResponseMessage Post([FromBody]CustomerTO model)
{
if (ModelState.IsValid)
{
//my stuff
}
else
{
var msg = ModelState.SelectMany(s => s.Value.Errors).FirstOrDefault().ErrorMessage;
}
}
If user passed any of the required field as Null, it returns the right Error message mentioned in the Data Annotations while if I pass string for CountryId, it enters into else condition(*ModelState.IsValid = false*)
But the ErrorMessage is empty.
While If I debug & put the below statement in quick watch.
msg = ModelState.SelectMany(s => s.Value.Errors).FirstOrDefault().Exception.Message;
It returns - Could not convert string to integer: en. Path 'Country', line 6, position 14.
Why in this scenario, I am not getting the Error message as CountryId Invalid
How do I get this?
Using a RegularExpressionAttribute does not prevent RangeAttribute from throwing an "Could not convert string to integer" exception. So just filter the right one:
var msg = ModelState.SelectMany(s => s.Value.Errors)
.FirstOrDefault(_ => _.Exception == null)
.ErrorMessage;
As far as I know, it is a common problem: SO question 1, SO question 2.
According to code, from any validation attribute there is creating a wrapper, derived from RequestFieldValidatorBase. Each wrapper calls IsValid method of ValidationAttribute. In the method Validate of RequestFieldValidatorBase passing form value for validation.
So, RequiredAttribute does not fails, because form value is not empty and is not null, and RangeAttribute does not fails, because it has problems converting this value to int.
To achieve your desired behaviour it is recommend to create your own validation attribute or use a RegularExpressionAttribute. You can take a look at this answer.
I believe the range validators dont cater for string entry, i.e. they only fire if it's a valid integer. It doesn't enforce the type passed in.
Try changing your annotation to a regex.
[RegularExpression("([1-9][0-9]*)", ErrorMessage = "Country code invalid")]
public string Country { get; set; }
With reference to this link Integer validation against non-required attributes in MVC
As dirty patch, I modified my property from int to string & decorated it with Regular Expression.
I have the following code:
public class EventController : ApiController
{
//public IHttpActionResult Post(List<Event> Events)
public IHttpActionResult Post(Newtonsoft.Json.Linq.JArray J)
{
//Debug.WriteLine(J.ToString());
List<Event> Events = Newtonsoft.Json.JsonConvert.DeserializeObject<List<Event>>(J.ToString(), new Newtonsoft.Json.JsonSerializerSettings {
Error = delegate(object sender, ErrorEventArgs args) {
Debug.WriteLine(args.ErrorContext.Error.Message);
args.ErrorContext.Handled = true;
},
Converters = { new IsoDateTimeConverter() }
}
);
foreach (Event Event in Events)
{
Debug.WriteLine(Event.Importance.ToString());
Debug.WriteLine(Event.Date.ToString());
Debug.WriteLine(Event.Description);
}
}
}
public class Event
{
[DataAnnotationsExtensions.Integer(ErrorMessage = "{0} must be a number.")]
[Range(0,10),Required]
public Int32 Importance { get; set; }
//[OnConversionError: "Please enter a valid date."]
[Required]
[DataAnnotationsExtensions.Date]
public object Date { get; set; }
[RegularExpression(#"^.{20,100}$", ErrorMessage="{0} must be between 20 and 100 characters.")]
[Required]
public string Description { get; set; }
}
I'm posting:
[{"Importancee":"adasdasd","Date":"2005-10-32","Descriptione":""},
{"Importance":"6.0","Date":"2015-10-02","Description":"a"}]
"Importance" is misspelled on purpose to simulate the scenario of missing data. When I post this I expect the delegate function to capture the invalid data and let me know the required fields are missing. I also expect the Regular Expression used for Description to cause an error for the 1 character description "a". Instead Json.net's Deserializer skips the missing fields and sets those properties to null and it sets the 2nd Description property to the "a" string. It's completely ignoring the Data Annotations. Is there any way to get Json.NET to recognize the Annotations?
You could generate a JSchema from the Data Annotation attributes:
http://www.newtonsoft.com/jsonschema/help/html/GenerateWithDataAnnotations.htm
And validate them them using:
http://www.newtonsoft.com/json/help/html/JsonSchema.htm
Data Annotations will not work directly, but with little effort, I believe you can get what you need.
Before I start ... I can't easily migrate the project to MVC3. So.
The problem I'm having is that I've defined a custom validator attribute to check the max AND min length of a string property, StringLengthInRangeAttribute.
When the Controller calls ModelState.IsValid, on a list of Passengers only the validation of a Date property is throwing invalid, when nothing has been supplied. I guess that means my problem is not with the custom validator but all validation?
Update (additional info for clarity):
I have two symptoms of this problem :
1.The Required validator on the strings doesn't fire when they are empty
and
2.My custom validator never gets called (a breakpoint I set never gets hit).
Model:
public class Passenger
{
[Required(ErrorMessageResourceType = typeof(Resources.Messages.Passenger),
ErrorMessageResourceName = "RequireNumber")]
public int Number { get; set; }
[Required(ErrorMessageResourceType = typeof(Resources.Messages.Passenger),
ErrorMessageResourceName = "RequireSurname")]
[StringLengthInRange(MinLength = 2, MaxLength = 30, ErrorMessageResourceType = typeof(Resources.Messages.Passenger),
ErrorMessageResourceName = "MaxLengthSurname")]
public string Surname { get; set; }
}
Custom Validator:
public class StringLengthInRangeAttribute:ValidationAttribute
{
public int MinLength { get; set; }
public int MaxLength { get; set; }
public override bool IsValid(object value)
{
if (((string)value).Length < MinLength)
{
return false;
}
if (((string)value).Length > MaxLength)
{
return false;
}
return true;
}
}
Controller Action:
public ViewResult TailorHoliday(List<SearchAndBook.Models.ViewModels.Passenger> passengers,
int leadPassengerIndex)
{
if(!ModelState.IsValid)
{
return View("PassengerDetails", GetBookingState(_currentSession));
}
//...
return View();
}
Any advice appreciated. This is the first time I've used Data Annotations, so I'm quite prepared to feel stupid for missing something!
If you check the content of the ModelState property (in the debugger) you should be able to see every property that gets validated in ModelState.Keys and for each value you see the actual state in the ModelState.Values. If I look at your controller you seem to post a whole list of passengers. You should check as well, whether your values are really posted (use Firebug or Fiddler). Maybe your fields are outside the form.
Maybe you should show part of your view as well to spot the bug.
I have a int property in my class and want to validate if the user has entered a string.
How can I do that using data annotations?
When I pass a non-integer value I get a excpetion like this:
The value 'asdasd' is not valid for property.
For example using this validation attribute:
[Range(0, Int32.MaxValue, ErrorMessage="Invalid Number")]
public int? Number { get; set; }
and entering 'aaa' in a field tha uses the model I've got an excepetion with this message:
The value 'aaa' is not valid for Number.
Instead of the "Invalid Number" message.
Any Ideas?
I've put
[Range(0, Int32.MaxValue, ErrorMessage="Invalid Number")]
public int? Number { get; set; }
but I've got this message from an excpetion
The value 'aaa' is not valid for Number.
There are two stages of validation at play here.
Before the validation set up in your attributes is called, the framework first attempts to parse the information.
So here are a couple of examples based on this code:
[Range(0, Int32.MaxValue, ErrorMessage="Invalid Number")]
public int? Number { get; set; }
I type nothing into the box...
"Invalid Number" (Framework will create a null integer, your validation rule fails)
I type "A" into the box...
"The value 'A' is not valid for Number." (Framework cannot convert "A" into a nullable int, so the framework validation rule fails and your validation rule it not checked.
** Solutions **
1 - Live with the default message until you are using MVC 3 / .NET 4, which makes it easier to override these messages
2 - Exclude the value from the binder, so it won't cause an error (but you will have to bind it and check it yourself)
[Bind(Exclude="MyNumber")]
3 - Make it a string on the model, then test it with a TryParse and add your own custom model error (This is a reasonable practice and reminds us all why View Models are used rather than Domain Objects!)
if (!Int32.TryParse("MyNumber", out myInteger)) {
ModelState.AddModelError("MyNumber", "That isn't a number!");
}
There are actually lots of solutions, but I would say go with option 3 for now.
You could put a Range-attribute on your model-field like so:
[Range(0, 9999)]
public int Something {get; set;}
Now whenever the user inputs a string it will not validate, and also the int must be between 0-9999
this should also work with
[Range(0, 9999)]
public string Something {get; set;}
and
[Range(0, 9999)]
public object Something {get; set;}