使用 Autofac 将 class 的单例实例注入 SignalR Hub

Injecting Singleton Instance of class into SignalR Hub using Autofac

我正在创建一个应用程序,其中 SignalR 用于将实时推文广播到地图。我正在使用 C# Tweetinvi 库 (tweetinvi.codeplex.com) 来处理与连接到 Twitter 流相关的所有逻辑 API。

推特API规定任何时候只能对推特开放一个流媒体连接。当我使用 SignalR 时,Streaming 连接和 Hub class 之间存在依赖关系。我知道 Hub class 是瞬态的,这意味着每次客户端请求它时都会创建它,所以我需要确保我的 Twitter Stream class 实例注入到 Hub class 是单例,或者至少 IFilteredStream 在应用程序的生命周期中只创建一次。这是连接到 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; }
        }

    }

IFilteredStream 接口公开了如下所示的 lambda 方法,它允许实时接收推文,我希望能够从我的 SignalR Hub class:

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

可以找到此方法的来源here

我已尝试实施 Autofac,似乎与 Twitter API 的连接发生了,但没有发生更多事情。我试过对此进行调试,但不确定如何使用依赖注入来调试这种情况。我的 Hub class 目前看起来像这样:

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

最后,我的 OWIN Startup class,我在其中使用 Autofac 注册了我的依赖项和 Hub:

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

抱歉,如果这个问题有点乱,我很难理解我需要实现什么样的架构才能让它正常工作!接受关于如何改进或应该如何完成的建议/建议!

IMO 这行不通,因为您正在连接事件以调用特定集线器实例的上下文,而不管与 Autofac 相关的任何代码(这也可能有问题,但我不是这方面的专家) . 每次发生新连接或从客户端调用方法时都会调用集线器的构造函数,因此:

  • 您可能会为每个客户多次订阅该事件。我不知道你用的是哪个 Twitter API,但在这张纸条上,你一直打电话给 i.StartStreamMatchingAllConditions() 对我来说似乎是错误的
  • 每次你在事件处理程序中 that 实例的 Clients 成员上创建一个闭包时,它应该在 hub 被销毁时消失(所以可能你正在泄漏内存)

鉴于您正在呼叫 Client.All,因此这是一个独立于任何特定呼叫者的纯广播,您需要做的是:

  • TwitterStream 服务的构造函数中初始化您的 Twitter 连接
  • 在同一个地方(也许有一些间接的,但可能没有必要)获取你的 TwitterHub
  • hub context 的实例
  • 订阅事件并使用您刚刚检索到的上下文对其进行广播

这样的构造函数可能如下所示:

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 必须存在,但如果您只需要它对 all 进行这种广播,则无需特殊代码来监视连接或处理客户端-生成的调用,它很可能是空的,你的实际与集线器相关的代码位于它之外并使用 IHubContext 来广播消息就好了。每次推文到达时,这样的代码将负责处理所有现有连接的客户端,因此无需跟踪它们。

当然,如果您对实际单独处理客户有更多要求,那么事情可能需要有所不同,但您的代码并没有让我有其他想法。