tutorial signal mvc example espaƱol c# signalr simple-injector

c# - mvc - Usando inyector simple con SignalR



signalr example c# (5)

Pensé que usar mi propio IoC sería bastante sencillo con SignalR y tal vez lo sea; Lo más probable es que esté haciendo algo mal. Aquí está mi código que tengo hasta ahora:

private static void InitializeContainer(Container container) { container.Register<IMongoHelper<UserDocument>, MongoHelper<UserDocument>>(); // ... registrations like about and then: var resolver = new SimpleInjectorResolver(container); GlobalHost.DependencyResolver = resolver; }

y luego mi clase:

public class SimpleInjectorResolver : DefaultDependencyResolver { private Container _container; public SimpleInjectorResolver(Container container) { _container = container; } public override object GetService(Type serviceType) { return _container.GetInstance(serviceType) ?? base.GetService(serviceType); } public override IEnumerable<object> GetServices(Type serviceType) { return _container.GetAllInstances(serviceType) ?? base.GetServices(serviceType); } }

Lo que termina sucediendo es que obtengo el error de que IJavaScriptProxyGenerator no se puede resolver, así que creo que, bueno, agregaré el registro:

container.Register<IJavaScriptProxyGenerator, DefaultJavaScriptProxyGenerator>( ConstructorSelector.MostParameters);

Pero luego hay un montón de otros! Puedo llegar a:

container.Register<IDependencyResolver, SimpleInjectorResolver>(); container.Register<IJavaScriptMinifier, NullJavaScriptMinifier>(); container.Register<IJavaScriptProxyGenerator, DefaultJavaScriptProxyGenerator>( ConstructorSelector.MostParameters); container.Register<IHubManager, DefaultHubManager>(); container.Register<IHubActivator, DefaultHubActivator>(); container.Register<IParameterResolver, DefaultParameterResolver>(); container.Register<IMessageBus, InProcessMessageBus>(ConstructorSelector.MostParameters);

Lo que todavía me da "No se pudo encontrar ningún registro para el tipo ITraceManager ". ... pero ahora me pregunto si estoy haciendo esto correctamente, ya que espero que no tenga que volver a conectar todo lo que hace SignalR ... ¿verdad? ¿Ojalá? Si no, seguiré andando, pero soy un nuevo SignalR y Simple Injector, así que pensé que primero lo haría. :)

Adicional: https://cuttingedge.it/blogs/steven/pivot/entry.php?id=88 ya que SignalR tenía varios constructores.


Bueno, lo intenté ayer y encontré una solución. Según yo, el único momento en el que quiero la inyección de dependencia en SignalR es para mis hubs: ¡No me importa cómo funciona SignalR en su interior! Entonces, en lugar de reemplazar el DependencyResolver, creé mi propia implementación de IHubActivator:

public class SimpleInjectorHubActivator : IHubActivator { private readonly Container _container; public SimpleInjectorHubActivator(Container container) { _container = container; } public IHub Create(HubDescriptor descriptor) { return (IHub)_container.GetInstance(descriptor.HubType); } }

Que puedo registrarme así (en Application_Start):

var activator = new SimpleInjectorHubActivator(container); GlobalHost.DependencyResolver.Register(typeof(IHubActivator), () => activator); RouteTable.Routes.MapHubs();


Desde SignalR 2.0 (y las versiones beta) hay una nueva forma de configurar el resolvedor de dependencias. SignalR se movió al inicio de OWIN para hacer la configuración. Con Simple Injector lo harías así:

public class Startup { public void Configuration(IAppBuilder app) { var config = new HubConfiguration() { Resolver = new SignalRSimpleInjectorDependencyResolver(Container) }; app.MapSignalR(config); } } public class SignalRSimpleInjectorDependencyResolver : DefaultDependencyResolver { private readonly Container _container; public SignalRSimpleInjectorDependencyResolver(Container container) { _container = container; } public override object GetService(Type serviceType) { return ((IServiceProvider)_container).GetService(serviceType) ?? base.GetService(serviceType); } public override IEnumerable<object> GetServices(Type serviceType) { return _container.GetAllInstances(serviceType) .Concat(base.GetServices(serviceType)); } }

