Building async Unit of Work with MVC 4

time to read 19 min | 3616 words

In the RavenDB mailing list, we had a question about how we can combine the standard unit of work pattern of working with RavenDB in MVC applications with async. In particular, the problematic code was:

   1: public class HomeController : Controller
   2: {
   3:     public IAsyncDocumentSession Db { get; set; }
   5:     public async Task<ActionResult> Index()
   6:     {
   7:         var person = new Person {Name = "Khalid Abuhakmeh"};
   8:         await Db.StoreAsync(person);
  10:         return View(person);
  11:     }
  13:     protected override void OnActionExecuting(ActionExecutingContext filterContext)
  14:     {
  15:         Db = MvcApplication.DocumentStore.OpenAsyncSession();
  16:         base.OnActionExecuting(filterContext);
  17:     }
  19:     protected override void OnActionExecuted(ActionExecutedContext filterContext)
  20:     {
  21:         Db.SaveChangesAsync()
  22:             .ContinueWith(x => { });
  24:         base.OnActionExecuted(filterContext);
  25:     }
  27:     public class Person
  28:     {
  29:         public string Id { get; set; } 
  30:         public string Name { get; set; }
  31:     }
  32: }

As you probably noticed, the problem is with line 21. We want to execute the save changes in an async manner, but we don’t want to do that in a way that would block the thread. The current code just assume the happy path, and any error would be ignored. That ain’t right. If we were using Web API, this would be trivially easy, but we aren’t. So let us see what can be done about it.

I created a new MVC 4 application and wrote the following code:


As you can see, I have a break point after the await, which means that when that break point is hit, I’ll be able to see what is responsible for handling async calls in MVC4. When the breakpoint was hit, I looked at the call stack, and saw:


Not very useful, right? But we can fix that:


And now we get:


This is a whole bunch of stuff that doesn’t really help, I am afraid. But then I thought about putting the breakpoint before the await, which gave me:


And this means that I can check the code here. I got the code and started digging. At first I thought that I couldn’t do it, but then I discovered that I could. See, all you have to do is to create you own async action invoker, like so:

   1: public class UnitOfWorkAsyncActionInvoker : AsyncControllerActionInvoker
   2: {
   3:     protected override IAsyncResult BeginInvokeActionMethod(
   4:         ControllerContext controllerContext,
   5:         ActionDescriptor actionDescriptor,
   6:         IDictionary<string, object> parameters, AsyncCallback callback,
   7:         object state)
   8:     {
   9:         return base.BeginInvokeActionMethod(controllerContext, actionDescriptor, parameters,
  10:                                             result => DoSomethingAsyncAfterTask().ContinueWith(task => callback(task)),
  11:                                             state);
  12:     }
  14:     public async Task DoSomethingAsyncAfterTask()
  15:     {
  16:         await Task.Delay(1000);
  17:     }
  18: }

And then register it:

   1: DependencyResolver.SetResolver(type =>
   2:     {
   3:         if (type == typeof (IAsyncActionInvoker))
   4:             return new UnitOfWorkAsyncActionInvoker();
   5:         return null;
   6:     }, type => Enumerable.Empty<object>());

And you are golden.

Note: Except for doing a minimum of F5 in the debugger, I have neither tested nor verified this code. It appears to do what I want it to, and since I am only getting to this because a customer asked about this in the mailing list, that is about as much investigation time that I can dedicate to it.