Hospedar SignalR de ASP.NET Core en servicios en segundo plano

Por Dave Pringle y Brady Gaster

En este artículo se proporcionan instrucciones para:

  • Hospedaje de concentradores de SignalR mediante un proceso de trabajo en segundo plano hospedado con ASP.NET Core.
  • Envío de mensajes a clientes conectados desde un backgroundService de .NET Core.

Vea o descargue el código de ejemplo(cómo descargarlo):

Habilitar SignalR al iniciar la aplicación

Hospedar concentradores de SignalR de ASP.NET Core en el contexto de un proceso de trabajo en segundo plano es idéntico al hospedaje de un concentrador en una aplicación web de ASP.NET Core. En Program.cs, al llamar a builder.Services.AddSignalR se agregan los servicios necesarios a la capa de inserción de dependencias (DI) ASP.NET Core para admitir SignalR. Se llama al método MapHub en WebApplicationapp para conectar los puntos de conexión del centro en la canalización de solicitud de ASP.NET Core.

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddSignalR();
builder.Services.AddHostedService<Worker>();

var app = builder.Build();

app.MapHub<ClockHub>("/hubs/clock");

app.Run();

En el ejemplo anterior, la clase de ClockHub implementa la clase de Hub<T> para crear un concentrador fuertemente tipado. ClockHub se ha configurado en Program.cs para responder a las solicitudes en el punto de conexión /hubs/clock.

Para obtener más información sobre los concentradores fuertemente tipados, consulte Usar concentradores en SignalR para ASP.NET Core.

Nota

Esta funcionalidad no se limita a la clase de concentrador<T>. Cualquier clase que herede del concentrador, como DynamicHub, funciona.

public class ClockHub : Hub<IClock>
{
    public async Task SendTimeToClients(DateTime dateTime)
    {
        await Clients.All.ShowTime(dateTime);
    }
}

La interfaz utilizada por el fuertemente tipado ClockHub es la interfaz de IClock.

public interface IClock
{
    Task ShowTime(DateTime currentTime);
}

Llamar a un concentrador de SignalR desde un servicio en segundo plano

Durante el inicio, la clase de Worker, un BackgroundService, se habilita mediante AddHostedService.

builder.Services.AddHostedService<Worker>();

Puesto que SignalR también se habilita durante la fase de inicio, en la que cada concentrador está asociado a un punto de conexión individual en la canalización de solicitudes HTTP de ASP.NET Core, cada concentrador se representa mediante un IHubContext<T> en el servidor. Con las características de inserción de dependencias de ASP.NET Core, otras clases de las que crea una instancia la capa de hospedaje, como las clases de BackgroundService, las clases de controlador de MVC o modelos de página de Razor, pueden obtener referencias a concentradores del lado servidor aceptando instancias de IHubContext<ClockHub, IClock> durante la construcción.

public class Worker : BackgroundService
{
    private readonly ILogger<Worker> _logger;
    private readonly IHubContext<ClockHub, IClock> _clockHub;

    public Worker(ILogger<Worker> logger, IHubContext<ClockHub, IClock> clockHub)
    {
        _logger = logger;
        _clockHub = clockHub;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            _logger.LogInformation("Worker running at: {Time}", DateTime.Now);
            await _clockHub.Clients.All.ShowTime(DateTime.Now);
            await Task.Delay(1000, stoppingToken);
        }
    }
}

Como el método ExecuteAsync se denomina iterativamente en el servicio en segundo plano, la fecha y hora actuales del servidor se envían a los clientes conectados mediante ClockHub.

Responder a eventos de SignalR con servicios en segundo plano

Al igual que una aplicación de página única mediante el cliente de JavaScript para SignalR, o una aplicación de escritorio de .NET mediante el cliente .NET SignalR de ASP.NET Core, también se puede usar una implementación BackgroundService o IHostedService para conectarse a concentradores de SignalR y responder a eventos.

