可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
Taken from: 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."
That sounds reasonable. However, how should one resolve IHubContext
from an ApiController, like the usual (non-OWIN):
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:
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
?
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.
回答1:
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
- Instead I use the Configuration created for OWIN
registration.
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
// 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).
public IHubContext<IMyHub> InstanceHubContext { get; [UsedImplicitly] set; }
回答2:
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.
First thing is to register IHubContext
instances, I assume you have multiple hubs in project. 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. 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
// 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:
// 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"));
回答3:
I did similar to yourself, which got it working in Owin for me
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
回答4:
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:
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);
Then, I basically use the ConnectionManager
very similar to GlobalHost
:
var context = ConnectionManager.GetHubContext<WorkshopsHub>();
then I call 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).
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).
Hope this helps! Best of luck!
回答5:
I did something similar to this answer on the question How to configure Autofac and SignalR in a MVC 5 application.
Since I was running in either an IIS Site OR a self hosted site I ran into another issue. I created all the hubs and controllers in a shared dll and then referenced that dll.
Which caused Autofac's SignalR IAssemblyLocator
to not bring back the required assemblies. So I registered the DefaultAssemblyLocator
in the container.
The rest is making sure that:
Autofac.Integration.SignalR.AutofacDependencyResolver
is resolved as a singlton and used as the HubConfiguration.Resolver
Microsoft.AspNet.SignalR.Infrastructure.ConnectionManager
is resolved as a singlton and injected where required - in the MessageService
in this example
Here is a gist with the entire required file and comment on required NuGet Package installs
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);
}
}