Utilisation D'un injecteur Simple avec SignalR

Je pensais que l'utilisation de mon propre IoC serait assez simple avec SignalR et peut - être que c'est le cas; très probablement, je fais quelque chose de mal. Voici mon code que j'ai jusqu'à présent:

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;
}

Et puis ma classe:

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);
    }
}

Ce qui finit par arriver est que je reçois une erreur que IJavaScriptProxyGenerator ne peut pas être résolu, donc je pense, Eh bien, je vais ajouter l'enregistrement:

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

Mais il y en a d'autres! J'arrive à:

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);

Ce qui me donne toujours " pas d'enregistrement pour le type ITraceManager a pu être trouvé." ... mais maintenant, je me demande si je fais cela correctement car j'espère que je n'aurais pas besoin de recâbler Tout ce que SignalR fait...droit? Espérons? Si ce n'est pas le cas, je vais continuer à marcher, mais je suis un SignalR et un simple injecteur newb, alors j'ai pensé que je demanderais d'abord. :)

Supplémentaire: https://cuttingedge.it/blogs/steven/pivot/entry.php?id=88 {[17] } puisque SignalR avait plusieurs constructeurs.

26
demandé sur Steven 2012-05-11 21:12:58

5 réponses

Eh Bien, j'ai essayé hier et j'ai trouvé une solution. Selon moi, le seul moment où je veux une injection de dépendance dans SignalR est pour mes hubs: Je ne me soucie pas de la façon dont SignalR fonctionne à l'intérieur ! Donc, au lieu de remplacer le DependencyResolver, j'ai créé ma propre implémentation 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 je peux enregistrer comme ceci (dans Application_Start):

var activator = new SimpleInjectorHubActivator(container);
GlobalHost.DependencyResolver.Register(typeof(IHubActivator), () => activator);
RouteTable.Routes.MapHubs();
43
répondu Nathanael Marchand 2014-05-03 20:31:59

Voulez jeter mes 2 cents ici avec les autres réponses, ce qui peut être utile pour trouver votre propre chemin avec l'injection de dépendance dans SignalR, soit en utilisant SimpleInjector ou un autre IoC.

En utilisant la réponse de @Steven

Si vous décidez d'utiliser Steven réponse, assurez-vous d'enregistrer votre hub route avant de composer la racine. La méthode d'extension SignalRRouteExtensions.MapHubs (alias routes.MapHubs()) appellera Register(Type, Func<object>) sur le GlobalHost.DependencyResolver lors du mappage des routes du concentrateur, donc si vous échangez le DefaultDependencyResolver avec Steven SimpleInjectorResolver avant que les routes ne soient cartographiées, vous rencontrerez son NotSupportedException.

En utilisant la réponse de @Nathanael Marchand

C'est mon préféré. Pourquoi?

  1. moins de code que le SimpleInjectorDependencyResolver.
  2. pas besoin de remplacer le DefaultDependencyResolver (alias GlobalHost.DependencyResolver), ce qui signifie encore moins de code.
  3. vous pouvez composer la racine avant ou après le mappage des routes du hub, puisque vous ne remplacez pas le DefaultDependencyResolver, cela"fonctionnera".

Comme Nathanaël dit cependant, c'est seulement si vous vous souciez des dépendances de vos classes Hub, ce qui sera probablement le cas pour la plupart. Si vous voulez jouer avec l'injection d'autres dépendances dans SignalR, vous voudrez peut-être aller avec la réponse de Steven.

Problèmes avec les dépendances par requête web dans un Hub

Il y a une chose intéressante à propos de SignalR... lorsqu'un client se déconnecte d'un concentrateur (par exemple en fermant la fenêtre de son navigateur), il crée une nouvelle instance de la classe Hub dans ordre d'invoquer OnDisconnected(). Quand cela arrive, HttpContext.Current est null . Donc, si Hub a des dépendances qui sont enregistrées par requête web, quelque chose va probablement mal tourner .

Dans Ninject

J'ai essayé L'injection de dépendance SignalR en utilisant Ninject et le résolveur de dépendance ninject signalr sur nuget . Avec cette configuration, les dépendances liées à .InRequestScope() seront créées de manière transitoire lorsqu'elles seront injectées dans un Hub lors d'un événement de déconnexion. Puisque HttpContext.Current est null, je suppose que Ninject décide simplement de l'ignorer et de créer des instances transitoires sans vous le dire. Peut-être qu'il y avait un paramètre de configuration pour dire à ninject d'avertir à ce sujet, mais ce n'était pas la valeur par défaut.

Dans SimpleInjector

SimpleInjector, d'autre part, lancera une exception lorsqu'un Hub dépend d'une instance enregistrée avec WebRequestLifestlyle:

Le délégué enregistré pour le type NameOfYourHub a lancé une exception. Le inscrit le délégué pour le type NameOfYourPerRequestDependency a lancé une exception. Le Votreprojet.Espace de noms.NameOfYourPerRequestDependency est enregistré comme 'PerWebRequest', mais l'instance est demandée en dehors du contexte de un HttpContext (HttpContext.Le courant est nul). Assurez-vous que les instances en utilisant ce mode de vie ne sont pas résolus lors de l'initialisation de l'application phase et lors de l'exécution sur un thread d'arrière-plan. Pour résoudre les instances sur les threads d'arrière-plan, essayez d'enregistrer cette instance en tant que 'Per Vie Champ d'application": https://simpleinjector.readthedocs.io/en/latest/lifetimes.html#scoped.

...notez que cette exception ne fera que gonfler lorsque HttpContext.Current == null, ce qui, pour autant que je sache, ne se produit que lorsque SignalR demande une instance Hub pour invoquer OnDisconnected().

Solutions pour les dépendances par requête web dans un Hub

Notez qu'aucun d'entre eux n'est vraiment idéal, tout dépendra des exigences de votre application.

Dans Ninject

Si vous avez besoin de dépendances non transitoires, ne remplacez pas OnDisconnected() ou ne faites rien de personnalisé avec les dépendances de classe. Si vous le faites, chaque dépendance dans le graphique sera une instance distincte (transitoire).

Dans SimpleInjector

Vous avez besoin d'un hybride de style de vie entre WebRequestLifestlye et soit Lifestyle.Transient, Lifestyle.Singleton, ou LifetimeScopeLifestyle. Lorsque HttpContext.Current n'est pas null, les dépendances ne vivront que tant que la requête web que vous attendez normalement. Cependant, quand HttpContext.Current est null, les dépendances seront injectées de manière transitoire, en tant que singletons, ou dans une portée de durée de vie.

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()
);

Plus sur LifetimeScopeLifestyle

Dans mon cas, j'ai une dépendance EntityFramework DbContext. Ceux-ci peuvent être difficiles car ils peuvent exposer des problèmes lorsqu'ils sont enregistrés de manière transitoire ou en tant que singletons. Lorsque vous êtes enregistré de manière transitoire, vous pouvez vous retrouver avec des exceptions tout en essayant de travailler avec des entités attachées à 2 instances DbContext ou plus. Lorsqu'il est enregistré en tant que singleton, vous vous retrouvez avec plus exceptions générales (n'enregistrez jamais un DbContext en tant que singleton). Dans mon cas, j'avais besoin du DbContext pour vivre dans une durée de vie spécifique dans laquelle la même instance peut être réutilisée dans de nombreuses opérations imbriquées, ce qui signifie que j'avais besoin du LifetimeScopeLifestyle.

Maintenant, si vous avez utilisé le code hybride ci-dessus avec la ligne falseLifestyle: new LifetimeScopeLifestyle(), Vous obtiendrez une autre exception lorsque votre méthode IHubActivator.Create personnalisée s'exécutera:

Le délégué enregistré pour le type NameOfYourHub a lancé une exception. Le NameOfYourLifetimeScopeDependency est enregistré comme "LifetimeScope", mais l'instance est demandée en dehors du contexte d'une portée à vie. Assurez-vous d'appeler container.BeginLifetimeScope () en premier.

