My solution for a fully testable Umbraco project

Umbraco has had support for MVC for a while now, and as a community, we are making it better every day. I want to put in my 2 pence here and try to get us a step closer.

One of the issues I have been looking into, and one that others have struggled with also, is the issue with unit testing SurfaceControllers. So what’s the problem? The UmbracoContext class is very locked down and near-impossible to mock, SurfaceControllers  have it as a dependency and if you fail to provide one, it will try to initialise it’s own using the singleton pattern. Furthermore, if the singleton is used without a proper initialised request, it will blow up whilst you are still setting up your tests and you’ll never get to the Act part of your tests.

Users have had a few solutions, some have managed to use reflection to create uninitialised objects to pass into the SurfaceController, and some dev’s at UmbracoHQ have provided a test dll which provides a few base classes that will create a mock UmbracoContext as well as a few other items. Now as much as it all helps, I’m a little bit of a perfectionist and I didn’t really like these solutions; so I went off and made my own and I want to share my experiences with you.

When I started, my goal was simply to allow users, and my team at work in particular, to create controllers that they could test with ease, defining their own dependencies and only mocking objects that are needed for the test in hand. I have to give credit to my amazing team who helped to research and provide valuable feedback during their downtime to help achieve this goal. I had tried to send in a pull request for making the SurfaceController testable, but my workaround was not ideal; it was a breaking change and did not fit into the overall goals of the Umbraco team. At the same time, the SurfaceController was just not suitable for me, so my solution involves a little set up to enable normal MVC Controllers to replace them.

This post may be a little long, so I will provide a little summary of titles here:

  • Dependency injection was a key assumption
  • Hijacking Umbraco Routes with normal controllers
  • RenderModel in all it’s glory
  • Enabling child actions from controllers without extending SurfaceController
  • Handling forms without SurfaceControllers
  • Making routes behave like ninjas
  • All the features of the SurfaceController brought back
  • ContentController and DocumentController
  • Objects to be aware of when writing testable code and avoiding the UmbracoContext.

Dependency injection was a key assumption

Dependency injection helps not only in unit testing, but also the readability, maintainability and quality of your classes. It is a core part of how we work and it is something that Umbraco tried hard to support. However, they also tried to support users who did not know anything about dependency injection, IoC containers, or the Dependency Resolver and they wanted to enable those users to access to all of their services and helpers.

I am not mad that the SurfaceController has a dependency of UmbracoContext, but I am upset that there are no alternative, less featured base classes to use out of the box. My solution avoids UmbracoContext like the plague, but by doing so, it loses access to a lot of those services and helpers. This is why dependency injection is vital and just a little sweeter in the solution below; now it is up to you, the developer, to decide exactly what services you require, and for those services that are still getting some love and attention, you can wrap them in testable objects before sending them into your controller.

Hijacking Umbraco Routes with normal controllers

First of all, I would like to point your attention to the Umbraco Documentation about route hijacking. If you have read this page, you have learnt that Umbraco provides a cool feature called Route Hijacking. Now here is something that we discovered: If you prefer not to extend RenderMvcController, you don’t have to. But you do need to implement IRenderMvcController. (We only checked version 6.3 but we know that some older versions did not allow this)

public class TextpageController : Controller, IRenderMvcController
{
    public TextpageController()
    {
        ActionInvoker = new RenderActionInvoker();
    }

    public ActionResult Index(RenderModel model)
    {
        return View(ControllerContext.RouteData.Values["action"].ToString(), model);
    }
}

