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

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!

Related

What does ModelState.IsValid do?

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.

Why does an ASP.NET MVC 4 Custom validator cause a routing error?

I have an ASP.NET MVC 4 app that seems to work fine. I write a custom ValidatorAttribute to make sure the value of one property is not smaller than another. Since there are two properties involved, I override IsValid(object, context).
I write unit tests using Validator.TryValidateObject and the Validate(object, context) member of the attribute, and they pass as expected. I include tests for the expected use with values that are valid and values that are invalid. I include tests where the attribute is applied to a property that is the right type, and get expected behavior (My design choice is to pass if either property type is wrong.)
I add the attribute to my model, hooking it in to the app. Something like:
public abstract class DataElement
{
...
[Required]
public string Version { get; set; }
[StringLength(8, ErrorMessage = "8 characters or less")]
[Required(ErrorMessage = "Required")]
[DisplayName("ID")]
public string DataElementNumber { get; set; }
...
}
public abstract class SimpleElement : DataElement
{
[Required]
[DisplayName("Minimum")]
public int MinimumLength { get; set; }
[Required]
[DisplayName("Maximum")]
[NotSmallerThan("MinimumLength")]
public int MaximumLength { get; set; }
}
public class CodeList: SimpleElement
{
public Collection<CodeValue> Values { get; set; }
}
I have a controller something like
[HttpGet]
public ActionResult Edit(string elementId, string version)
{
CodeList model = Store.GetCodeList(elementId, version);
return View(model);
}
[HttpPost]
public ActionResult Edit(CodeList model)
{
ActionResult result;
if (ModelState.IsValid)
{
Store.Upsert(model);
result = RedirectToAction("Index", "SomeOtherController");
}
else
{
result = View(model.DataElementNumber, model.Version);
}
return result;
}
Simple, I think. If the model is valid, commit to the data store. If it's not valid, re-display the form, with a validation message. In cases where I enter valid values in the form, the validator behaves as expected, that is, the application commits values to the data store and move on.
In the case where I enter a value for Minimum that is smaller than Maximum, the case I am guarding against, instead of seeing my view, again, I see an error screen, something like this for the case where DataElementNumber="XML-25" and Version="201301"
The view 'XML-25' or its master was not found or no view engine supports the searched locations. The following locations were searched:
~/Views/CodeListBuilder/XML-25.aspx
~/Views/CodeListBuilder/XML-25.ascx
~/Views/Shared/XML-25.aspx
~/Views/Shared/XML-25.ascx
~/Views/CodeListBuilder/201301.master
~/Views/Shared/201301.master
~/Views/CodeListBuilder/XML-25.cshtml
~/Views/CodeListBuilder/XML-25.vbhtml
~/Views/Shared/XML-25.cshtml
~/Views/Shared/XML-25.vbhtml
~/Views/CodeListBuilder/201301.cshtml
~/Views/CodeListBuilder/201301.vbhtml
~/Views/Shared/201301.cshtml
~/Views/Shared/201301.vbhtml
Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.
Exception Details: System.InvalidOperationException:...
I can comment out the custom NotSmallerThanAttribute and the system behaves as I expect, apart form being able to enter number fo maximum that are smaller than minimum. I am not sure how to diagnose this. What kind of behavior in a validator can confuse the routing engine? How do I find it? TIA
Your problem has nothing to do with your validator.
With the result = View(model.DataElementNumber, model.Version); you are using the following overload of the View method:
protected internal ViewResult View(
string viewName,
string masterName
)
So the framework thinks that your model.DataElementNumber is your viewName and your model.Version your masterName that is why you get this strange view missing exception.
To fix this you just need to use the correct overload with passing in your model
result = View(model);
and MVC will take care of re-displaying your previously posted DataElementNumber and Version values.

ASP.NET MVC: Ignore field(s) when editing