Tendrías que inyectar explícitamente tus hubs así:

container.Register<MessageHub>(() => new MessageHub(new EFUnitOfWork()));

Esta configuración se está ejecutando en vivo en un sitio web de alto tráfico sin problemas.


Lo siguiente funcionó para mí. Además, deberá registrar un delegado en el contenedor para su clase de hub antes de crear una instancia del resolvedor de dependencias.

ex: container.Register<MyHub>(() => { IMyInterface dependency = container.GetInstance<IMyInterface>(); return new MyHub(dependency); }); public class SignalRDependencyResolver : DefaultDependencyResolver { private Container _container; private HashSet<Type> _types = new HashSet<Type>(); public SignalRDependencyResolver(Container container) { _container = container; RegisterContainerTypes(_container); } private void RegisterContainerTypes(Container container) { InstanceProducer[] producers = container.GetCurrentRegistrations(); foreach (InstanceProducer producer in producers) { if (producer.ServiceType.IsAbstract || producer.ServiceType.IsInterface) continue; if (!_types.Contains(producer.ServiceType)) { _types.Add(producer.ServiceType); } } } public override object GetService(Type serviceType) { return _types.Contains(serviceType) ? _container.GetInstance(serviceType) : base.GetService(serviceType); } public override IEnumerable<object> GetServices(Type serviceType) { return _types.Contains(serviceType) ? _container.GetAllInstances(serviceType) : base.GetServices(serviceType); } }


Quiero lanzar mis 2 centavos aquí con las otras respuestas, que pueden ser útiles para encontrar su propio camino con la inyección de dependencia en SignalR, ya sea utilizando SimpleInjector u otro IoC.

Usando la respuesta de @ Steven

Si decide utilizar la respuesta de Steven, asegúrese de registrar las rutas de su hub antes de componer la raíz. El método de extensión SignalRRouteExtensions.MapHubs (también routes.MapHubs() como routes.MapHubs() ) llamará a Register(Type, Func<object>) en el GlobalHost.DependencyResolver al mapear las rutas del hub, por lo que si DefaultDependencyResolver el DefaultDependencyResolver con el DefaultDependencyResolver de Steven antes de que las rutas sean mapeado, se encontrará con su NotSupportedException .

Usando la respuesta de @Nathanael Marchand

Este es mi favorito. ¿Por qué?

  1. Menos código que el SimpleInjectorDependencyResolver .
  2. No es necesario reemplazar el DefaultDependencyResolver (también GlobalHost.DependencyResolver como GlobalHost.DependencyResolver ), lo que significa incluso menos código.
  3. Puede componer la raíz antes o después de asignar las rutas del hub, ya que no está reemplazando el DefaultDependencyResolver , simplemente "funcionará".

Sin embargo, como dijo Nathanael, esto es solo si te preocupas por las dependencias en tus clases de Hub , que probablemente será el caso para la mayoría. Si quiere perder el tiempo inyectando otras dependencias en SignalR, puede ir con la respuesta de Steven.

Problemas con las dependencias de solicitud web en un Hub

Hay una cosa interesante acerca de SignalR ... cuando un cliente se desconecta de un concentrador (por ejemplo, cerrando la ventana de su navegador), creará una nueva instancia de la clase Hub para invocar a OnDisconnected() . Cuando esto sucede, HttpContext.Current es nulo . Por lo tanto, si este Hub tiene dependencias registradas por solicitud web, algo saldrá mal .

En ninject

Probé la inyección de dependencia SignalR usando Ninject y el resolvedor de dependencia ninject signalr en nuget . Con esta configuración, las dependencias que están vinculadas .InRequestScope() se crearán de forma transitoria cuando se inyecten en un Hub durante un evento de desconexión. Dado que HttpContext.Current es nulo, supongo que Ninject simplemente decide ignorarlo y crear instancias transitorias sin avisar. Tal vez había una configuración para indicar a ninject que advirtiera sobre esto, pero no era la predeterminada.

En SimpleInjector

Por otro lado, SimpleInjector lanzará una excepción cuando un Hub dependa de una instancia que esté registrada con WebRequestLifestlyle :

