Creating WindsorViewPageActivator

匆匆过客 提交于 2019-12-25 08:31:56

问题


I was playing with new Asp net mvc 3 RC2. I have created a WindsorViewPageActivator class as follows

public class WindsorViewPageActivator : IViewPageActivator
    {
        object IViewPageActivator.Create(ControllerContext controllerContext, Type type)
        {
            return DependencyResolver.Current.GetService(type);              
        }
    }

and then a WindsorDependencyResolver class

public class WindsorDependencyResolver : IDependencyResolver
{
    private readonly IWindsorContainer container;

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

    #region IDependencyResolver Members

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

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

    public IEnumerable<TService> GetAllInstances<TService>()
    {
        return container.ResolveAll<TService>();
    }

    public TService GetInstance<TService>()
    {
        return (TService)Resolve(typeof(TService));
    }

    #endregion
    private object Resolve(Type serviceType)
    {
        try
        {
            return container.Resolve( serviceType);
        }
        catch (Exception ex)
        {
            return null;
        }
    }
}

}

Now I am doing in Global.asax something like this

container.Register(Component.For<IControllerActivator>().ImplementedBy<WindsorControllerActivator>());
container.Register(Component.For<IViewPageActivator>().ImplementedBy<WindsorViewPageActivator>());
container.Register(Component.For<IControllerFactory>().ImplementedBy<DefaultControllerFactory>());               
DependencyResolver.SetResolver (new WindsorDependencyResolver(container));

' Now I am getting the following error The view found at '~/Views/Account/LogOn.cshtml' was not created. Do I need to register each view page in windsor container If yes then how can I register each view. I am using Razor view engine. Thanks


回答1:


Yes, in order to resolve stuff you need to register it. Have a look at the documentation to familiarise yourself with the API.




回答2:


I have tried this myself, and unfortunately I can't seem to get it to work properly. I do the following in my solution:

public class WindsorViewPageActivator : IViewPageActivator
{
    private readonly IKernel _kernel;

    /// <summary>
    /// Initializes a new instance of the <see cref="WindsorViewPageActivator"/> class.
    /// </summary>
    /// <param name="kernel">The kernel.</param>
    public WindsorViewPageActivator([NotNull] IKernel kernel)
    {
        if (kernel == null) throw new ArgumentNullException("kernel");
        _kernel = kernel;
    }

    public object Create(ControllerContext controllerContext, Type type)
    {
        if (!_kernel.HasComponent(type))
        {
            if (IsSupportedView(type))
            {
                _kernel.Register(Component.For(type).LifestylePerWebRequest());
            }
            else
            {
                return Activator.CreateInstance(type);
            }
        }

        var viewPageInstance = _kernel.Resolve(type);

        return viewPageInstance;
    }

    /// <summary>
    /// Determines whether the specified type is of a supported view type.
    /// </summary>
    /// <param name="viewType">Type of the service.</param>
    /// <returns>
    ///     <c>true</c> if the specified type is of a supported view type; otherwise, <c>false</c>.
    /// </returns>
    private static bool IsSupportedView(Type viewType)
    {
        return viewType.IsAssignableTo<WebViewPage>()
            || viewType.IsAssignableTo<ViewPage>()
            || viewType.IsAssignableTo<ViewMasterPage>()
            || viewType.IsAssignableTo<ViewUserControl>()
            ;
    }
}

This works as long as you don't change anything in your markup. If you do, you'll get a registration error, as the view now will generate a new type, that doesn't exist in the container (but it has the same name!). What I thought of doing, was to aggressively release the view component as soon as it's resolved, but I can't get rid of it in the container for some reason. Not even an explicit call to _kernel.ReleaseComponent(viewPageInstance) would do it (but that's of course just releasing the instance, not the type).

What we really need to do, is make Windsor inject properties into existing instances. That is, use Activator.CreateInstance(type) and then tell Windsor to inject the properties into the instance. But Windsor doesn't support injecting properties into existing instances, so we need to hack something together, that will do that for us. I've seen this one http://www.jeremyskinner.co.uk/2008/11/08/dependency-injection-with-aspnet-mvc-action-filters/ (at the bottom), but that wouldn't perform very well.

My solution was simply to manually set my properties in the viewpage activator (you have a base viewpage type), but maybe there's some better solution?

EDIT

I managed to get it working after all!

My solution is to simply create a custom component activator and mimic what's being done in the MVC framework, like so:

public class ViewPageComponentActivator : DefaultComponentActivator
{
    public ViewPageComponentActivator(ComponentModel model, IKernel kernel, ComponentInstanceDelegate onCreation, ComponentInstanceDelegate onDestruction)
        : base(model, kernel, onCreation, onDestruction)
    {
    }

    protected override object CreateInstance(CreationContext context, ConstructorCandidate constructor, object[] arguments)
    {
        // Do like the MVC framework.
        var instance = Activator.CreateInstance(context.RequestedType);
        return instance;
    }
}

The component activator simply always return a new instance of the view. Because the component is registered as being transient, CreateInstanceis always called. There might be some tweaking possibilities here.

The viewpage activator is now much simpler. Note that the type of service is different whenever you change the view, so we must register the type based on it's unique name (I haven't tweaked this yet, but there might be a nicer way to name the component).

/// <summary>
/// An activator using Castle Kernel for activating views.
/// </summary>
public class WindsorViewPageActivator : IViewPageActivator
{
    private readonly IKernel _kernel;

    /// <summary>
    /// Initializes a new instance of the <see cref="WindsorViewPageActivator"/> class.
    /// </summary>
    /// <param name="kernel">The kernel.</param>
    public WindsorViewPageActivator([NotNull] IKernel kernel)
    {
        if (kernel == null) throw new ArgumentNullException("kernel");
        _kernel = kernel;
    }

    public object Create(ControllerContext controllerContext, Type type)
    {
        if (!_kernel.HasComponent(type.FullName))
        {
            _kernel.Register(Component.For(type).Named(type.FullName).Activator<ViewPageComponentActivator>().LifestyleTransient());
        }
        return _kernel.Resolve(type.FullName, type);
    }
}

I hope this might be of use to someone in similar situations.



来源:https://stackoverflow.com/questions/4433485/creating-windsorviewpageactivator

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!