Above is my controller that will kick in the moment a page is loaded with the Textpage document type. There are a couple of things to note about it; firstly, the constructor has a line setting the ActionInvoker property to the RenderActionInvoker. This is a neccesary step required if you want your Index method to be called when no template specific action is found (see #Routingviatemplate). The good news, is that the RenderActionInvoker has no dependencies and will be irrelevant when testing so I did not see a problem with instantiating it directly in the controllers constructor.

The second thing to note, is the Index method itself; the index method is required. It was defined in the IRenderMvcController interface and must be implemented. Looking at the actual RenderMvcController base class, their logic for choosing and rendering a layout file is a little more complex than mine, but what I am doing is very similar: theirs will check to see if a matching layout file exists and if not, it will return a blank response, but mine will allow the MVC framework to do the checking and if not present, the framework will throw an exception as is expected from a normal MVC project.

RenderModel in all it’s glory

RenderModel is an object that Umbraco can provide to any top level action. Umbraco will not provide it to child actions, but you can pass it in from the parent view. You have to be careful though; having a parameter on a child-action that asks for a RenderModel will be handled by the RenderModelBinder and you will recieve a null object even though you had provided a value. I was able to work around this by making the type object in the method parameters and then re-casting inside my method (not very nice, or ideal, but you won’t always need it so we should be good).

With a RenderModel, we have access to the current published page and the culture of our request. RenderModel has 2 constructors, and we have to be careful if we want to create our own:

  • The first constructor will ask simply for a published item.
    • This is where it will try to figure out the page’s current culture automatically. Unfortunately, this requires invoking the all powerful and mighty UmbracoContext.
  • The second constructor will allow us to pass in a published item and culture.
    • If you use this, UmbracoContext will never get involved and we won’t have problems when unit testing.

Enabling child actions from controllers without extending SurfaceController

Enabling child actions without the SurfaceController was easy. The hard part was the handling forms which I have mentioned below. I will write about my initial investigations here, but what I talk about is only a piece of the pie required to get normal controllers working.

Umbraco automatically routes all SurfaceControllers found in your project. But if you want your own controller to work, you will need to route your own controller. It is as simple as adding a default route in your global.asax.

RouteTable.Routes.MapRoute(
    name: "defaultMvcRoute",
    url: "{controller}/{action}", //I omitted the optional Id part because it is rarely used in practice.
);

Doing this will also allow normal MVC forms to work also; so you should be aware that doing this allows users to type matching urls into the browser and instead of receiving a 404 page as they would normally get, they will recieve a yellow screen of death (until you set up your custom errors) with information about how your action can only be run as a child action and a 500 error response. If you did not put a ChildActionOnlyAttribute on your action, then I guess you wanted users to access this action directly. If this is the case, you should know that Umbraco will not be able to match the URL to any document in the CMS and the UmbracoContext will not know what the current page is. Therefore I recommend that you use this technique carefully and only do non-Umbraco specific actions here and you finish with a redirect rather than trying to render a layout designed for Umbraco.

I mentioned that adding this route would change what was a 404 to a YSOD or a 500 page, well I have a nice workaround for this, and it is something that you could apply not only to Umbraco sites, but also normal MVC projects. Read more about it in the Making routes behave like ninjas section. Trust me, it’s pretty cool.

Handling forms without SurfaceControllers

This is where it gets a little more involved. When creating forms, you should only use Html.BeginUmbracoForm in your views, unless you have that default route and want to post to an action which has no Umbraco specific actions. I think it is a safe to say, that you should almost always use the UmbracoForm and not the MVC form.

Because of how umbraco handles these forms, our routing has to be modified from above. Ignore the above code snippet since what we will do here will be enough to make child actions work as well as forms. When submitting an Umbraco form, the url that you post to will still be the url for a document. Therefore, Umbraco’s RenderMvcHandler will handle the request. It will at this point sniff the request to see if it was a form post and if it was, it will try to re-route to the correct action.

The only niche here, is how it tries to re-route. I think one of the assumptions that they made was that only SurfaceControllers will be found here, and as I mentioned above, my plan is to avoid it completely. Umbraco will try to find another matching route and let it take over, but it will do it by searching for a route with a default controller set as the controller you were asking for in the Umbraco form.

If the above explanation does not make sense, I recommend looking at the source yourself, or if you ask me what you didn’t understand in the comments, I can try to re-phrase and clear up your questions. For now just keep in mind, the new way to route the request will be as follows:

foreach (var controllerType in assembly.GetTypes().Where(t => TypeExtensions.Inherits<ControllerBase>(t)))
{
    var controllerName = controllerType.Name;
    if (!controllerName.EndsWith("Controller", StringComparison.Ordinal))
        continue;

    controllerName = controllerName.Substring(0, controllerName.Length - "Controller".Length);

    var surfaceRouteHandler = RouteTable.Routes.OfType<Route>()
        .First(r =>
            r.DataTokens != null &&
            r.DataTokens["umbraco"].ToString() == "surface"
        )
        .RouteHandler;

    RouteTable.Routes.MapRoute(
        name: "thesearenotthedroidsyouarelookingfor_" + controllerName,
        url: "thesearenotthe/" + controllerName + "/or/{action}/youarelookingfor",
        defaults: new {controller = controllerName},
        constraints: new
            {
                nonMatching = new HiddenRouteConstraint()
            }
        ).RouteHandler = surfaceRouteHandler;
}

There are a few things going on here, so I’ll try to explain it best I can.

  1. The first thing I do, is to find all classes that extend ControllerBase within a given assembly.
  2. I will ignore any controller that does not end in the word Controller and then I will trim that word off so that I get the real controller name.
  3. Next, I will find the first route that Umbraco has made for a surface controller and I pull the RouteHandler that was used for it for use later on.
  4. For each controller found, I will create a unique route with its own default controller set.
  5. There is also a HiddenRouteConstraint which is something I will get into in the next section.
  6. Oh and, Star Wars 😉

The SurfaceRouteHandler

I have explained why it is necessary to create unique routes for each controller, but I have not explained why I set the RouteHandler. Surface controller should always use the SurfaceRouteHandler, and currently, the reason for this is purely for the UmbracoPageResult ActionResult in Umbraco and I will explain this in more depth in the All the features of the SurfaceController brought back section. In order for that result to work properly, this handler should be used.

Unfortunately, this handler is internal and so we cannot create our own instance of the class, but we don’t need to. So long as we have one SurfaceController in our codebase, we will be able to pull the handler from that route and re-use it.

Making routes behave like ninjas

So here where I share a cool little code snippet I made and it is something that I feel could be used in the Umbraco codebase as well as in any other MVC project. The idea here is to put a constraint onto any route so that the route will not work when being matched with the request url.

This is useful for making routes that only match when executing child actions, and in the case of Umbraco, when matching routes based on CMS content and post data. When you try to go to a URL that matches this route, this route will be ignored and the MVC framework will continue to search for another matching route. What is great about this, is that where you would normally get unpredictable behaviour or last minute exceptions explaining that a matching route was found, but later it was discovered that it was a false positive, you will instead get a 404 page. Completely hiding the fact that this route even exists from a potential hacker; the more you can hide from a hacker, the better.

Take for example this url: YOURSITE.COM/umbraco/RenderMvc/action/id, type that into your umbraco site and you will find a null reference exception explaining that this route cannot be used directly. Using my constraint could hide this and you would instead get a 404 page, whilst all of the normal Umbraco functionality will still work correctly.

So, enough about the features, here is the code snippet:

/// <summary>
/// Ensures that the route using this constraint cannot be matched by the</pre>
/// request Url.
///
/// This is useful if you like having very specific routes set up, but you
/// need that generic route just for child actions.
/// </summary>
public class HiddenRouteConstraint : IRouteConstraint
{
    public bool IsRecursing(HttpContextBase httpContext)
    {
        var inUse = httpContext.Items["NonMatchingRoute"] as bool?;
        return inUse != null;
    }

    public void EndRecursionCheck(HttpContextBase httpContext)
    {
        httpContext.Items["NonMatchingRoute"] = null;
    }

    public void BeginRecursionCheck(HttpContextBase httpContext)
    {
        httpContext.Items["NonMatchingRoute"] = (bool?)true;
    }

    public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
    {
        if (IsRecursing(httpContext))
            return true; // pretend I am not here.

        try
        {
            BeginRecursionCheck(httpContext);

            var routeData = route.GetRouteData(httpContext);
            return
                routeData == null ||
                routeData.Values["controller"] == null ||
                routeData.Values["action"] == null ||
                routeData.Values["controller"].ToString() != values["controller"].ToString() ||
                routeData.Values["action"].ToString() != values["action"].ToString();
        }
        finally
        {
            EndRecursionCheck(httpContext);
        }
    }
}

I will try to put up a new blog post in the future with this bit of code in Github, and a nuget package so that you can easily include it in your projects without copying code or having to re-compile it every time.

All the features of the SurfaceController brought back

There are a few features that you should be aware of which were available in the SurfaceController but are not available anymore. The features are as follows:

CurrentPage property

The SurfaceController will provide a property called CurrentPage which will provide you an instance of IPublishedContent representing the user requested document in the CMS. If you want this in your new controllers, there are a number of ways to get it: you could pass it in to your action if you are simply rendering a child action, you could make your own UmbracoContext like object that implements an interface which will be bound in an IOC container in the Global.asax and can provide this value by reading the UmbracoContext, infact, the number of possibilities are limitless and it’s up to you to chose the best way. For us, we will try to wrap the ContextualPublishedContentCache in a testable class and use that. At least until the Umbraco team have provided another way to access published content via an interface.

RedirectToUmbracoPage and RedirectToCurrentUmbracoPage

You could try to return RedirectToUmbracoPageResult, but you will undoubtedly find that it’s dependency on the UmbracoContext will be it’s downfall when testing. Instead, you could use the Redirect method and pass in the Url property from the IPublishedContent object that you would have somewhere lying about. For the CurrentUmbracoPage, just ensure that you have the above part covered, and you’re done.

CurrentUmbracoPage

Simply:

 return new UmbracoPageResult(); 

As mentioned earlier, for this to work, we had to do some clever binding in our global.asax. Also, you can only use this method if the action is handling a post from an Umbraco form. This helpful result will allow you to re-render the current page after doing your form handling without redirecting back.

MergeModelStateToChildActionAttribute

Normally, if you have a form in an MVC project, you will likely be rendering forms in your main views rather than from child actions. In Umbraco, you are less likely to do this, but you will realise that the model state is not shared between your main actions and your child actions. Therefore, things like form validation may not be working out of the box. Umbraco has fixed this issue by using the MergeModelStateToChildActionAttribute on their Surface controller. Therefore, keep in mind, that you will need to do the same.

ContentController and DocumentController

In light of all the workarounds needed, I propose that we create 2 new base controllers: DocumentController and ContentController. I am unsure why the name SurfaceController was chosen, but I do remember that a fair bit of thought was put into it. I however want to suggest that DocumentController is the base controller used for Hijacking umbraco routes. It will implement IRenderMvcController, and will have a base constructor that sets the ActionInvoker. It will leave the index method to you by making the method abstract.

ContentController is responsible for preparing content as well as other bits of data for the views. Basically the SurfaceController. It will have the attribute pre-applied and that is all. In order to provide the other features such as knowing the current page or redirecting to the current page, you would have to enforce some sort of dependency and I feel that this should not be the case for base Controllers.

Objects to be aware of when writing testable code and avoiding the UmbracoContext

Phew!, what a post, but we’re finally near the end. I am going to end off just by re-capping some of the points I raised earlier so that you can be aware of which objects I found to rely on the UmbracoContext:

  • RedirectToUmbracoPageResult
    • Although it seems innocent, it is just impossible to use this result without affecting testability. If the initialisation of the UmbracoContext was to be done lazily rather than in the constructor, then we could test our actions properly. After all, most of the time, all we would need to do to test our controllers would be to check that the correct ActionResult was returned.
  • RenderModel
    • This model type is unavoidable. Fortunately, there is a constructor we can use which will not blow up and we can use it in our tests when sending in a mock render model. Now it is possible for developers to create their own RenderModel’s to pass onto their views. In this case, we have to be careful to use that same constructor otherwise our method will become untestable.
  • RenderMvcController
    • The RenderMvcController is not actually dependant on the UmbracoContext, but since it extends UmbracoController, it has to pass one on to that class. This is a shame, and I have not yet seen any reasons why the UmbracoController type is neccessary.
  • SurfaceController and PluginController
    • Both of these types are dependant on the UmbracoContext and are the reason why I even started this investigation in the first place. Until the SurfaceController is properly testable and there are some clear guidelines on how to work with them properly in a testable way, I will be avoiding these types.

Let me know what your thoughts are in the comments below, because I would love to hear feedback and I love to be challenged so if you can convince me to change my thoughts about any of these types, or you can provide better alternatives, fixes and workarounds, I want to hear them.

Advertisements

4 thoughts on “My solution for a fully testable Umbraco project

  1. Brilliant, thank you! We’re going through the exact same process. It would have taken me a long time to find out that 2nd overload of RenderModel avoided UmbracoContext.

  2. Thanks for the post, really helpful. I’ve found that you do need to register the above routes in the OnApplicationStarted method, so that you have the Umbraco ones already in the Route table to grab the SurfaceRouteHandler. We were previously registering our routes first in the OnApplicationStarting method.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s