El delegado registrado para el tipo NameOfYourHub lanzó una excepción. El delegado registrado para el tipo NameOfYourPerRequestDependency lanzó una excepción. El YourProject.Namespace.NameOfYourPerRequestDependency se registra como ''PerWebRequest'', pero la instancia se solicita fuera del contexto de un HttpContext (HttpContext.Current es nulo). Asegúrese de que las instancias que usan este estilo de vida no se resuelvan durante la fase de inicialización de la aplicación y cuando se ejecutan en un hilo de fondo. Para resolver instancias en subprocesos en segundo plano, intente registrar esta instancia como ''Por vida útil'': https://simpleinjector.readthedocs.io/en/latest/lifetimes.html#scoped .

... tenga en cuenta que esta excepción solo se HttpContext.Current == null cuando HttpContext.Current == null , que, por lo que puedo decir, solo ocurre cuando SignalR solicita una instancia de Hub para invocar a OnDisconnected() .

Soluciones para dependencias por solicitud web en un Hub

Tenga en cuenta que ninguno de estos son realmente ideales, todo dependerá de los requisitos de su aplicación.

En ninject

Si necesita dependencias no transitorias, simplemente no OnDisconnected() ni haga nada personalizado allí con las dependencias de clase. Si lo hace, cada dependencia en el gráfico será una instancia separada (transitoria).

En SimpleInjector

Necesita un estilo de vida híbrido entre WebRequestLifestlye y Lifestyle.Transient , Lifestyle.Singleton o LifetimeScopeLifestyle . Cuando HttpContext.Current no es nulo, las dependencias solo se mantendrán durante el tiempo que la solicitud web lo esperaría normalmente. Sin embargo, cuando HttpContext.Current es nulo, las dependencias se inyectarán de forma transitoria, como singletons, o dentro de un alcance de por vida.

