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.
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();
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?
- moins de code que le
SimpleInjectorDependencyResolver
. - pas besoin de remplacer le
DefaultDependencyResolver
(aliasGlobalHost.DependencyResolver
), ce qui signifie encore moins de code. - 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<>)
);
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.
À 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.
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);
}
}