Sunday, June 3, 2012
On my crusade to eliminate common mistakes causing bugs in my projects I found was that my metadata on my entities didn’t always match my viewmodels. I wrote 2 fairly simple providers to copy the metadata from the entity to the ViewModel at runtime which leverages my AutoMapper configuration so it supports field names that are renamed.
I use the approach below to automatically copy data annotations from my entities to my view model. This ensures that things like StringLength and Required values are always the same for entity/viewmodel.
It works using the Automapper configuration, so works if the properties are named differently on the viewmodel as long as AutoMapper is setup correctly.
You need to create a custom ModelValidatorProvider and custom ModelMetadataProvider to get this to work. My memory on why is a little foggy, but I believe it's so both server and client side validation work, as well as any other formatting you do based on the metadata (eg an asterix next to required fields).
Note: I have simplified my code slightly as I added it below, so there may be a few small issues.
Helper Method Referenced in above 2 classes
If you're using dependency injection, make sure your container isn't already replacing the built in metadata provider or validator provider. In my case I was using the Ninject.MVC3 package which bound one of them after creating the kernel, I then had to rebind it afterwards so my class was actually used. I was getting exceptions about Required only being allowed to be added once, took most of a day to track it down.
My StackOverflow Post: http://stackoverflow.com/questions/9989785/technique-for-carrying-metadata-to-view-models-with-automapper/10100042#10100042
Often I've wanted to check whether something exists in the database in a route constraint, unfortunately MVC doesn't support dependency injection for RouteConstraints out of the box, and due to them being alive for the entire life of the application it isn't that easy. If the scope of the dependency is in request scope, for example, then it will work for the first request then not for every request after that.
You can get around this by using a very simple DI wrapper for your route constraints.
then create your routes like this
I recently posted a method of using AutoMapper in an ActionResult (http://bzbetty.blogspot.co.nz/2012/06/thoughts-on-actionfilters.html). This became an issue when I was trying to return a paginated model to the view, as I wanted to do pagination in the database (on the entities) but AutoMapper had no idea how to map the IPagination result to the ViewModel.
Result - Build a custom IObjectMapper to do the mapping
Result - Build a custom IObjectMapper to do the mapping
Custom ModelBinders don’t seem to run validation on the newly created model automatically, so while you may add validation attributes to your model you need to add in some extra code to force validation to occur.
There may be a class you can override to get this automatically but I haven’t found it yet.
An interesting technique I’ve seen used is using modelbinding to automatically load an entity based on the id passed in. So your actions ask for an entity instead of an Id.
Doesn’t seem like much but it should remove 1 line from most actions (loading the entity from the database). However it requires you to never use your entities as edit models (which you shouldn’t be doing anyway). It also doesn’t allow extra filtering/including of data that I can tell.
This idea could be extended to work on list pages by creating a filter criteria from all route values (page, sort, search)
Dependency Injecting Model Binders
Out of the box you can’t use dependency injection with ModelBinders, you can however pass objects in when you create them at registration time. If your ModelBinders only take singletons then that approach works, otherwise create a fairly basic modelbinder that takes your kernel as a parameter and when binding method is called use the kernel to create your real model binder complete with full dependency injection.
Instead I made an Editor Template for DateTimes which outputs 3 fields, and a custom ModelBinder that when binding dates looks for 3 fields instead of just 1.
Source: Ayende/Rob Conery http://wekeroad.com/2011/03/21/using-entityframework-with-aspnet-mvc-3/
I have a very basic UnitOfWork ActionFilter which runs after every action, when no exceptions are thrown it calls SaveChanges on my DbContext (which is set to be 1 per request in ninject). Mostly because we occasionally forgot to call this method.
Because I use AutoMapper to create my ViewModels from my entities I ended up with a bunch of code in my views that basically looked like
In my crusade to remove repetitive code in my actions, I created an extra ActionFilter that can perform the mapping after the action has finished executing.
The main benefit of this turned out to not be less code, as it’s still the same number of lines of code. It was that my tests no longer needed to setup AutoMapper in order to test an Action (except for the create/update actions).
Jimmy has actually moved on from using an ActionFilter and started using an ActionResult that decorates another ActionResult and performs the mapping. I don’t use that approach as I find it hard/impossible to add code between the mapping and the final view being displayed, which I needed to do to populate dropdowns (see ViewModelEnricher). Jimmy’s new approach looks like the following:
Another piece of repetitive code was loading the contents dropdowns and adding them to the ViewModels. I created yet another ActionFilter (run after the AutoMap one) which looks at all the properties on a ViewModel for an attribute telling it where to load dropdowns from.
This was added globally to all actions and meant I could no longer accidentally forget to add the code to set the dropdowns on Validation failure of an update/create action (most common place to forget to do it).
I’m still not 100% convinced this is the best approach; I’ve been playing with the concept that dropdowns are dependencies of the ViewModel and should be created by the DI container. This would either require the AutoMap ActionFilter and ModelBinder to create all ViewModels using the DependencyResolver or a small modification to the ViewModelEnricher ActionFilter to use property injection into an existing object on the way through.
It’s fairly common to check ModelState.IsValid on all postbacks and return a view if validation fails. I added another global action filter which performs this check on any post request. Again this is so it isn’t accidentally forgotten and to reduce the amount of repetitive code in Actions.
ActionResults are a good way to make your controllers more testable. For example, HttpContext.Current is not available in unit tests a null reference exception occurs when testing code that calls HttpContext.Redirect, however because the Redirect ActionResult is returned and not executed by the controller unittests can check the action returned and ensure that it is a redirect without needing to mock an entire HttpContext.
Note: While MVC does wrap HttpContext which allows you to mock it while testing, it’s a lot easier if you don’t have to deal with it at all.
A similar approach can be used for LogIn/LogOut, you could abstract the FormsAuth call to a custom service but again you would need to pass a mock in your tests or risk another null reference, using a ActionResult instead makes testing much easier
They also are a great method of code reuse. You would never imagine of putting the code that reads a view and renders it in every action, so why add the code that outputs a pdf/ical/rss/xls/graph/report inline in the controller? Simply return a Result that knows how to render the pdf and pass the data to it.
You can use the decorator pattern to extend what a view does. I like adding a status message at the top of a page after an action has taken place eg “User created successfully”. Instead of adding extra properties to every ViewModel to include a status message, I just put the message into TempData (Not ViewBag as that won’t live through a redirect) and consume it in the view.
I then use an extension method to make it a bit nicer to use
Unit tests can then check that a StatusResult is returned that contains another action which performs the redirect.