I've got a simple address entry app that I'm trying to use the IDataErrorInfo interface as explained on the asp.net site.
It works great for items that can be validated independently, but not so well when some items depend on others. For example, validating the postal code depends on the country:
private string _PostalCode;
public string PostalCode
{
get
{
return _PostalCode;
}
set
{
switch (_Country)
{
case Countries.USA:
if (!Regex.IsMatch(value, @"^[0-9]{5}$"))
_errors.Add("PostalCode", "Invalid Zip Code");
break;
case Countries.Canada:
if (!Regex.IsMatch(value, @"^([a-z][0-9][a-z]) ?([0-9][a-z][0-9])$", RegexOptions.IgnoreCase))
_errors.Add("PostalCode", "Invalid postal Code");
break;
default:
throw new ArgumentException("Unknown Country");
}
_PostalCode = value;
}
}
So you can only validate the postal code after the country has been set, but there seems to be no way of controlling that order.
I could use the Error string from IDataErrorInfo, but that doesn't show up in the Html.ValidationMessage next to the field.
Here's the best solution I've found for more complex validation beyond the simple data annotations model.
I'm sure I'm not alone in trying to implement
IDataErrorInfo
and seeing that it has only created for me two methods to implement. I'm thinking wait a minute - do i have to go in there and write my own custom routines for everything now from scratch? And also - what if I have model level things to validate. It seems like you're on your own when you decide to use it unless you want to do something like this or this from within your IDataErrorInfo implementation.I happened to have the exact same problem as the questioner. I wanted to validate US Zip but only if country was selected as US. Obviously a model-level data annotation wouldn't be any good because that wouldn't cause zipcode to be highlighted in red as an error. [good example of a class level data annotation can be found in the MVC 2 sample project in the PropertiesMustMatchAttribute class].
The solution is quite simple :
First you need to register a modelbinder in global.asax. You can do this as an class level [attribute] if you want but I find registering in global.asax to be more flexible.
Then create the modelbinder class, and write your complex validation. You have full access to all properties on the object. This will run after any data annotations have run so you can always clear model state if you want to reverse the default behavior of any validation attributes.
The beauty of this is that all your existing validation attributes will still work - [Required], [EmailValidator], [MyCustomValidator] - whatever you have.
You can just add in any extra code into the model binder and set field, or model level ModelState errors as you wish.
Please note that for me an
Address
is a child of the main model - in this caseCheckoutModel
which looks like this :That's why I have to do
bindingContext.ModelName
+ ".ZipOrPostal"
so that the model error will be set for 'BillingAddress.ZipOrPostal' and 'ShippingAddress.ZipOrPostal'.PS. Any comments from 'unit testing types' appreciated. I'm not sure about the impact of this for unit testing.
For more complex business rule validation, rather than type validation it is maybe better to implement design patterns such as a service layer. You can check the ModelState and add errors based on your logic.
You can view Rob Conroys example of patterns here
http://www.asp.net/learn/mvc/tutorial-29-cs.aspx
This article on Data Annotations ay also be useful.
http://www.asp.net/learn/mvc/tutorial-39-cs.aspx
Hope this helps.
Regarding the comment on Error string, IDataErrorInfo and the Html.ValidationMessage, you can display object level vs. field level error messages using:
In your controller decorate the post method handler parameter for the object with [Bind(Prefix = "address")]. In the HTML, name the input fields as such...
I don't generally use the Html helpers. Note the naming convention between id and name.