As I'm in the progress of learning ASP.NET MVC, I ran into a question and into some trouble
I'm trying to create a simple blog, just to test out what I have learned so far. But when it comes to editing and leaving a field i run into a problem.
I'm trying to edit an already submitted post on my blog, the post contains few fields: Id, Headline, Message, Author and Date for the submission which should not be edited, just left as it is.
Here is some code:
My post model:
namespace MyBlock.Models
{
public class Post
{
public int Id { get; set; }
[Required]
public string Author { get; set; }
[Required]
public string Headline { get; set; }
[Required]
public string Message { get; set; }
public DateTime Date { get; set; }
}
}
My edit:
[HttpGet]
public ActionResult Edit(int id = 0)
{
Post post = db.Posts.Find(id);
if (post != null) {
return View(post);
}
return HttpNotFound();
}
[HttpPost]
public ActionResult Edit(Post post)
{
if (ModelState.IsValid) {
db.Entry(post).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index", "Home");
}
return View(post);
}
And my view for edit:
#model MyBlock.Models.Post
#{
ViewBag.Title = "Edit";
}
<h2>Rediger "#Model.Headline"</h2>
#using (Html.BeginForm()) {
#Html.LabelFor(u => u.Author)
#Html.TextBoxFor(u => u.Author)
#Html.LabelFor(u => u.Headline)
#Html.TextBoxFor(u => u.Headline)
#Html.LabelFor(u => u.Message)
#Html.TextAreaFor(u => u.Message)
<input type="submit" value="Gem" />
}
I know I could throw in a #HiddenFor(u => u.Date) and the same date would be submitted. But I bet there is another way than having it as a hidden field in the source code? I mean this isn't that secure in another example? So I want something else than hidden field here. Can you guys help me out?
If I try to run this as it is. I'm getting an error which is my Date isn't set, which is logic because it want to update that one aswell. But I dont want it to. I want to leave it optional if you could say that.
Don't take candy from strangers
In other words, don't take the information from the client and directly update the DB. You should enforce your business rules on the server side and not trust the client to do it for you.
[HttpPost]
public ActionResult Edit(Post post)
{
if (ModelState.IsValid) {
var dbPost = db.Posts.FirstOrDefault(p => p.Id == post.Id);
if (dbPost == null)
{
return HttpNotFound();
}
dbPost.Author = post.Author;
dbPost.Message = post.Message;
dbPost.Headline = post.Headline;
db.SaveChanges();
return RedirectToAction("Index", "Home");
}
return View(post);
}
[HttpPost]
public ActionResult Add(Post post)
{
if (ModelState.IsValid) {
var dbPost = db.Create<Post>();
dbPost.Author = post.Author;
dbPost.Message = post.Message;
dbPost.Headline = post.Headline;
dbPost.Date = DateTime.Now(); // Don't trust client to send current date
db.SaveChanges();
return RedirectToAction("Index", "Home");
}
return View(post);
}
In my own project I enforce rules like this at the domain layer by adding custom validation rules to the ValidateEntity method.
DateTime is a value type, and cannot be null. Thus, it can never be optional.
You need to make a it a nullable type. ie.
public DateTime? Date {get;set;}
In general, most value types in a ViewModel should be nullable, then you use Required attributes to enforce that they contain a value. This allows you to tell whether they failed to enter a value, or whether it's a default value.
In your controller, you can then check if the Date has a value with Date.HasValue and if so, then save the date.
In regards to security, in this case it's not raelly an issue. Assuming someone has access to the page (they pass authorization) and they have the right to update the date, then it doesn't matter if the user can bypass it. All they can do is submit a valid date format. Unless you want to add logic to ensure that the date is within a specific time period, then you don't have to worry. The ModelBinder will not bind to a non-valid date format.
If you want to control whether the user can update the date, say based on role, then you could add logic to your controller to check if the date has a value and the user is in the correct role, otherwise issue an error.
UPDATE:
I think the easiest solution here is to do two things. The first is to make Date nullable, as I mention above. Although this is not strictly necessary if you do not have a form field for Date in your view, if you were to add a form field later then you would get a validation error if you left the textbox empty. I like to prevent future errors from occurring if possible. Also, should someone be posting values to your Edit action manually, and they include a blank Date field, it will fail to validate, rather than simply ignore it. Making the value nullable allows the value to be completely ignored regardless of its value.
Second, is do what #p.s.w.g suggests, and only update the fields that you want updated. Retrieve the post from the database, then update all fields except Id and Date. Then call SaveChanges().
Just my 2cents here. I know this is a simple situation and the answer given is nice and straightforward. But as that list of attributes grows then it could get difficult.
So a different approuch would be along these lines
var t = _db.Blog.Where(x => x.ID == id).FirstOrDefault();
var info = typeof(Blog).GetProperties();
//properties you don't want to update
var properties = info.Where(x => x.Name != "xxx" && x.Name != "xxxx").ToList();
foreach(var p in properties)
{
p.SetValue(t, p.GetValue(temp.Volunteer));
}
_db.Entry(t).State = EntityState.Modified;
_db.SaveChanges();
But if you are just doing a few fields then the above makes sense.
Just use your noggin!

How does DataAnnotations really work in MVC?