La clase de ClockHubClient implementa la interfaz de IClock y la interfaz de IHostedService. De este modo, se puede habilitar durante el inicio para que se ejecute continuamente y responder a eventos del concentrador desde el servidor.

public partial class ClockHubClient : IClock, IHostedService
{
}

Durante la inicialización, ClockHubClient crea una instancia de HubConnection y habilita el método IClock.ShowTime como controlador para el evento de ShowTime del concentrador.

private readonly ILogger<ClockHubClient> _logger;
private HubConnection _connection;

public ClockHubClient(ILogger<ClockHubClient> logger)
{
    _logger = logger;
    
    _connection = new HubConnectionBuilder()
        .WithUrl(Strings.HubUrl)
        .Build();

    _connection.On<DateTime>(Strings.Events.TimeSent, ShowTime);
}

public Task ShowTime(DateTime currentTime)
{
    _logger.LogInformation("{CurrentTime}", currentTime.ToShortTimeString());

    return Task.CompletedTask;
}

En la implementación de IHostedService.StartAsync, HubConnection se inicia de forma asincrónica.

public async Task StartAsync(CancellationToken cancellationToken)
{
    // Loop is here to wait until the server is running
    while (true)
    {
        try
        {
            await _connection.StartAsync(cancellationToken);

            break;
        }
        catch
        {
            await Task.Delay(1000, cancellationToken);
        }
    }
}

Durante el método IHostedService.StopAsync, HubConnection se elimina de forma asincrónica.

public async Task StopAsync(CancellationToken cancellationToken)
{
    await _connection.DisposeAsync();
}

Vea o descargue el código de ejemplo(cómo descargarlo):

Habilitar SignalR en el inicio

Hospedar concentradores de SignalR de ASP.NET Core en el contexto de un proceso de trabajo en segundo plano es idéntico al hospedaje de un concentrador en una aplicación web de ASP.NET Core. En el método Startup.ConfigureServices, al llamar a services.AddSignalR se agregan los servicios necesarios a la capa de inserción de dependencias (DI) ASP.NET Core para admitir SignalR. En Startup.Configure, se llama al método MapHub en la devolución de llamada de UseEndpoints para conectar los puntos de conexión del concentrador en la canalización de solicitud de ASP.NET Core.

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddSignalR();
        services.AddHostedService<Worker>();
    }

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }

        app.UseRouting();
        app.UseEndpoints(endpoints =>
        {
            endpoints.MapHub<ClockHub>("/hubs/clock");
        });
    }
}

En el ejemplo anterior, la clase de ClockHub implementa la clase de Hub<T> para crear un concentrador fuertemente tipado. ClockHub se ha configurado en la clase de Startup para responder a las solicitudes en el punto de conexión /hubs/clock.

Para obtener más información sobre los concentradores fuertemente tipados, consulte Usar concentradores en SignalR para ASP.NET Core.

Nota

Esta funcionalidad no se limita a la clase de concentrador<T>. Cualquier clase que herede del concentrador, como DynamicHub, funciona.

public class ClockHub : Hub<IClock>
{
    public async Task SendTimeToClients(DateTime dateTime)
    {
        await Clients.All.ShowTime(dateTime);
    }
}

La interfaz utilizada por el fuertemente tipado ClockHub es la interfaz de IClock.

public interface IClock
{
    Task ShowTime(DateTime currentTime);
}

Llamar a un concentrador de SignalR desde un servicio en segundo plano

Durante el inicio, la clase de Worker, un BackgroundService, se habilita mediante AddHostedService.

services.AddHostedService<Worker>();

Puesto que SignalR también se habilita durante la fase de Startup, en la que cada concentrador está asociado a un punto de conexión individual en la canalización de solicitudes HTTP de ASP.NET Core, cada concentrador se representa mediante un IHubContext<T> en el servidor. Con las características de inserción de dependencias de ASP.NET Core, otras clases de las que crea una instancia la capa de hospedaje, como las clases de BackgroundService, las clases de controlador de MVC o modelos de página de Razor, pueden obtener referencias a concentradores del lado servidor aceptando instancias de IHubContext<ClockHub, IClock> durante la construcción.

