Sunday, June 3, 2012
Using AutoMapper to copy Metadata from Entities to ViewModels
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.
Metadata Provider
Validator Provivder
Helper Method Referenced in above 2 classes
Other Notes
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
Labels:
AutoMapper,
Metadata,
MVC,
Stackoverflow
Dependency Injection for RouteConstraints
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
Pagination with AutoMapper
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
Resulting Action
IObjectMapper
Result - Build a custom IObjectMapper to do the mapping
Resulting Action
IObjectMapper
Labels:
AutoMapper,
MVC,
MVCContrib
Updating collections using AutoMapper
By default AutoMapper replaces child lists with a completely new instance only containing the items in the original list. Because of the way EF works you need to change the existing items in the list for it to track changes as updates instead of adds. This method also means deletes can be tracked succesfully.
Basic steps were
Ensure the destination collection is loaded from db and attached to the object graph for change tracking
.ForMember(dest => dest.Categories, opt => opt.UseDestinationValue())
Then create a custom IObjectMapper for mapping IList<> to IList<T> where T : Entity
The custom IObject mapper used some code from http://groups.google.com/group/automapper-users/browse_thread/thread/8c7896fbc3f72514
Finally one last piece of logic to check all Id's in targetCollection exist in sourceCollection and delete them if they don't.
It wasn't all that much code in the end and is reusable in other actions.
My Stackoverflow Post: http://stackoverflow.com/questions/9739568/when-using-dtos-automapper-nhibernate-reflecting-changes-in-child-collections/9856360#9856360
Recently found a library that does just this - https://github.com/TylerCarlson1/Automapper.Collection My Stackoverflow Post: http://stackoverflow.com/questions/9739568/when-using-dtos-automapper-nhibernate-reflecting-changes-in-child-collections/9856360#9856360
Labels:
AutoMapper,
EF,
Stackoverflow
Thoughts on ModelBinders
Validation
Source: http://www.markeverard.com/blog/2011/07/18/creating-a-custom-modelbinder-allowing-validation-of-injected-composite-models/
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.
Entities
Source: http://lostechies.com/jimmybogard/2011/07/07/intelligent-model-binding-with-model-binder-providers/
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
Source: http://iridescence.no/post/Constructor-Injection-for-ASPNET-MVC-Model-Binders.aspx
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.
DateTimes
For a recent projet I didn’t want to use a javascript datetime picker as it was meant to be used on a horrible android tablet, but I still wanted something a bit friendlier than a textbox. I chose to split dates into day/month/year fields, which would be a lot of effort to do to every date manually.
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.
Labels:
ModelBinder,
MVC
Thoughts on ActionFilters
UnitOfWork
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.
AutoMapperActionFilter
Source: http://lostechies.com/jimmybogard/2009/06/30/how-we-do-mvc-view-models/
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:
ViewModelEnricher
Source: http://ben.onfabrik.com/posts/better-form-handling-in-aspnet-mvc
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.
ValidationActionFilter
Source: http://trycatchfail.com/blog/post/Cleaning-up-POSTs-in-ASPNET-MVC-the-Fail-Tracker-Way.aspx
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.
Labels:
ActionFilter,
MVC
Thoughts on ActionResults
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.
LoginAction
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
File Result
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.
Status Results
Source: https://github.com/MattHoneycutt/Fail-Tracker/blob/master/FailTracker.Web/ActionResults/StatusResult.cs
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.
Labels:
ActionResult,
MVC
Subscribe to:
Posts (Atom)