Integrating Castle Windsor with SignalR - how should I approach this?

我是研究僧i 提交于 2019-11-30 09:18:10

August 2016 update

Following from a comment I no longer use the approach below but now use the GlobalHost.DependencyResolver

So in Global.asax.cs I initialise things

public static void Init(IWindsorContainer container)
{
    var conn = configurationManager.ConnectionStrings["SRSQL"].ConnectionString;
    GlobalHost.DependencyResolver.Register(typeof(IHubActivator), 
                                      () => new SignalHubActivator(container));
    GlobalHost.DependencyResolver.Register(typeof(ILoggingService), 
                                      container.Resolve<ILoggingService>);
    //etc or you could just pass your existing container to the resolver
    GlobalHost.DependencyResolver.UseSqlServer(conn);    
}

and then in the hub

private ILoggingService LoggingService{ get; set; }

    public NotificationHub()
    {
        LoggingService = GlobalHost.DependencyResolver.Resolve<ILoggingService>();
    }

and for completeness

public class SignalHubActivator: IHubActivator
{
    private readonly IWindsorContainer _container;

    public SignalHubActivator(IWindsorContainer container)
    {
        _container = container;
    }


    public IHub Create(HubDescriptor descriptor)
    {
                var result=  _container.Resolve(descriptor.HubType) as IHub;

        if (result is Hub)
        {
            _container.Release(result);
        }

    return result;
    }

}

OLD ANSWER from 2012

I went with the first option of setting our own DependencyResolver

AspNetHost.SetResolver(new SignalResolver(_container));

I can provide SignalResolver if desired but leaving out for readability for now.

Another important note is that the hubs must have an empty constructor so our castle container injects through properties, e.g.

