imdadhusen
imdadhusen

Reputation: 2494

How to use Dependency Injection with a Controller

I have below code which will work without any issue MAUserController.cs

public class MAUserController : ApiController
    {
        ILogService loggerService;
        IMAUserService _service;

        public MAUserController(ILogService loggerService, IMAUserService Service)
        {
            this.loggerService = loggerService;
            this._service = Service;
        }
}

DependencyInstaller.cs

public class DependencyInstaller : IWindsorInstaller
    {
        public void Install(IWindsorContainer container, IConfigurationStore store)
        {
            container.Register(
                Component.For<ILogService>().ImplementedBy<LogService>().LifeStyle.PerWebRequest,
                Component.For<IDatabaseFactory>().ImplementedBy<DatabaseFactory>().LifeStyle.PerWebRequest,
                Component.For<IUnitOfWork>().ImplementedBy<UnitOfWork>().LifeStyle.PerWebRequest,
                AllTypes.FromThisAssembly().BasedOn<IHttpController>().LifestyleTransient(),
                AllTypes.FromAssemblyNamed("ISOS.Health.Service").Where(type => type.Name.EndsWith("Service")).WithServiceAllInterfaces().LifestylePerWebRequest(),
                AllTypes.FromAssemblyNamed("ISOS.Health.Repository").Where(type => type.Name.EndsWith("Repository")).WithServiceAllInterfaces().LifestylePerWebRequest()               
                );
        }
    }

If I am using normal Controller instead ApiController then it gives me an error UserController.cs

public class UserController : Controller
    {
        ILogService loggerService;
        IMAUserService _service;
        public UserController(ILogService loggerService, IMAUserService Service)
        {
            this.loggerService = loggerService;
            this._service = Service;
        }
}

This will give an error:

No parameterless constructor defined for this object

I am using CastleDI Windsor for Dependency injection.

Do I need to do anything or register something?

Upvotes: 4

Views: 4209

Answers (3)

Guillermo Guti&#233;rrez
Guillermo Guti&#233;rrez

Reputation: 17859

FIRST APPROACH

Advice: Use with caution, because it may cause memory leaks for Castle Windsor.

You have to create a controller activator, which should implement the IControllerActivator interface, in order to use your DI container to create the controller instances:

public class MyWindsorControllerActivator : IControllerActivator
{
    public MyWindsorControllerActivator(IWindsorContainer container)
    {
        _container = container;
    }

    private IWindsorContainer _container;

    public IController Create(RequestContext requestContext, Type controllerType)
    {
        return _container.Resolve(controllerType) as IController;
    }
}

Then, add this class to your DependencyInstaller:

public class DependencyInstaller : IWindsorInstaller
{
    public void Install(IWindsorContainer container, IConfigurationStore store)
    {
        container.Register(
                // Current code...

                Component.For<IControllerActivator>()
                    .ImplementedBy<MyWindsorControllerActivator>()
                    .DependsOn(Dependency.OnValue("container", container))
                    .LifestyleSingleton();
            );
    }
}

Also, create your own dependency resolver based on the Windsor container:

public class MyWindsorDependencyResolver : IDependencyResolver
{
    public MyWindsorDependencyResolver(IWindsorContainer container)
    {
        _container = container;
    }

    private IWindsorContainer _container;

    public object GetService(Type serviceType)
    {
        return _container.Resolve(serviceType);
    }

    public IEnumerable<object> GetServices(Type serviceType)
    {
        return _container.ResolveAll(serviceType).Cast<object>();
    }
}

Then, finally, register your dependency resolver in the Application_Start method in Global.asax.cs:

DependencyResolver.SetResolver(new MyWindsorDependencyResolver(windsorContainer));

This way, when MVC requires the controller activator through it's dependency resolver, it will get ours, which will use our Windsor container to create the controllers with all it's dependencies.

In order to avoid memory leaks using IControllerActivator, the easiest solution will be to use lifestyles like per thread or per web request, rather than the default (Singleton), transient and pooled, for the registered components. Check this link for more info about how to avoid memory leaks using Castle Windsor Container.

SECOND APPROACH

However, as pointed out by @PhilDegenhardt, a much better and correct approach will be to implement a custom controller factory, in order to be able to release the controller component created by the Castle Windsor DI Container. Here you can find an example (see the section about Dependency Injection).

Taken from that example, the implementation could be:

Global.asax.cs:

public class MvcApplication : System.Web.HttpApplication
{
    private WindsorContainer _windsorContainer;

    protected void Application_Start()
    {
        var _windsorContainer = new WindsorContainer();
        _windsorContainer.Install(
            new DependencyInstaller(),
            // Other installers...
        );

        ControllerBuilder.Current.SetControllerFactory(new WindsorControllerFactory(_windsorContainer.Kernel));
    }

    protected void Application_End()
    {
        if (_windsorContainer != null)
        {
            _windsorContainer.Dispose();
        }
    }
}

WindsorControllerFactory.cs:

public class WindsorControllerFactory : DefaultControllerFactory
{
    private readonly IKernel _kernel;

    public WindsorControllerFactory(IKernel kernel)
    {
        _kernel = kernel;
    }

    public override void ReleaseController(IController controller)
    {
        _kernel.ReleaseComponent(controller);  // The important part: release the component
    }

    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);
    }
}

Upvotes: 5

Ognyan Dimitrov
Ognyan Dimitrov

Reputation: 6273

Do not forget to catch your errors in global.asax.cs!

Registration :

container.Register(Component.For<IControllerFactory>().ImplementedBy<WindsorControllerFactory>());

Implementation of MVC controller factory :

using System;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;
using Castle.MicroKernel;

namespace Installer.Mvc
{
    public class WindsorControllerFactory : DefaultControllerFactory
    {
        private readonly IKernel _kernel;

        public WindsorControllerFactory(IKernel kernel)
        {
            _kernel = kernel;
        }

        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));
            }

            if (_kernel.GetHandler(controllerType) != null)
            {
                return (IController)_kernel.Resolve(controllerType);
            }
            return base.GetControllerInstance(requestContext, controllerType);
        }

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

Upvotes: 0

Fabio
Fabio

Reputation: 11990

Look at the following project link https://github.com/rarous/Castle.Windsor.Web.Mvc

Add this reference via NuGet to your MVC project, it will do the registering job for you.

Upvotes: 2

Related Questions