Получение контекста концентратора для ASPNet.Core Signal-R (.NET Core 2.1) RC

Я использую ASP.NET Core 2.1 RC1. Я также использую для этого Signal-R (здесь):

https://docs.microsoft.com/en-us/aspnet/core/signalr/javascript-client?view=aspnetcore-2.1

Я создаю консольное приложение .NET Core, в котором размещается Kestrel и используется Signal-R. Я в значительной степени настроил его точно, как указано в документации по началу работы для настройки Startup.

Все это отлично работает. Я могу подключиться к нему, получить свой HTML-код со сценарием signal-R в нем, получать сообщения, созданные с помощью Clients.All.SendAsync. Работает отлично.

НО Я хочу иметь возможность отправлять сообщения клиентам за пределами Хаба. Когда в моем приложении происходит какое-то событие, и сообщение отправляется клиентам. В полной версии .NET я бы использовал GlobalHost и получил контекст. В ВСЕ мои поисковые запросы в Stack Overflow они ссылаются на то, что больше не работает или используется в контроллере REST API, переданном в IHubContext.

У меня есть прослушиватель событий в моем файле program.cs, и когда событие запускается, я хотел бы иметь возможность отправлять сообщение на свой UserInterfaceHub.

Итак - как мне получить контекст концентратора в Program.CS - чтобы я мог отправлять ему сообщения (вызывать метод SwitchUI) из делегата события, который у меня есть в Program.CS?

StartUp.cs

public void ConfigureServices(IServiceCollection services)  {
    services.Configure<CookiePolicyOptions>(options => {
        options.CheckConsentNeeded      = context => true;
        options.MinimumSameSitePolicy   = SameSiteMode.None;
    });
    services.AddMvc();

    services.AddCors(options => options.AddPolicy("CorsPolicy",
        builder =>  {builder.AllowAnyMethod().AllowAnyHeader().AllowAnyOrigin().AllowCredentials();}));
    services.AddSignalR();

    var provider = services.BuildServiceProvider();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env) {
    if (env.IsDevelopment())    app.UseDeveloperExceptionPage();

    app.UseStaticFiles();
    app.UseCookiePolicy();
    app.UseCors("CorsPolicy");
    app.UseSignalR(routes => {routes.MapHub<UserInterfaceHub>("/uihub");});
    app.UseMvc();



    //app.Run(async (context) =>{await context.Response.WriteAsync("Active");});
}

Program.CS

    CreateWebHostBuilder(args)
        .UseKestrel()
        .UseUrls("http://0.0.0.0:" + appProperties.HostPort.ToString().Trim())
        .UseContentRoot(Directory.GetCurrentDirectory())
        .UseIISIntegration()
        .Build()
    .Start();

UserInterfaceHub.cs

namespace InterfaceModule.Hubs  {
    public class UserInterfaceHub : Hub  {
        public async Task SwitchUI(string message) {
            await Clients.All.SendAsync("ReceiveEvent", message);
        }

        public override async Task OnConnectedAsync()   {
            //await SwitchUI("HOWDY NEW PERSON!");
            await base.OnConnectedAsync();
        }
    }
}

редактировать, добавляя ясности.

В Program.CS у меня есть делегат события: // теперь, когда мы готовы, начинаем слушать. Ждите

deviceClient.SetInputMessageHandlerAsync(ModuleProperties.InputName, OnReceiveEvent, deviceClient);
            Console.WriteLine("INIT: Event Message Input handler created: [{0}]", ModuleProperties.InputName);

что это:

        static async Task<MessageResponse> OnReceiveEvent(Message message, object userContext)  {
//HOW DO I REACH THE HUB FROM HERE SO I CAN SEND A MESSAGE TO THE LISTENERS?
}

person Jason    schedule 01.06.2018    source источник


Ответы (3)


Я столкнулся с подобной ситуацией, и вот как ее решить:

На уровне обслуживания создайте интерфейс, который назовите что-то вроде ISendHubMessage. Имейте метод под названием Send (), который принимает параметры, которые вы хотите отправить через SignalR. Создайте в том же файле класс SendHubMessage, реализующий интерфейс. Просто сделай возврат.

В вашем проекте верхнего уровня (где находится ваш файл Startup.cs) создайте другой класс с именем SendHubMessage, который реализует тот же интерфейс ISendHubMessage из уровня вашего сервиса. В этом SendHubMessage вы можете использовать DI для доступа к концентратору, как описано выше. Этот метод будет выполнять фактическую логику отправки через SignalR.

В методе Startup ConfigureServices () добавьте следующую строку:

services.AddTransient ‹" Сервис ".ISendHubMessage," TopLevel ".SendHubMessage> ();

(где «Service» - это пространство имен для вашего проекта уровня сервиса, а «TopLevel» - в пространстве имен для вашего проекта верхнего уровня).

