Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Castle Windsor - Injecting IActionInvoker Implementation Issue

I am trying to use the approach from this article, but I am missing something - I am currently getting an error within WindsorControllerFactory.GetControllerInstance when it tries to resolve IActionInvoker since WindsorActionInvoker has a dependency on IWindsorContainer.

Given that WindsorControllerFactory already has a reference to IWindsorContainer, could I pass that reference in? If so - how? The only examples I have found are about passing value types as constructor parameters, not reference types.

I'm feeling I'm missing something obvious...

Current setup as follows: Within Global.asax Application_Start I call the following method:

 protected virtual IWindsorContainer InitializeServiceLocator()
    {
        IWindsorContainer container = new WindsorContainer();
        ControllerBuilder.Current.SetControllerFactory(new WindsorControllerFactory(container));

        container.RegisterControllers(typeof(HomeController).Assembly);
        ComponentRegistrar.AddComponentsTo(container);

        return container;
    }

ComponentRegistrar:

 public static void AddComponentsTo(IWindsorContainer container) 
 {
      //add other components.....

      container.AddComponentLifeStyle<IActionInvoker, WindsorActionInvoker>(LifestyleType.PerWebRequest);

 }

WindsorActionInvoker:

public class WindsorActionInvoker : ControllerActionInvoker, IActionInvoker
{
    readonly IWindsorContainer container;

    public WindsorActionInvoker(IWindsorContainer container)
    {
        this.container = container;
    }

    protected override ActionExecutedContext InvokeActionMethodWithFilters(
            ControllerContext controllerContext,
            IList<IActionFilter> filters,
            ActionDescriptor actionDescriptor,
            IDictionary<string, object> parameters)
    {
        foreach (IActionFilter actionFilter in filters)
        {
            container.Kernel.InjectProperties(actionFilter);
        }
        return base.InvokeActionMethodWithFilters(controllerContext, filters, actionDescriptor, parameters);
    }
}

WindsorControllerFactory:

 public class WindsorControllerFactory : DefaultControllerFactory
{
    private readonly IWindsorContainer container;

    public WindsorControllerFactory(IWindsorContainer container)
    {
        if (container == null)
        {
            throw new ArgumentNullException("container");
        }
        this.container = container;
    }

    public override void ReleaseController(IController controller)
    {
        var disposable = controller as IDisposable;

        if (disposable != null)
        {
            disposable.Dispose();
        }

        this.container.Release(controller);
    }

    protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
    {
        if (controllerType == null)
        {
            return base.GetControllerInstance(requestContext, controllerType);
        }

        var controller = this.container.Resolve(controllerType) as Controller;

        if (controller != null)
        {
            controller.ActionInvoker = this.container.Resolve<IActionInvoker>(this.container);
        }

        return controller;
    }
}

Update

I missed a subtlety: I was trying to use this behaviour for the following:

public class CustomAuthorize : AuthorizeAttribute {...}

which doesn't implement IActionFilter.

Added the following to WindsorActionInvoker:

 protected override AuthorizationContext InvokeAuthorizationFilters(ControllerContext controllerContext, IList<IAuthorizationFilter> filters, ActionDescriptor actionDescriptor)
    {
        foreach (IAuthorizationFilter authorizeFilter in filters)
        {
            this.kernel.InjectProperties(authorizeFilter);
        }
        return base.InvokeAuthorizationFilters(controllerContext, filters, actionDescriptor);
    }

This now works as required. Thanks to Cristiano since it was analysis of his kindly provided code which put me on the right path.

like image 863
BonyT Avatar asked Feb 24 '23 15:02

BonyT


1 Answers

Global.asax

private static void bootstrapContainer()
{
    container = new WindsorContainer()
        .Install(FromAssembly.This());
    var controllerFactory = new WindsorControllerFactory(container.Kernel);

    ControllerBuilder.Current.SetControllerFactory(controllerFactory);
}

Installer / filling container

public class ControllersInstaller : IWindsorInstaller
{
    #region IWindsorInstaller Members

    public void Install(IWindsorContainer container, IConfigurationStore store)
    {
        container.Register(Component.For<WpRegistration.Web.Filters.AgencyAuthorize>().LifeStyle.Transient);
        container.Register(Component.For<IActionInvoker>().ImplementedBy<WindsorExtensions.Mvc.WindsorActionInvoker>().LifeStyle.Transient);

        container.Register(AllTypes.FromThisAssembly()
                            .BasedOn<IController>()
                            .If(Component.IsInSameNamespaceAs<HomeController>())
                            .If(t => t.Name.EndsWith("Controller"))
                            .Configure((c => c.LifeStyle.Transient)));
    }

    #endregion
}

WindsorControllerFactory

using System;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;

using Castle.MicroKernel;

public class WindsorControllerFactory : DefaultControllerFactory
{
    private readonly IKernel 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));
        }

    IController iController = (IController)kernel.Resolve(controllerType);

    // new code    
    if (iController is Controller)    
    {
        ((Controller)iController).ActionInvoker = kernel.Resolve<IActionInvoker>();
    }

    return iController;
    }
}

WindsorActionInvoker

namespace WindsorExtensions.Mvc
{

    public class WindsorActionInvoker : ControllerActionInvoker
    {
        readonly IKernel kernel;
        public WindsorActionInvoker(IKernel kernel) 
        { 
            this.kernel = kernel; 
        }
        protected override ActionExecutedContext InvokeActionMethodWithFilters(
            ControllerContext controllerContext
            , IList<IActionFilter> filters
            , ActionDescriptor actionDescriptor
            , IDictionary<string, object> parameters)
        {
            foreach (IActionFilter actionFilter in filters)
            {
                kernel.InjectProperties(actionFilter);
            }
            return base.InvokeActionMethodWithFilters(controllerContext, filters, actionDescriptor, parameters);
        }
    }

    public static class WindsorExtension 
    { 
        public static void InjectProperties(this IKernel kernel, object target) 
        { 
            var type = target.GetType(); 
            foreach (var property in type.GetProperties(BindingFlags.Public | BindingFlags.Instance)) 
            { 
                if (property.CanWrite && kernel.HasComponent(property.PropertyType)) 
                { 
                    var value = kernel.Resolve(property.PropertyType); 
                    try { property.SetValue(target, value, null); } 
                    catch (Exception ex) 
                    { 
                        var message = string.Format("Error setting property {0} on type {1}, See inner exception for more information.", property.Name, type.FullName);
                        throw new ComponentActivatorException(message, ex); 
                    }
                }
            }
        }
    }
}

AgencyAuthorizeAttribute

namespace WpRegistration.Web.Filters 
{
    [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
    public sealed class AgencyAuthorize : ActionFilterAttribute
    {
        CurrentUserService _currentUserSvc;
        public AgencyAuthorize() { }

        public CurrentUserService Service
        {
            get { return _currentUserSvc; }
            set
            {
                this._currentUserSvc=value;
            }
        }
like image 67
Crixo Avatar answered Apr 08 '23 06:04

Crixo