Architecture based on IoC/DI for Umbraco packages

1) Introduction

This article is about using Inversion Of Control (IoC), Dependency Injection (DI) in an Umbraco MVC website and how it fits well with auto mapping of umbraco document type properties to view model.
We will see that this architecture fits very well with the creation of a custom Umbraco package where you need to extend or modify the core functionalities of the package without having to give the source code.

To fully understand this article, you need to have some basic notions about MVC in Umbraco and IoC/DI in general :

Doc about MVC in Umbraco :
- http://our.umbraco.org/documentation/Reference/Templating/Mvc/
Doc about IoC/DI with autofac :
- http://docs.autofac.org/en/latest/getting-started/index.html
- http://docs.autofac.org/en/latest/integration/mvc.html
Doc about getting started with Umbraco and autofac :
 - http://our.umbraco.org/documentation/Reference/Mvc/using-ioc

2) Background

I'm a developer at www.novicell.dk. I have developed a Novicell internal Umbraco 6 package called "Novicell Event Module".
The package allows to create and manage event pages. You can signup for an event choosing options like "which session you want to participate" or "what kind of food you want at the event".
A custom section allows the client to manage the participants per event and export them to excel.

Each time we sold the module, we encountered the same problem : the customer wanted to highly customize the behaviour, like adding a maximum of participants per event, send an email/sms reminder etc.
The architecture was not very flexible and we end up having to modify the source code per project in order to satisfy the needs.

I decided to rewrite the project entirely (together with the talented Mads Pedersen) for Umbraco 7 MVC with a more flexible architecture based on IoC, DI and auto-mapping of view models.

This architecture provides a solution to extend the behaviours of the package without having to change the source code.

The package is finally called uEvent and will be available as an Umbraco commercial package in February 2015.

The package will come together with some extended DI classes (that add new functionalities to the package) available for free on Github.

3) Inversion of Control in an Umbraco MVC architecture.

In a "true" MVC Umbraco solution, you would have a custom Controller (umbraco hijacking routes) creating a View Model based on the current node properties and return this view model to a View.

So your controller classes should look something like :

// Custom controller to display the view
public class DocTypeNameController : RenderMvcController 
{
    public override ActionResult Index(RenderModel model)
    {
	    MyViewModelClass myViewModel = BusinessHelper.CreateViewModel(model);
        return CurrentTemplate(myViewModel);        
    }	
}

// Custom controller for actions like post a form or render a Action Child view
public class MySurfacteController : SurfaceController
{
	[HttpPost]
    public ActionResult DoSomething(MyViewModelClass model)
    {
		if (!ModelState.IsValid)
            return CurrentUmbracoPage();
				
	    BusinessHelper.DoSomeLogic(model);
		
		return to partial view or something else
	}
}

Even if the logic is encapsulated in a business layer (BusinessHelper in the example), there is no way for a user of these classes to change the logic. IoC/DI can solve this problem.

At first, we will execute the logic by an external "Service" class (IoC):

// Custom controller to display the view
public class DocTypeNameController : RenderMvcController 
{
    private readonly IMyService _myService;

    public UEventListController(IMyService theService)
    {
         _myService = theService;
    }

    public override ActionResult Index(RenderModel model)
    {
	    MyViewModelClass myViewModel = _myService.CreateViewModel(model);
        return CurrentTemplate(myViewModel);        
    }	
}

// Custom controller for actions like post a form
public class MySurfacteController : SurfaceController
{
	private readonly ISurfaceMyService _myService;

    public UEventListController(ISurfaceMyService theService)
    {
         _myService = theService;
    }

	[HttpPost]
    public ActionResult DoSomething(MyViewModelClass model)
    {
		if (!ModelState.IsValid)
                return CurrentUmbracoPage();
				
	    _myService.DoSomeLogic(model);
		
		return to partial view or something else
	}
}

In this case, the services are responsible to do all logics instead of the BusinessHelper in the controllers.
So if you create an instance of your controller with a different service, you can have a different/enhance behaviour for your page.

But how to "inject" a service into a controller ? That's where Dependency Injection comes into the picture...

4) Dependency Injection in Umbraco

We first had to choose a Dependency Injection Framework. There are lots available for .NET but we chose Autofac after reading this great reference article : http://our.umbraco.org/documentation/Reference/Mvc/using-ioc
This article explains in more detail what I wrote above and also explains how to use Autofac to inject a service class into a controller. I won't repeat the article more, please read it thraugh if you want more detail about it.

To install Autofac, just use nuget : https://www.nuget.org/packages/Autofac.Mvc4/

We will go a bit further in this chapter and explain how to use a config file to select which classes to inject.

The initialisation of Autofac is done in the OnApplicationStarted method.
In the official Umbraco reference article mentioned above, the implementation of the service/context that will execute the logic is hardcoded in OnApplicationStarted :
"builder.RegisterType<MyAwesomeService>();"
And the class MyAwesomeService, will be injected in all constructors that need it.

It would be more flexible to use a config file to define these classes. And it can be done very easily with Autofac by adding a config file path as parameter of the RegisterModule method.

Example of OnApplicationStarted :

public void OnApplicationStarted(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext)
{
	var builder = new ContainerBuilder();
	
	//register all controllers found in this assembly
	builder.RegisterControllers(typeof(SomeClassInMyAssembly).Assembly);

	// specify the config file here :
	builder.RegisterModule(new XmlFileReader(HttpContext.Current.Server.MapPath("config/autofac.config")));
	builder.RegisterModelBinderProvider();	
	builder.RegisterApiControllers(typeof(UmbracoContext).Assembly);

	// my custom section (the class the inherit from TreeController) stopped working after adding autofac and I found the solution here :
	// http://our.umbraco.org/forum/getting-started/installing-umbraco/46674-U701-build-200-Failed-to-retrieve-data-for-application-tree-content
	// see answer from Martin that point to http://blogs.msdn.com/b/roncain/archive/2012/07/16/dependency-injection-with-asp-net-web-api-and-autofac.aspx	
	builder.RegisterAssemblyTypes(typeof(SomeClassInMyAssembly).Assembly).Where(t => !t.IsAbstract && typeof(ApiController).IsAssignableFrom(t)).InstancePerMatchingLifetimeScope(AutofacWebApiDependencyResolver.ApiRequestTag);

	IContainer container = builder.Build();
	DependencyResolver.SetResolver(new AutofacDependencyResolver(container));
}

Example of config file :

<autofac defaultAssembly="myProject.Library">
  <components>        
    <!-- Service to map to View Model properties -->
    <component type="myProject.Library.Services.MapService, myProject.Library" service="myProject.Library.Services.IMapService" />    
    <!-- Service to custom logic on post -->
    <component type="myProject.Library.Services.CustomLogicOnPostService, myProject.Library" service="myProject.Library.Services.ICustomLogicOnPostService" />    
    <!-- View model to use -->    
    <component type="myProject.Library.ViewModels.MyViewModel, myProject.Library" service="myProject.Library.ViewModels.IMyViewModel" />   
  </components>
</autofac>

As you can see, you can use dependency injection to specify the services that do some logic (myProject.Library.Services.MapService in the example) but also any kind of classes like which ViewModel class to use (myProject.Library.ViewModels.MyViewModel).
This is really useful when you add new property to your document type, you just need to inherit and extend the base view class (or interface) and if the service uses auto mapping, it will automatically map the extended node to the custom view model.

With this architecture, you can distribute an Umbraco package with some basic doc types and basic services, and the developers should be able to add new functionalities by extending the base classes/interfaces and register the new classes in the autofac.config

Example of Controller using the custom service and view :

public class DocTypeNameController : RenderMvcController
{
	private readonly IMyService _myService;

    public UEventListController(IMyService theService) // "theService" is injected automatically by Autofac with the class specified in the autofac.config
    {
         _myService = theService;
    }

    public override ActionResult Index(RenderModel model)
    {
		// we get the view model configured in the autofac.config. You can use an interface or a class if some properties are mandatory.
        IMyViewModelClass viewModel = DependencyResolver.Current.GetService<IMyViewModel>();            
        
		// we will speak later about how to do the Map method that will get all properties and init the view model
		IMyViewModelClass mappedViewModel = eventListService.Map(viewModel, model.Content);		
		
        return CurrentTemplate(myAutoMappedViewModel);        
    }	   
}   

5) Property mapping

The last step of our "architecture package" is to map the Umbraco properties (from the doc type) to our view model.

A first genuine way to implement the "Map" method would be:

public void Map(MyViewModelClass viewmodel, IPublishedContent node)  
{	
	viewmodel.Title = node.GetPropertyValue<string>("title");
	etc...	
}


If a developer using your package need to add a new document type property, (like "image"), he will have to :
1) Create an extended ViewModel with a string ImagePath property
2) Create an extended Service that get the "image" property from umbraco and put the image url into the ImagePath of the view model
3) Update the autofac.config to use these classes.

This works ok but for uEvent we wanted to simplify the process and have a Service that can automap the Umbraco properties to the View model in a flexible way.

There are plenty of great frameworks available in Umbraco to solve this problem, like :
 - https://github.com/zpqrtbnk/Zbu.ModelsBuilder/wiki/Zbu.ModelsBuilder
 - https://github.com/leekelleher/umbraco-ditto
 - https://github.com/AndyButland/UmbracoMapper
 - many more...

But we didn't want to add an extra dependency to uEvent and have more flexibility so we rewrite our own based on .NET reflection.

Our implementation of automapping is doing the following algorithm:

For each property of the view model do :

  • Step 1 : try to find a method in the service that has the exact same name of the property and use it to init the view model property. => this allows great flexibility for custom mapping
  • Step 3 : finally if we still can't map the property, we try to map with the IPublishedContent native properties like Name, Url, CreateDate etc...


And we have developped the same mapper for the Archetype (but we use GetValue instead of GetPropertyValue for Step 2) as the "signup options" of each events are based on the great Archetype.

I won't go further in the implementation on our automapping, if you want to know more about it, please contact Mads (mpe@novicell.dk) or me (flo@novicell.dk).
You can also get a extended version of our automapping that Mads Pedersen released on Github here : https://github.com/kalabakas1/UmbracoObjectMapping/tree/master/MyUmbraco7

Bonus:

If you are interested in strongly type view in MVC with hijacked route controller, I would recommend you to read :
- http://web-matters.blogspot.dk/2014/06/umbraco-mvc-and-strongly-typed-view.html
- http://our.umbraco.org/projects/developer-tools/hybrid-framework-for-umbraco-v7

If you have any questions or improvements, please write them in the comments.

 

Fabrice Loudet