The project I am using at work currently uses Castle Windsor as its IOC container. My colleague did the initial setup so I decided to do this blog post based on a new MVC project to explain the setup and get more familiar with the steps needed to make MVC and Castle Windsor work together.
First thing, create a new MVC 4 website (which template you use is up to you). After that I updated all the existing NuGet packages and added this one Castle Windsor.
Create a Infrastructure folder at the root of the MVC application. Inside the Infrastructure folder create a WindsorControllerFactory class. This will inherit MVC’s DefaultControllerFactory and override the GetControllerInstance and ReleaseController methods. We will use this class in a later step to tell MVC to use this controller factory instead of its default one.
public class WindsorControllerFactory : DefaultControllerFactory
{
private readonly kernel;
public WindsorControllerFactory(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);
}
}
{
private readonly kernel;
public WindsorControllerFactory(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);
}
}
Next create a folder in the Infrastructure called Installers. This is where all of the installers used by Windsor will go. Our first installer will be a class named ControllersInstaller. This inherits the IWindowsInstaller interface which requires the implementation of the Install method. The code below basically says all classes in the assembly that are based on the IController interface should be created using LifestyleTransient which basically means instantiate an instance every time.
public class ControllersInstaller : IWindsorInstaller
{
public void Install(IWindsorContainer container, IConfigurationStore store)
{
container.Register(Classes.FromThisAssembly()
.BasedOn<IController>()
.LifestyleTransient());
}
}
{
public void Install(IWindsorContainer container, IConfigurationStore store)
{
container.Register(Classes.FromThisAssembly()
.BasedOn<IController>()
.LifestyleTransient());
}
}
The last step needed to get things running is the modifications needed in the global.asax.cs file. There are several things going on the code below. Note the static field used to store the container, the value of the field is set in the static method BootstrapContainer. This method does two things.
- Set the value of the container by instantiating an instance of the WindsorContainer and telling it to Install all the installers in the assembly (basically any class that inherits form the IWindsorInstaller interface).
- Tell MVC to use our WindsorControllerFactory instead of the default MVC controller factory implementation.
The BootstrapContainer method is called on application start after the other MVC registration code. The last change is to dispose the container on application end.
public class MvcApplication : System.Web.HttpApplication
{
private static IWindsorContainer container;
private static void BootstrapContainer()
{
container = new WindsorContainer().Install(.This());
var controllerFactory = new WindsorControllerFactory(container.Kernel);
ControllerBuilder.Current.SetControllerFactory(controllerFactory);
}
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
WebApiConfig.Register(GlobalConfiguration.Configuration);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
MvcApplication.BootstrapContainer();
}
protected void Application_End()
{
container.Dispose();
}
}
{
private static IWindsorContainer container;
private static void BootstrapContainer()
{
container = new WindsorContainer().Install(.This());
var controllerFactory = new WindsorControllerFactory(container.Kernel);
ControllerBuilder.Current.SetControllerFactory(controllerFactory);
}
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
WebApiConfig.Register(GlobalConfiguration.Configuration);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
MvcApplication.BootstrapContainer();
}
protected void Application_End()
{
container.Dispose();
}
}
At this point adding a controller and view should work correctly. All these changes to make MVC work the way it would have before doing all this. So what is the big deal, what if in my Home controller I wanted to do something like this:
private IContactManager contactManager;
public HomeController(IContactManager contactManager)
{
this.contactManager = contactManager;
}
public HomeController(IContactManager contactManager)
{
this.contactManager = contactManager;
}
Using the code we have now this would not work since the container at this point would not know how to resolve IContactManager into its concrete implementation. To get this to work we would need to add a new installer to our installer folder. For example:
public class ManagerInstaller : IWindsorInstaller
{
public void Install(IWindsorContainer container, IConfigurationStore store)
{
container.Register(Classes.FromThisAssembly()
.Where(type => type.Name.EndsWith("Manager"))
.WithServiceDefaultInterfaces()
.Configure(c => c.LifestyleTransient()));
}
}
{
public void Install(IWindsorContainer container, IConfigurationStore store)
{
container.Register(Classes.FromThisAssembly()
.Where(type => type.Name.EndsWith("Manager"))
.WithServiceDefaultInterfaces()
.Configure(c => c.LifestyleTransient()));
}
}
This installer’s install method would get called by the instantiation of the container in the global.asax.cs application start event. This installer says to register all the classes in the assembly that end in Manager and follow the default interface pattern (info) and instantiate them on a per use basis.
So now lets say the CustomerManager had a dependency on the CustomerRepository. This would require a new installer. I prefer to use a separate installer for each convention I am going to use. This way if a specific convention needs to have a different lifestyle (per call, singleton, etc…) it easier to setup. No need to show this installer since it would be just like the one above with a separate lambda in the where clause.
Không có nhận xét nào:
Đăng nhận xét