MVC 3 custom DataAnnotation: associate error message with particular property - c#

I've defined a custom DataAnnotation attribute similar to this one that goes on the class but ensures that at least one property is populated. It works correctly and adds an error message to the model's ValidationSummary. However, I want to be able to associate the error message with a particular property (or any string, really) so that I can display it in a particular place on my view.
Thus, if my custom attribute is used like this:
[RequireAtLeastOne(GroupId = 0, ErrorMessage = "You must specify at least one owner phone number.")]
public class UserViewModel: User {
...
}
then I want to be able to say something like:
[RequireAtLeastOne(GroupId = 0, ErrorMessage = "You must specify at least one owner phone number.", ValidationErrorKey = "my_key")]
public class UserViewModel: User {
...
}
...and use it in a view like this:
#Html.ValidationMessage("my_key")
It would also be fine if I had to associate the error message with a particular property on my model instead of an arbitrary string. How can I accomplish this?

Using ryudice's answer and this question as the starting point, I was able to solve this problem using IValidatableObject. For anyone interested, here is the full code I ended up with:
1. Define a custom validation attribute, RequireAtLeastOneAttribute
This attribute goes on the class to indicate that validation should check for property groups and ensure that at least one property from each group is populated. This attribute also defines the error message and an ErrorMessageKey, which will be used to keep track of the error messages and display them in the view instead of using the general-purpose ValidationSummary collection.
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
public class RequireAtLeastOneAttribute: ValidationAttribute {
/// <summary>
/// This identifier is used to group properties together.
/// Pick a number and assign it to each of the properties
/// among which you wish to require one.
/// </summary>
public int GroupId { get; set; }
/// <summary>
/// This defines the message key any errors will be associated
/// with, so that they can be accessed via the front end using
/// #Html.ValidationMessage(errorMessageKey).
/// </summary>
public string ErrorMessageKey { get; set; }
public override bool IsValid(object value) {
// Find all properties on the class having a "PropertyGroupAttribute"
// with GroupId matching the one on this attribute
var typeInfo = value.GetType();
var propInfo = typeInfo.GetProperties();
foreach (var prop in propInfo) {
foreach (PropertyGroupAttribute attr in prop.GetCustomAttributes(typeof(PropertyGroupAttribute), false)) {
if (attr.GroupId == this.GroupId
&& !string.IsNullOrWhiteSpace(prop.GetValue(value, null).GetString())) {
return true;
}
}
}
return false;
}
}
2. Define a custom attribute PropertyGroupAttribute
This will be used to define which property groups need to have at least one value filled in.
[AttributeUsage(AttributeTargets.Property)]
public class PropertyGroupAttribute : Attribute {
public PropertyGroupAttribute(int groupId) {
this.GroupId = groupId;
}
public int GroupId { get; set; }
}
3. Attach the attributes to the model and properties
Group properties together using the "GroupId" integer (which can be anything, as long as it's the same for all properties among which at least one must be filled in).
[RequireAtLeastOne(GroupId = 0, ErrorMessage = "You must specify at least one owner phone number.", ErrorMessageKey = "OwnerPhone")]
[RequireAtLeastOne(GroupId = 1, ErrorMessage = "You must specify at least one authorized producer phone number.", ErrorMessageKey = "AgentPhone")]
public class User: IValidatableObject {
#region Owner phone numbers
// At least one is required
[PropertyGroup(0)]
public string OwnerBusinessPhone { get; set; }
[PropertyGroup(0)]
public string OwnerHomePhone { get; set; }
[PropertyGroup(0)]
public string OwnerMobilePhone { get; set; }
#endregion
#region Agent phone numbers
// At least one is required
[PropertyGroup(1)]
public string AgentBusinessPhone { get; set; }
[PropertyGroup(1)]
public string AgentHomePhone { get; set; }
[PropertyGroup(1)]
public string AgentMobilePhone { get; set; }
#endregion
}
4. Implement IValidatableObject on the model
public class User: IValidatableObject {
...
#region IValidatableObject Members
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext) {
var results = new List<ValidationResult>();
// This keeps track of whether each "RequireAtLeastOne" group has been satisfied
var groupStatus = new Dictionary<int, bool>();
// This stores the error messages for each group as defined
// by the RequireAtLeastOneAttributes on the model
var errorMessages = new Dictionary<int, ValidationResult>();
// Find all "RequireAtLeastOne" property validators
foreach (RequireAtLeastOneAttribute attr in Attribute.GetCustomAttributes(this.GetType(), typeof(RequireAtLeastOneAttribute), true)) {
groupStatus.Add(attr.GroupId, false);
errorMessages[attr.GroupId] = new ValidationResult(attr.ErrorMessage, new string[] { attr.ErrorMessageKey });
}
// For each property on this class, check to see whether
// it's got a PropertyGroup attribute, and if so, see if
// it's been populated, and if so, mark that group as "satisfied".
var propInfo = this.GetType().GetProperties();
bool status;
foreach (var prop in propInfo) {
foreach (PropertyGroupAttribute attr in prop.GetCustomAttributes(typeof(PropertyGroupAttribute), false)) {
if (groupStatus.TryGetValue(attr.GroupId, out status) && !status
&& !string.IsNullOrWhiteSpace(prop.GetValue(this, null).GetString())) {
groupStatus[attr.GroupId] = true;
}
}
}
// If any groups did not have at least one property
// populated, add their error messages to the
// validation result.
foreach (var kv in groupStatus) {
if (!kv.Value) {
results.Add(errorMessages[kv.Key]);
}
}
return results;
}
#endregion
}
5. Use the validation messages in the view
The validation messages will be saved as whatever ErrorMessageKey you specified in the RequireAtLeastOne attribute definition - in this example, OwnerPhone and AgentPhone.
#Html.ValidationMessage("OwnerPhone")
Caveats
The built-in validation also adds an error message to the ValidationSummary collection, but only for the first attribute defined on the model. So in this example, only the message for OwnerPhone would show up in ValidationSummary, since it was defined first on the model. I haven't looked for a way around this because in my case it didn't matter.

