I wanted to elaborate on jfar's suggestion. I am not agreeing that I need to wait till MVC 3; however, his point for creating a custom model binder is actually THE ONLY way to do what I am trying to do. I've tried both field/property level attributes and class level attributes. Neither of these are sufficient for the sort of messages I need.
For example, recall that my situation is that I have a template control that is strongly typed against a model with properties Index, Heading, & Rating. So a page of these controls displayed would be something like:
Heading: Technical Knowledge
Rating: [text box]
Heading: Leadership
Rating: [text box]
Heading: Work Ethic
Rating: [text box]
...etc, etc.
For my validation messages, I wanted something very customized. It turns out my needs were too specific for the out of the box validating in MVC 2. I needed error messages for the Rating field to reference the Heading that Rating is associated with. So the validation would be something like:
The Rating for Work Ethic is required and must be a number between 1 and 5.
The Rating value of 'asdf' for Work Ethic is not valid.
The issue with field level attributes is that they didn't have access to the Heading value. Furthermore, each object actually contains an IsEditable field and should that field's value be false, I bypass validations altogether.
The issue with class level attributes is two fold. For one, I can't control the key used in the ModelStateCollection. The default is the class name and index of the object on the page. This yields a result akin to: PersonRating[0], PersonRating[1]. The problem with this is that it means you can only have 1 error message at a class level validation. If you have 2 class level attributes, they both get put into the ModelStateCollection with the same key. I'm not really sure how that works as I wouldn't think a dictionary would let you do that. Perhaps it fails silently or the second message just overwrites the first. In addition to this, I still need the fields themselves to have their css changes to denote an error. With class level validations, I could not figure out how to do this because the key does not reference a field...so I have no idea which message goes with what field unless I do hard string checks, which seems a really bad solution.
The blog post I referenced earlier does provide a solution, but it requires far too much code and effort per attribute and just seemed like overkill.
In addition to all of this, MVC data annotations has basic validations hard wired into it to sort of save you from yourself. This includes invoking a Required validator if a type on your model is non-nullable. The internal MVC validations also do data checking to ensure you're not trying to submit a string value to a int type. In both of these cases, I didn't not see a viable way to correct the validation message without doing hard checks against the validation text.
So, in the end, I wrote my own model binder for this object. While this is not the recommended way, I take comfort in that this really is the only viable solution in my case due to the internal validations hardwired in the MVC framework. My model binder looked something like below:
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
Object o = base.BindModel(controllerContext, bindingContext);
string ratingKey = bindingContext.ModelName + ".Rating";
PersonRating pr = (PersonRating)o;
ValueProviderResult ratingVpr = controllerContext.
Controller.
ValueProvider.
GetValue(ratingKey);
String ratingVal = ratingVpr.AttemptedValue;
String ratingErrorMessage = getRatingModelErrorMessage(
ratingKey,
ratingVal,
pr);
if (!String.IsNullOrEmpty(ratingErrorMessage))
{
bindingContext.ModelState[ratingKey].Errors.Clear();
bindingContext.ModelState.AddModelError(ratingKey, ratingErrorMessage);
}
return o;
}
The getRatingModelErrorMessage method is a custom method that performs validations on the Rating field of the PersonRating object and returns a string that represents the error message. If the string is null, no error was returned by the getRatingModelErrorMessage method.
I'll be the first to admit this isn't great code. There's always room to improve. Still it gets the job done. It's also worth noting that in situations where a value such as a text value is submitted for a non compatible type on the model, such as int, it will not be bound to the model. I am getting that value from the FormCollection via it's key.
Let me know if anyone has any suggestions on other ways this could be done or comments on the code in general. I'm always open for constructive criticism.