Umbraco MVC with Castle Windsor
Asked Answered
S

2

5

Does anyone have any example code for getting Umbraco MVC working with the Castle Windsor dependency injection framework? The problem I'm having is getting my surface controllers to use injectable parametised constructors. I know I'm doing something wrong but not sure what.

I have followed the (non-Umbraco) tutorial here - http://docs.castleproject.org/Windsor.Windsor-tutorial-part-four-putting-it-all-together.ashx - which basically means on App_Start I'm running this code:

var container = new WindsorContainer().Install(FromAssembly.This());
var controllerFactory = new MyCustomControllerFactory(container.Kernel);
ControllerBuilder.Current.SetControllerFactory(controllerFactory);

Code for MyCustomControllerFactory is below.

Also, my implementation of IWindsorInstaller contains the following:

container.Register(Classes.FromThisAssembly()
    .BasedOn<SurfaceController>()
    .LifestyleTransient());

The exception I'm getting is 'No component for supporting the service Umbraco.Web.Mvc.RenderMvcController was found', thrown by the GetControllerInstance method below when I call a surface controller with a parametised constructor:

public class TestSurfaceController : SurfaceController
{
    public TestSurfaceController(INameService nameService)
    {
        ....
    }
}

If anyone has some example code which works I'd really appreciate it. I've wired up Ninject with Umbraco before with no trouble, but on this project I'm tied to Castle Windsor and getting nowhere fast! Thanks in advance.

MyCustomControllerFactory.cs:

public class MyCustomControllerFactory : DefaultControllerFactory
{
    private readonly IKernel kernel;

    public FastStartControllerFactory(IKernel kernel)
    {
        this.kernel = kernel;
    }

    public override void ReleaseController(IController controller)
    {
        kernel.ReleaseComponent(controller);
    }

    protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
    {
        if (controllerType == null)
        {
            throw new HttpException(404, string.Format("The controller for path '{0}' could not be found.", requestContext.HttpContext.Request.Path));
        }
        return (IController)kernel.Resolve(controllerType);
    }
}
Safekeeping answered 17/2, 2014 at 17:29 Comment(0)
L
7

I believe your problem is here:

ControllerBuilder.Current.SetControllerFactory(controllerFactory);

This is replacing the controller factory for ALL controllers, including the RenderMVCController, and Castle can't find a matching component for that type.

The trick is to use the FilteredControllerFactoryResolver, which lets Umbraco decide which controller to use based on some criteria that you provide (in this case, whether your container can resolve the controller type). Composition is not as clean as in a straight MVC app (IMHO), but it works.

Here's an (Umbraco 7.x) example of a filtered controller that implements the IFilteredControllerFactory interface:

public class FilteredControllerFactory : ControllerFactory, IFilteredControllerFactory
{
    public bool CanHandle(RequestContext request)
    {
        Type controllerType = GetControllerType(request, request.RouteData.Values["controller"].ToString());
        return ApplicationStartup.Container.Kernel.HasComponent(controllerType);
    }
}

And the corresponding code to set up composition (using ApplicationEventHandler):

public class ApplicationStartup : ApplicationEventHandler
{
    internal static IWindsorContainer Container;

    protected override void ApplicationStarting(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext)
    {
        base.ApplicationStarting(umbracoApplication, applicationContext);

        Container = new WindsorContainer()
            .Install(Configuration.FromAppConfig())
            .Register(Classes.FromThisAssembly().BasedOn<IController>().LifestyleTransient());

        FilteredControllerFactoriesResolver.Current.InsertType<FilteredControllerFactory>(0);
    }
}

This approach should work both for route hijacking and for surface controllers.

Finally, note that if you also want to support injection into API controllers, you'll need to wire this up separately. For example:

GlobalConfiguration.Configuration.Services.Replace(typeof(IHttpControllerActivator), new CompositionRoot(Container.Kernel))

where CompositionRoot is your own Windsor composition root class.

The Gist here may also prove useful.

Loughlin answered 4/3, 2014 at 21:55 Comment(2)
Thank you so much for your time on this. I won't have time to test it for a few days but it all makes perfect sense. I'll be sure to report back once I've tried it.Safekeeping
The object factory thing in your gist complicated a bit, but it worked. Cheers!Eliciaelicit
H
0

I've read Kristopher's answer and I've found it interesting, because I didn't know IFilteredControllerFactory and its use. Thanks for sharing.

Anyway, usually in my projects I have a lot of dll containing each its own controllers, so I prefer to register all the controllers in a more general way:

container.Register(
            Classes
            .FromAssemblyInDirectory(new AssemblyFilter(AssemblyDirectory))
            .BasedOn<IController>()
            .LifestyleTransient());

where

    /// <summary>
    /// Local Directory where are present all the assemblies
    /// </summary>
    static public string AssemblyDirectory
    {
        //Snippet code from: https://gist.github.com/iamkoch/2344638
        get
        {
            var codeBase = Assembly.GetExecutingAssembly().CodeBase;
            var uri = new UriBuilder(codeBase);
            var path = Uri.UnescapeDataString(uri.Path);
            return Path.GetDirectoryName(path);
        }
    }

In this way also the Umbraco's RenderMVCController will be mapped and correctly resolved.

Recently I wrote a couple of articles about DI in a Umbraco app:

Hope it can help

Heyde answered 7/3, 2015 at 14:57 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.