I have an int? view model property that is validated at client-side as if it was required. That is, if I leave the field blank, it will not submit. The same does not happen for string properties.
The HTML rendered for my editor is:
<input type="text" value="" name="StatusIdSearch" id="StatusIdSearch" data-val-number="The field Status must be a number." data-val="true" class="text-box single-line">
I believe that data-val-number is causing an error because nothing is not a number, but I cannot determine why.
Any ideas?
Edit
The view-model:
public class CompromissoSearchModel
{
// other properties removed for the sake of clarity
[Display(Name = "Status")]
[EnumDataType(typeof(StatusCompromisso))]
public int? StatusIdSearch { get; set; }
// other properties removed for the sake of clarity
}
The message you are seeing it's not related to a required field validation. You're gettings this because ClientDataTypeModelValidatorProvider adds client numeric validation and it ignores if the type is nullable or nor not. You can check the code yourself:
private static IEnumerable<ModelValidator> GetValidatorsImpl(
ModelMetadata metadata,
ControllerContext context)
{
Type type = metadata.RealModelType;
if (IsNumericType(type)) {
yield return new NumericModelValidator(metadata, context);
}
}
And the IsNumericType implementation:
private static bool IsNumericType(Type type)
{
// strip off the Nullable<>
Type underlyingType = Nullable.GetUnderlyingType(type);
return _numericTypes.Contains(underlyingType ?? type);
}
Since the nullable is not considered you always get that validation. In terms of solution, you need to remove ClientDataTypeModelValidatorProvider from the used providers or maybe replace it with a custom one that does not ignore nullable.
You should be able to add the following code to your Application_Start method in Global.asax file to fix this issue:
DataAnnotationsModelValidatorProvider.AddImplicitRequiredAttributeForValueTypes = false;
I was having the exact same problem and managed to find a solution. None of these solutions worked for me so I thought I'd post my solution for anyone else having this problem.
The problem was not that the model binder was validating the field as invalid, but that when using TryUpdateModel the nullable property of the viewmodel wasn't nullable in the database entity.
Clearer explanation:
TryUpdateModel(dbUser, "", new[]{
"DecimalProperty"
}));
"DecimalProperty" in the viewmodel was nullable, but it wasn't nullable in dbUser.
Related
I've got a checkbox that I want to display on my view related to a field called public, which basically says whether the particular row is public or not. In the database this is a bit field, but it allows nulls, due to how the table previously used to work.
I'm using Html.CheckBoxFor but it is complaining about this field because in the system it is not a bool type, but rather a bool? type. What I want to do is have it so that if the field value is null, then it counts as a false on the front end (unfortunately updating the database values themselves is not an option).
I have tried using the GetValueOrDefault, and putting a default value in my model file along the lines of:
public class Model
{
public bool? Public { get; set; }
public SearchModel()
{
Public = false;
}
}
however it was complaining about this, giving me the following error:
An exception of type 'System.InvalidOperationException' occurred in System.Web.Mvc.dll but was not handled in user code
Additional information: Templates can be used only with field access, property access, single-dimension array index, or single-parameter custom indexer expressions.
So i'm not sure how I can progress from here, can someone help point me in the right direction?
EDIT:
This is the code on the view that i'm trying to use to show the checkbox. In this instance i'm adding some extra html attributes so that it appears as a toggle rather than a simple checkbox:
Html.CheckBoxFor(model => model.Public, new {data_toggle = "toggle", data_off = "No", data_on = "Yes", data_size = "small"})
The specific exception you're getting occurs when you pass an expression to one of the templated helpers that can't be evaluated. Bear in mind that when you're using the expression-based helpers, you're not actually passing a property by value but rather an expression that represents a property on your model and which the helper will use to reference that property, generate field names from, etc.
You haven't shown the actual code where you're doing this, but this means essentially you can't do something like:
#Html.EditorFor(m => m.Public.GetValueOrDefault())
Because the templated helper cannot resolve that as an expression that matches up with a property on your model.
As to your actual base concern here, namely setting the value to false if it's null, you just need a custom getter and setter. #utaco's answer provides the new easier C# 6.0 method of auto-implemented properties with defaults:
public bool? Public { get; set; } = false;
For previous versions of C#, you need the following:
private bool? public;
public bool? Public
{
get { return public ?? false; }
set { public = value; }
}
However, keeping Public as a nullable bool when you have no intention of it ever actually being null just makes your code more difficult. Assuming you can change that to just bool (i.e. this is a view model and not the actual entity class tied to your database table), then you should do so. You still want to keep the private as a nullable though. That allows you accept nulls in the setter but coerce them into false values in the getter, meaning the actual value of public will always be either true or false, i.e. not null.
if you use c# 6.0 or higher you can use this:
public bool YourProp { get; set; } = false;
I've taken Chris Pratt idea but used it differently. I created a ViewModel and added a non-nullable property to update the nullable property and vice versa.
public bool? Public { get; set; }
private bool _public;
public bool _Public
{
get { return Public ?? false; }
set
{
_public = value;
Public = value;
}
}
Now in the View, you will use the non-nullable value for updating instead of the nullable value
Html.CheckBoxFor(model => model._Uploaded)
The only issue with this approach is that you will not get back null if saving changes. This worked for me as NULL value represent false in our program.
This don't need initialisation. #Html.EditorFor(m => m.AnyNullableProperty)
Below worked for me as expected.
<div class="form-group">
#Html.LabelFor(model => model.RequiresBatchNumberOnReceipt, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(m => m.RequiresBatchNumberOnReceipt)
#Html.ValidationMessageFor(model => model.RequiresBatchNumberOnReceipt)
</div>
</div>
When I do a create method i bind my object in the parameter and then I check if ModelState is valid so I add to the database:
But when I need to change something before I add to the database (before I change it the ModelState couldn't be valid so I have to do it)
why the model state still non valid.
What does this function check exactly?
This is my example:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "EncaissementID,libelle,DateEncaissement,Montant,ProjetID,Description")] Encaissement encaissement) {
encaissement.Montant = Convert.ToDecimal(encaissement.Montant);
ViewBag.montant = encaissement.Montant;
if (ModelState.IsValid) {
db.Encaissements.Add(encaissement);
db.SaveChanges();
return RedirectToAction("Index", "Encaissement");
};
ViewBag.ProjetID = new SelectList(db.Projets, "ProjetId", "nomP");
return View(encaissement);
}
ModelState.IsValid indicates if it was possible to bind the incoming values from the request to the model correctly and whether any explicitly specified validation rules were broken during the model binding process.
In your example, the model that is being bound is of class type Encaissement. Validation rules are those specified on the model by the use of attributes, logic and errors added within the IValidatableObject's Validate() method - or simply within the code of the action method.
The IsValid property will be true if the values were able to bind correctly to the model AND no validation rules were broken in the process.
Here's an example of how a validation attribute and IValidatableObject might be implemented on your model class:
public class Encaissement : IValidatableObject
{
// A required attribute, validates that this value was submitted
[Required(ErrorMessage = "The Encaissment ID must be submitted")]
public int EncaissementID { get; set; }
public DateTime? DateEncaissement { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
var results = new List<ValidationResult>();
// Validate the DateEncaissment
if (!this.DateEncaissement.HasValue)
{
results.Add(new ValidationResult("The DateEncaissement must be set", new string[] { "DateEncaissement" });
}
return results;
}
}
Here's an example of how the same validation rule may be applied within the action method of your example:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "EncaissementID,libelle,DateEncaissement,Montant,ProjetID,Description")] Encaissement encaissement) {
// Perform validation
if (!encaissement.DateEncaissement.HasValue)
{
this.ModelState.AddModelError("DateEncaissement", "The DateEncaissement must be set");
}
encaissement.Montant = Convert.ToDecimal(encaissement.Montant);
ViewBag.montant = encaissement.Montant;
if (ModelState.IsValid) {
db.Encaissements.Add(encaissement);
db.SaveChanges();
return RedirectToAction("Index", "Encaissement");
};
ViewBag.ProjetID = new SelectList(db.Projets, "ProjetId", "nomP");
return View(encaissement);
}
It's worth bearing in mind that the value types of the properties of your model will also be validated. For example, you can't assign a string value to an int property. If you do, it won't be bound and the error will be added to your ModelState too.
In your example, the EncaissementID value could not have a value of "Hello" posted to it, this would cause a model validation error to be added and IsValid will be false.
It is for any of the above reasons (and possibly more) that the IsValid bool value of the model state will be false.
ModelState.IsValid will basically tell you if there is any issues with your data posted to the server, based on the data annotations added to the properties of your model.
If, for instance, you have a [Required(ErrorMessage = "Please fill")], and that property is empty when you post your form to the server, ModelState will be invalid.
The ModelBinder also checks some basic stuff for you. If, for instance, you have a BirthDate datepicker, and the property that this picker is binding to, is not a nullable DateTime type, your ModelState will also be invalid if you have left the date empty.
Here, and here are some useful posts to read.
You can find a great write-up on ModelState and its uses here.
Specifically, the IsValid property is a quick way to check if there are any field validation errors in ModelState.Errors. If you're not sure what's causing your Model to be invalid by the time it POST's to your controller method, you can inspect the ModelState["Property"].Errors property, which should yield at least one form validation error.
Edit: Updated with proper dictionary syntax from #ChrisPratt
This is not meant to be the best answer, but I find my errors by stepping through the ModelState Values to find the one with the error in Visual Studio's debugger:
My guess is that everyone with a question about why their ModelState is not valid could benefit from placing a breakpoint in the code, inspecting the values, and finding the one (or more) that is invalid.
This is not the best way to run a production website, but this is how a developer finds out what is wrong with the code.
I have a model where I am using DataAnnotations to perform validation, such as
public class OrderDTO
{
[Required]
public int Id { get; set; }
[Required]
public Decimal Amount { get; set; }
}
Then I am checking the ModelState in each request to make sure that the JSON is valid.
However, I am having trouble for number properties such as Amount above. Even though it is set as [Required], if it's not included in the JSON it will skip the ModelState validation because it is automatically defaulted to 0 instead of null, so the model will seem valid even though it isn't.
An easy way to 'fix' this is to set all the number properties as nullable (int?, Decimal?). If I do this, the defaulting to 0 doesn't happen, but I don't like this as a definitive solution as I need to change my model.
Is there a way to set the properties to null if they are not part of the JSON?
Because Decimal is a non-nullable type so you cannot do that.
You need Decimal? to bind null value.
You have to use a nullable type. Since a non-nullable value, as you know, cannot be null then it will use 0 as a default value and therefore appear to have a value and always pass the validation.
As you have said it has to be null for the validation to work and therefore be nullable. Another option could be to write your own validation attribute but this could then cause a problem as you would most likely be saying if is null or 0 then not valid, a big issue when you want to have 0 as an accepted value because you then need another way of deciding when 0 is and isn't valid.
Example for custom validation, not specific to this case.
Web API custom validation to check string against list of approved values
A further option could be to add another property that is nullable and provides the value to the non-nullable property. Again, this could cause issues with the 0 value. Here is an example with the Id property, your json will now need to send NullableId rather than Id.
public class OrderDTO
{
//Nullable property for json and validation
[Required]
public int? NullableId {
get {
return Id == 0 ? null : Id; //This will always return null if Id is 0, this can be a problem
}
set {
Id = value ?? 0; //This means Id is 0 when this is null, another problem
}
}
//This can be used as before at any level between API and the database
public int Id { get; set; }
}
As you say another option is to change the model to nullable values through the whole stack.
Finally you could look at having an external model coming into the api with nullable properties and then map it to the current model, either manually or using something like AutoMapper.
I agree with others that Decimal being a non Nullable type cannot be assigned with a null value. Moreover, Required attribute checks for only null, empty string and whitespaces. So for your specific requirement you can use CustomValidationAttribute and you can create a custom Validation Type to do the "0" checking on Decimal properties.
There is no way for an int or Decimal to be null. That is why the nullables where created.
You have several options [Edit: I just realized that you are asking for Web-API specifically and in this case I believe the custom binder option would be more complex from the code I posted.]:
Make the fields nullable in your DTO
Create a ViewModel with nullable types, add the required validation attributes on the view model and map this ViewModel to your DTO (maybe using automapper or something similar).
Manually validate the request (bad and error prone thing to do)
public ActionResult MyAction(OrderDTO order)
{
// Validate your fields against your possible sources (Request.Form,QueryString, etc)
if(HttpContext.Current.Request.Form["Ammount"] == null)
{
throw new YourCustomExceptionForValidationErrors("Ammount was not sent");
}
// Do your stuff
}
Create a custom binder and do the validation there:
public class OrderModelBinder : DefaultModelBinder
{
protected override bool OnPropertyValidating(ControllerContext controllerContext, ModelBindingContext bindingContext,
PropertyDescriptor propertyDescriptor, object value)
{
if ((propertyDescriptor.PropertyType == typeof(DateTime) && value == null) ||
(propertyDescriptor.PropertyType == typeof(int) && value == null) ||
(propertyDescriptor.PropertyType == typeof(decimal) && value == null) ||
(propertyDescriptor.PropertyType == typeof(bool) && value == null))
{
var modelName = string.IsNullOrEmpty(bindingContext.ModelName) ? "" : bindingContext.ModelName + ".";
var name = modelName + propertyDescriptor.Name;
bindingContext.ModelState.AddModelError(name, General.RequiredField);
}
return base.OnPropertyValidating(controllerContext, bindingContext, propertyDescriptor, value);
}
}
And register your binder to your model using one of the techniques described in the following answer: https://stackoverflow.com/a/13749124/149885
For example:
[ModelBinder(typeof(OrderBinder))]
public class OrderDTO
{
[Required]
public int Id { get; set; }
[Required]
public Decimal Amount { get; set; }
}
I have a Parent Model that contains a property used by a Sub Model for pre-filling a field on the View.
I would like to move the property into the Sub Model where it belongs, but another property's range attribute fails when i do this.
Why is the range attribute failing validation when i have a property used only to be hidden on the EditorTemplate?
The Model looks like
public class ParentModel
{
public SubModel subModel { get; set; }
}
public class SubModel
{
public uint? DefaultValue { get; set; }
[Required]
[Range(1,100)]
public uint RangedId { get; set;}
public bool EnableRange { get; set; }
}
The View (EditorTemplate) Looks like
#model SubModel
#Html.HiddenFor(model => model.DefaultValue)
#Html.TextBoxFor(model => model.RangeId)
<script>
$('#EnableRange').change(function() {
if($('#EnableRange').val()){
// remove the disabled attribute from the RangeId Field
} else {
// add the disabled attribute from the RangeId Field
}
}
</script>
The Controller Looks Like
public ActionResult Create(TViewModel model)
{
try
{
if (ModelState.IsValid)
{
//Do Something Meaningful
}
//Redisplay the view
}
}
With the DefaultValue property in the SubModel the RangeId's Range Attribute fires even when the RangeId is disabled on the form. This causes the ModelState.IsValid to be false.
When I move the DefaultValue property up to the ParentModel the Range attribute for RangeId no longer fires (because the field is disable). Which causes the ModelState.IsValid to be true, because the RangeId is never evaluated for validation.
Whatever you think is happening is NOT happening. The server side Model.IsValid does not care anything about, nor is it directly affected by disabling the control on the client side (though it can be indirectly affected as we will see below). The validation will always occur if nested form fields are posted and nested objects have required properties.
More likely, the real issue here is that when you have DefaultValue in the child model, then when you submit the model to the parent, the model binder creates an instance of SubModel because it contains a value for DefaultValue. When you move it to the parent, and you disable the RangeId, there is no value to post and therefore no SubModel gets created, and thus no validation occurs.
Ie, my guess is that when you move DefaultValue to the parent, SubModel is null on postback, thus because there is no instance to validate, there is nothing to fail validation, particularly since you are not persisting the EnableRange value.
So you really have several issues. First, disabling a control on the client will not disable validation on the server. Second, if there are no nested form fields posted to the server, then no nested objects will be created and no validation will occur (so in that respect, validation can be disabled as a side-effect if you are very careful). Third, If you DO post some nested form fields but not others, validation then nested objects WILL get created and validation will occur on fields that are not posted because they were disabled.
I've tried to override error message when input incorrect data type in input field on HTML form.
For example I have the model like this.
public class Person
{
public string FirstName {get;set;}
public int Age {get;set;}
}
For view, I put text input for Age to get it value.
When type some string in Age text box like 'test' and press submit button.
I got this error message
The value 'xxx' is not valid for Age
However, I want to change this message and try many way. There 's nothing effect this message value.
Please could you help me to solve this problem.
After spending a couple of hours looking around, I see no one really has a suitable answer to this. I have found a solution that works for me so I thought I'd post it.
The problem for me was validating a nullable int. When a non valid value was entered by the user, the default message "The value 'dsfd' is not valid for Issue'.
The solution I came up with was to remove the errors from the model state and add my own.
The classes to perform the validation are below:
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Method | AttributeTargets.Parameter | AttributeTargets.Property, AllowMultiple = false)]
public class ValidInteger : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
if (value == null || value.ToString().Length == 0)
{
return ValidationResult.Success;
}
int i;
return !int.TryParse(value.ToString(), out i) ? new ValidationResult(ErrorMessage) : ValidationResult.Success;
}
}
public class ValidIntegerValidator : DataAnnotationsModelValidator<ValidInteger>
{
public ValidIntegerValidator(ModelMetadata metadata, ControllerContext context, ValidInteger attribute)
: base(metadata, context, attribute)
{
if(!attribute.IsValid(context.HttpContext.Request.Form[attribute.ObjectId]))
{
var propertyName = metadata.PropertyName;
context.Controller.ViewData.ModelState[propertyName].Errors.Clear();
context.Controller.ViewData.ModelState[propertyName].Errors.Add(attribute.ErrorMessage);
}
}
}
Don't forget you'll also need to register the adapter in the global Application_Start()
DataAnnotationsModelValidatorProvider.RegisterAdapter(typeof(ValidInteger), typeof(ValidIntegerValidator));
And decorate your property with the new attribute
[ValidInteger(ErrorMessage = "Please enter a valid number for issue")]
The response from wechel dated February 27, 12 worked for me, except that I needed to replace the line
if(!attribute.IsValid(context.HttpContext.Request.Form[attribute.ObjectId]))
with
if (!attribute.IsValid(context.HttpContext.Request.Form[metadata.PropertyName]))
I am guessing that the change is needed because I am using MVC4 and the code snippet provided was written in an earlier version of MVC?
You can use DataAnnotations to override the default error messages in MVC, as well as provide your own validation for whatever fields you need to. See the following:
http://weblogs.asp.net/scottgu/archive/2010/01/15/asp-net-mvc-2-model-validation.aspx
http://www.asp.net/mvc/tutorials/validation-with-the-data-annotation-validators-cs
If you are using EF, you will need to create a MetaData class off the EF generated class and then add the data annotations. The end of the 2nd article covers how to write these classes for entity framework.
In your specific case you will want to do something like:
using System.ComponentModel.DataAnnotations;
public class Person
{
public string FirstName {get;set;}
[Range(0, 110, ErrorMessage = "<your error message>")]
public int Age {get;set;}
}
UPDATE I did forget one thing that is easy to overlook, you need to include the following JS files for the data annotations to be picked up client side without having to do a post:
MicrosoftAjax.js
MicrosfotMvcValidation.js
These should be stock in the Scripts folder of your project (if you are using MVC 2), and you need to include them either on your page or in your master page,
<script src="<%= Url.Content("~/Scripts/MicrosoftAjax.js") %>" type="text/javascript"></script>
<script src="<%= Url.Content("~/Scripts/MicrosoftMvcValidation.js") %>" type="text/javascript"></script>
you will also need to include
<% Html.EnableClientValidation(); %>
on the page in question.
The client side validation will not let you submit the form until all fields meet validation requirements, as Kaspars Ozols points out you will still need to call Model.IsValid on your controller.
I just wanted to show the Range attribute error message so I used the answer from wechel and Christna and changed it so the RangeAttribute is used. After adding the Validator class, only a custom Validator needs to be created and registered in the global.asax as shown in wechel's answer.
You also need to add a validation message with name "FieldRangeValidation" to your resource bundle. In my project it contains the following text: "Value must be between {0} and {1}"
public class ValidIntegerRangeValidator : DataAnnotationsModelValidator<RangeAttribute>
{
public ValidIntegerRangeValidator(ModelMetadata metadata, ControllerContext context, RangeAttribute attribute)
: base(metadata, context, attribute)
{
try
{
if (attribute.IsValid(context.HttpContext.Request.Form[metadata.PropertyName]))
{
return;
}
}
catch (OverflowException)
{
}
var propertyName = metadata.PropertyName;
context.Controller.ViewData.ModelState[propertyName].Errors.Clear();
context.Controller.ViewData.ModelState[propertyName].Errors.Add(string.Format(Resources.Resources.FieldRangeValidation, attribute.Minimum, attribute.Maximum));
}
}
if (ModelState["Age"].Errors.Count > 0)
{
ModelState["Age"].Errors.Clear();
ModelState["Age"].Errors.Add("<your error message>");
}