2015-04-23 1 views
4

J'utilise un Ninject DI dans mon application web avec un tas de technologies de la pile Asp.Net (MVC, Web Api 2, SignalR).Ninject avec Web Api, SignalR, MVC et OWIN

J'ai réussi à faire fonctionner DI pour toutes les technologies utilisées avec l'approche suivante:

public static class NinjectWebCommon 
{ 
    private static readonly Bootstrapper bootstrapper = new Bootstrapper(); 

    /// <summary> 
    /// Starts the application 
    /// </summary> 
    public static void Start() 
    { 
     DynamicModuleUtility.RegisterModule(typeof(OnePerRequestHttpModule)); 
     DynamicModuleUtility.RegisterModule(typeof(NinjectHttpModule)); 
     bootstrapper.Initialize(CreateKernel); 
    } 

    /// <summary> 
    /// Stops the application. 
    /// </summary> 
    public static void Stop() 
    { 
     bootstrapper.ShutDown(); 
    } 

    /// <summary> 
    /// Creates the kernel that will manage your application. 
    /// </summary> 
    /// <returns>The created kernel.</returns> 
    internal static IKernel CreateKernel() 
    { 
     var kernel = new StandardKernel(); 

     kernel.Bind<Func<IKernel>>().ToMethod(ctx =>() => new Bootstrapper().Kernel); 
     kernel.Bind<IHttpModule>().To<HttpApplicationInitializationHttpModule>(); 

     RegisterServices(kernel); 

     return kernel; 
    } 

    /// <summary> 
    /// Load your modules or register your services here! 
    /// </summary> 
    /// <param name="kernel">The kernel.</param> 
    private static void RegisterServices(IKernel kernel) 
    { 
     GlobalHost.DependencyResolver = new Microsoft.AspNet.SignalR.Ninject.NinjectDependencyResolver(kernel); 
     DependencyResolver.SetResolver(new NinjectDependencyResolver(kernel)); 

     // Binding services here 
    }   
} 

Jusqu'à présent, si bon.

Tout cela fonctionnait avec l'initialisation de Web Api dans Global.asax.

Maintenant, je passe à OWIN pipeline. Donc, je l'ai enlevé GlobalConfiguration.Configure(WebApiConfig.Register); de Global.asax et ajouté

HttpConfiguration config = new HttpConfiguration(); 
WebApiConfig.Register(config); 
app.UseWebApi(config); 

à ma classe OwinStartup. DI pour Web Api a cessé de fonctionner.

J'ai commencé à chercher la solution appropriée et j'ai trouvé le paquet Ninject.Web.WebApi.OwinHost. Ainsi, afin d'avoir une seule résolution des dépendances du noyau pour toutes les technologies, j'ai apporté les modifications suivantes:

dans OwinStartup:

app.UseNinjectMiddleware(NinjectWebCommon.CreateKernel); 
app.UseNinjectWebApi(config); 

dans NinjectWebCommon:

//[assembly: WebActivatorEx.PreApplicationStartMethod(typeof(App.TradingServer.ConfiguratorApp.App_Start.NinjectWebCommon), "Start")] 
//[assembly: WebActivatorEx.ApplicationShutdownMethodAttribute(typeof(App.TradingServer.ConfiguratorApp.App_Start.NinjectWebCommon), "Stop")] 

Ces lignes ont été désactivées pour éviter d'initialiser le noyau deux fois.

Cette DI fixe pour API Web mais pas pour SignalR. Lorsque le client tente de se connecter au centre, je reçois l'exception suivante:

System.NullReferenceException: Object reference not set to an instance of an object. 
    at Microsoft.AspNet.SignalR.PersistentConnection.ProcessNegotiationRequest(HostContext context) 
    at Microsoft.AspNet.SignalR.PersistentConnection.ProcessRequest(HostContext context) 
    at Microsoft.AspNet.SignalR.Hubs.HubDispatcher.ProcessRequest(HostContext context) 
    at Microsoft.AspNet.SignalR.PersistentConnection.ProcessRequest(IDictionary`2 environment) 
    at Microsoft.AspNet.SignalR.Owin.Middleware.HubDispatcherMiddleware.Invoke(IOwinContext context) 
    at Microsoft.Owin.Infrastructure.OwinMiddlewareTransition.Invoke(IDictionary`2 environment) 
    at Microsoft.Owin.Mapping.MapMiddleware.<Invoke>d__0.MoveNext() 