La façon dont vous avez configuré une dépendance étendue à vie va comme ceci:

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

Toutes les dépendances enregistrées avec la portée de vie doivent être résolues dans ce bloc using. De plus, si l'une de ces dépendances implémente IDisposable, elle sera éliminée à la fin du bloc using. Ne soyez pas tenté de faire quelque chose comme ça:

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

J'ai demandé à Steven (qui se trouve aussi être l'auteur de SimpleInjector au cas où vous ne le saviez pas) à ce sujet, et il a dit:

Eh bien.. Si vous ne disposez pas du LifetimeScope, vous serez en grand des ennuis, alors assurez-vous qu'ils soient disposés. Si vous ne jetez pas l' scopes, ils vont traîner pour toujours dans ASP.NET. C'est parce que les étendues peuvent être imbriquées et référencer leur portée parente. Donc un fil maintient en vie la portée la plus interne (avec son cache) et cette portée conserve vivant sa portée parent (avec son cache) et ainsi de suite. ASP.NET piscines threads et ne réinitialise pas toutes les valeurs lorsqu'il saisit un thread du piscine, donc cela signifie que toutes les étendues restent en vie et la prochaine fois que vous prenez un fil de la piscine et commencer une nouvelle portée de vie, vous il suffit de créer une nouvelle portée imbriquée et cela continuera à s'empiler. Tôt ou tard, vous obtiendrez un OutOfMemoryException.

Vous ne pouvez pas utiliser IHubActivator pour étendre les dépendances car il ne vit pas aussi longtemps que l'instance Hub qu'il crée. Ainsi, même si vous enveloppez la méthode BeginLifetimeScope() dans un bloc using, vos dépendances seront éliminées immédiatement après la création de l'instance Hub. Ce dont vous avez vraiment besoin ici est une autre couche d'indirection.

Ce que j'ai fini avec, grâce à L'aide de Steven, c'est un décorateur de commandes (et un décorateur de requêtes). Un Hub ne peut pas dépend des instances per-web-request elles-mêmes, mais doit plutôt dépendre d'une autre interface dont l'implémentation dépend des instances per-request. L'implémentation injectée dans le constructeur Hub est décorée (via simpleinjector) avec un wrapper qui commence et dispose de la portée lifetime.

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);
        }
    }
}

... ce sont les instances ICommandHandler<T> décorées qui dépendent des instances par requête web. Pour plus d'informations sur le modèle utilisé, lisez ce et ce.

Exemple d'enregistrement

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

container.RegisterSingleDecorator(
    typeof(ICommandHandler<>),
    typeof(CommandLifetimeScopeDecorator<>)
);
24
répondu danludwig 2018-03-31 11:56:44

Mise À JOUR Cette réponse a été mise à jour pour SignalR version 1.0

Voici comment construire un SignalR IDependencyResolver pour un injecteur Simple:

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();
    }
}

Malheureusement, il y a un problème avec la conception du DefaultDependencyResolver. C'est pourquoi l'implémentation ci-dessus n'en hérite pas, mais l'enveloppe. J'ai créé un problème à ce sujet sur le site SignalR. Vous pouvez lire à ce sujet ici. Bien que le concepteur soit d'accord avec moi, malheureusement, le problème n'a pas été corrigé dans la version 1.0.

J'espère que cela aide.

5
répondu Steven 2014-05-02 12:49:16

À partir de SignalR 2.0 (et de la version bêta), il existe une nouvelle façon de définir le résolveur de dépendances. SignalR déplacé au démarrage OWIN pour faire la configuration. Avec un injecteur Simple, vous le feriez comme ceci:

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));
    }
}

Vous devrez injecter explicitement vos hubs comme ceci:

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

Cette configuration fonctionne en direct sur un site web à fort trafic sans problème.

4
répondu Elger Mensonides 2014-09-30 17:38:01

Ce qui suit a fonctionné pour moi. En outre, vous devrez enregistrer un délégué avec le conteneur de votre classe hub avant d'instancier le résolveur de dépendances.

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);
    }
}
0
répondu mg712 2013-03-29 17:50:43