То, что вы делаете с этой строкой, говорит: «Всякий раз, когда объект запрашивает зависимость ISendHubMessage от уровня службы, предоставьте ему класс SendHubMessage, определенный в моем проекте верхнего уровня».

Наконец, во всех местах кода за пределами вашего проекта верхнего уровня, где вы хотите отправлять сообщения через концентратор, вставьте эту зависимость ISendHubMessage в конструктор. Затем вы можете ссылаться на него в методах класса, и когда вы вызываете Send (), он вызывает метод Send (), определенный в вашем классе SendHubMessage в вашем проекте верхнего уровня.

person Courier    schedule 27.06.2018

Эта строка кода:

 app.UseSignalR(routes => {routes.MapHub<UserInterfaceHub>("/uihub");});

зарегистрирует ваш хаб в контейнере DI. Затем, чтобы получить к нему доступ, вы либо используете инъекцию конструктора для внедрения в IHubContext<UserInterfaceHub> (это работает, например, в веб-контроллере), либо обращаетесь к нему напрямую из контейнера DI, выполнив следующие действия:

 var hub = app.ApplicationServices.GetRequiredService<IHubContext<UserInterfaceHub>>();

(например, если выполняется в методе startup.cs Configure)

Если у вас нет доступа к app.ApplicationServices, который в основном представляет собой IServiceProvider в том месте, где вам нужно получить доступ к хабу, тогда вам нужно будет либо 1) заставить этот класс работать с инъекцией зависимостей для внедрения в IHubContext<UserInterfaceHub> или IServiceProvider 2) Настройте глобальную переменную static Services через Configure, чтобы вы могли иметь доступ к одному из них глобально, или найдите другой способ доступа к контейнеру DI (также известному как IServiceProvider), чтобы получить ваш хаб с помощью приведенной выше строки кода.

После того, как у вас есть концентратор, для отправки сообщения зарегистрированным клиентам нужно просто вызвать метод на вашем концентраторе.

 await hub.Clients.All.SendAsync("ReceiveEvent", message); 
person RonC    schedule 02.06.2018
comment
Просто чтобы добавить мои 2 цента, всегда есть некоторые противоречия из-за антипаттерна ServiceLocator (передача контейнера DI через конструкторы). Вы также можете передать HttpContext или IHttpContextAccessor и получить доступ к своим зарегистрированным службам через контекст (HttpContext.RequestServices), который вернет IServiceProvider. - person Francisco Tena; 02.07.2018
comment
@FranciscoTena Я согласен с тем, что использование HttpContext.RequestServices - отличный подход. Он, конечно, по-прежнему подпадает под антишаблон локатора сервисов, но даже предполагаемые антишаблоны время от времени находят свое место в хорошо продуманной архитектуре. - person RonC; 05.07.2018

Ваш вопрос немного неясен, но я предполагаю, что вы имеете в виду, что хотите заменить следующее на что-то, что может отправлять сообщение через ваш хаб:

app.Run(async (context) =>{await context.Response.WriteAsync("Active");});

Поскольку это находится в вашем Configure методе, вы можете просто добавить IServiceCollection services в параметры Configure методов. Затем вы можете:

var hub = services.GetRequiredService<IHubContext<MyHub>>();

Однако я не уверен, что это действительно принесет что-то полезное в конечном итоге. По логике, при запуске у вас еще не было бы клиентов с подписками. В результате отправка сообщения через ваш хаб на этом этапе практически ни к чему не приведет. К тому времени, когда пользователь действительно заходит на ваш сайт и подключается к вашему хабу, эта часть вашего приложения уже запущена и больше не будет задействована.

person Chris Pratt    schedule 01.06.2018
comment
В конечном итоге я хотел бы получить доступ к контексту хаба извне класса хаба. У меня есть метод в Program.CS, который обрабатывает получение события концентратора событий Azure. Когда это происходит, я хочу отправить сообщение на SwitchUI UserInterfaceHub (или просто на Clients.All.SendAsyc), но я не вижу никакого способа получить контекст концентратора. GlobalHost недоступен, и все старые способы не работают. Я попытался добавить то, что вы сказали, но нет никакого .GetRequiredService, который предоставляет себя в Configure / ConfigureServices, давая ему IServiceCollection в параметрах метода. Не видеть его появления. - person Jason; 01.06.2018
comment
По сути, у меня есть делегат события, определенный в Program / Main. Когда это событие срабатывает, я хочу отправить сообщение signal-R всем, кто слушает концентратор / eventUI, который я создал. Как мне получить контекст этого хаба, чтобы я мог с ним поговорить? - person Jason; 01.06.2018
comment
немного отредактировал к основному (см. внизу). В Main я объявляю обработчик делегата события. Это простой асинхронный метод, но оттуда я хочу отправить сообщение в концентратор, чтобы любой слушающий клиент получил сообщение. Как мне подключиться к контексту хаба, чтобы я мог это сделать? - person Jason; 01.06.2018