At the end of last week, I was tinkering with the Entity Framework 4.0 and MVC3; MVC which we’ve been using a long time here and we all know and love, Entity Framework which quite frankly feels unfinished unless you have the CTP5 patch so that one can write true code-first with POCO’s. Now I know it’s illegal to use CTP patches on production environments, but apparently this is the last CTP release before a full release first quarter this year, so we can begin prototyping with it at least. Anyway getting to the point, during my tinkering, I think I may have worked out a means of custom validating objects in the controller, setting ModelState, and creating errors well before a controller method is invoked, essentially creating a validation layer that at run time sits between a UI post and the controller. I'm sure I'm not the first who's done this, and this has been worked out and blogged a gazillion times before but I figure I'll have my say on the matter...
Firstly lets create an interface for our validation class, something simple like...
public interface ICustomValidator
{
IEnumerable<KeyValuePair<string, string>> ValidateObject(object typeObject);
IEnumerable<KeyValuePair<string, string>> ValidateObject(object typeObject, out bool isValid);
}
... and create an implementation. Now I was following Scott Gu's blog for re-creating NerdDinner with the Entity Framwork when I wrote this, so I have a Dinner class type that I want to validate on the call to my 'Create' method on my DinnerController which takes as the parameter a single Dinner object, so I've created an aptly named DinnerValidator class.
public interface ICustomValidator
{
IEnumerable<KeyValuePair<string, string>> ValidateObject(object typeObject);
IEnumerable<KeyValuePair<string, string>> ValidateObject(object typeObject, out bool isValid);
}
... and create an implementation. Now I was following Scott Gu's blog for re-creating NerdDinner with the Entity Framwork when I wrote this, so I have a Dinner class type that I want to validate on the call to my 'Create' method on my DinnerController which takes as the parameter a single Dinner object, so I've created an aptly named DinnerValidator class.
public class DinnerValidator : ICustomValidator
{
public IEnumerable<KeyValuePair<string, string>> ValidateObject(object typeObject)
{
Dinner dinner = (Dinner)typeObject;
if (dinner.EventDate < DateTime.Now)
yield return new KeyValuePair<string, string>("EventDate",
"Sorry I seem to have misplaced my time machine!");
}
public IEnumerable<KeyValuePair<string, string>> ValidateObject(object typeObject,
out bool isValid)
{
var errors = ValidateObject(typeObject);
isValid = (errors.Count() == 0);
return errors;
}
}
So the code above you can chop and change to your heart's content, it's hardly rocket science, it's all very simple. We pass in objects as the parameters because later we are going to use reflection to invoke this classes constructor to use the method. We can turn these straight to Dinner types because by now if it's here, we know these objects are dinners... or at least should be if you've implemented it right. The reason my method's return IEnumerable<KeyValuePair<string, string>> is so that they are compatible with the Model State Dictionary, which is also a list of key value pairs. Really, everything is done for simplicity later on. This might well change when I use it in production I'm just giving you a taster of what can be done and how you can implement custom validating.
The next step is to create the Action Filter itself, so create a class that implements the IActionFilter interface. We also need a means of telling our ActionFilter that we want it to run on certain controller methods. So for that we're going to use the FilterAttribute, so allow your class to inherit that also. So your class name should look like something like the following...
public class ValidateParameterTypeAttribute : FilterAttribute, IActionFilter
...
Add Attribute to the end, of your class name so we know what it is; don't worry that the class name is too long, when we use it as a filter on your controller method the CLR will just chop "Attribute" off.
The constructor and members ought to look something like this.
Type ValidatorType { get; set; }
Type ObjectType { get; set; }
ValidateParameterTypeAttribute(Type type, Type validatorType)
{
ObjectType = type;
ValidatorType = validatorType;
}
Now lets implement the IActionFilter interface. You'll notice that it creates placeholders for two methods called OnActionExecuting and OnActionExecuted. OnActionExecuting is invoked before a controller method is invoked and OnActionExecuted invokes after a controller method is executed. So that means we can use OnActionExecuting too pre-validate our objects before they hit the controller method. Neat. First though you need to stop OnActionExecuted from throwing an exception so go ahead and remove that line of code and just let it fall through.
Now for the minor magic of this implementation; the OnActionExecuting method. We're going to use a tiny bit of reflection to invoke the constructor of our ValidatorType and create the validator object, then use its interfaced method to validate the parameters on our controller method by their object type. Then add any errors it finds to the ModelState dictionary as found in the OnActionExecuting parameter ActionExecutingContext filterContext.
Following is the full implentation of the ValidateParameterTypeAttribute class.
public class ValidateParameterTypeAttribute : FilterAttribute, IActionFilter
{
Type ValidatorType { get; set; }
Type ObjectType { get; set; }
ValidateParameterTypeAttribute(Type type, Type validatorType)
{
ObjectType = type;
ValidatorType = validatorType;
}
public void OnActionExecuted(ActionExecutedContext filterContext)
{
// Don't have a good reason to do anything here as yet.
}
public void OnActionExecuting(ActionExecutingContext filterContext)
{
var isCustomValidator = ValidatorType.GetInterfaces().Contains(typeof(ICustomValidator));
if (isCustomValidator)
{
ConstructorInfo ci = ValidatorType.GetConstructor(new Type[] { });
var typeObjects = filterContext.ActionParameters.Values
.Where(o => o.GetType() == ObjectType);
var modelState = filterContext.Controller.ViewData.ModelState;
var validator = (ICustomValidator)ci.Invoke(new object[] { });
foreach (var obj in typeObjects)
{
bool isValid;
var errors = validator.ValidateObject(obj, out isValid);
if (!isValid)
foreach (var error in errors)
modelState.AddModelError(error.Key, error.Value);
}
}
}
}
All that is left is to add the validation attribute to your controller method...
[HttpPost]
[ValidateParameterType(typeof(Dinner), typeof(DinnerValidator))]
public ActionResult Create(Dinner dinner)
{
if (ModelState.IsValid)
{
nerdDinners.Dinners.Add(dinner);
nerdDinners.SaveChanges();
return RedirectToAction("Index");
}
return View(dinner);
}
So there you have it. Now you could modify this code so it checks against a database or entity context. You could modify the filter so that it accepts only one arguement, the type you wish to validate, and then see if a filter called typeNameValidator exists. You might want to supply it something to help with translations, who knows, the world's yours oyster. The point is, is that you can supply validation business logic that is separate from your business logic. Using it as an action filter is handy because you know that at the point in which your object exist in a controller method, it has been pre-validated. You could separate the code out into a different .dll and use it with other applications and you can use it throughout your application, it need not be used as just an action filter (that's just a nifty way of using it), and you can maintain the 'Don't repeat yourself' principle. Hope you find this remotely helpful. Enjoy.
Hi,
ReplyDeleteOne question, should validation be on the model rather than a filter on the controller action?
this can be done with Data annotations. Brad Wilson has a good video on how you can extend and customise this in mvc3 including client side validation.
http://channel9.msdn.com/Series/mvcConf/mvcConf-2011-Brad-Wilson-Advanced-MVC-3
Ali,
ReplyDeleteSorry for the late reply, haven't checked on this in a few days, but, yeah, absolutely. I completely agree that validation where possible should be provided using the Data Annotations, and they're really good for validating lengths, ranges, regular expressions etc. The validation method I've provided in the code on this page is a bad example of what you would use this method of validation for. A better use would be to use a custom validation action filter on a 'Create' method that checks a data context that has been provided with the instantiation of the controller to check that a record with the same ID doesn't already exist in a data source. This way we know well before we even need to begin a Save operation that such a record exists, however, had I wrote this example it probably wouldn't have been as short.