What does ModelState.IsValid do? - c#

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.

Related

ASP.NET MVC not validating view model when bind/cast fails

In ASP.NET MVC, I have a model class implementing IValidatableObject and have a property int? Day {get; set;}.
If the user tries to submit a string for this, ModelState is correctly marked as invalid, but the rest of my validation inside the Validate method doesn't run, so the user only sees this one error instead of everything.
I need all errors to be made available at once (it's a really frustrating user experience to get given one error at a time). So I was hoping to either be able to override this behaviour and set Day to be null or hoping that ASP.NET MVC has some built in attribute or something to do this for me (my own validation would then pick up Day as being null and ask the user to correct this).
My ViewModel:
public class FormViewModel : IValidatableObject
{
public int? Day { get; set; }
public IEnumerable<string> ErrorMessageOrdering { get; } = new List<string>()
{
nameof(Date),
};
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
// This method doesn't get run when the user inputs "s" for Day. This is NOT the desired behaviour as other validations in this method wont run
if (!Day.HasValue)
{
yield return new ValidationResult("Date must include a day", new[] {nameof(Day)});
}
// ... Validate other fields
}
}
My Controller:
[ServiceFilter(typeof(ConfigSettingsAttribute))]
public class HomeController : Controller
[Route("/form")]
[HttpPost]
public async Task<IActionResult> FormAsync(FormViewModel model)
{
if (!ModelState.IsValid)
{
return View(model)
}
}
}
My View:
#model FormViewModel
#if (!ViewData.ModelState.IsValid)
{
<partial name="_ErrorSummary" model=#(Model.ErrorMessageOrdering) />
}
<form method="post">
<input asp-for="Day" type="text" pattern="[0-9]*" inputmode="numeric" maxlength="2">
#* ... Other fields #*
</form>
When the user enters "s" for the Day input (the numeric html values seem to have no effect on restricting user input btw) the Validate method on the view model is skipped and the ModelState is populated with Day being an invalid field with the message The value 's' is invalid for Day..
When the user doesn't enter anything in the Day input, the Validate method does run and the ModelState is populated with Day being invalid with the message being what I set it to in Validate.
The problem here is that because Validate isn't called on the view model when the user enters a string (presumably because it failed to cast/bind values), any further errors don't get added to ModelState. I would prefer it if the framework set Day to be null if it can't bind instead of short circuiting. If I could override this behaviour that would also be great.
Note that my problem seems to be that Validate is not called if there are bind/casting errors. As opposed to the Validate method not being written correctly. When binding errors dont occur, I do get a list of validation failures as expected
Well - it's because YOU wrote it that way! Look at your code:
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
// This method doesn't get run when the user inputs "s" for Day. This is NOT the desired behaviour as other validations in this method wont run
if (!Day.HasValue)
{
return yield new ValidationResult("Date must include a day", new[] {nameof(Day)});
}
// ... Validate other fields
}
When your first check returns false - you return the error (using return yield new ValidationResult(....)) - so the method execution stops here.
If you want to avoid this - you can build a List<ValidationResult> internally, and add any error detected to that list - and then return it once at the end - with all the detected errors in it.
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
List<ValidationResult> results = new List<ValidationResult>();
// This method doesn't get run when the user inputs "s" for Day. This is NOT the desired behaviour as other validations in this method wont run
if (!Day.HasValue)
{
results.Add(new ValidationResult("Date must include a day", new[] {nameof(Day)}));
}
// ... Validate other fields
if (someOtherCondition)
{
results.Add(new ValidationResult("Some other condition was true - returning another error", new[] {nameof(Day)}));
}
// and more - as needed
..
// in the end - return the list
return results;
}
Of course, this approach only works if an error you detected earlier doesn't make it impossible to continue on - if e.g. some of your data is null, you might not be able to do any further checking.
But basically - instead of returning back for each individual error, this allows you to validate numerous aspects and report back once, with a list of validation results - now your UI needs to be able to handle getting back multiple validation results, and displaying them, too!

MVC Range Attribute fails when a hidden field is added to the model

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.

MVC Object Change Tracking

