Injizieren Sie SignalR IHubContext mit Autofac in die Service-Schicht
In einer App, in der Framework 4.72 und nicht .NET Core ausgeführt wird, versuche ich, einen SignalR IHubContext in einen Web API 2.x-Dienst einzufügen. Ich habe meine Lösung in drei Projekte unterteilt: Web, Service, Daten. Der SignalR-Hub befindet sich in der Webschicht. Ich habe Hintergrundcode, der in der Service-Schicht ausgeführt wird, und wenn er abgeschlossen ist, muss er eine Nachricht über den Hub senden. Diese Hintergrundaufgabe wird nicht von der Steuerung initiiert.
Mein Global.asax ist ziemlich Standard:
protected void Application_Start()
{
GlobalConfiguration.Configure(WebApiConfig.Register);
// Set JSON serializer to use camelCase
var json = GlobalConfiguration.Configuration.Formatters.JsonFormatter;
json.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
DIConfig.Setup();
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
var logConfigFilePath = Server.MapPath("~/log4net.config");
log4net.Config.XmlConfigurator.ConfigureAndWatch(new System.IO.FileInfo(logConfigFilePath));
}
Meine DIConfig enthält:
internal static void Setup()
{
var config = System.Web.Http.GlobalConfiguration.Configuration;
var builder = new ContainerBuilder();
builder.Register(c => new ShopAPDbContext()).AsImplementedInterfaces().InstancePerBackgroundJob().InstancePerLifetimeScope();
builder.RegisterType<ShopAPRepository>().As<IShopAPRepository>().InstancePerBackgroundJob().InstancePerLifetimeScope();
builder.RegisterType<ShopAPService>().As<IShopAPService>().InstancePerBackgroundJob().InstancePerLifetimeScope();
builder.AddAutoMapper(typeof(InvoiceMappingProfile).Assembly);
builder.RegisterApiControllers(Assembly.GetExecutingAssembly());
var container = builder.Build();
DependencyResolver.SetResolver(new AutofacDependencyResolver(container));
config.DependencyResolver = new AutofacWebApiDependencyResolver(container);
Hangfire.GlobalConfiguration.Configuration.UseAutofacActivator(container);
}
Und meine Startup.cs:
public class Startup
{
public void Configuration(IAppBuilder app)
{
var container = DependencyConfiguration.Configure(app);
SignalRConfiguration.Configure(app, container);
HangFireDashboardConfig.Configure(app);
}
}
public static class DependencyConfiguration
{
public static IContainer Configure(IAppBuilder app)
{
var builder = new ContainerBuilder();
builder.RegisterHubs(typeof(SignalRConfiguration).Assembly);
var container = builder.Build();
app.UseAutofacMiddleware(container);
return container;
}
}
public static class SignalRConfiguration
{
public static void Configure(IAppBuilder app, IContainer container)
{
HubConfiguration config = new HubConfiguration();
config.Resolver = new AutofacDependencyResolver(container);
app.Map("/messages", map =>
{
map.UseCors(CorsOptions.AllowAll);
var hubConfiguration = new HubConfiguration
{
EnableDetailedErrors = true,
EnableJavaScriptProxies = false
};
map.RunSignalR(hubConfiguration);
});
}
}
Die Konstruktoren meiner Service-Schicht sehen folgendermaßen aus:
public ShopAPService()
{
_shopAPRepository = new ShopAPRepository();
_mapper = new Mapper((IConfigurationProvider)typeof(InvoiceMappingProfile).Assembly);
_hubContext = null; // what here?
}
public ShopAPService(IShopAPRepository shopAPRepository, IMapper mapper, IHubContext hubContext)
{
_shopAPRepository = shopAPRepository;
_mapper = mapper;
_hubContext = hubContext;
}
Ich weiß, dass ich eine Instanz des IHubContext an den Dienst übergeben muss, aber bisher war ich nicht erfolgreich. Wie ich weiß, wird der erste Konstruktor verwendet, wenn der Dienst von etwas anderem als dem Controller aufgerufen wird.
ERSTE ÜBERARBEITUNG
Ok, ich verstehe, dass alles in einen einzigen Container gehen sollte. Basierend auf dem Feedback und dem Betrachten dieser Links erstelle ich einen Container und gebe ihn weiter. Hier ist mein überarbeitetes Startup:
public class Startup
{
public void Configuration(IAppBuilder app)
{
//var config = System.Web.Http.GlobalConfiguration.Configuration;
var container = GetDependencyContainer();
RegisterWebApi(app, container);
RegisterSignalR(app, container);
GlobalHost.DependencyResolver = new AutofacDependencyResolver(container);
HubConfiguration config = new HubConfiguration();
config.Resolver = new AutofacDependencyResolver(container);
//config.DependencyResolver = new AutofacWebApiDependencyResolver(container);
Hangfire.GlobalConfiguration.Configuration.UseAutofacActivator(container);
HangFireDashboardConfig.Configure(app);
}
private IContainer GetDependencyContainer()
{
return AutofacConfig.RegisterModules();
}
private void RegisterWebApi(IAppBuilder app, IContainer container)
{
var configuration = new HttpConfiguration
{
DependencyResolver = new AutofacWebApiDependencyResolver(container)
};
WebApiConfig.Register(configuration);
app.UseAutofacMiddleware(container);
app.UseAutofacWebApi(configuration);
app.UseWebApi(configuration);
}
private void RegisterSignalR(IAppBuilder app, IContainer container)
{
var configuration = new HubConfiguration
{
Resolver = new AutofacDependencyResolver(container)
};
app.MapSignalR(configuration);
}
}
Und meine AutofacConfig erstellt den Container und führt den größten Teil der Registrierung durch:
internal class AutofacConfig
{
public static IContainer RegisterModules()
{
var builder = new ContainerBuilder();
builder.Register(c => new ShopAPDbContext()).AsImplementedInterfaces().InstancePerBackgroundJob().InstancePerLifetimeScope();
builder.RegisterType<ShopAPRepository>().As<IShopAPRepository>().InstancePerBackgroundJob().InstancePerLifetimeScope();
builder.RegisterType<ShopAPService>().As<IShopAPService>().InstancePerBackgroundJob().InstancePerLifetimeScope();
builder.RegisterAutoMapper(typeof(InvoiceMappingProfile).Assembly);
builder.RegisterApiControllers(Assembly.GetExecutingAssembly());
// Register Autofac resolver into container to be set into HubConfiguration later
builder.RegisterType<AutofacDependencyResolver>().As<IDependencyResolver>().SingleInstance();
// Register ConnectionManager as IConnectionManager so that you can get hub context via IConnectionManager injected to your service
builder.RegisterType<ConnectionManager>().As<IConnectionManager>().SingleInstance();
builder.RegisterHubs(Assembly.GetExecutingAssembly());
var container = builder.Build();
return container;
}
}
Ich kann jedoch immer noch nicht den Verweis auf den Hub in meinem Dienst abrufen (der sich in einem separaten Projekt als die API befindet, jedoch in derselben Lösung.

Meine Dienstmethode wird von HangFire aufgerufen und hat keinen Verweis auf den Hub. Wie kann ich in meinem parameterlosen Konstruktor darauf verweisen?
public partial class ShopAPService : IShopAPService
{
private static readonly ILog _log = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
readonly IShopAPRepository _shopAPRepository;
readonly IMapper _mapper;
private IHubContext _hubContext;
public ShopAPService()
{
_shopAPRepository = new ShopAPRepository();
_mapper = new Mapper((IConfigurationProvider)typeof(InvoiceMappingProfile).Assembly);
_hubContext = connectionManager.GetHubContext<MessageHub>();
}
public ShopAPService(IShopAPRepository shopAPRepository, IMapper mapper, IHubContext hubContext)
{
_shopAPRepository = shopAPRepository;
_mapper = mapper;
_hubContext = hubContext;
}
}
Antworten
Sie können nicht IHubContext
direkt auflösen . Sie können jedoch die generische Implementierung dieser Schnittstelle auflösen. Weitere Details hier und hier beschrieben .
Ich erstelle nur eine sehr einfache Implementierung von OWIN
(Stratup.cs). Ich habe das Autofac.SignalR- Nugget-Paket installiert und die Methode RegisterHubs für die Registrierung und die Methode MapSignalR für die Zuordnung verwendet. Es ist ein Standardansatz und es ist enghoy, typisierte Hub-Implementierung zu lösen.
Wenn Sie jedoch den Kontext korrekter auflösen möchten, müssen Sie zwei weitere Registrierungen hinzufügen: AutofacDependencyResolver und ConnectionManager (weitere Informationen finden Sie hier ).
Bitte überprüfen Sie das vollständige Beispiel:
using Autofac;
using Autofac.Integration.SignalR;
using Autofac.Integration.WebApi;
using Microsoft.AspNet.SignalR;
using Microsoft.Owin;
using Owin;
using System.Reflection;
using System.Web.Http;
[assembly: OwinStartup(typeof(Startup))]
namespace Sample
{
public class Startup
{
public void Configuration(IAppBuilder app)
{
var container = GetDependencyContainer();
RegisterWebApi(app, container);
RegisterSignalR(app, container);
}
private IContainer GetDependencyContainer()
{
var builder = new ContainerBuilder();
AutofacConfig.RegisterModules(builder);
builder.RegisterApiControllers(Assembly.GetExecutingAssembly());
builder.RegisterHubs(Assembly.GetExecutingAssembly());
// Register Autofac resolver into container to be set into HubConfiguration later
builder.RegisterType<AutofacDependencyResolver>().As<IDependencyResolver>().SingleInstance();
// Register ConnectionManager as IConnectionManager so that you can get hub context via IConnectionManager injected to your service
builder.RegisterType<ConnectionManager>().As<IConnectionManager>().SingleInstance();
var container = builder.Build();
return container;
}
private void RegisterWebApi(IAppBuilder app, IContainer container)
{
var configuration = new HttpConfiguration
{
DependencyResolver = new AutofacWebApiDependencyResolver(container)
};
WebApiConfig.Register(configuration);
app.UseAutofacMiddleware(container);
app.UseAutofacWebApi(configuration);
app.UseWebApi(configuration);
}
private void RegisterSignalR(IAppBuilder app, IContainer container)
{
var configuration = new HubConfiguration
{
Resolver = new AutofacDependencyResolver(container)
};
app.MapSignalR(configuration);
}
}
}
Mein Hub ist Standard und einfach:
public class MaintenanceHub : Hub
{
public MaintenanceHub(IMaintenanceLogProvider maintenanceLogProvider)
{
maintenanceLogProvider.TaskProgressStatusEvent += (s, e) => GetTaskLogStatus(e);
}
public void GetTaskLogStatus(LongTaskProgressStatus taskProgressStatus)
{
Clients.All.getTaskLogStatus(taskProgressStatus);
}
}
Nach der Registrierung können Sie den Hub-Inject-Kontext verwenden. So ähnlich (2 Optionen verfügbar, Sie können nur eine verwenden, was Sie möchten):
public AccountController(IConnectionManager connectionManager, MaintenanceHub maintenanceHub)
{
var context = connectionManager.GetHubContext<MaintenanceHub>();
var hub = maintenanceHub;
}
