Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Ninject with Web Api, SignalR, MVC and OWIN

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.

like image 756
user1921819 Avatar asked Apr 23 '15 13:04

user1921819


2 Answers

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();
    }        
}
like image 186
user1921819 Avatar answered Oct 21 '22 04:10

user1921819


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);
}
like image 39
hmd.ai Avatar answered Oct 21 '22 04:10

hmd.ai