--- End of stack trace from previous location where exception was thrown --- 
    at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) 
    at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) 
    at System.Runtime.CompilerServices.TaskAwaiter.GetResult() 
    at System.Web.Http.Owin.HttpMessageHandlerAdapter.<InvokeCore>d__0.MoveNext() 
--- End of stack trace from previous location where exception was thrown --- 
    at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) 
    at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) 
    at System.Runtime.CompilerServices.TaskAwaiter.GetResult() 
    at Ninject.Web.Common.OwinHost.OwinBootstrapper.<Execute>d__1.MoveNext() 
--- End of stack trace from previous location where exception was thrown --- 
    at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) 
    at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) 
    at System.Runtime.CompilerServices.TaskAwaiter.GetResult() 
    at Microsoft.Owin.Host.SystemWeb.IntegratedPipeline.IntegratedPipelineContextStage.<RunApp>d__5.MoveNext() 
--- End of stack trace from previous location where exception was thrown --- 
    at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) 
    at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) 
    at System.Runtime.CompilerServices.TaskAwaiter.GetResult() 
    at Microsoft.Owin.Host.SystemWeb.IntegratedPipeline.IntegratedPipelineContext.<DoFinalWork>d__2.MoveNext() 
--- End of stack trace from previous location where exception was thrown --- 
    at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() 
    at Microsoft.Owin.Host.SystemWeb.Infrastructure.ErrorState.Rethrow() 
    at Microsoft.Owin.Host.SystemWeb.IntegratedPipeline.StageAsyncResult.End(IAsyncResult ar) 
    at Microsoft.Owin.Host.SystemWeb.IntegratedPipeline.IntegratedPipelineContext.EndFinalWork(IAsyncResult ar) 
    at System.Web.HttpApplication.AsyncEventExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute() 
    at System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously) | RuntimeMethodInfo.UnsafeInvokeInternal => RuntimeMethodHandle.InvokeMethod => Application.Application_Error 

Je suis un peu perdu. Je lis deux douzaines d'articles mais aucun d'entre eux ne m'a donné la solution. Apprécierait toute aide.

Mon but final est de disposer d'un seul noyau qui serve Web Api, MVC et SignalR et supporte le pipeline OWIN.

Modifier: Depuis que j'ai un commentaire que mon cas pourrait être une copie d'une autre question, je crois que je dois donner quelques explications.

J'ai trois scénarios.

  1. initialisation du WebAPI dans Global.asax avec GlobalConfiguration.Configure(WebApiConfig.Register), l'initialisation Ninject avec NinjectWebCommon et d'amorçage.

    Cela me donne à la fois l'injection dans WebApi et SignalR. Mais puisque je voudrais déplacer l'initialisation de WebApi au démarrage d'OWIN, cette approche est obsolète.

  2. Initialisation WebApi avec démarrage OWIN, initialisation Ninject avec NinjectWebCommon et Bootstrapper.

    L'injection SignalR fonctionne, l'injection WebApi ne fonctionne pas.

  3. Initialisation WebApi avec démarrage OWIN, initialisation Ninject avec UseNinjectMiddleware, UseNinjectWebApi.

    L'injection WebApi fonctionne, l'injection SignalR ne fonctionne pas.

Donc, fondamentalement, j'ai besoin de savoir comment mettre cela ensemble pour que les deux WebAPI et le travail d'injection SignalR quand j'initialize WebAPI sur pipeline OWIN.

Le code de NinjectWebCommon est dans la question originale ci-dessous. Il contient du code pour créer résolveur SignalR mais il ne permet pas dans le scénario 3.

Edit 2: Après quelques heures de méthode essais et erreurs, je suis venu à la conclusion que l'appel

app.UseNinjectMiddleware(NinjectWebCommon.CreateKernel); 
app.UseNinjectWebApi(config); 