public class Worker : BackgroundService
{
    private readonly ILogger<Worker> _logger;
    private readonly IHubContext<ClockHub, IClock> _clockHub;

    public Worker(ILogger<Worker> logger, IHubContext<ClockHub, IClock> clockHub)
    {
        _logger = logger;
        _clockHub = clockHub;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            _logger.LogInformation("Worker running at: {Time}", DateTime.Now);
            await _clockHub.Clients.All.ShowTime(DateTime.Now);
            await Task.Delay(1000);
        }
    }
}

Como el método ExecuteAsync se denomina iterativamente en el servicio en segundo plano, la fecha y hora actuales del servidor se envían a los clientes conectados mediante ClockHub.

Responder a eventos de SignalR con servicios en segundo plano

Al igual que una aplicación de página única mediante el cliente de JavaScript para SignalR, o una aplicación de escritorio de .NET mediante el cliente .NET SignalR de ASP.NET Core, también se puede usar una implementación BackgroundService o IHostedService para conectarse a concentradores de SignalR y responder a eventos.

La clase de ClockHubClient implementa la interfaz de IClock y la interfaz de IHostedService. De este modo, se puede habilitar durante el Startup para que se ejecute continuamente y responder a eventos del concentrador desde el servidor.

public partial class ClockHubClient : IClock, IHostedService
{
}

Durante la inicialización, ClockHubClient crea una instancia de HubConnection y habilita el método IClock.ShowTime como controlador para el evento de ShowTime del concentrador.

private readonly ILogger<ClockHubClient> _logger;
private HubConnection _connection;

public ClockHubClient(ILogger<ClockHubClient> logger)
{
    _logger = logger;
    
    _connection = new HubConnectionBuilder()
        .WithUrl(Strings.HubUrl)
        .Build();

    _connection.On<DateTime>(Strings.Events.TimeSent, ShowTime);
}

public Task ShowTime(DateTime currentTime)
{
    _logger.LogInformation("{CurrentTime}", currentTime.ToShortTimeString());

    return Task.CompletedTask;
}

En la implementación de IHostedService.StartAsync, HubConnection se inicia de forma asincrónica.

public async Task StartAsync(CancellationToken cancellationToken)
{
    // Loop is here to wait until the server is running
    while (true)
    {
        try
        {
            await _connection.StartAsync(cancellationToken);

            break;
        }
        catch
        {
            await Task.Delay(1000);
        }
    }
}

Durante el método IHostedService.StopAsync, HubConnection se elimina de forma asincrónica.

public Task StopAsync(CancellationToken cancellationToken)
{
    return _connection.DisposeAsync();
}

Vea o descargue el código de ejemplo(cómo descargarlo):

Habilitar SignalR en el inicio

Hospedar concentradores de SignalR de ASP.NET Core en el contexto de un proceso de trabajo en segundo plano es idéntico al hospedaje de un concentrador en una aplicación web de ASP.NET Core. En el método Startup.ConfigureServices, al llamar a services.AddSignalR se agregan los servicios necesarios a la capa de inserción de dependencias (DI) ASP.NET Core para admitir SignalR. En Startup.Configure, se llama al método UseSignalR para conectar el(los) punto(s) de conexión del concentrador en la canalización de solicitud de ASP.NET Core.

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddSignalR();
        services.AddHostedService<Worker>();
    }

    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }

        app.UseSignalR((routes) =>
        {
            routes.MapHub<ClockHub>("/hubs/clock");
        });
    }
}

En el ejemplo anterior, la clase de ClockHub implementa la clase de Hub<T> para crear un concentrador fuertemente tipado. ClockHub se ha configurado en la clase de Startup para responder a las solicitudes en el punto de conexión /hubs/clock.

Para obtener más información sobre los concentradores fuertemente tipados, consulte Usar concentradores en SignalR para ASP.NET Core.

