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;
});
Related
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!
We're using DataAnnotations to validate our model.
A very simplified version of our model is:
public class Model
{
public List<Thing> Things;
}
public class Thing
{
[Required]
public string Name {get;set;}
}
Now, the funny thing is that if I create a Thing with no name and add it to the model, I would expect validation to fail, but it passes (shock horror!).
var model = new Model ();
var invalidThing = new Thing (); // No name would fail validation
model.Things.Add(invalidThing );
var validationContext = new ValidationContext(model);
var validationResults = new List<ValidationResult>();
var isValid = Validator.TryValidateObject(model, validationContext, validationResults, true);
Assert.False (isValid); // This fails!
I think the reason for this is that when you validate the model, it validates each property but not items in the property if it's a collection. Things is a property that has no validation, so it passes (despite the fact that it contains invalid item).
How can we ensure that validation also validates items in collection properties? Is there some out-of-the-box validator I could use?
I have fixed this by creating a custom validator for collections that checks validation on each item. A simplified code would look like this:
public class ValidateEachItemAttribute : ValidationAttribute
{
protected readonly List<ValidationResult> validationResults = new List<ValidationResult>();
public override bool IsValid(object value)
{
var list = value as IEnumerable;
if (list == null) return true;
var isValid = true;
foreach (var item in list)
{
var validationContext = new ValidationContext(item);
var isItemValid = Validator.TryValidateObject(item, validationContext, validationResults, true);
isValid &= isItemValid;
}
return isValid;
}
// I have ommitted error message formatting
}
Now decorating the model this way would work as expected:
public class Model
{
[ValidateEachItem]
public List<Thing> Things;
}
Another alternative, if this is ASP.NET MVC, would be to implement IValidatableObject in your model. Like:
public class Model: IValidatableObject
{
public List<Thing> Things;
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
//your validation logic here
}
}
Then the result of ModelState.IsValid in your controller will depend on that implementation of Validate method. This is useful when multiple properties of your model are dependent on each other.
The default behavior in your question is not surprising, let's describe it.
Let's say you have a property of type Dictionary<string, Thing> or a property of type Something<Thing> or an untyped collection containing Thing objects in the Model, then how could we expect the Validator perform a validation against Thing objects which are stored in those properties?
We cannot expect Validator to perform a validation against Thing objects which are stored in those properties, because it doesn't have any information about how it should validate those properties. To validate a property, Validator looks for ValidationAttribute for that property and since it doesn't find any validation attribute, it doesn't validate that property.
As a result, you need to create some ValidationAttribute to do that for you and decorate properties with validation attributes. You can implement something like what you implemented in your answer.
Note: In context of ASP.NET MVC you don't need to be worried about it. Default model binder takes care of all validation attributes when model binding, even when model binding to a list.
It's what the default model binder does. When creating each element, when assigning values to properties, it validates property and add the validation errors to model state. At last, all properties and objects are validated and model state contains all validation errors.
This is an extension of an older question from this post:
Enforcing a model's boolean value to be true using data annotations
This gave me a lot of insight on how to get the client side custom validation working with jquery-unubtrusive-ajax.js.
Here is the answer code:
namespace Checked.Entitites
{
public class BooleanRequiredAttribute : ValidationAttribute, IClientValidatable
{
public override bool IsValid(object value)
{
return value != null && (bool)value == true;
}
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
//return new ModelClientValidationRule[] { new ModelClientValidationRule() { ValidationType = "booleanrequired", ErrorMessage = this.ErrorMessage } };
yield return new ModelClientValidationRule()
{
ValidationType = "booleanrequired",
ErrorMessage = this.ErrorMessageString
};
}
}
}
When i use this custom attribute in my models it works fine.
ex:
[BooleanRequired(ErrorMessage = "Please accept the terms")]
public bool Terms { get; set; }
When i use textboxfor in my views the error messages populate in the html validation summary.
#Html.CheckBoxFor(model => model.Terms, new { #class = "legalbox", #placeholder = "Terms" })
I also had to add this line to my jquery.unubtrusive-ajax.js
$.validator.unobtrusive.adapters.addBool("BooleanRequired", "required");
The issue i am having with this fix arises when i reference this model in the controller and check the modelstate. The modelstate is always false no matter what i have as the value for this custom validated field. It looks like the override isvalid is working in the client side validation but not on the server side. I need it to work on both and have been scouring trying to find a solution that isn't "ditch dataannotations and use something else".
The specific line i am using in my controller that will never pass with this field is as follows:
if (ModelState.IsValid) {
//Logic here
}
The rest of the model using the standard data-annotations validate correctly, i just cannot get this custom one to work the same way as the built in attributes. Its possible that the right solution would prevent me from having to add that line to the unobtrusive ajax file.
Any help would be appreciated.
Thanks
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>");
}
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.