I've currently got an issue where I need to see which fields have been changed on an Edit field for auditing purposes, in which I have code for, but I think my problem lies within my MVC View.
I have (test code):
[HttpPost]
public ActionResult Adjustment(GroupPolicy groupPolicy)
{
if (ModelState.IsValid)
{
_service.SaveGroupPolicy(groupPolicy);
return RedirectToAction("Index");
}
return View(groupPolicy);
}
Which is fine, the Policy saves. However, take this into consideration:
GroupPolicy has, say, 3 fields (in reality there are, maybe, 60):
bool IsPolicy
string Name
string Description
Name and Description are on the form, so that's fine. IsPolicy isn't used on the form, so that gets defaulted to false when posted back to the GroupPolicy object in the Adjustment method.
I can't really put IsPolicy in a Hidden field on the form, as that won't be elegant for 60+ fields in my actual solution, the HTML would be all over the place.
Now that the bool is defaulted to false, it completely abolishes the chance of me knowing if the field has changed or not. All I really want is a method for this data to be preserved, whilst keeping the new information on the Edit form.
Is this possible, am I missing something obvious?
Well first of all, GroupPolicy should be a view model and not an entity - and as such it should be tailored for the view e.g.
public class GroupPolicyViewModel
{
[HiddenInput]
public Guid Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
...
}
Then in your action you don't need to worry about assigning values that have changed, you just map the view model directly across e.g.
public ActionList Adjustment(GroupPolicyViewModel viewModel)
{
if (ModelState.IsValid)
{
// pull actual entity from service
var groupPolicy = _service.GetGroupPolicy(viewModel.Id);
// update entity from view model
groupPolicy.Name = viewModel.Name;
groupPolicy.Description = viewModel.Description;
...
}
}
This keeps a clean separation between your view & business logic. Also, it allows you to add annotations for client-side validation without affecting your real model.
GroupPolicy has, say, 3 fields (in reality there are, maybe, 60)
I would recommend using AutoMapper for this e.g.
// call this once only e.g. Application_Start in the Global.asax
Mapper.CreateMap<GroupPolicyViewModel, GroupPolicy>();
...
// in your Adjustment action
var groupPolicy = _service.GetGroupPolicy(viewModel.Id);
groupPolicy = Mapper.Map<GroupPolicyViewModel, GroupPolicy>(viewModel, groupPolicy);
_service.SaveGroupPolicy(groupPolicy);
If IsPolicy not on the form then it shouldn't even be part of your model - this will prevent posting of this field into your model and so your check won't even be needed for IsPolicy.
Rather than accepting GroupPolicy as the parameter into the action, create a cut down object GroupPolicyInputModel with only fields that are on the form.
Then use your generic auditing to only compare all the posted fields, as per any other form.

Why is my int? value being validated as if it was required?

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.

asp.NET MVC 2 DataAnnotations UpdateModel<T> validation

I'm trying to use DataAnnotations to add validation to my models in asp.NET MVC 2 RC2, using TryUpdateModel
var user = UserManager.Find(id);
this.TryUpdateModel<IProvisioningObject>(user, form.ToValueProvider());
This updates the model, but the validation is never called. I tried using TryUpdateModel as well (which is the direct type of user), not using the form value provider, using ProvisioningObject directly (which has the validation metadata), to no avail.
Googling for examples only gives me ways to use DataAnnotations by binding through a parameter
public ActionResult Update(User user)
Which I dislike for update scenarios.
Any tips and/or solutions?
EDIT
My objects are auto-generated objects from a WCF service.
I made partials to be able to add DataAnnotations.
I call TryUpdateModel three times because it apparently doesn't support inheritance, which I think is also my problem with DataAnnotations. I specify the validation attributes for ProvisioningObject, and the binding doesn't look for inherited stuff like that.
[MetadataType(typeof(ProvisioningObjectMetadata))]
public partial class ProvisioningObject : IProvisioningObject
{
public string DisplayNameInvariant { get { return string.IsNullOrEmpty(this.DisplayName) ? this.Name : this.DisplayName; } }
}
[MetadataType(typeof(UserMetadata))]
public partial class User : IUser
{
}
public class ProvisioningObjectMetadata
{
[DisplayName("Country")]
public string CountryIsoCode { get; set; }
[Required(ErrorMessageResourceType = typeof(Properties.Validation), ErrorMessageResourceName = "DisplayNameIsRequired")]
[TempValidator]
public string DisplayName { get; set; }
}
public class UserMetadata
{
[DisplayName("Username")]
public string Name { get; set; }
}
// Controller action
public ActionResult Update(string id, FormCollection form)
{
var user = UserManager.Find(id);
this.TryUpdateModel<IUser>(user.User, form.ToValueProvider());
this.TryUpdateModel<IPerson>(user.User, form.ToValueProvider());
this.TryUpdateModel<IProvisioningObject>(user.User, form.ToValueProvider());
if (ModelState.IsValid) // always true
{
return Redirect;
}
else
{
return View();
}
}
If I add the metadata for DisplayName in UserMetadata, it works as expected, but that seems very redundant for nothing. And it would mean I would also have to copy/paste all my inherited interfaces so TryUpdateModel behaves appropriately.
I guess I'm looking for a way that doesn't require me to copy and paste my validation attributes to inherited classes.
New Answer:
"My objects are auto-generated objects from a WCF service."
Autogenerated objects won't have any attributes on them. Are you defining your objects and their attributes on the server side or on the client side?
Old Answer:
If your metadata is not on IProvisioningObject then no validation will be called. The MVC2 default model binder only knows how to find "extra" [MetadataType(buddyClass)] validation information.
For update scenarios bind against DTOs and then map the DTOs, if IsValid() to your main model classes.
Implement IDataErrorInfo interface in your partial class
You will have to write custom validation for each field(where you can use data annotation class to validate each required property)
If you need code example then let me know. I will write it for you!
source: http://www.asp.net/(S(pdfrohu0ajmwt445fanvj2r3))/learn/mvc/tutorial-37-cs.aspx
How do you know that the validation is not being called? Are you checking ModelState.IsValid in your update controller and finding that it is erroneously coming back true?
A typical update pattern is:
UpdateModel(model);
if(!ModelState.IsValid) return View(model);
return RedirectToAction("Index");
If you are expecting some "IsValid" on your model to automatically be called, that will not happen. The data annotations work behind the scenes with the ModelState dictionary on the Controller base class.

Categories

Resources