var lifestyle = Lifestyle.CreateHybrid( lifestyleSelector: () => HttpContext.Current != null, trueLifestyle: new WebRequestLifestyle(), falseLifestyle: Lifestyle.Transient // this is what ninject does //falseLifestyle: Lifestyle.Singleton //falseLifestyle: new LifetimeScopeLifestyle() );

Más sobre LifetimeScopeLifestyle

En mi caso, tengo una dependencia EntityFramework DbContext . Estos pueden ser complicados porque pueden exponer problemas cuando se registran transitoriamente o como singletons. Cuando se registra de forma transitoria, puede terminar con excepciones al intentar trabajar con entidades vinculadas a 2 o más instancias de DbContext . Cuando se registra como un singleton, termina con excepciones más generales (nunca registre un DbContext como un singleton). En mi caso, necesitaba el DbContext para vivir dentro de un tiempo de vida específico en el que la misma instancia se puede reutilizar en muchas operaciones anidadas, lo que significa que necesitaba el LifetimeScopeLifestyle .

Ahora, si falseLifestyle: new LifetimeScopeLifestyle() el código híbrido anterior con la línea falseLifestyle: new LifetimeScopeLifestyle() , obtendrás otra excepción cuando tu método IHubActivator.Create personalizado se ejecute:

El delegado registrado para el tipo NameOfYourHub lanzó una excepción. El NameOfYourLifetimeScopeDependency se registra como ''LifetimeScope'', pero la instancia se solicita fuera del contexto de un alcance de por vida. Asegúrate de llamar primero a container.BeginLifetimeScope ().

La forma en que ha configurado una dependencia con alcance de por vida es así:

using (simpleInjectorContainer.BeginLifetimeScope()) { // resolve solve dependencies here }

Cualquier dependencia que esté registrada con un alcance de por vida debe resolverse dentro de este bloque de using . Además, si cualquiera de esas dependencias se implementa como IDisposable , se eliminarán al final del bloque de using . No te sientas tentado a hacer algo como esto:

public IHub Create(HubDescriptor descriptor) { if (HttpContext.Current == null) _container.BeginLifetimeScope(); return _container.GetInstance(descriptor.HubType) as IHub; }

Le pregunté a Steven (quien también es el autor de SimpleInjector en caso de que no lo supieras) sobre esto, y dijo:

Bueno ... Si no desecha el LifetimeScope, tendrá un gran problema, así que asegúrese de que se deshagan de él. Si no dispone de los ámbitos, se quedarán para siempre en ASP.NET. Esto se debe a que los ámbitos se pueden anidar y hacer referencia a su ámbito principal. Entonces, un hilo mantiene vivo el alcance más interno (con su caché) y este alcance mantiene vivo su alcance primario (con su caché) y así sucesivamente. ASP.NET agrupa los subprocesos y no restablece todos los valores cuando toma un subproceso del conjunto, por lo que esto significa que todos los ámbitos permanecen activos y la próxima vez que tome un subproceso del conjunto y comience un nuevo alcance de por vida, simplemente creando un nuevo ámbito anidado y esto seguirá apilando. Tarde o temprano, obtendrá una excepción OutOfMemoryException.

No puede usar IHubActivator para IHubActivator las dependencias porque no vive mientras la instancia de Hub se cree. Entonces, incluso si envolvió el método BeginLifetimeScope() en un bloque de using , sus dependencias se eliminarán inmediatamente después de crear la instancia de Hub . Lo que realmente necesitas aquí es otra capa de direccionamiento indirecto.

Lo que terminé, con muchas gracias a la ayuda de Steven, es un decorador de comandos (y un decorador de consultas). Un Hub no puede depender de las instancias de solicitud web, sino que debe depender de otra interfaz cuya implementación dependa de las instancias de solicitud. La implementación que se inyecta en el constructor de Hub está decorada (mediante un simple inyector) con un envoltorio que comienza y elimina el alcance de la vida útil.

public class CommandLifetimeScopeDecorator<TCommand> : ICommandHandler<TCommand> { private readonly Func<ICommandHandler<TCommand>> _handlerFactory; private readonly Container _container; public CommandLifetimeScopeDecorator( Func<ICommandHandler<TCommand>> handlerFactory, Container container) { _handlerFactory = handlerFactory; _container = container; } [DebuggerStepThrough] public void Handle(TCommand command) { using (_container.BeginLifetimeScope()) { var handler = _handlerFactory(); // resolve scoped dependencies handler.Handle(command); } } }

... son las instancias de ICommandHandler<T> decoradas que dependen de las instancias de solicitud por web. Para obtener más información sobre el patrón utilizado, lea this y this .

Ejemplo de registro

container.RegisterManyForOpenGeneric(typeof(ICommandHandler<>), assemblies); container.RegisterSingleDecorator( typeof(ICommandHandler<>), typeof(CommandLifetimeScopeDecorator<>) );


ACTUALIZACIÓN Esta respuesta ha sido actualizada para SignalR versión 1.0

Esta es la forma de crear un SignalR IDependencyResolver para Simple Injector:

public sealed class SimpleInjectorResolver : Microsoft.AspNet.SignalR.IDependencyResolver { private Container container; private IServiceProvider provider; private DefaultDependencyResolver defaultResolver; public SimpleInjectorResolver(Container container) { this.container = container; this.provider = container; this.defaultResolver = new DefaultDependencyResolver(); } [DebuggerStepThrough] public object GetService(Type serviceType) { // Force the creation of hub implementation to go // through Simple Injector without failing silently. if (!serviceType.IsAbstract && typeof(IHub).IsAssignableFrom(serviceType)) { return this.container.GetInstance(serviceType); } return this.provider.GetService(serviceType) ?? this.defaultResolver.GetService(serviceType); } [DebuggerStepThrough] public IEnumerable<object> GetServices(Type serviceType) { return this.container.GetAllInstances(serviceType); } public void Register(Type serviceType, IEnumerable<Func<object>> activators) { throw new NotSupportedException(); } public void Register(Type serviceType, Func<object> activator) { throw new NotSupportedException(); } public void Dispose() { this.defaultResolver.Dispose(); } }

Desafortunadamente, hay un problema con el diseño de DefaultDependencyResolver . Es por eso que la implementación anterior no se hereda de ella, sino que la envuelve. He creado un problema sobre esto en el sitio de SignalR. Puedes leer sobre esto here . Aunque el diseñador estuvo de acuerdo conmigo, desafortunadamente el problema no se ha solucionado en la versión 1.0.

Espero que esto ayude.