Как зарегистрировать строго типизированный концентратор signalR в AutoFac для внедрения IHubContext в IHostedService или BackgroundService

Я новый пользователь signalR и Autofac. Я использую signalR с сервером ASP.NET Core Blazor и получаю указанную ниже ошибку со страницы, которая пытается подключиться к концентратору. Мой концентратор строго типизирован (IHubContext ‹Hub, Interface›) и используется в реализации класса IHostedService. У него есть конструктор, который принимает экземпляр ILogger.

Если я удалю конструктор из реализации Hub, ошибки не возникнет. Однако IHubContext ‹Hub, IHub›, похоже, не отправляет клиентам ни в том, ни в другом случае. Сообщение журнала в методе SendMotionDetection на концентраторе не отображается.

Официальная документация autofac рекомендует установить Autofac.SignalR Пакет NuGet для интеграции с signalR. Однако после установки пакет предназначен для фреймворков: .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. Я нацелен на netcoreapp3.1 разработку на MacOS.

Вопрос:

Как зарегистрировать строго типизированный концентратор signalR в AutoFac ASP.NET Core 3.1 с целью внедрения IHubContext ‹Hub, IHub› в IHostedService или BackgroundService?

В настоящее время введенный параметр IHubContext ‹Hub, IHub› не отправляет сообщение SendMotionDetection всем клиентам, т.е. сообщение журнала консоли от сообщения концентраторов не отображается. Тем не менее, никаких исключений не выбрасывается ???

Ошибка

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)

Исходный код для концентратора SignalR и запуска приведен ниже. В течение ConfigureServices от Startup.cs я попытался зарегистрировать концентратор SignalR в реестре контейнеров autofac, но все равно получаю сообщение об ошибке. Интересно, что если я не включу конструктор для концентратора SignalR, ошибки не возникнет. Однако я внедряю IHubContext в фоновую службу, и при отправке сообщений из фоновой службы через IHubContext он, похоже, не отправляется.

Интерфейс

public interface IMotion
{
    Task SendMotionDetection(MotionDetection message);
}

Хаб

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

Запуск

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

person dcs3spp    schedule 17.09.2020    source источник


Ответы (1)


Думаю, я решил это.

Реализация методов в классе Hub вызывается из клиент- ›сервера, поэтому я никогда не увижу вывода из этого, потому что в этом случае сервер отправляет запрос клиенту.

На данный момент я изменил параметр на метод в интерфейсе IMotion, чтобы он был строкой, и обновил код на клиентской странице Blazor, чтобы отразить строковый параметр.

Я также удалил код, который вводит Hub в autofac. Я подозреваю, что этим занимается Microsoft DI автоматически ???

Я думаю, что проблема могла быть в сериализации / десериализации объекта.

Я включил приведенный ниже код для страницы с блейзером.

Следующим шагом является разработка того, как сериализовать / десериализовать объект через соединение signalR, а также подключиться к signalRHub после того, как страница была отрисована, а не когда она была инициализирована (выполняется дважды!).

Страница Blazor

@page "/"

@using System.Threading
@using System.Collections.Generic;
@using Microsoft.AspNetCore.SignalR.Client
@inject NavigationManager NavigationManager

@using WebApp.Data


<h1>Blazor Server App</h1>

<div>Latest message is => @_latestMessage</div>

<div id="scrollbox">
    @foreach (var item in _messages)
    {
        <div>
            <div>@item</div>
        </div>
    }
    <hr />
</div>


@code {
    private HubConnection hubConnection;
    private string _latestMessage = "";
    private List<string> _messages = new List<string>();
    public bool IsConnected => hubConnection.State == HubConnectionState.Connected;

    protected override async Task OnInitializedAsync()
    {
        var hubUrl = NavigationManager.BaseUri.TrimEnd('/') + "/motionhub";
        // Uri uri = NavigationManager.ToAbsoluteUri("/motionhub");

        try
        {
            hubConnection = new HubConnectionBuilder()
                .WithUrl(hubUrl)
                .Build();

            hubConnection.On<string>("SendMotionDetection", ReceiveMessage);

            await hubConnection.StartAsync();

            Console.WriteLine("Index Razor Page initialised, listening on signalR hub url => " + hubUrl.ToString());
            Console.WriteLine("Hub Connected => " + IsConnected);
        }
        catch (Exception e)
        {
            Console.WriteLine("Encountered exception => " + e);
        }
    }
   
    private void ReceiveMessage(string message)
    {
        try
        {
            Console.WriteLine("Hey! I received a message");
            _latestMessage = message;
            _messages.Add(_latestMessage);

            StateHasChanged();
        }
        catch (Exception ex)
        {
            Console.Error.WriteLine("An exception was encountered => " + ex.ToString());
        }
    }
}
person dcs3spp    schedule 18.09.2020