I am a new user of signalR and Autofac. I am using signalR with ASP.NET Core Blazor Server and receiving the error listed below from a page that tries to connect to the hub. My Hub is strongly typed (IHubContext<Hub,Interface>) and is used within an IHostedService class implementation. It has a constructor that accepts an ILogger instance.
If I remove the constructor from the Hub implementation then the error does not occur. However, the IHubContext<Hub, IHub> appears not to be despatching to the clients in either case. The log message within the SendMotionDetection method on the hub is not displayed.
The official autofac documentation recommends installing the Autofac.SignalR NuGet package for integration with signalR. However, upon installing the package it is targeted for frameworks :.NETFramework,Version=v4.6.1, .NETFramework,Version=v4.6.2, .NETFramework,Version=v4.7, .NETFramework,Version=v4.7.1, .NETFramework,Version=v4.7.2, .NETFramework,Version=v4.8. I am targeting netcoreapp3.1 developing on MacOS.
Question:
How to register a strongly typed signalR Hub in AutoFac ASP.NET Core 3.1 for the purpose of injecting IHubContext<Hub, IHub> in IHostedService or BackgroundService?
Currently, the IHubContext<Hub, IHub> injected parameter is not sending the SendMotionDetection message to all clients, i.e. the console log message from the hubs message is not displayed. Yet, no exception is being thrown???
The error
fail: Microsoft.AspNetCore.SignalR.HubConnectionHandler[1]
Error when dispatching 'OnConnectedAsync' on hub.
Autofac.Core.DependencyResolutionException: An exception was thrown while activating WebApp.Realtime.SignalR.MotionHub.
---> Autofac.Core.Activators.Reflection.NoConstructorsFoundException: No accessible constructors were found for the type 'WebApp.Realtime.SignalR.MotionHub'.
at Autofac.Core.Activators.Reflection.DefaultConstructorFinder.GetDefaultPublicConstructors(Type type)
at Autofac.Core.Activators.Reflection.DefaultConstructorFinder.FindConstructors(Type targetType)
at Autofac.Core.Activators.Reflection.ReflectionActivator.ActivateInstance(IComponentContext context, IEnumerable`1 parameters)
at Autofac.Core.Resolving.InstanceLookup.CreateInstance(IEnumerable`1 parameters)
--- End of inner exception stack trace ---
at Autofac.Core.Resolving.InstanceLookup.CreateInstance(IEnumerable`1 parameters)
at Autofac.Core.Resolving.InstanceLookup.Execute()
at Autofac.Core.Resolving.ResolveOperation.GetOrCreateInstance(ISharingLifetimeScope currentOperationScope, ResolveRequest request)
at Autofac.Core.Resolving.ResolveOperation.ResolveComponent(ResolveRequest request)
at Autofac.Core.Resolving.ResolveOperation.Execute(ResolveRequest request)
at Autofac.Core.Lifetime.LifetimeScope.ResolveComponent(ResolveRequest request)
at Autofac.ResolutionExtensions.TryResolveService(IComponentContext context, Service service, IEnumerable`1 parameters, Object& instance)
at Autofac.ResolutionExtensions.ResolveOptionalService(IComponentContext context, Service service, IEnumerable`1 parameters)
at Autofac.ResolutionExtensions.ResolveOptional(IComponentContext context, Type serviceType, IEnumerable`1 parameters)
at Autofac.ResolutionExtensions.ResolveOptional(IComponentContext context, Type serviceType)
at Autofac.Extensions.DependencyInjection.AutofacServiceProvider.GetService(Type serviceType)
at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetService[T](IServiceProvider provider)
at Microsoft.AspNetCore.SignalR.Internal.DefaultHubActivator`1.Create()
at Microsoft.AspNetCore.SignalR.Internal.DefaultHubDispatcher`1.OnConnectedAsync(HubConnectionContext connection)
at Microsoft.AspNetCore.SignalR.Internal.DefaultHubDispatcher`1.OnConnectedAsync(HubConnectionContext connection)
at Microsoft.AspNetCore.SignalR.HubConnectionHandler`1.RunHubAsync(HubConnectionContext connection)
Source code for the SignalR hub and Startup are listed below.
Within the ConfigureServices of Startup.cs, I have tried registering the SignalR Hub with autofac container registry but still getting the error. Interestingly, if I do not include a constructor for the SignalR hub the error does not occur. However, I am injecting an IHubContext into a background service and when sending a messages from the background service via the IHubContext it does not appear to be dispatching.
Interface
public interface IMotion
{
Task SendMotionDetection(MotionDetection message);
}
Hub
public class MotionHub : Hub<IMotion>
{
private ILogger<MotionHub> _logger;
MotionHub(ILogger<MotionHub> logger)
{
_logger = logger;
_logger.LogInformation("Motion SignalR Hub Created");
}
// send the motion detection event to all clients
public async Task SendMotionDetection(MotionDetection message)
{
_logger.LogInformation("MotionHub => SignalR Hub => SendMotionDetection");
await Clients.All.SendMotionDetection(message);
}
}
Startup
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public ILifetimeScope AutofacContainer { get; private set; }
// This method gets called by the runtime. Use this method to add services to the container.
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
public void ConfigureServices(IServiceCollection services)
{
services.AddRazorPages();
services.AddServerSideBlazor();
services.AddSignalR(o => o.EnableDetailedErrors = true);
services.AddHostedService<MqttListenerWorker>();
services.AddHostedService<ConsumerService>();
services.AddLogging();
}
// ConfigureContainer is where you can register things directly
// with Autofac. This runs after ConfigureServices so the things
// here will override registrations made in ConfigureServices.
// Don't build the container; that gets done for you by the factory.
public void ConfigureContainer(ContainerBuilder builder)
{
// Register your own things directly with Autofac here. Don't
// call builder.Populate(), that happens in AutofacServiceProviderFactory
// for you.
builder.RegisterModule(new MotionDetectionRepositoryModule());
builder.RegisterModule(new KafkaModule());
//builder.RegisterHubs(typeof());
builder.RegisterAssemblyTypes(typeof(MotionDetection).GetTypeInfo().Assembly);
builder.RegisterType<MotionHub>()
.AsSelf();
// builder.RegisterTypes(typeof(MotionHub).GetTypeInfo().Assembly)
// .Where(t => t.Name.EndsWith("Hub"))
// .As(typeof(Hub<MotionHub>))
// .ExternallyOwned();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
// app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapHub<MotionHub>("/motionhub");
endpoints.MapBlazorHub();
endpoints.MapFallbackToPage("/_Host");
});
}
}
IHostedService
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.SignalR;
using Microsoft.Extensions.Hosting;
using Confluent.Kafka;
using Confluent.Kafka.SyncOverAsync;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using WebApp.Data;
using WebApp.Data.Serializers.Contracts;
using WebApp.Kafka.Contracts;
using WebApp.Kafka.SchemaRegistry.Serdes;
using WebApp.Realtime.SignalR;
namespace WebApp.Kafka
{
public class ConsumerService : IHostedService, IDisposable
{
// At the time of writing Kafka Consumer isn't async so....
// making a long running background thread with a consume loop.
private Thread _pollLoopThread;
private CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();
private ConsumerConfig _consumerConfig = new ConsumerConfig();
private HashSet<string> _cameras { get; }
private string _topic;
private IHubContext<MotionHub, IMotion> _messagerHubContext;
private JsonDeserializer<MotionDetection> _serializer { get; }
private ILogger<ConsumerService> _logger;
// Using SignalR with background services:
// https://docs.microsoft.com/en-us/aspnet/core/signalr/background-services?view=aspnetcore-2.2
public ConsumerService(
IConfiguration config,
IHubContext<MotionHub, IMotion> messagerHubContext,
JsonDeserializer<MotionDetection> serializer,
ILogger<ConsumerService> logger
)
{
_logger = logger;
config.GetSection("Consumer").Bind(_consumerConfig);
// consider extension method for those settings that cannot be set in cnofig
if (_consumerConfig.EnablePartitionEof != null)
{
throw new Exception("shouldn't allow this to be set in config.");
}
_consumerConfig.EnableAutoCommit = false;
_topic = config.GetValue<string>("Topic");
_messagerHubContext = messagerHubContext;
_serializer = serializer;
_cameras = new HashSet<string>();
_cameras.Add("shinobi/group/monitor/trigger");
}
public Task StartAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("ConsumerService starting a thread to poll topic => {}...", _topic);
_pollLoopThread = new Thread(async () =>
{
try
{
var consumerBuilder = new ConsumerBuilder<string, MotionDetection>(_consumerConfig);
consumerBuilder.SetValueDeserializer(_serializer.AsSyncOverAsync());
using (var consumer = consumerBuilder.Build())
{
consumer.Subscribe(_topic);
try
{
while (!_cancellationTokenSource.IsCancellationRequested)
{
var consumerResult = consumer.Consume(_cancellationTokenSource.Token);
_logger.LogInformation("Consumer consumed message => {}", consumerResult.Message.Value);
if (_cameras.Contains(consumerResult.Message.Key))
{
// we need to consider here security for auth, only want for user
await _messagerHubContext.Clients.All.SendMotionDetection(consumerResult.Message.Value);
_logger.LogInformation("Consumer dispatched message to SignalR");
}
}
}
catch (OperationCanceledException) { }
consumer.Close();
_logger.LogInformation("Consumer closed, preparing to stop");
}
}
catch (Exception e)
{
_logger.LogCritical("Unexpected exception occurred in consumer thread");
_logger.LogError(e, "Consumer Error");
// update to take remdial action or retry to ensure consumer is available
// during lifetime
}
});
_pollLoopThread.Start();
_logger.LogInformation("Consumer thread started");
return Task.CompletedTask;
}
public async Task StopAsync(CancellationToken cancellationToken)
{
await Task.Run(() =>
{
_cancellationTokenSource.Cancel();
_pollLoopThread.Join();
});
_logger.LogInformation("Consumer stopped...");
}
public void Dispose()
{
_logger.LogInformation("Consumer disposed");
}
}
}