conflits avec cet appel:

GlobalHost.DependencyResolver = new Microsoft.AspNet.SignalR.Ninject.NinjectDependencyResolver(kernel); 

Donc la description du problème se réduit à ceci. Lorsque j'utilise le modèle suivant SignalR cesse de fonctionner:

public void Configuration(IAppBuilder app) 
{ 
    HttpConfiguration config = new HttpConfiguration(); 
    WebApiConfig.Register(config); 

    app.UseNinjectMiddleware(CreateKernel); 
    app.UseNinjectWebApi(config); 

    GlobalHost.HubPipeline.AddModule(new GlobalSignalRExceptionHandler()); 
    app.MapSignalR(); 
} 


private static IKernel CreateKernel() 
{ 
    var kernel = new StandardKernel(); 

    GlobalHost.DependencyResolver = new Microsoft.AspNet.SignalR.Ninject.NinjectDependencyResolver(kernel); 
    DependencyResolver.SetResolver(new Ninject.Web.Mvc.NinjectDependencyResolver(kernel)); 

    return kernel; 
} 

Mais si je commente la ligne

//GlobalHost.DependencyResolver = new Microsoft.AspNet.SignalR.Ninject.NinjectDependencyResolver(kernel); 

SignalR commence à travailler. Mais pas d'injection à l'intérieur des moyeux bien sûr.

+0

double possible de [2 SignalR injection de dépendance avec Ninject] (http://stackoverflow.com/questions/21285934/signalr-2-dependency-injection-with-ninject) – mason

+1

Le sujet mentionné ne couvre pas l'intégration avec le tuyau OWIN. Tous les trucs avec app.UseNinjectMiddleware et app.UseNinjectWebApi. Fondamentalement, ils décrivent l'approche que j'utilisais avant de passer à OWIN. – user1921819

+0

Un commentaire général sur ce type de problème: Ma recherche montre qu'une 'NullReferenceException' avec cette trace de pile est levée si le conteneur DI utilisé par SignalR distribue plusieurs instances de certains types qui devraient être des singletons. Cela peut se produire pour diverses raisons, l'une étant que 'GlobalHost.DependencyResolver' est modifié après que le conteneur SignalR par défaut a déjà été utilisé. Dans OWIN, vous pouvez appeler MapSignalR tard dans le pipeline. Par exemple. avec Nancy 'app.UseNancy (o => o.PerformPassThrough = c => c.Request.Path.ToUpperInvariant(). StartsWith ("/SIGNALR ")). MapSignalR()'. –

Répondre

4

Enfin, j'ai réussi à obtenir la configuration de travail Ninject qui prend en charge OWIN pipe, WebApi, MVC et SignalR. Au moment où j'ai posté la question, j'ai eu une solution de contournement (qui désactivait DI dans les hubs SignalR) alors j'ai décidé de ne plus perdre de temps et de passer à autre chose. Mais quand j'ai essayé d'exécuter le serveur de test en mémoire OWIN avec ma classe de démarrage, il s'est produit que DI ne fonctionnait pas. La méthode CreateKernel a été appelée trop tard, ce qui a entraîné la création de plusieurs instances d'un objet utilisé dans la portée de sengleton. Après avoir joué avec différentes variations d'initialisation, j'ai fait fonctionner DI pour OWIN Test Server et j'ai également corrigé le DependResolver de SignalR.

La solution:

Je cessé d'utiliser les paquets Ninject.Web.Common.OwinHost et Ninject.Web.WebApi.OwinHost de sorte que ces appels ont été retirés de ma méthode de configuration:

//app.UseNinjectMiddleware(NinjectWebCommon.CreateKernel); 
//app.UseNinjectWebApi(config); 

Au lieu de cela, je procédez comme suit:

public void Configuration(IAppBuilder app) 
{ 
    ConfigureOAuth(app); 

    HttpConfiguration config = new HttpConfiguration(); 
    WebApiConfig.Register(config); 
    app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll); 

    NinjectWebCommon.Start(); 
    config.DependencyResolver = new NinjectDependencyResolver(NinjectWebCommon.bootstrapper.Kernel); 
    app.UseWebApi(config); 

    app.MapSignalR(); 
} 

