Aplicación web en tiempo real con ASP.NET Core SignalR

    En este tema, veremos cómo crear aplicaciones para transmisión y recepción de datos con ASP.NET Core SignalR.

    Lo que necesitarás:

    • Un conocimiento básico de ASP.NET Core y Angular.
    • .NET Core 3.1 instalado e IDE como Visual Studio.

    Lo que sabrá al final de este artículo:

    • Cómo agregar y usar SignalR.
    • Cómo abrir la conexión del Cliente y utilizar el concepto de invocación de método para transmitir datos por Cliente.
    • Cómo consumir el servicio SignalR con la aplicación Angular usando Observables.

    SignalR aprovecha varios transportes y selecciona automáticamente el mejor transporte disponible según las capacidades del cliente y del servidor: WebSockets, Server Send Events o Long-polling.

    Cuando hablamos en términos de WebSockets (excluyendo SSE y Long-polling de la ecuación) cuando el cliente está conectado en tiempo real al servidor, cada vez que sucede algo, el servidor sabrá enviar un mensaje a través de ese WebSocket al cliente. Con clientes y servidores de la vieja escuela, se utilizaría el transporte de sondeo largo.

    Así es como SignalR maneja clientes y servidores modernos, utiliza WebSockets internamente cuando está disponible y recurre elegantemente a otras técnicas y tecnologías cuando no lo está:

    Aplicación web en tiempo real con ASP.NET Core SignalR

    Es como un apretón de manos, el Cliente y el Servidor acuerdan qué usar y lo usan. Esto se llama proceso de negociación.

    Aplicación web en tiempo real con Web Sockets

    SignalR Example

    El propósito de esta demostración es mostrar un tablero de pantalla financiera con un flujo de datos en tiempo real utilizando ASP.NET Core SignalR.

    SignalR Server Configuration

    Create ASP.NET Core App

    Veamos cómo configurar la aplicación ASP.NET Core SignalR. En Visual Studio desde Archivo >> Nuevo proyecto, elija Aplicación web ASP.NET Core y siga la configuración. No dude en seguir el tutorial de documentación oficial de Microsoft si experimenta alguna dificultad de configuración.

    Create ASP.NET Core App project

    SignalR Config Setup

    Agregue el siguiente código al archivo Startup.cs:

    • Parte del punto final del método Configure.
    app.UseEndpoints(endpoints =>
    {
        endpoints.MapControllers();
        endpoints.MapHub<StreamHub>("/streamHub");
    });
    
    • Agregue el uso de SignalR al método ConfigureServices.
    services.AddSignalR(options =>
    {
        options.EnableDetailedErrors = true;
    });
    

    Los cambios anteriores agregan SignalR al sistema de enrutamiento e inyección de dependencias ASP.NET Core.

    Ahora, configuremos una configuración básica adicional. Abra el archivo properties/launchSettings.json y modifíquelo en consecuencia:

    "profiles": {
        "WebAPI": {
          "commandName": "Project",
          "launchBrowser": false,
          "applicationUrl": "https://localhost:5001;http://localhost:5000",
          "environmentVariables": {
            "ASPNETCORE_ENVIRONMENT": "Development"
          }
        }
      }
    

    Nuestro proyecto del lado del servidor se ejecutará en localhost:5001 y el lado del cliente se ejecutará en localhost:4200, por lo que para establecer comunicación entre esos dos, debemos habilitar CORS. Abramos la clase Startup.cs y modifíquela:

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddCors(options =>
        {
            options.AddPolicy("CorsPolicy", builder => builder
            .AllowAnyMethod()
            .AllowAnyHeader()
            .AllowCredentials()
            .WithOrigins("http://localhost:4200"));
        });
        ...
    
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            ...
            app.UseCors("CorsPolicy");
            ...
    

    Si experimenta un problema específico al habilitar el uso compartido de recursos entre orígenes, consulte el tema oficial de Microsoft.

    SignalR Hub Setup

    Comencemos explicando qué es un concentrador SignalR. La API de SignalR Hub le permite llamar a métodos en clientes conectados desde el servidor. En el código del servidor, usted define los métodos que llama el cliente. En SignalR existe este concepto llamado Invocación: en realidad, puedes llamar al centro desde el cliente con un método particular. En el código del cliente, usted define métodos que se llaman desde el servidor.

    El centro real se encuentra en el lado del servidor. Imagina que tienes Clientes y el Hub está entre todos ellos. Puede decirle algo a todos los Clientes con Clients.All.doWork() invocando un método en el centro. Esto irá a todos los clientes conectados. Además, puede comunicarse con un solo cliente, que es el que llama, porque es el que llama de ese método en particular.

    Configuración de SignalR Hub con personas que llaman

    Hemos creado una clase StreamHub que hereda la clase base Hub, que es responsable de administrar conexiones, grupos y mensajería. Es bueno tener en cuenta que la clase Hub no tiene estado y cada nueva invocación de un determinado método se realiza en una nueva instancia de esta clase. Es inútil guardar el estado en las propiedades de la instancia; más bien sugerimos usar propiedades estáticas; en nuestro caso usamos una colección estática de pares clave-valor para almacenar datos para cada cliente conectado.

    Otras propiedades útiles de esta clase son Clientes, Contexto y Grupos. Pueden ayudarle a gestionar cierto comportamiento basándose en el ConnectionID único. Además, esta clase le proporciona los siguientes métodos útiles:

    • OnConnectedAsync(): se llama cuando se establece una nueva conexión con el concentrador.
    • OnDisconnectedAsync (Excepción): se llama cuando finaliza una conexión con el concentrador.

    Nos permiten realizar cualquier lógica adicional cuando se establece o cierra una conexión. En nuestra aplicación, también agregamos el método UpdateParameters que obtiene una ID de conexión de contexto y la usa para enviar datos en un intervalo determinado. Como puede ver, nos comunicamos a través de un ConnectionID único que evita una intervención de transmisión por parte de otros Clientes.

    public async void UpdateParameters(int interval, int volume, bool live = false, bool updateAll = true)
    {
        ...
        var connection = Context.ConnectionId;
        var clients = Clients;
        ...
        if (!clientConnections.ContainsKey(connection))
        {
            clientConnections.Add(connection, new TimerManager(async() =>
            {
                ...
                await Send(newDataArray, client, connection);
            }, interval));
        } else
        {
            clientConnections[connection].Stop();
            clientConnections[connection] = new TimerManager(async () =>
            {
                var client = clients.Client(connection);
                ..
                await Send(newDataArray, client, connection);
            }, interval);
        }
        ...
    }
    

    Cuando los datos están listos, los transferimos emitiendo un evento transferdata con la ayuda del método SendAsync.

    public async Task Send(FinancialData[] array, IClientProxy client, string connection)
    {
        await client.SendAsync("transferdata", array);
    }
    ...
    
    // Called when a connection with the hub is terminated
    public override Task OnDisconnectedAsync(Exception exception)
    {
        StopTimer();
        clientConnections.Remove(Context.ConnectionId);
        return base.OnDisconnectedAsync(exception);
    }
    

    Nuestra aplicación cliente estaría escuchando los eventos registrados:

    private registerSignalEvents() {
        this.hubConnection.onclose(() => {
            this.hasRemoteConnection = false;
        });
        this.hubConnection.on('transferdata', (data) => {
            this.data.next(data);
        })
    }
    

    El repositorio público de GitHub de la aplicación ASP.NET Core se puede encontrar aquí.

    Create SignalR Client Library

    Crearemos un proyecto Angular para poder consumir el servicio SignalR. El repositorio de Github con la aplicación real se puede encontrar aquí.

    First, start by installing SignalR:

    npm install @microsoft/signalr
    

    Tenga en cuenta que enviaremos la solicitud HTTP a nuestro servidor, por lo que también necesitamos HttpClientModule.

    A continuación encontrará el archivo signal-r.service.ts que maneja el generador de conexiones del concentrador.

    export class SignalRService implements OnDestroy {
        public data: BehaviorSubject<any[]>;
        public hasRemoteConnection: boolean;
        private hubConnection: signalR.HubConnection;
        ...
    
        constructor(private zone: NgZone, private http: HttpClient) {
            this.data = new BehaviorSubject([]);
        }
        ...
    
        // Start Hub Connection and Register events
        public startConnection = (interval = 500, volume = 1000, live = false,  updateAll = true) => {
            this.hubConnection = new signalR.HubConnectionBuilder()
                .configureLogging(signalR.LogLevel.Trace)
                .withUrl('https://es.infragistics.com/angular-apis/webapi/streamHub')
                .build();
            this.hubConnection
                .start()
                .then(() => {
                    ...
                    this.registerSignalEvents();
                    this.broadcastParams(interval, volume, live, updateAll);
                })
                .catch(() => { ... });
        }
    
        // Change the broadcast parameters like frequency and data volume
        public broadcastParams = (frequency, volume, live, updateAll = true) => {
            this.hubConnection.invoke('updateparameters', frequency, volume, live, updateAll)
                .then(() => console.log('requestLiveData', volume))
                .catch(err => {
                    console.error(err);
                });
        }
    
        // Register events
        private registerSignalEvents() {
            this.hubConnection.onclose(() => {
                this.hasRemoteConnection = false;
            });
            this.hubConnection.on('transferdata', (data) => {
                this.data.next(data);
            });
        }
        ...
    

    En su app.component agregue use el método startConnection recién creado

    constructor(public dataService: SignalRService) {}
    public ngOnInit() {
        this.dataService.startConnection(this.frequency, this.dataVolume, true, false);
    }
    ...
    

    Grid Data Binding

    Como hemos visto hasta ahora en nuestro código de cliente, configuramos un detector para el evento transferdata, que recibe como argumento la matriz de datos actualizada. Para pasar los datos recién recibidos a nuestra grilla utilizamos un observable. Para configurar eso, necesitamos vincular la fuente de datos de la cuadrícula a los datos observables de esta manera:

    <igx-grid [data]='data | async'> ... </igx-grid>
    

    Cada vez que se reciben nuevos datos del servidor al cliente, llamamos al método next() de los datos observables.

        this.hubConnection.on('transferdata', (data) => {
            this.data.next(data);
        })
    

    Topic Takeaways

    Si no desea actualizar su aplicación, sino simplemente ver cuándo se actualizan los datos, debería considerar ASP.NET Core SignalR. Definitivamente recomiendo optar por la transmisión de contenido cuando crea que sus datos son grandes o si desea una experiencia de usuario fluida sin bloquear al cliente mostrando controles giratorios interminables.

    Usar la comunicación SignalR Hub es fácil e intuitivo y, con la ayuda de Angular Observables, puede crear una aplicación potente que utilice la transmisión de datos con WebSockets.