You can implement IValidatableObject on your model and do the custom logic there, it will let you add the message using whichever key you want.

Related

Custom validation based on other value

I make a Booking form for restaurant, which asks for the name of the restaurant, the date of the meal and the number of person.
I have a booking class, which has an ID, an ID of the restaurant, a date and a number of people :
public class Booking
{
public int Id { get; set; }
public int IDRestaurant{ get; set; }
[CustomPlaceValidator]
public int Nbpeople { get; set; }
[CustomDateValidator]
public DateTime Date { get; set; }
}
As well as a Resto class, which has an ID, a name, phone number and a number of table :
public class Resto
{
public int Id { get; set; }
[Required(ErrorMessage = "Le nom du restaurant doit être saisi")]
public string Nom { get; set; }
[Display(Name = "Téléphone")]
[RegularExpression(#"^0[0-9]{9}$", ErrorMessage = "Le numéro de téléphone est incorrect")]
public string Telephone { get; set; }
[Range(0, 9999)]
public int Size { get; set; }
}
I would like to make a validation to check with each new reservation, that the restaurant is not full.
To do this, when validating the "Number of persons" field of the Booking, I need the value of the "restaurant name" field and the value of the "date" field, and then retrieve all the bookings on this Restaurant at that date, and check whether the sum of the number of persons is much lower than the capacity of the restaurant.
public class CustomPlaceValidator : ValidationAttribute
{
private IDal dal = new Dal();
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
int nb = 0;
if (dal.GetAllBooking() != null)
{
foreach (var booking in dal.GetAllBooking())
nb += booking.Nbpeople;
if (nb ..... ) return ValidationResult.Success;
return new ValidationResult("The restaurant is full for this date.");
}
return ValidationResult.Success;
}
}
(It's a draft, the tests are not finished obviously)
How can I have the value of the other proprieties for my validation ?
This is not appropriate for a validation attribute. First, a validation attribute should be independent, or at least self-contained. Since the logic here depends on two different properties (the number of people and the date of the booking) a validation attribute would require too much knowledge of the domain in order to perform the necessary validation. In other words, it's not reusable, and if it's not reusable, then there's no point in using an attribute.
Second, a validation attribute should not do something like make a database query. The controller alone should be responsible for working with your DAL. When you start littering database access across your application, you're going to start running into all sorts of issues in very short order. If you use a DI container to inject your DAL where it needs to go, it's less problematic to use it outside of the controller, but importantly, attributes really don't play well with dependency injection. You can make it work with some DI containers, but it's never easy and you're probably going to regret it later. So, again, this really shouldn't be something a validation attribute handles.
The best approach in my opinion is to simply create a private/protected method on your controller to handle this validation. Something like:
public void ValidateCapacity(Booking booking)
{
var restaurant = dal.GetRestaurant(booking.IDRestaurant);
var existingBookings = dal.GetBookings(booking.IDRestaurant, booking.Date);
var available = restaurant.Size - existingBookings.Sum(b => b.Nbpeople);
if (booking.Nbpeople > available)
{
ModelState.AddModelError("Nbpeople", "There is not enough capacity at the restaurant for this many people on the date you've selected");
}
}
Then, in your post action for the booking, simply call this before checking ModelState.IsValid.
I'm looking at this question: Group validation messages for multiple properties together into one message asp.net mvc
My guess is something like:
public class Booking
{
public int Id { get; set; }
public int IDRestaurant{ get; set; }
[CustomPlace("IDRestaurant", "Date", ErrorMessage = "the restaurant is full")]
public int Nbpeople { get; set; }
[CustomDateValidator]
public DateTime Date { get; set; }
}
and the custom validation:
public class CustomPlaceAttribute : ValidationAttribute
{
private readonly string[] _others
public CustomPlaceAttribute(params string[] others)
{
_others= others;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
// TODO: validate the length of _others to ensure you have all required inputs
var property = validationContext.ObjectType.GetProperty(_others[0]);
if (property == null)
{
return new ValidationResult(
string.Format("Unknown property: {0}", _others[0])
);
}
// This is to get one of the other value information.
var otherValue = property.GetValue(validationContext.ObjectInstance, null);
// TODO: get the other value again for the date -- and then apply your business logic of determining the capacity
}
}
However, it feels a bit messy to do a database call for the validationAttribute though
What you are asking for is cross-property validation. If you are not strongly opposed to implementing an interface on your data objects you should take a look at the following:
https://msdn.microsoft.com/en-us/library/system.componentmodel.dataannotations.ivalidatableobject.aspx
A simple example implementation for a small rectangle class where we want its area not to exceed 37 (whatever that unit is).
public class SmallRectangle : IValidatableObject
{
public uint Width { get; set; }
public uint Height { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
var area = Width * Height;
if (area > 37)
{
yield return new ValidationResult($"The rectangle is too large.");
}
}
}
Alternatives
The the second parameter of the IsValid function in your ValidationAttribute provides you with the ValidationContext which has the property ObjectInstance which you can cast to your object type and access its other members. That, however, will make your validation attribute specific to your class. I would generally advise against that.
You could also opt to use a different validation approach altogether such as using a validation library such as FluentValidations, see:
https://github.com/JeremySkinner/FluentValidation
A different perspective
Last but not least I would like to note that usually validation should be used to validate the integrity of the data. A booking request which requests more seats than available is not invalid. It can not be granted, but it is a valid request which will, unfortunately, be answered with a negative result. To give that negative result is, in my opinion not the responsibility of the validation, but the business logic.

Is it wrong to dynamically add "data-val" and "data-val-required" in the View?

I have a ViewModel that I can decorate with the [Required] attribute (see below). I've come to the point where I need to let the client control which fields are required or not. They can configure this trough XML and all this info is stored in the Model when it's first created. Now I have fields that are not decorated with [Required] but still need to get validated (as per "user settings") before submitting (for example the Phone field).
public class MyBusinessObjectViewModel
{
[Required]
public string Email { get; set; } //compulsory
public string Phone { get; set; } //not (yet) compulsory, but might become
}
If the user will not enter the Phone number, the data will still get posted. Wanting not to mess with custom validators, I just add the "data-val" and "data-val-required" attributes to the Html, like this:
Dictionary<string, object> dict = new Dictionary<string, object>();
dict.Add("data-val", "true");
dict.Add("data-val-required", "This field is required.");
#Html.TextBoxFor(x => x, dict);
This forces the client side validation for all the properties that are dynamically set as required. Is this good practice? What kind of side effects can I expect?
You should look into extending the meta model framework with your own metadata provider to do the actual binding between your site's configuration and the model metadata. You can actually set the required property flag to true on the property model metadata during the metadata creation process. I can't remember for sure whether this causes the built in editor templates to generate the attribute, but I think it does. Worst case scenario you can actually create and attach a new RequiredAttribute to the property, which is a tad bit kluggy, but works pretty well in certain scenarios.
You could also do this with IMetadataAware attributes, especially if Required is the only metadata aspect your users can customize, but the implementation really depends on what you're trying to do.
One major advantage of using a custom ModelMetadataProvider in your specific case is that you can use dependency injection (via ModelMetadataProviders) to get your customer settings persistence mechanism into scope, whereas with the data attribute you only get to write an isolated method that runs immediately after the metadata model is created.
Here is a sample implementation of a custom model metadata provider, you'd just have to change the client settings to whatever you wanted to use.
UPDATED but not tested at all
public class ClientSettingsProvider
{
public ClientSettingsProvider(/* db info */) { /* init */ }
public bool IsPropertyRequired(string propertyIdentifier)
{
// check the property identifier here and return status
}
}
public ClientRequiredAttribute : Attribute
{
string _identifier;
public string Identifier { get { return _identifer; } }
public ClientRequiredAttribute(string identifier)
{ _identifier = identifier; }
}
public class RequiredModelMetadataProvider : DataAnnotationsModelMetadataProvider
{
ClientSettings _clientSettings;
public RequiredModelMetadataProvider(ClientSettings clientSettings)
{
_clientSettings = clientSettings;
}
protected override ModelMetadata CreateMetadata(IEnumerable<Attribute> attributes, Type containerType, Func<object> modelAccessor, Type modelType, string propertyName)
{
// alternatively here is where you could 'inject' a RequiredAttribute into the attributes list
var clientRequiredAttribute = attributes.OfType<ClientRequiredAttribute>().SingleOrDefault();
if(clientRequiredAttribute != null && _clientSettings.IsPropertyRequired(clientRequiredAttribute.Identifier))
{
// By injecting the Required attribute here it will seem to
// the base provider we are extending as if the property was
// marked with [Required]. Your data validation attributes should
// be added, provide you are using the default editor templates in
// you view.
attributes = attributes.Union(new [] { new RequiredAttribute() });
}
var metadata = base.CreateMetadata(attributes, containerType, modelAccessor, modelType, propertyName);
// REMOVED, this is another way but I'm not 100% sure it will add your attributes
// Use whatever attributes you need here as parameters...
//if (_clientSettings.IsPropertyRequired(containerType, propertyName))
//{
// metadata.IsRequired = true;
//}
return metadata;
}
}
USAGE
public class MyModel
{
[ClientRequired("CompanyName")]
public string Company { get; set; }
}
public class MyOtherModel
{
[ClientRequired("CompanyName")]
public string Name { get; set; }
public string Address { get; set; }
}
Both of these models would validate the string "CompanyName" against your client settings provider.
Not wanting to mess with custom validators, so you messed in the View obfuscating the logic of your validation by removing it from the place where it is expected to be found.
Really, don't be afraid of creating a custom attribute validator. What you are doing right now is getting a technical debt.

IValidatableObject only in some actions

I have a model that implement IValidatlableObject, and so custom error checking through Validate method.
When I create an object all is fine, but when I try to edit that object, I wan't to do that custom validation.
How can I know from wich action I'm calling the Validate method in order to no do the validation?
UPDATED:
This is mi model:
public class Ingredient : IValidatableObject
{
public int Id { get; set; }
[Required(ErrorMessage = "Required!!")]
public string Name { get; set; }
public virtual List<Product> Products { get; set; }
public Ingredient()
{
Products = new List<Product>();
}
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
using (var uow = new UnitOfWork())
{
var ingredient = uow.IngredientRepository.Get(i => i.Name ==Name).FirstOrDefault();
if (ingredient != null)
yield return new ValidationResult("Duplicate!!!.", new[] { "Name" });
}
}
}
}
So When I create an Ingredient I want to validate ALL (Attributes + IValidatable)
but when I edit an Ingrendient I only want to validate attributes (so I mean skip IValidatable)
Any method to know, inside the IValidatable method, from where I'm calling Validate ?
Thanks!!!
Check primary key of model - whether it is not null :)
The more "MVCish" correct way here is you actually have two classes, one for the Create method one for the edit. You can call off to a base class for any shared validation, anything then not shared wouldn't be checked here.
If you don't want to validate an object, don't call Model.IsValid (or Validate(), if you're doing it explicitly. Can't answer more than that without knowing more details about your problem.

How do you do web forms model validation?

We have an application with three layers: UI, Business, and Data. The data layer houses Entity Framework v4 and auto-generates our entity objects. I have created a buddy class for the entity VendorInfo:
namespace Company.DataAccess
{
[MetadataType(typeof(VendorInfoMetadata))]
public partial class VendorInfo
{
}
public class VendorInfoMetadata
{
[Required]
public string Title;
[Required]
public string Link;
[Required]
public string LinkText;
[Required]
public string Description;
}
}
I want this validation to bubble up to the UI, including custom validation messages assigned to them. In MVC this is a piece of cake but in web forms I have no clue where to begin. What is the best way to utilize model validation in asp.net web forms?
I did find an article that explains how to build a server control for it, but I can't seem to get it working. It compiles and even recognizes the control but I can never get it to fire.
Any ideas?
Thanks everyone.
I solved it. It would appear that the server control I found was not designed to read fields in a buddy class via the MetadataType attribute. I modified the code to look for its validation attributes in the buddy class rather than the entity class itself.
Here is the modified version of the linked server control:
[DefaultProperty("Text")]
[ToolboxData("<{0}:DataAnnotationValidator runat=server></{0}:DataAnnotationValidator>")]
public class DataAnnotationValidator : BaseValidator
{
#region Properties
/// <summary>
/// The type of the source to check
/// </summary>
public string SourceTypeName { get; set; }
/// <summary>
/// The property that is annotated
/// </summary>
public string PropertyName { get; set; }
#endregion
#region Methods
protected override bool EvaluateIsValid()
{
// get the type that we are going to validate
Type source = GetValidatedType();
// get the property to validate
FieldInfo property = GetValidatedProperty(source);
// get the control validation value
string value = GetControlValidationValue(ControlToValidate);
foreach (var attribute in property.GetCustomAttributes(
typeof(ValidationAttribute), true)
.OfType<ValidationAttribute>())
{
if (!attribute.IsValid(value))
{
ErrorMessage = attribute.ErrorMessage;
return false;
}
}
return true;
}
private Type GetValidatedType()
{
if (string.IsNullOrEmpty(SourceTypeName))
{
throw new InvalidOperationException(
"Null SourceTypeName can't be validated");
}
Type validatedType = Type.GetType(SourceTypeName);
if (validatedType == null)
{
throw new InvalidOperationException(
string.Format("{0}:{1}",
"Invalid SourceTypeName", SourceTypeName));
}
IEnumerable<MetadataTypeAttribute> mt = validatedType.GetCustomAttributes(typeof(MetadataTypeAttribute), false).OfType<MetadataTypeAttribute>();
if (mt.Count() > 0)
{
validatedType = mt.First().MetadataClassType;
}
return validatedType;
}
private FieldInfo GetValidatedProperty(Type source)
{
FieldInfo field = source.GetField(PropertyName);
if (field == null)
{
throw new InvalidOperationException(
string.Format("{0}:{1}",
"Validated Property Does Not Exists", PropertyName));
}
return field;
}
#endregion
}
This code only looks in the buddy class. If you want it to check an actual class and then its buddy class, you'll have to modify it accordingly. I did not bother doing that because usually if you are using a buddy class for validation attributes it's because you are not able to use the attributes in the main entity class (e.g. Entity Framework).
For model validation in web forms I'm using DAValidation library. It supports validation on client side (including unobtrusive validation), extensibility based on same principles as in MVC. It is MS-PL licensed and available via Nuget.
And here is bit out of date article describing with what thoughts control was build.

How to make my custom data annotations work( it is in a list and each item in the list overrides the last validation test)

I am having problems with my custom validation in asp.net mvc 3.0
What I want to it to do.
Be set on a property (right now I only can figure out how to make it on the class)
Be smart enough to realize there are multiple instances being used.
Scenario
textbox ( id = "textbox1")
dropdownlist (id ="ddl1")
textbox (id = "textbox2")
4 dropdownlist (id = "ddl2")
the values in the dropdownlist are the same. ("Days" and "Minutes")
Now user types in textbox1 30 and chooses "days" in ddl1. He then types in textbox2 10000 and "days" in ddl2. First one is valid second one is invalid as there is only 365 days in a year.
Scenario 2
User types in texbox1 99 and chooses "minutes" in ddl1. This of course is invalid and should fail.
So there could be any combination of where they might choose a valid day and invalid minute time.
Or both could be valid
So I have this so far
My viewmodel
[ReminderFormat(ErrorMessage = "test")]
public class ReminderViewModel
{
public List<SelectListItem> DurationTypes { get; set; }
public DurationTypes SelectedDurationType { get; set; }
public string ReminderLength { get; set; }
}
This will be in a list of view models and generates the number of ones I need
List<ReminderViewModel> viewModel = new List<ReminderViewModel>()
// add ReminderviewModel to this collection
View
// do a for loop through the viewModel ( List<ReminderViewModel> )
// generate a textbox html box and drop down list for each one
Data annotation
// set attribute to class for now but I want it on a property but I can't
// figure out how to get SelectedDurationType and pass it in. if I have my attribute on ReminderLength
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
public class ReminderFormatAttribute : ValidationAttribute
{
public override bool IsValid(object value)
{
var reminderViewModel = (ReminderViewModel)value;
switch (reminderViewModel.SelectedDurationType)
{
case DurationTypes.Days:
int length = Convert.ToInt32(reminderViewModel.ReminderLength);
if (length > 30)
{
return false;
}
else
{
return true;
}
default:
throw new ArgumentOutOfRangeException();
}
}
Problem 1
So as you can see I have the annotation on the class and I rather have it on the ReminderLength property.
Problem 2
Right now I just have days at 30 so it's less to type(I will change it later). The problem I am finding right now is that if textbox1 has 31 in it this will fail my validation.
This is correct. But if I have textbox2 that has 1 as the value it will pass validation. This is also correct.
What is not correct is that it will override the first invalid validation. So now it thinks all validation passed and goes into my action method. When it should reject it and go back to the view and tell the user that textbox1 failed validation.
Here's what I would do for your validation attribute:
[AttributeUsage(AttributeTargets.Property, AllowMultiple = true)]
public class ReminderFormatAttribute: ValidationAttribute, IClientValidatable
{
public string DurationProperty { get; set; }
public ReminderFormatAttribute(string durationProperty)
{
DurationProperty = durationProperty;
ErrorMessage = "{0} value doesn't work for selected duration";
}
public override string FormatErrorMessage(string propName)
{
return string.Format(ErrorMessage, propName);
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var durationType = validationContext.ObjectType.GetProperty(DurationProperty).GetValue(validationContext.ObjectInstance, null);
var reminderLength = value;
// Do your switch statement on durationType here
// Here is a sample of the correct return values
switch (durationType)
{
case DurationTypes.Days:
if (reminderLength > 30)
{
return new ValidationResult(FormatErrorMessage(validationContext.DisplayName));
}
else
{
return null;
}
}
}
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
var rule = new ModelClientValidationRule
{
ErrorMessage = FormatErrorMessage(metadata.GetDisplayName()),
ValidationType = "reminderformat",
};
rule.ValidationParameters["durationproperty"] = DurationProperty;
yield return rule;
}
}
Now in your ViewModel you can annotate the ReminderLength property like so:
[ReminderFormat("SelectedDurationType")]
public string ReminderLength { get; set; }
This is how I usually do it when the validation depends on the value of another property. The GetClientValidationRules method is the server side piece you need to tie into the unobtrusive client validation via jquery validate.
Check out this link for another ValidationAttribute example: http://www.devtrends.co.uk/blog/the-complete-guide-to-validation-in-asp.net-mvc-3-part-2
The example on this site also goes into writing the necessary jQuery to tie in the client validation

Categories