public static class NinjectWebCommon 
{ 
    private static bool _isStarted; 

    internal static readonly Bootstrapper bootstrapper = new Bootstrapper(); 

    /// <summary> 
    /// Starts the application 
    /// </summary> 
    public static void Start() 
    { 
     // When creating OWIN TestService instances during unit tests 
     // Start() method might be called several times 
     // This check ensures that Ninject kernel is initialized only once per process 
     if (_isStarted) 
      return; 

     _isStarted = true; 

     bootstrapper.Initialize(CreateKernel); 
    } 

    /// <summary> 
    /// Creates the kernel that will manage your application. 
    /// </summary> 
    /// <returns>The created kernel.</returns> 
    internal static IKernel CreateKernel() 
    { 
     var kernel = new StandardKernel(); 
     RegisterServices(kernel); 
     return kernel; 
    } 

    /// <summary> 
    /// Load your modules or register your services here! 
    /// </summary> 
    /// <param name="kernel">The kernel.</param> 
    private static void RegisterServices(IKernel kernel) 
    { 
     // DI for SignalR 
     GlobalHost.DependencyResolver = new Microsoft.AspNet.SignalR.Ninject.NinjectDependencyResolver(kernel); 
     // DI for MVC 
     DependencyResolver.SetResolver(new NinjectDependencyResolver(kernel)); 

     // Binding code here 
     kernel.Bind<Something>().ToSelf().InSingletonScope(); 
    }   
} 
+0

quel cauchemar, comment avez-vous même compris cela! – tofutim

+0

D'où vient le DependencyResolver dans '' 'DependencyResolver.SetResolver (nouveau NinjectDependencyResolver (kernel))' ''? Est-ce différent de celui de GlobalHost? – tofutim

+0

Ceci est un singleton défini dans l'espace de noms 'System.Web.Mvc'. – user1921819

1

afin d'utiliser un résolveur de dépendance pour les WebAPI et SignalR vous devez implémenter une classe qui ressemble à ceci:

public class NinjectDependencyResolver : Microsoft.AspNet.SignalR.DefaultDependencyResolver, 
    System.Web.Http.Dependencies.IDependencyResolver 
{ 
    public readonly IKernel Kernel; 

    public NinjectDependencyResolver(string moduleFilePattern) 
     : base() 
    { 
     Kernel = new StandardKernel(); 
     Kernel.Load(moduleFilePattern); 

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

    public override IEnumerable<object> GetServices(Type serviceType) 
    { 
     IEnumerable<object> services = Kernel.GetAll(serviceType).ToList(); 
     if (services.IsEmpty()) 
     { 
      services = base.GetServices(serviceType) ?? services; 
     } 
     return services; 
    } 

    public System.Web.Http.Dependencies.IDependencyScope BeginScope() 
    { 
     return this; 
    } 

    public void Dispose() 
    { } 
} 

puis dans votre classe de démarrage vous devez vous inscrire NinjectDependencyResolver pour les WebAPI et SignalR, comme ceci:

public void Configuration(IAppBuilder app) 
{ 
    var dependencyResolver = new NinjectDependencyResolver("*.dll"); 

    var httpConfiguration = new HttpConfiguration(); 
    httpConfiguration.DependencyResolver = dependencyResolver; 
    app.UseWebApi(httpConfiguration); 

    var hubConfig = new HubConfiguration { Resolver = dependencyResolver }; 
    app.MapSignalR(hubConfig); 
} 
+0

d'où vient votre IsEmpty()? Je viens d'utiliser '' 'services.Count() == 0''', aussi le Dispose() est un remplacement de DefaultDependencyResolver. – tofutim

+0

Erreur lors de l'activation de ModelValidatorProvider à l'aide de la liaison de ModelValidatorProvider à NinjectDefaultModelValidatorProvider Une dépendance cyclique a été détectée entre les constructeurs de deux services. : P – tofutim

+0

J'ai aimé cette solution, j'ai trouvé que je devais me débarrasser de Ninject.Web. * Pour que cela fonctionne. – tofutim