Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Umbraco MVC with Castle Windsor

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);
    }
}
like image 533
Tom Troughton Avatar asked Feb 17 '14 17:02

Tom Troughton


1 Answers

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.

like image 171
Kristopher Avatar answered Oct 14 '22 17:10

Kristopher