I've devoted a significant amount of time this week to finding the best way to implement inversion of control for proper dependency injection in Umbraco.
I'm using Autofac because I've used it before, and @shazwazza's original IOC documentation on our.umbraco used it. If you're confident with your container's api you should be able to follow my instructions with any container you like.
Step 1: Setup
Step one is to add some custom startup code to Umbraco with a custom
OnApplicationStarted method. For those familiar with dependency-injection lingo, this is our application's composition root, or where we will register the dependencies with our container. My preferred way to do this is by implementing
IApplicationEventHandler. The mere presence of this class somewhere in your code should cause your
OnApplicationStarted method to be run, well... on application started. If that makes any grammatical sense.
Step 2: Injecting an IContentService
Let's suppose we're doing some fancy stuff with Umbraco and have written a class that depends on
ContentService. Nothing too fancy that the docs won't be able to help you with.
ApplicationContext.Current.Services.ContentService.CreateContent(name, parentID, contentTypeAlias);
Your trusty copy of Mark Seeman's Dependency Injection in .NET should have just dislodged itself from the shelf, wiggled free, and hurled itself at the back of your head at the sight of the above code. I hope it hurt. How could you be so naive? You've just gone and created an explicit dependency on
ApplicationContext.Current.Services.ContentService. How are you going to unit test that? What happens when your boss tells you to rewrite the whole thing with EntityFramework and you have explicit references to Umbraco services all over the place?
Since the content service implements
IContentService anyway, you should be writing your own wrappers around Umbraco's core services, and then using your dependency injection mojo to inject an instance of
IContentService into them. Here's an example of a "wrapper" class.
Notice how it doesn't depend on Umbraco's
ContentService directly? It only depends on the interface
IContentService. Providing our own implementation of
IContentService for unit testing should now be a piece of cake. Depending on this class will appease the SOLID gods and guarantee you a place in programmer paradise.
We just need to make sure that our
IContentService is being injected properly at our app's composition root, which takes only a few lines of code in our
This shouldn't be spectacular to you, but all we're doing is telling Autofac to use
applicationContext.Services.ContentService, the instance of
IContentService that's helpfully setup by Umbraco, whenever one of our classes asks for an
IContentService as a dependency, like our
MyAppContentService wrapper does in the constructor.
Lastly, we tell ASP.NET MVC to use Autofac as a dependency resolver.
// Set up MVC to use Autofac as a dependency resolver var container = builder.Build(); System.Web.Mvc.DependencyResolver.SetResolver(new AutofacDependencyResolver(container));
Now you can live out the rest of your days in the endless bliss of dependency injection paradise.
OR CAN YOU?
Step 3: Trouble in Para-DI-se
If you're smart, you will have noticed that the above code has broken the Umbraco backoffice. If you're not smart, you will have shipped your code and upset your colleagues/clients/boss/deity. Well done. Yes, I'll have fries with that.
It looks like the code that sets the
DependencyResolver for ASP.NET MVC causes the Umbraco backoffice to freak out when it tries to work with API controllers. Or something like that.
Happily, we can resolve this issue with very few lines of code by registering all the mvc controllers and web api controllers in Umbraco's assembly with our IOC. Autofac has quite a slick API for doing this.
In case you missed it, we added two LOC:
Step 4: .... Profit?
That's all there is to it folks. IOC in Umbraco. I've experience the pain of the many gotchas during this process so that you don't have to.
Thanks for reading! If you enjoyed this post, have anything to add, or would otherwise like to get in touch you can do so on Twitter @glcheetham.