5

With SignalR 2.0 in a self-hosted application, from these instructions you have something like this:

class Startup
{
   public void Configuration(IAppBuilder app)
   {
       app.MapSignalR(new HubConfiguration { Resolver = ... });
   }
}
class Program
{
    static void Main(string[] args)
    {
        using (WebApp.Start("http://localhost:8080")) // constructs Startup instance internally
        {
            Console.WriteLine("Server running on {0}", url);
            Console.ReadLine();
        }
    }
}

You will notice that the Startup class instance is created with some behind-the-scenes magic. I can't figure out how to fill in dependencies on it. Is there some way to override the construction of the Startup class so that I can inject dependencies into it?

Kevin Brechbühl
  • 4,717
  • 3
  • 24
  • 47
Brannon
  • 5,324
  • 4
  • 35
  • 83

3 Answers3

3

Instead of replacing the IAppActivator, you can simply register Startup's constructor arguments with Katana's ServiceProvider.

The default IAppActivator will resolve any services matching the Startup constructor's argument types for you. The only downside is you can't use WebApp.Start, since that doesn't expose the ServiceProvider:

public class MyService : IMyService
{
    private readonly IMyOtherService _myOtherService;

    // Services will be recursively resolved by Katana's ServiceProvider
    public MyService(IMyOtherService myOtherService)
    {
        _myOtherService = myOtherService;
    }

    // Implementation
}

public class Startup
{
   private readonly IMyService _myService;

   // Startup must have exactly one constructor.
   public Startup(IMyService myService)
   {
       _myService = myService
   }

   public void Configuration(IAppBuilder app)
   {
       app.MapSignalR(new HubConfiguration { Resolver = ... });
   }
}

using System;
using Microsoft.Owin.Hosting;
using Microsoft.Owin.Hosting.Services;
using Microsoft.Owin.Hosting.Starter;

public class Program
{
    static void Main(string[] args)
    {
        var url = "http://localhost:8080";

        var services = (ServiceProvider)ServicesFactory.Create();
        var options = new StartOptions(url);

        services.Add<IMyOtherService, MyOtherService>();
        services.Add<IMyService, MyService>();

        var starter = services.GetService<IHostingStarter>();

        using (starter.Start(options)) // constructs Startup instance internally
        {
            Console.WriteLine("Server running on {0}", url);
            Console.ReadLine();
        }
    }
}

I copied the default implementation of WebApp.Start into Program.Main, but instead of calling IHostingStarter.Start immediately, I add custom services first: http://katanaproject.codeplex.com/SourceControl/changeset/view/c726b87e90c05677a256ca1821bac481f402d6bd#src/Microsoft.Owin.Hosting/WebApp.cs

There are a bunch of other overloads for ServiceProvider.Add if you need them: http://msdn.microsoft.com/en-us/library/microsoft.owin.hosting.services.serviceprovider(v=vs.111).aspx

This should be much simpler than replacing Katana's IAppActivator using StartOptions.Settings like I suggest in my previous answer.

I am leaving my previous answer up, however, as it does explain in more detail how the Startup class is constructed and how to replace default service implementations using the Settings dictionary.

halter73
  • 15,059
  • 3
  • 49
  • 60
  • I had to cast `ServicesFactory.Create()` to ServiceProvider to get the Add methods. Was that expected? – Brannon Jan 28 '14 at 20:55
  • You're absolutely right. I updated my example to cast the return value from `ServicesFactory.Create()` to a `ServiceProvider`. You can tell this is safe by looking at the implementation of `Create`: http://katanaproject.codeplex.com/SourceControl/changeset/view/c726b87e90c05677a256ca1821bac481f402d6bd#src/Microsoft.Owin.Hosting/Services/ServicesFactory.cs – halter73 Jan 28 '14 at 23:44
  • 2
    This is hugely overcomplicated. Move the stuff in the Startup class into a lambda function like this: `WebApp.Start(baseAddress, app => { app.MapSignalR(new HubConfiguration { Resolver = ... }); }`. Then you don't need to inject things into the startup class, you can inject them into the class that called WebApp.Start, and reference them via the closure. – Mark Jan 22 '15 at 14:38
2

Checkout the dependency injection information here: http://www.asp.net/signalr/overview/signalr-20/extensibility/dependency-injection

Should have everything you need to know :)

Hope this helps!

N. Taylor Mullen
  • 18,061
  • 6
  • 49
  • 72
  • 1
    I have already read through that particular document. Alas, it only applies to IIS and not self-hosted stuff. You'll notice that the method they use `App.MapSignalR(config)` does not exist when self-hosting. – Brannon Jan 27 '14 at 18:57
  • Read through it again... Look right above the IoC Containers section – N. Taylor Mullen Jan 27 '14 at 19:14
  • Right above the IoC Containers section is a discussion of injection into Hub classes. I don't have a problem there. I want to inject the hub injector. – Brannon Jan 27 '14 at 19:23
  • @Brannon `App.MapSignalR(config)` absolutely does exist when self hosting! You can replace SignalR's IAssemblyLocator, IHubDescriptorProvider and/or IHubActivator if you want to "inject the hub injector". I give a brief explanation of the purposes of those three interfaces here: http://stackoverflow.com/questions/21111436/signalr-with-ioc-castle-windsor-which-lifetime-for-hubs/21126475#21126475 – halter73 Jan 27 '14 at 20:32
  • @halter73, can you tell me where in the code the Startup class is constructed? And I still don't believe that App == WebApp. – Brannon Jan 27 '14 at 21:04
0
class Startup
{
   private readonly IDependencyResolver _resolver;

   public Startup(IDependencyResolver resolver)
   {
        _resolver = resolver;
   }

   public void Configuration(IAppBuilder app)
   {
       app.MapSignalR(new HubConfiguration { Resolver = _resolver; });
   }
}

class Program
{
    static void Main(string[] args)
    {
        Startup startup = new Statrtup(new MyResolver());
        using (WebApp.Start("http://localhost:8080", startup.Configuration))
        {
            Console.WriteLine("Server running on {0}", url);
            Console.ReadLine();
        }
    }
}