Search code examples
c#asp.net-mvcsignalrautofactweetinvi

Injecting Singleton Instance of class into SignalR Hub using Autofac


I am creating an application where SignalR is used to broadcast real-time tweets to a map. I am using the C# Tweetinvi library (tweetinvi.codeplex.com) to handle all of the logic associated with connecting to the Twitter Streaming API.

The Twitter API specifies that only one streaming connection can be open to Twitter at any time. As I am using SignalR, there is a dependency between the Streaming connection and the Hub class. I know that the Hub class is transient, meaning that it is created each time a client requests it, so I need to ensure that the instance of my Twitter Stream class injected into the Hub class is a singleton, or at least IFilteredStream is only created once in the lifetime of the application. Here is the boilerplate code to connect to the API:

public class TweetStream
    {
        private IFilteredStream _stream;
        public TweetStream()
        {
            var consumerKey = ConfigurationManager.AppSettings.Get("twitter:ConsumerKey");
            var consumerSecret = ConfigurationManager.AppSettings.Get("twitter:ConsumerSecret");

            var accessKey = ConfigurationManager.AppSettings.Get("twitter:AccessKey");
            var accessToken = ConfigurationManager.AppSettings.Get("twitter:AccessToken");

            TwitterCredentials.SetCredentials(accessKey, accessToken, consumerKey, consumerSecret);

            _stream = Stream.CreateFilteredStream();

        }
        // Return singular instance of _stream to Hub class for usage.
        public IFilteredStream Instance
        {
            get { return _stream; }
        }

    }

The IFilteredStream interface exposes a lambda method as below which allows for receiving Tweets in real-time, which I would like to be able to access from within my SignalR Hub class:

_stream.MatchingTweetReceived += (sender, args) => {
        Clients.All.broadcast(args.Tweet);
};

The source for this method can be found here

I've tried to implement Autofac, and it seems that the connection to the Twitter API happens, however nothing more happens. I've tried to debug this, but am unsure how to debug such a scenario using dependency injection. My Hub class currently looks like this:

public class TwitterHub : Hub
{
    private readonly ILifetimeScope _scope;
    private readonly TweetStream _stream;

    // Inject lifetime scope and resolve reference to TweetStream
    public TwitterHub(ILifetimeScope scope)
    {
        _scope = scope.BeginLifetimeScope();

        _stream = scope.Resolve<TweetStream>();

        var i = _stream.Instance;

        _stream.MatchingTweetReceived += (sender, args) => {
            Clients.All.broadcast(args.Tweet);
        };

        i.StartStreamMatchingAllConditions();
    }
}

And finally, my OWIN Startup class, where I register my dependencies and Hub with Autofac:

[assembly: OwinStartup(typeof(TwitterMap2015.App_Start.OwinStartup))]

namespace TwitterMap2015.App_Start
{
    public class OwinStartup
    {
        public void Configuration(IAppBuilder app)
        {
            var builder = new ContainerBuilder();

            // use hubconfig, not globalhost
            var hubConfig = new HubConfiguration {EnableDetailedErrors = true};

            builder.RegisterHubs(Assembly.GetExecutingAssembly()); // register all SignalR hubs

            builder.Register(i => new TweetStream()).SingleInstance(); // is this the correct way of injecting a singleton instance of TweetStream?

            var container = builder.Build();

            hubConfig.Resolver = new AutofacDependencyResolver(container);

            app.MapSignalR("/signalr", hubConfig);
        }
    }
}

Sorry if this question is a bit of a mess, I'm having a hard time of understand what kind of architecture I need to implement to get this working! Open to advice / recommendations on how this could be improved, or how it should be done!


Solution

  • IMO this cannot work because you are wiring your event to call over the context of a specific hub instance, regardless of any code related to Autofac (which might have issues too but I'm not a big expert on it). Your hub's constructor will be called each time a new connection happens or a method is called from a client, so:

    • you are subscribing that event potentially multiple times per client. I don't know the Twitter API you are using, but on this note the fact that you call i.StartStreamMatchingAllConditions() all these times seems wrong to me
    • each time you create a closure over the Clients member of that instance in your event handler, which is supposed to go away when the hub is destroyed (so probably you are leaking memory)

    What you need to do, given that your are calling over Client.All, and therefore this is a pure broadcast independent on any specific caller, is:

    • initialize your Twitter connection in the constructor of your TwitterStream service
    • in that same place (maybe with some indirection, but probably not necessary) take an instance of the hub context of your TwitterHub
    • subscribe to the event and use the context you just retrieved to broadcast over it

    Such constructor might look like this:

    public service TwitterStream : ??? <- an interface here?
    {
        ...
    
        public TwitterStream (ILifetimeScope scope ??? <- IMO you don't need this...)
        {
            //Autofac/Twitter stuff
            ...
    
            var context = GlobalHost.DependencyResolver.GetHubContext<TwitterHub>();
    
            _stream.MatchingTweetReceived += (sender, args) => {
                context.Clients.All.broadcast(args.Tweet);
            };
    
            //maybe more Autofac/Twitter stuff
            ...
        }
    
        ...
    }
    

    TwitterHub must exist, but in case you just need it to do this kind of broadcast to all, with no special code needed to monitor connections or handle client-generated calls, it could well be empty and it's just fine that your actual hub-related code lives outside of it and uses a IHubContext to broadcast messages. Such a code would take care of handling all the existing connected clients each time a tweet arrives, so no need to track them.

    Of course if you have more requirements for actually handling clients separarely, then things might need to be different, but your code does not make me think otherwise.