public class NotificationHub : Hub, INotificationHub
    { 

public INotificationService NotificationService { get; set; }

and the resolver requested

public class SignalResolver : DefaultDependencyResolver
    {
        private readonly IWindsorContainer _container;

        public SignalResolver(IWindsorContainer container) 
        {
            if (container == null)
            {
                throw new ArgumentNullException("container");
            }
            _container = container;
        }

        public override object GetService(Type serviceType) 
        {
            return TryGet(serviceType) ?? base.GetService(serviceType);
        }

        public override IEnumerable<object> GetServices(Type serviceType)
        {
            return TryGetAll(serviceType).Concat(base.GetServices(serviceType));
        }

        private object TryGet(Type serviceType)
        {
            try
            {
                return _container.Resolve(serviceType);
            }
            catch (Exception)
            {
                return null;
            }
        }

        private IEnumerable<object> TryGetAll(Type serviceType)
        {
            try
            {
                var array = _container.ResolveAll(serviceType);
                return array.Cast<object>().ToList();
            }
            catch (Exception)
            {
                return null;
            }
        }
    }

Here's what I ended up doing. First I followed along with the Windsor wiki to get my ASP.NET MVC3 setup. My Global.asax.cs:

private static IWindsorContainer _container;

    protected void Application_Start()
    {
        BootstrapContainer();
        RegisterRoutes(RouteTable.Routes);
        AreaRegistration.RegisterAllAreas();
        RegisterGlobalFilters(GlobalFilters.Filters);
    }

    protected void Application_End()
    {
        _container.Dispose();
    }

    private static void BootstrapContainer()
    {
        _container = new WindsorContainer().Install(FromAssembly.This());
        RouteTable.Routes.MapHubs(new CastleWindsorDependencyResolver(_container));
        var controllerFactory = new WindsorControllerFactory(_container.Kernel);
        ControllerBuilder.Current.SetControllerFactory(controllerFactory);
    }
    ...

CastleWindsorDependencyResolver came from here

Copied:

public class CastleWindsorDependencyResolver : DefaultDependencyResolver
{
    private readonly IWindsorContainer _container;

    public CastleWindsorDependencyResolver(IWindsorContainer container)
    {
        if (container == null)
        {
            throw new ArgumentNullException("container");
        }

        _container = container;

        // perform the lazy registrations
        foreach (var c in _lazyRegistrations)
            _container.Register(c);

        _lazyRegistrations.Clear();
    }

    public override object GetService(Type serviceType)
    {
        if (_container.Kernel.HasComponent(serviceType))
            return _container.Resolve(serviceType);
        return base.GetService(serviceType);
    }

    public override IEnumerable<object> GetServices(Type serviceType)
    {
        IEnumerable<object> objects;
        if (_container.Kernel.HasComponent(serviceType))
            objects = _container.ResolveAll(serviceType).Cast<object>();
        else
            objects = new object[] { };

        var originalContainerServices = base.GetServices(serviceType);
        if (originalContainerServices != null)
            return objects.Concat(originalContainerServices);

        return objects;
    }

    public override void Register(Type serviceType, Func<object> activator)
    {
        if (_container != null)
            // cannot unregister components in windsor, so we use a trick
            _container.Register(Component.For(serviceType).UsingFactoryMethod<object>(activator, true).OverridesExistingRegistration());
        else
            // lazy registration for when the container is up
            _lazyRegistrations.Add(Component.For(serviceType).UsingFactoryMethod<object>(activator));

        // register the factory method in the default container too
        //base.Register(serviceType, activator);
    }

    // a form of laxy initialization is actually needed because the DefaultDependencyResolver starts initializing itself immediately
    // while we now want to store everything inside CastleWindsor, so the actual registration step have to be postponed until the
    // container is available
    private List<ComponentRegistration<object>> _lazyRegistrations = new List<ComponentRegistration<object>>();
}

public static class WindsorTrickyExtensions
{
    /// <summary>
    /// Overrideses the existing registration:
    /// to overide an existiong component registration you need to do two things:
    /// 1- give it a name.
    /// 2- set it as default.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="componentRegistration">The component registration.</param>
    /// <returns></returns>
    public static ComponentRegistration<T> OverridesExistingRegistration<T>(this ComponentRegistration<T> componentRegistration) where T : class
    {
        return componentRegistration
        .Named(Guid.NewGuid().ToString())
        .IsDefault();
    }
}

I wasn't sure WTF the HubsInstaller was trying to do from that same project but I made my own which seems to work fine (I am of course open to any suggestions why this could suck):

public class HubsInstallers : IWindsorInstaller
{
    public void Install(IWindsorContainer container, IConfigurationStore store)
    {
        container.Register(Classes.FromThisAssembly()
                            .BasedOn<IHub>()
                            .LifestyleTransient());
    }
}

Also this is for the newer SignalR versions 0.5+

dove answer is fine but it is a bit confusing, adding another more specific answer. My main goal is this to work:

[HubName("MyHub")]
public class MyHub : Hub
{
    public IJobRepository JobRepository { get; }

    public MyHub(IJobRepository jobRepository)
    {
        JobRepository = jobRepository ?? throw new ArgumentNullException(nameof(jobRepository));
    }
...
}

Of course what you want is your Hubs to be created for you, they are usually created by SignalR but now that they have some dependencies SignalR cannot create them. SignalR itself has a Dependency Resolver (in SignalR namespace) which uses to get its own dependencies, you can add stuff to it, but we want Windsor remember? So we are going to change just how the IHubActivator creates hubs, we are not going to use SignalR's but this one:

public class SignalRHubActivator : IHubActivator
{
    private readonly IWindsorContainer _container;

    public SignalRHubActivator(IWindsorContainer container)
    {
        _container = container;
    }

    public IHub Create(HubDescriptor descriptor)
    {
        var result = _container.Resolve(descriptor.HubType) as IHub;
        if (result is Hub)
        {
            _container.Release(result);
        }
        return result;
    }
}

To replace this in SignalR container you have to do something like:

// Get an instance of the hub creator (see note below)
var _hubActivator = new SignalRHubActivator(container);

// Get the SignalR's Default Dependency Resolver
var signalRResolver = new Microsoft.AspNet.SignalR.DefaultDependencyResolver();
// Override the IHubActivator service
signalRResolver.Register(typeof(IHubActivator), () => _hubActivator);
// now map SignalR with this configuration
appBuilder.MapSignalR(new HubConfiguration { Resolver = signalRResolver });

And that's it, you should also register all your Hubs with Windsor

container.Register(Classes.FromThisAssembly()
    .BasedOn(typeof(Microsoft.AspNet.SignalR.Hub)));
...
container.Register(Component.For<IJobRepository>()).ImplementedBy<JobRepository>());

Note: I registered the SignalRHubActivator as a component too, this is because the Startup class I use receives the activator as a dependency:

container.Register(Component.For<SignalRHubActivator>().
    DependsOn(Dependency.OnValue("container", container)));
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!