Nota

Esta funcionalidad no se limita a la clase de concentrador<T>. Cualquier clase que herede del concentrador, como DynamicHub, funciona.

public class ClockHub : Hub<IClock>
{
    public async Task SendTimeToClients(DateTime dateTime)
    {
        await Clients.All.ShowTime(dateTime);
    }
}

La interfaz utilizada por el fuertemente tipado ClockHub es la interfaz de IClock.

public interface IClock
{
    Task ShowTime(DateTime currentTime);
}

Llamar a un concentrador de SignalR desde un servicio en segundo plano

Durante el inicio, la clase de Worker, un BackgroundService, se habilita mediante AddHostedService.

services.AddHostedService<Worker>();

Puesto que SignalR también se habilita durante la fase de Startup, en la que cada concentrador está asociado a un punto de conexión individual en la canalización de solicitudes HTTP de ASP.NET Core, cada concentrador se representa mediante un IHubContext<T> en el servidor. Con las características de inserción de dependencias de ASP.NET Core, otras clases de las que crea una instancia la capa de hospedaje, como las clases de BackgroundService, las clases de controlador de MVC o modelos de página de Razor, pueden obtener referencias a concentradores del lado servidor aceptando instancias de IHubContext<ClockHub, IClock> durante la construcción.

public class Worker : BackgroundService
{
    private readonly ILogger<Worker> _logger;
    private readonly IHubContext<ClockHub, IClock> _clockHub;

    public Worker(ILogger<Worker> logger, IHubContext<ClockHub, IClock> clockHub)
    {
        _logger = logger;
        _clockHub = clockHub;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            _logger.LogInformation("Worker running at: {Time}", DateTime.Now);
            await _clockHub.Clients.All.ShowTime(DateTime.Now);
            await Task.Delay(1000);
        }
    }
}

Como el método ExecuteAsync se denomina iterativamente en el servicio en segundo plano, la fecha y hora actuales del servidor se envían a los clientes conectados mediante ClockHub.

Responder a eventos de SignalR con servicios en segundo plano

Al igual que una aplicación de página única mediante el cliente de JavaScript para SignalR, o una aplicación de escritorio de .NET mediante el cliente .NET SignalR de ASP.NET Core, también se puede usar una implementación BackgroundService o IHostedService para conectarse a concentradores de SignalR y responder a eventos.

La clase de ClockHubClient implementa la interfaz de IClock y la interfaz de IHostedService. De este modo, se puede habilitar durante el Startup para que se ejecute continuamente y responder a eventos del concentrador desde el servidor.

public partial class ClockHubClient : IClock, IHostedService
{
}

Durante la inicialización, ClockHubClient crea una instancia de HubConnection y habilita el método IClock.ShowTime como controlador para el evento de ShowTime del concentrador.

private readonly ILogger<ClockHubClient> _logger;
private HubConnection _connection;

public ClockHubClient(ILogger<ClockHubClient> logger)
{
    _logger = logger;
    
    _connection = new HubConnectionBuilder()
        .WithUrl(Strings.HubUrl)
        .Build();

    _connection.On<DateTime>(Strings.Events.TimeSent, 
        dateTime => _ = ShowTime(dateTime));
}

public Task ShowTime(DateTime currentTime)
{
    _logger.LogInformation("{CurrentTime}", currentTime.ToShortTimeString());

    return Task.CompletedTask;
}

En la implementación de IHostedService.StartAsync, HubConnection se inicia de forma asincrónica.

public async Task StartAsync(CancellationToken cancellationToken)
{
    // Loop is here to wait until the server is running
    while (true)
    {
        try
        {
            await _connection.StartAsync(cancellationToken);

            break;
        }
        catch
        {
            await Task.Delay(1000);
        }
    }
}

Durante el método IHostedService.StopAsync, HubConnection se elimina de forma asincrónica.

public Task StopAsync(CancellationToken cancellationToken)
{
    return _connection.DisposeAsync();
}

Recursos adicionales