This is more of a theoretical question.
I'm currently examining the MVC 3 validation by using ComponentModel.DataAnnotations, and everything works automagically, especially on client side.
Somehow something checks for those attributes, and generates javascript for the validation (or html5 attributes, if using unobtrusive mode), and it works.
My question is that what generates the client side javascript and how can I access and modify it? For example I want to handle the given dataannotation attributes a little differently, or handle custom attributes (I have found that I can derive them from ValidationAttribute, but maybe for some reason I don't want).
Can someone explain it to me what really happens?
(Or links to good explanations would also be good, as I have only found tutorials for actually using dataannotations)
EDIT: Also with deriving from ValidationAttribute, the client-side validation is not working automatically. Why?
MVC3 has a new jQuery Validation mechanism that link jQuery Validation and Validation Attributes Metadata, this is the jquery.validate.unobtrusive file that takes all data- attributes and work with them, just like before when you set the
<add key="UnobtrusiveJavaScriptEnabled" value="false" />
All you need to do is come up with your own Custom Validation Attributes, for that you have 2 options:
Create a Custom Validation Attribute that inherits the ValidationAttribute interface and
override the IsValid
or
Create a Self Validate Model use the model IValidatebleObject that all you need is to return the Validate method
in MVC3 you now have a method that you can override that has a ValidationContext object, where you can simply get all references, properties and values of any other object in the form
Create your own, and that unobtrusive file will handle the mapping of what your custom validator needs and will work out together with the jQuery Validation plugin.
YOU DO NOT Change the javascript... that's sooo 90's and not MVC way!
for example if you want to validate, let's say 2 dates that the last can not be less than the first (period of time for example)
public class TimeCard
{
public DateTime StartDate { get; set; }
[GreaterThanDateAttribute("StartDate")]
public DateTime EndDate { get; set; }
}
creating a Custom Validation
public class GreaterThanDateAttribute : ValidationAttribute
{
public string GreaterThanDateAttribute(string otherPropertyName)
:base("{0} must be greater than {1}")
{
OtherPropertyName = otherPropertyName;
}
public override string FormatErrorMessage(string name)
{
return String.Format(ErrorMessageString, name, OtherPropertyName);
}
public override ValidateionResult IsValid(object value, ValidationContext validationContext)
{
var otherPropertyInfo = validationContext.ObjectTYpe.GetProperty(OtherPropertyName);
var otherDate = (DateTime)otherPropertyInfo.GetValue(validationContext.ObjectInstance, null);
var thisDate = (DateTime)value;
if( thisDate <= otherDate )
{
var message = FormatErrorMessage(validationContext.DisplayName);
return new ValidationResult(message);
}
return null;
}
}
if using the Self Validating model then the code would be just
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if( EndDate <= StartDate )
yield return new ValidationResult("EndDate must be grater than StartDate");
}
Keep in mind that the Custom Validation is Generic, that's why much code, and Self Validating Model only works on the model applied.
Hope it helps
added
I didn't explain the Custom Client Validation part, fell free to ask if you need examples, but basically:
It's easier in MVC3 (if of course, you understand jQuery.Validate) all you need to do is:
Implement IClientValidateble
Implement a jQuery validation method
Implement an unobtrusive adapter
To create this 3 things, let's take this GreaterThanDateAttribute into account and create the Custom Client Side Validation. For that we need to code this:
append to the GreaterThanDateAttribute
public IEnumerable<ModelCLientValidation> GetCLientValidationRules(ModelMetadata metadata, ControllerContext context)
{
var rule = new ModelCLientValidationRule();
rule.ErrorMessage = FormatErrorMessage(metadata.GetDisplayName());
rule.ValidationType = "greater"; // This is what the jQuery.Validation expects
rule.ValidationParameters.Add("other", OtherPropertyName); // This is the 2nd parameter
yield return rule;
}
Then you need to write the new jQuery Validator and the metadata adapter that will link the jQuery.Validation with your code providing the correct data- attributes for that field (if of course, UnobtrusiveJavaScriptEnabled is true)
create a new js file and attach to your <head> for example as
<script src="#Url.Content("~/Scripts/customValidation.js")" type="text/javascript"></script>
and append the new validation
jQuery.validator.addMethod("greater", function(value, element, param) {
// we need to take value and compare with the value in 2nd parameter that is hold in param
return Date.parse(value) > Date.parse($(param).val());
});
and then we write the adapter
jQuery.validator.unobtrusive.adapters.add("greater", ["other"], function(options) {
// pass the 'other' property value to the jQuery Validator
options.rules["greater"] = "#" + options.param.other;
// when this rule fails, show message that comes from ErrorMessage
options.messages["greater"] = options.message;
});

override error message (The value 'xxx' is not valid for Age) when input incorrect data type for input field asp.net mvc

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

Categories

Resources