OWIN + SignalR + Autofac

OWIN + SignalR + Autofac

取自:http://docs.autofac.org/en/latest/integration/signalr.html:

"A common error in OWIN integration is use of the GlobalHost. In OWIN you create the configuration from scratch. You should not reference GlobalHost anywhere when using the OWIN integration."

听起来很有道理。但是,应该如何从 ApiController 解析 IHubContext,就像通常的(非 OWIN):

GlobalHost.ConnectionManager.GetHubContext<MyHub>()?

我在任何地方都找不到关于这个的参考,我现在唯一的方法是在同一个容器中注册 HubConfiguration 实例并执行此操作:

public MyApiController : ApiController {
  public HubConfiguration HubConfig { get; set; } // Dependency injected by
                                                  // PropertiesAutowired()

  public IHubContext MyHubContext { 
    get { 
      return HubConfig
        .Resolver
        .Resolve<IConnectionManager>()
        .GetHubContext<MyHub>(); 
     } 
  }

  // ...

}

但是,这对我来说似乎很冗长。正确的做法是什么?更具体地说,有没有一种干净的方法来注册IConnectionManager

编辑:

我最后做的是这样的:

var container = builder.Build();
hubConfig.Resolver = new AutofacDependencyResolver(container); 

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

var builder2 = new ContainerBuilder();
builder2
  .Register(ctx => hubConfig.Resolver.Resolve<IConnectionManager>())
  .As<IConnectionManager>();

builder2.Update(container);

但我觉得一定有一种更简单的方法可以将 IConnectionManager 注入控制器。

你可以做的是将一些重复代码(我假设 IHubContext 也用在其他一些 类 中,并且以相同的方式获取)移动到容器注册中。

首先是注册 IHubContext 个实例,我假设您在项目中有多个集线器。在这种情况下,服务必须注册为 named services.

builder
    .Register<IHubContext>(c => c.Resolve<IConnectionManager>().GetHubContext<MyHub>())
    .Named<IHubContext>("MyHub");
想要使用 IHubContext

类 现在可以将其作为构造函数参数或 属性 接收。但是我们必须告诉容器它应该注入哪个实例。这可以在容器配置中以多种方式完成

构造函数可以使用ResolvedParameter来正确selectIHubContext实现

// example class 
public class SampleClass {
    public SampleClass(IHubContext context) { }
}

// and registration for this class
builder.RegisterType<SampleClass>()
    .WithParameter(new ResolvedParameter((pi, ctx) =>
    {
        // only apply this to parameters of IHubContext type
        return pi.ParameterType == typeof(IHubContext);
    }, (pi, ctx) =>
    {
        // resolve context
        return ctx.ResolveNamed<IHubContext>("MyHub");
    }));

属性注入,也是有点取巧。需要在 OnActivated 回调中解析正确的实例,例如:

// example class
public class SampleClass2
{
    public IHubContext Context { get; set; }
}

// registration for this case
builder.RegisterType<SampleClass2>()
    .PropertiesAutowired()
    .OnActivated(e => e.Instance.Context = e.Context.ResolveNamed<IHubContext>("MyHub"));

我做了与你类似的事情,这让它在 Owin 中对我有用

builder.RegisterInstance(config.Resolver).As<IDependencyResolver>();
builder.Update(container);

然后用它来获取我的集线器

Resolve<IDependencyResolver>().Resolve<IConnectionManager>().GetHubContext<MyHub>();

希望这对其他人有帮助

我能找到的最简单的解决方案是以某种方式将此处的答案混合在一起,但对我来说似乎是处理此问题并保持 SignalR 和 Autofac SignalR 集成最佳实践的最佳方法:

在我想要集线器上下文的 类 中,我有一个 属性

 public IConnectionManager ConnectionManager { get; set; }

我注册如下:

 newBuilder.RegisterInstance(resolver.Resolve<IConnectionManager>());

其中 resolvernew AutofacDependencyResolver(container);

然后,我基本上使用与GlobalHost非常相似的ConnectionManager

var context = ConnectionManager.GetHubContext<WorkshopsHub>();

那我打电话给context.Clients.All.clientMethod();

这样我就可以轻松地从中心外部更新客户端,拥有易于维护的代码并遵循最佳实践(我认为 hope:D)。

我也想过在Startup上注册并解决,但是这似乎是一个非常困难的任务,而且没有什么好处(除了成功时感觉很好)。

希望对您有所帮助!祝你好运!

这个答案有点迟了,但这里是。

  • 我推荐强类型集线器。
  • 您需要添加具体的 强类型集线器的注册。
  • 我不使用 全球主机
    • 相反,我使用为 OWIN 创建的配置 注册.

枢纽宣言

public interface IMyHub
{
    // Any methods here for strongly-typed hubs
}

[HubName("myHub")]
public class MyHub : Hub<IMyHub>

中心注册

来自您的 Autofac 注册

// SignalR Configuration
var signalRConfig = new HubConfiguration();

var builder = // Create your normal AutoFac container here

builder.RegisterType<MyHub>().ExternallyOwned(); // SignalR hub registration

// Register the Hub for DI (THIS IS THE MAGIC LINE)
builder.Register(i => signalRConfig.Resolver.Resolve<IConnectionManager>().GetHubContext<MyHub, IMyHub>()).ExternallyOwned();

// Build the container
var container = builder.Build();

// SignalR Dependency Resolver
signalRConfig.Resolver = new Autofac.Integration.SignalR.AutofacDependencyResolver(container);

app.UseAutofacMiddleware(container);
app.MapSignalR("/signalr", signalRConfig);

正在后台代码解析hub

使用 AutoFacs AutowiredProperties() 扩展方法然后它可以解析正确的上下文(如果你愿意,也可以在构造函数中)。

public IHubContext<IMyHub> InstanceHubContext { get; [UsedImplicitly] set; }

我做了类似于 on the question 的事情。

由于我 运行 在 IIS 站点或自托管站点中,所以我 运行 进入了另一个问题。我在共享 dll 中创建了所有集线器和控制器,然后引用了该 dll。 这导致 Autofac 的 SignalR IAssemblyLocator 无法带回所需的程序集。所以我在容器中注册了DefaultAssemblyLocator

剩下的就是确保:

  • Autofac.Integration.SignalR.AutofacDependencyResolver 被解析为单例并用作 HubConfiguration.Resolver
  • Microsoft.AspNet.SignalR.Infrastructure.ConnectionManager 被解析为单例并在需要的地方注入 - 在这个例子中的 MessageService

Here is a gist 包含完整的所需文件并评论所需的 NuGet 包安装

工作示例如下:

public class ServiceModule : Module
{
    protected override void Load(ContainerBuilder builder)
    {
        // What ever registrations you need here

        // IMessageService interacts with the hub
        builder.RegisterType<MessageService>().As<IMessageService>();

        // Register the controllers and the hubs
        builder.RegisterApiControllers(typeof(ServiceModule).Assembly);
        builder.RegisterHubs(typeof(ServiceModule).Assembly);

        // Register the default Assembly Locator since otherwise the hub will no be created by Signalr correctly IF it is NOT in the entry executables assembly.
        builder.RegisterType<DefaultAssemblyLocator>().As<IAssemblyLocator>();

        // Register Autofac resolver into container to be set into HubConfiguration later
        builder.RegisterType<Autofac.Integration.SignalR.AutofacDependencyResolver>()
            .As<Microsoft.AspNet.SignalR.IDependencyResolver>()
            .SingleInstance();

        // Register ConnectionManager as IConnectionManager so that you can get
        // hub context via IConnectionManager injected to your service
        builder.RegisterType<Microsoft.AspNet.SignalR.Infrastructure.ConnectionManager>()
            .As<Microsoft.AspNet.SignalR.Infrastructure.IConnectionManager>()
            .SingleInstance();
    }
}

public class Startup
{
    /// <summary>
    /// Perform the configuration 
    /// </summary>
    /// <param name="app">The application builder to configure.</param>
    public void Configuration(IAppBuilder app)
    {
        var builder = new ContainerBuilder();
        builder.RegisterModule(new ServiceModule());
        var container = builder.Build();

        var config = new HttpConfiguration
        {
            DependencyResolver = new AutofacWebApiDependencyResolver(container),
#if DEBUG
            IncludeErrorDetailPolicy = IncludeErrorDetailPolicy.Always,
#endif
        };

        app.UseAutofacMiddleware(container);
        app.UseAutofacWebApi(config);
        app.UseWebApi(config);

        var hubConfig = new HubConfiguration()
        {
#if DEBUG
            EnableDetailedErrors = true,
#endif
        };
        hubConfig.Resolver = container.Resolve<Microsoft.AspNet.SignalR.IDependencyResolver>();

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

public interface IMessageService
{
    void BroadcastMessage(string message);
}

public class MessageService : IMessageService
{
    private readonly IHubContext _hubContext;

    public MessageService(IConnectionManager connectionManager)
    {
        _hubContext = connectionManager.GetHubContext<MessageHub>();
    }

    public void BroadcastMessage(string message)
    {
        _hubContext.Clients.All.Message(message);
    }
}

public interface IMessage
{
    void Message(string message);
}

public class MessageHub : Hub<IMessage>
{
    private readonly ILifetimeScope _scope;

    public MessageHub(ILifetimeScope scope)
    {
        _scope = scope;
    }

    public void Message(string message)
    {
        Clients.Others.Message(message);
    }

    public override Task OnConnected()
    {
        Clients.Others.Message("Client connected: " + Context.ConnectionId);
        return base.OnConnected();
    }

    public override Task OnDisconnected(bool stoppedCalled)
    {
        Clients.Others.Message("Client disconnected: " + Context.ConnectionId);
        return base.OnDisconnected(stoppedCalled);
    }
}