Ninject with Web Api, SignalR, MVC and OWIN

拟墨画扇 提交于 2020-06-27 11:28:29

问题


I am using a Ninject DI in my web application with a bunch of technoligies from Asp.Net stack (MVC, Web Api 2, SignalR).

I have managed to make DI work for all technologies in use with the following approach:

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

So far so good.

All this was working with Web Api being initialized in Global.asax.

Now I'm switching to OWIN pipeline. So I removed GlobalConfiguration.Configure(WebApiConfig.Register); from Global.asax and added

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

to my OwinStartup class. DI for Web Api stoped working.

I started searching for appropriate solution and found Ninject.Web.WebApi.OwinHost package. So in order to have a single Kernel resolving dependencies for all technologies, I've made the following changes:

in OwinStartup:

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

in NinjectWebCommon:

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

These lines were disabled to avoid initializing kernel twice.

This fixed DI for Web Api but not for SignalR. When client tries to connect to hub I get the following exception:

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

I am kind of lost. I'we read around two dozens of articles but none of them gave me the solution. Would appreciate any help.

My final aim is to have a single kernel that serves Web Api, MVC and SignalR and supports OWIN pipeline.

Edit: Since I've got a comment that my case might be a duplicate of another question I believe I need to give some explanations.

I have three scenarios.

  1. WebApi initialization in Global.asax with GlobalConfiguration.Configure(WebApiConfig.Register), Ninject initialization with NinjectWebCommon and Bootstrapper.

    This gives me both injection in WebApi and SignalR. But since I would like to move WebApi initialization to OWIN startup this approach is obsolete.

  2. WebApi initialization with OWIN Startup, Ninject initialization with NinjectWebCommon and Bootstrapper.

    SignalR injection works, WebApi injection does not work.

  3. WebApi initialization with OWIN Startup, Ninject initialization with UseNinjectMiddleware, UseNinjectWebApi.

    WebApi injection works, SignalR injection does not work.

So basically I need to figure out how to put this together so that both WebApi and SignalR injection work when I initialize WebApi on OWIN pipeline.

The code for NinjectWebCommon is in original question below. It contains code for creating SignalR resolver but it does not help in scenario 3.

Edit 2: After a couple of hours of trial and error method I came to a conclusion that calling

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

conflicts with this call:

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

So problem description narrows to this. When I use the following pattern SignalR stops working:

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

But if I comment the line

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

SignalR starts working again. But no injection inside hubs of course.


回答1:


Finally I managed to get the working Ninject configuration that supports OWIN pipe, WebApi, MVC and SignalR.

By the time when I posted the question I had a work-around (which was disabling DI in SignalR hubs) so I decided to waste no more time on this and moved on.

But when I tried running OWIN in-memory Test Server with my Startup class it occurred that DI was not working. CreateKernel method was called too late which resulted in creating several instances of an object that was used in sengleton scope.

After playing with different variations of initialization I've made DI work for OWIN Test Server and it also fixed the SignalR DependencyResolver.

The Solution:

I stopped using packages Ninject.Web.Common.OwinHost and Ninject.Web.WebApi.OwinHost so these calls were removed from my Configuration method:

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

Instead I do the following:

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



回答2:


in order to use a dependency resolver for both WebApi and SignalR you need to implement a class that looks like this:

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

then in your startup class you should register NinjectDependencyResolver for both WebApi and SignalR, like this:

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



回答3:


SignalR must be configured after your dependency injection configuration. So, in your OWIN Startup class, make sure that app.MapSignalR() is called after setting the MVC dependency resolver (System.Web.MVC.DependencyResolver), the WebApi dependency resolver (System.Web.Http.GlobalConfiguration.Configuration.DependencyResolver) and the SignalR dependency resolver (Microsoft.AspNet.SignalR.GlobalHost.DependencyResolver).



来源:https://stackoverflow.com/questions/29824596/ninject-with-web-api-signalr-mvc-and-owin

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!