简体   繁体   English

OWIN + SignalR + Autofac

[英]OWIN + SignalR + Autofac

Taken from: http://docs.autofac.org/en/latest/integration/signalr.html : 摘自: 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." “OWIN集成中的常见错误是使用GlobalHost。在OWIN中,您可以从头开始创建配置。在使用OWIN集成时,不应该在任何地方引用GlobalHost。”

That sounds reasonable. 这听起来很合理。 However, how should one resolve IHubContext from an ApiController, like the usual (non-OWIN): 但是,如何从ApiController解析IHubContext ,就像通常的(非OWIN):

GlobalHost.ConnectionManager.GetHubContext<MyHub>() ? GlobalHost.ConnectionManager.GetHubContext<MyHub>()

I can't find a reference on this one anywhere, and the only method I have by now is to register the HubConfiguration instance within the same container and do this: 我无法在任何地方找到这个参考,我现在唯一的方法是在同一个容器中注册HubConfiguration实例并执行以下操作:

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

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

  // ...

}

However, this seems quite verbose to me. 但是,这对我来说似乎相当冗长。 What is the proper way to do it? 这样做的正确方法是什么? To be more specific, is there a clean way to register IConnectionManager ? 更具体一点,是否有一种注册IConnectionManager的简洁方法?

EDIT: 编辑:

What I ended up doing is something like: 我最终做的是:

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

but I have a feeling there must be an easier way to get that IConnectionManager injected in the controller. 但我觉得必须有一种更简单的方法让IConnectionManager注入控制器。

This answer is a little belated but here goes. 这个答案有点迟了但是这里有。

  • I recommend strongly typed hubs. 我推荐强类型集线器。
  • You need to add specific registrations for the strongly typed hubs. 您需要为强类型集线器添加特定注册。
  • I don't use the GlobalHost 我不使用GlobalHost
    • Instead I use the Configuration created for OWIN registration. 相反,我使用为OWIN注册创建的配置。

Hub Declaration 枢纽声明

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

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

Hub Registration 集线器注册

From your Autofac registration 来自您的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);

Resolving the hub in background code 在后台代码中解析集线器

Using AutoFacs AutowiredProperties() extension method then it can resolve the correct context (can also be in the constructor if you like). 使用AutoFacs AutowiredProperties()扩展方法然后它可以解析正确的上下文(如果你愿意,也可以在构造函数中)。

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

What you can do is to move some of this repeating code (I assume IHubContext is also used in some other classes and it is fetched in the same way) into container registration. 你可以做的是将一些重复的代码(我假设IHubContext也用于其他一些类,并以相同的方式获取)进入容器注册。

First thing is to register IHubContext instances, I assume you have multiple hubs in project. 首先是注册IHubContext实例,我假设你在项目中有多个集线器。 I that case, services have to be registered as named services . 在这种情况下,服务必须注册为命名服务

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

Classes, which want to use IHubContext can now receive it as constructor parameter or as property. 想要使用IHubContext类现在可以作为构造函数参数或属性接收它。 But we have to tell container which instance it should inject. 但是我们必须告诉容器它应该注入哪个实例。 This can be done in container configuration, in multiple ways 这可以通过多种方式在容器配置中完成

Constructor can use ResolvedParameter to correctly select IHubContext implementation 构造函数可以使用ResolvedParameter来正确选择IHubContext实现

// 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");
    }));

Property injection, is also a bit tricky. 物业注入,也有点棘手。 It is needed to resolve correct instance in OnActivated callback, for example like this: 需要在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"));

I did similar to yourself, which got it working in Owin for me 我做了类似于你自己的事情,这让我在Owin工作

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

Then use this to get my hub 然后使用它来获取我的中心

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

Hope this helps others out there 希望这有助于其他人

The easiest solution I could find is somehow a mix between the answers here, but to me seems the best way to handle this and maintain best practices for both SignalR and Autofac SignalR Integration: 我能找到的最简单的解决方案是这里的答案之间的混合,但对我来说似乎是处理这个并保持SignalR和Autofac SignalR集成的最佳实践的最佳方法:

In the classes that I want a hub context I have a property 在我想要集线器上下文的类中,我有一个属性

 public IConnectionManager ConnectionManager { get; set; }

which I register as following: 我注册如下:

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

where resolver is a new AutofacDependencyResolver(container); resolver是一个new AutofacDependencyResolver(container);

Then, I basically use the ConnectionManager very similar to GlobalHost : 然后,我基本上使用与GlobalHost非常相似的ConnectionManager

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

then I call context.Clients.All.clientMethod(); 然后我调用context.Clients.All.clientMethod();

This way I am easily able to update clients from outside the hub, have easily maintainable code and follow the best practices (I think and hope:D). 通过这种方式,我可以轻松地从集线器外部更新客户端,具有易于维护的代码并遵循最佳实践(我认为并希望:D)。

I also thought of registering and resolving them at Startup, but it seems like a very difficult task to do, with very little benefit (other than feeling good when it succeeds). 我还考虑过在Startup上注册和解决它们,但这似乎是一项非常困难的任务,只有很少的好处(除了在成功时感觉良好)。

Hope this helps! 希望这可以帮助! Best of luck! 祝你好运!

I did something similar to this answer on the question How to configure Autofac and SignalR in a MVC 5 application . 我在如何在MVC 5应用程序中配置Autofac和SignalR的问题上做了类似的回答

Since I was running in either an IIS Site OR a self hosted site I ran into another issue. 由于我在IIS站点或自托管站点上运行,我遇到了另一个问题。 I created all the hubs and controllers in a shared dll and then referenced that dll. 我在共享dll中创建了所有集线器和控制器,然后引用了该dll。 Which caused Autofac's SignalR IAssemblyLocator to not bring back the required assemblies. 这导致Autofac的SignalR IAssemblyLocator无法恢复所需的程序集。 So I registered the DefaultAssemblyLocator in the container. 所以我在容器中注册了DefaultAssemblyLocator

The rest is making sure that: 其余的是确保:

  • Autofac.Integration.SignalR.AutofacDependencyResolver is resolved as a singlton and used as the HubConfiguration.Resolver Autofac.Integration.SignalR.AutofacDependencyResolver被解析为单元并用作HubConfiguration.Resolver
  • Microsoft.AspNet.SignalR.Infrastructure.ConnectionManager is resolved as a singlton and injected where required - in the MessageService in this example Microsoft.AspNet.SignalR.Infrastructure.ConnectionManager被解析为单个并在需要时注入 - 在此示例中的MessageService

Here is a gist with the entire required file and comment on required NuGet Package installs 以下是关于所需NuGet Package安装的完整必需文件和注释的要点

The working example follows: 工作示例如下:

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM