Following on from my original post about a bug in async/await actions in custom controllers in Umbraco 6 & 7, I did some investigations and found the cause, reported it here and submitted a pull request. It's well worth reading through the issue thread since although my pull request was initially accepted, it was replaced with a much better fix by Shannon Deminick. The issue still exists in Umbraco but will probably be resolved in 7.3.2.
However, I thought I'd recap on my solution for all those who can't upgrade. It's also an interesting trip into code that you wouldn't normally touch.
The root of the issue was specifically in how Umbraco resolves actions for controllers that implement the IRenderMvController interface. This all occurs in the RenderActionInvoker class but makes the assumption that all actions are synchronous.
You wouldn't know to look at the code but ReflectedActionDescriptor identifies the requested action as being synchronous. As you can see from the source, ReflectedActionDescriptor inherits from the abstract ActionDescriptor class. Another class that inherits from this is the AsyncActionDescriptor. So now you can see that what Umbraco's RenderActionInvoker code does is treat each action request as being synchronous instead of checking the action and returning an AsyncActionDescriptor when the action is async.
So the solution then is simple, register a custom ControllerFactory in your application and from this instantiate a custom ActionInvoker.
The custom controller needs to inherit from the UmbracoControllerFactory class:
The custom action invoker is simply an extension of the RenderActionInvoker but doesn't need to inherit from it.
You can then register the controller factory from the application start-up as described in the Umbraco documentation, using the code:
As described in the previous post, you can't just mark a method/action as being async, it generally has to return a Task or Task<T> as well. This means we can't change the signature of Umbraco's default Index(RenderModel model) action but we can overload it. So you can use the following type of action instead:
Or alternatively you can target the template instead using: