Integrating an application into Umbraco (Using Ninject)
Heads Up!
This article is several years old now, and much has happened since then, so please keep that in mind while reading it.
But I think an important one - at Moriyama we spend a lot of time consulting with people about how to add content management to their .NET applications (with Umbraco). Unfortunately a fair few of these have given it a go already and frankly made a really bad job of it.
In this article - I'm going to spout some theory about how you may approach integrating code into Umbraco - along with a practical albeit very trivial example.
We'll touch the surface of dependency injection using Ninject and provide food for thought about how you could go full TDD.
Who is this article for?
Sorry - techies only, with some experience of OO applications and a working knowledge of Umbraco and MVC. It'll sound a bit crazy otherwise!
This isn't a starting out with Umbraco article - it is for those who've mastered simple Umbraco websites and want to complement that ability with a nicely separated application integration.
And all of my opinions are my own - as seems fit to state in blogs nowadays. You don't have to agree with me!
Umbraco isn't a relational database
In case this article turns into a classic TL;DR for you - I'll make the most important point up front so that you can go about your day.
If you are approaching an integration of an application into Umbraco by trying to map Umbraco document types to your existing application tables - you are setting yourself up for a fall. Step away from the keyboard - take a cold shower and come back and read the rest of the article.
I'd make an exception to the previous statement for trivial apps that maybe have a couple of dozen rows/records.
To perhaps quantify a little better - Umbraco isn't for:
- Storing thousands of holidays from external data feeds - and importing these feeds on a nightly basis.
- Storing thousands of posts for a high used discussion forum.
The majority of Umbraco fails that we see attempt to do similar things to the above, or even worse - joining your existing application tables onto the umbracoNode table.
Although you can use LINQ over the Umbraco cache to join nodes and map them into some kind of entities - I'd advise you don't. Don't try and use Examine as a relational database either!
If you are some kind of .NET guru you may achieve great results - but for maintainability and sanity - I'd suggest you stay away. I've literally lhad nightmares of Examine queries, joined in memory onto Umbraco content and then filtered using LINQ.
So what should I do?
I've been quick to embark on a mini rant with lots of advice on what not to do, but how should one approach the integration of an application into Umbraco?
The most important thing to do is to realise the difference between:
- Content
- Data
- User generated content
My rule of thumb is that only data edited by a CMS editor belongs in Umbraco - and everything else doesn't. User generated content (blog comments etc) is always a wooly area - but for the sake brevity I'm just saying that it doesn't belong in Umbraco.
So once the separation above is made - one can hopefully go away and forget that the CMS exists just now, and design our application and the interfaces between it and Umbraco.
Can I see some code now?
Just a second. If you haven't already please buy the following books and read them (then we can continue):
If you've wanted to build complex OO applications and struggled - the first book will change your life (I'm not on commission).
Let's go!
You may already have an application that you want to move to Umbraco in mind - and you may need to apply some heavy refactoring to make use of the patterns that I'm going to try and demonstrate.
I'm going to invent an example application to integrate - and it is going to be very trivial. It isn't going to do any CRUD - it is just going to list out some data.
I want some Rock in my Umbraco pages
Specifically I'd like to set and pass a document type property from Umbraco to my application that retrieves the name of a rock track and displays it on the page (why? this is the example that came to mind at 3 in the morning).
I think this is a fairly common pattern - use a CMS editor UI to control application content.
Editor note: While reviewing what I wrote the next day I think that using Product Type to display a specific type of product on a page is a much better example - but too late to rewrite it all!
It is too early to think about the Umbraco part yet:
namespace Moriyama.App.Interfaces
{
public enum RockType
{
Soft = 0,
Hard = 1,
Glam = 2
}
public interface IRockService
{
string GetRock(RockType type);
}
}
Like any good developer we start by defining an interface.
Here is an incredibly simple implementation that we could later refactor - to include the whole hall of fame.
using Moriyama.App.Interfaces;
namespace Moriyama.App.Services
{
public class SimpleRockService : IRockService
{
public string GetRock(RockType type)
{
var track = string.Empty;
switch (type)
{
case RockType.Soft:
track = "Boston - More Than a Feeling";
break;
case RockType.Glam:
track = "T-Rex - Hot Love";
break;
case RockType.Hard:
track = "Led Zeppelin – Whole Lotta Love";
break;
}
return track;
}
}
}
So that is my application - that is all I am going to integrate. A quick note on how things are structured.
Do keep your application in a separate project from your Umbraco application (as above). This helps enforce a separation of concerns, it makes it easy to make sure that your application doesn't depend upon Umbraco.
We've even got a test for my SimpleRockService - which obviously I wrote before the implementation.
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moriyama.App.Interfaces;
using Moriyama.App.Services;
namespace Moriyama.Tests.Services
{
[TestClass]
public class SimpleRockServiceTests
{
[TestMethod]
public void DoesReturnCorrectRockTrack()
{
var simpleRockService = new SimpleRockService();
Assert.AreEqual(simpleRockService.GetRock(RockType.Soft), "Boston - More Than a Feeling");
Assert.AreEqual(simpleRockService.GetRock(RockType.Glam), "T-Rex - Hot Love");
Assert.AreEqual(simpleRockService.GetRock(RockType.Hard), "Led Zeppelin – Whole Lotta Love");
}
}
}
Time to integrate
Before we move on just a re-cap on what I am trying to emphasise:
- Sit down with a pen and paper before you start coding, work out what is content and what is data
- Design interfaces to handle any CRUD operations that you need
- Sketch out these interfaces in Visual Studio
- Keep them in a separate project to avoid dependencies on Umbraco
- Ideally write some tests (but that is another topic altogether).
To allow our editor to choose the type of Rock track that will be displayed on the page we'll create a simple Property Editor called "Rock Type" with options Soft, Hard and Glam.
Next we'll add the Property Editor to our Homepage Document Type as a document type property to allow the editor to select a Rock Type:
Call a plumber!
Our content editor can specify a type of Rock to display in a page and our application can provide it - but how do we glue it together?
There are a number of ways that you could call our IRockService - and I'm just going to place a call to an Umbraco Surface Controller child action on my homepage template.
Before I do though it is worth mentioning that one could use a Partial View Macro if you'd like the editor to place the Rock track within a rich text area or Umbraco Grid.
Show me the template already
@inherits Umbraco.Web.Mvc.UmbracoTemplatePage
@{
Layout = "Master.cshtml";
}
@Html.Action("Index","Rock")
The Html.Action directive calls the Index action of the following RockController:
using System;
using System.Web.Mvc;
using Moriyama.App.Interfaces;
using Moriyama.Models;
using Umbraco.Web;
using Umbraco.Web.Mvc;
namespace Moriyama.Controllers
{
public class RockController : SurfaceController
{
private readonly IRockService _rockService;
public RockController()
{
}
public RockController(IRockService rockService)
{
_rockService = rockService;
}
public ActionResult Index()
{
var content = Umbraco.TypedContent(UmbracoContext.PageId);
RockType rockType;
Enum.TryParse(content.GetPropertyValue<string>("PageRockType"), out rockType);
var track = _rockService.GetRock(rockType);
var model = new RockViewModel {Track = track};
return View(model);
}
}
}
I'll shorten the snippet above to the important part that does the following:
- Get the stored Rock Type (string) from Umbraco
- Convert it to the Enum required by our service
- Call the service to get the rock track
public ActionResult Index()
{
var content = Umbraco.TypedContent(UmbracoContext.PageId);
RockType rockType;
Enum.TryParse(content.GetPropertyValue<string>("PageRockType"), out rockType);
var track = _rockService.GetRock(rockType);
.....
Next we simply instantiate a Model class and pass it to a partial view that displays the result as follows:
var model = new RockViewModel {Track = track};
return View(model);
The Model and the partial View are displayed below for completeness but as you'd expect they are pretty trivial.
namespace Moriyama.Models
{
public class RockViewModel
{
public string Track { get; set; }
}
}
@model Moriyama.Models.RockViewModel
<p>
Why don't you go a and put @Model.Track on your stereo.
</p>
A couple of points
The section above is all about passing Umbraco data to your service to get data from your application onto an Umbraco page.
What I am trying to emphasise here:
- Use your controller to turn Umbraco content into something the your service expects
- Turn the result into a viewModel to pass back to the presentation layer (partial view)
The two points above are significant - as our application shouldn't depend upon Umbraco - Umbraco shouldn't depend on our application - specifically we shouldn't pass the return value of our service directly to a view, it should be mapped to a Model.
If you are thinking why? then go and find your closest MVC hipster bar.
In the screen shot below you can see that the Controller, (View)Model and the Partial View all belong within our Umbraco solution - but don't depend upon our application in any way.
We're Done
So there we have it - we have a nice pattern for separating our external application into another visual studio project.
We can define services that are testable and integrate them into Umbraco - or elsewhere for that matter.
But you mentioned Ninject!
Busted - OK. So you are sharp and notice that my RockController would blow up because I haven't passed an IRockService into the constructor...
But before we delve into the dark world of dependency injection - you could just instantiate it in your constructor:
namespace Moriyama.Controllers
{
public class RockController : SurfaceController
{
private readonly IRockService _rockService;
public RockController()
{
_rockService = new SimpleRockService();
}
Now I'm no code purist - and the above would work just fine for our simple application. It does have some issues though:
- If I want to swap out my implementation of IRockService i need to go and find everywhere it is used and replace it.
- If a new implementation of IRockService depended on other services I'd have to change my Controller everywhere IRockServive is used.
Yes - it is the same reason twice, because it is really important.
Ninject?
Ninject is a dependency injection Framework for .NET (there are dozens of alternatives).
If I tell Ninject about my implementation of IRockService then it will create and provide it to my controller when it is created (dependency injection):
public class RockController : SurfaceController
{
private readonly IRockService _rockService;
public RockController(IRockService rockService)
{
_rockService = rockService
}
Ninject can do much more than this simple instantiation of objects - but we'll focus on construcor injection into controllers for now.
Getting Setup
I'm working with Umbraco 7 - and be aware that there will be nuances in getting set up on older versions of Umbraco.
Start by installing the nuget package Ninject.MVC5 in your Umbraco project.
All being well you'll get a new class added to an App_Start folder called NinjectWebCommon.
This class will automatically register Ninject at application startup. The class has an empty method where we can tell Ninject about our IRockService.
private static void RegisterServices(IKernel kernel)
{
kernel.Bind<IRockService>().To<SimpleRockService>();
}
That is all there is to do. If we run our application now - Ninject knows that for an IRockService it should use a SimpleRockService, and it will create and pass one to any controller that requires it.
Just one more thing
If you want to use Ninject to inject Umbraco API controllers - there is a little trick. I can't claim any credit for this trick it comes courtesy of the following post on the our.umbraco forum.
In application startup call the following code:
GlobalConfiguration.Configuration.Services.Replace(typeof(IHttpControllerActivator), new UmbracoWebApiHttpControllerActivator(kernel));
The implementation of UmbracoWebApiHttpControllerActivator is provided in the post - along with a discussion of why it is needed.
Further Reading
My colleague Gary has gone a step further than I have here and worked out how to mock out some important Umbraco objects so that you can test your Umbraco surface controllers, see: https://github.com/garydevenay/garydevenay.github.io/wiki/Unit-Testing-Umbraco
People may be interested in mapping more complex domain/entity objects to ViewModels using AutoMapper - this is a useful read: http://bengtbe.com/blog/2009/04/14/using-automapper-to-map-view-models-in-asp-net-mvc/
Thanks
I've been Darren - and if you'd like to get in touch about the article please comment below or drop me a tweet. I'm happy to provide the full solution for this example - just ask.
Darren Ferguson
Darren is on Twitter as @darrenferguson