Ejercicio: Habilitar las actualizaciones automáticas en una aplicación web mediante SignalR Service

Completado

Para admitir la nueva funcionalidad, debe crear algunas funciones nuevas y actualizar JavaScript en el cliente.

Crear una cuenta de SignalR

Tiene que agregar una cuenta de SignalR a la suscripción de espacio aislado.

  1. El primer paso es ejecutar el siguiente comando en Cloud Shell para crear una nueva cuenta de SignalR en el grupo de recursos de espacio aislado. Este comando puede tardar un par de minutos en terminar, así que espere a que finalice antes de continuar con el paso siguiente.

    SIGNALR_SERVICE_NAME=msl-sigr-signalr$(openssl rand -hex 5)
    az signalr create \
      --name $SIGNALR_SERVICE_NAME \
      --resource-group <rgn>[sandbox resource group name]</rgn> \
      --sku Free_DS2 \
      --unit-count 1
    
  2. Para que SignalR Service funcione correctamente con Azure Functions, debe establecer el modo de servicio en Sin servidor. Configure el modo de servicio mediante el comando siguiente.

    az resource update \
      --resource-type Microsoft.SignalRService/SignalR \
      --name $SIGNALR_SERVICE_NAME \
      --resource-group <rgn>[sandbox resource group name]</rgn> \
      --set properties.features[flag=ServiceMode].value=Serverless
    

Actualización de la configuración local

Para que la aplicación se ejecute, debe agregar la cadena de conexión de SignalR guardada a la configuración local.

  1. Ejecute los comandos siguientes en Cloud Shell para obtener las cadenas de conexión de los recursos creados en este ejercicio.

    SIGNALR_CONNECTION_STRING=$(az signalr key list \
      --name $(az signalr list \
        --resource-group <rgn>[sandbox resource group name]</rgn> \
        --query [0].name -o tsv) \
      --resource-group <rgn>[sandbox resource group name]</rgn> \
      --query primaryConnectionString -o tsv)
    
    printf "\n\nReplace <SIGNALR_CONNECTION_STRING> with:\n$SIGNALR_CONNECTION_STRING\n\n"
    
  2. Vaya a la ubicación en donde ha clonado la aplicación y abra la carpeta start en Visual Studio Code. Abra local.settings.json en el editor para poder actualizar el archivo.

  3. En local.settings.json, actualice la variable AzureSignalRConnectionString con el valor que aparece en Cloud Shell y guarde el archivo.

Administrar las conexiones de cliente

El cliente web usa el SDK de cliente de SignalR para establecer una conexión al servidor. El SDK recupera la conexión a través de una función denominada negotiate (por convención) para conectarse al servicio.

  1. Presione F1 para abrir la paleta de comandos de Visual Studio Code.

  2. Busque y seleccione el comando Azure Functions: Crear función.

  3. Cuando se le pida, proporcione la siguiente información.

    Nombre Valor
    Plantilla Desencadenador HTTP
    Nombre negotiate
    Nivel de autorización Anónimo

    Actualice la ventana del explorador en Visual Studio Code para ver las actualizaciones. Ahora hay una carpeta denominada negotiate disponible en la aplicación de función.

  4. Abra negotiate/function.json y agregue la siguiente definición de enlace de SignalR a la matriz bindings.

    {
        "type": "signalRConnectionInfo",
        "name": "connectionInfo",
        "hubName": "stocks",
        "direction": "in",
        "connectionStringSetting": "AzureSignalRConnectionString"
    }
    

    Esta configuración permite que la función devuelva la información de conexión al servidor, que se usa para identificar a los clientes conectados.

  5. Luego abra negotiate/index.js y reemplace el código de función existente por el siguiente.

    module.exports = async function (context, req, connectionInfo) {
        context.res.body = connectionInfo;
    };
    

    Cuando se llama a la función, la conexión de SignalR se devuelve como respuesta a la función.

Ahora que se ha implementado la función para devolver la información de conexión de SignalR, puede crear una función responsable de la inserción de cambios en el cliente.

Detectar y difundir cambios de la base de datos

En primer lugar, tiene que crear una nueva función que escuche los cambios en la base de datos. Esta función usa un desencadenador de Azure Cosmos DB que se conecta a la fuente de cambios de la base de datos.

  1. Presione F1 para abrir la paleta de comandos de Visual Studio Code.

  2. Busque y seleccione el comando Azure Functions: Crear función.

  3. Cuando se le pida, proporcione la siguiente información.

    Nombre Valor
    Plantilla Desencadenador de Azure Cosmos DB
    Nombre stocksChanged
    Configuración de aplicación para la cuenta de Azure Cosmos DB AzureCosmosDBConnectionString
    Nombre de la base de datos stocksdb
    Nombre de la colección stocks
    Nombre de la colección de concesiones leases
    Crear colección de concesiones si no existe true

    Ya se ha creado una carpeta denominada stocksChanged que contiene los archivos de la nueva función.

  4. Abra stocksChanged/function.json en Visual Studio Code.

  5. Anexe la propiedad "feedPollDelay": 500 a la definición de enlace de desencadenador existente. Este valor indica a Azure Cosmos DB cuánto tiempo debe esperar antes de buscar cambios en la base de datos. La aplicación que está creando se basa en una arquitectura basada en inserción, aunque, en segundo plano, Azure Cosmos DB supervisa de forma continua la fuente de cambios para detectar cambios. feedPollDelay hace referencia a cómo los elementos internos de Azure Cosmos DB reconocen los cambios, no a cómo la aplicación web expone los cambios en los datos.

    El enlace de Azure Cosmos DB de la función ahora debe ser similar al siguiente código.

    {
      "type": "cosmosDBTrigger",
      "name": "documents",
      "direction": "in",
      "leaseCollectionName": "leases",
      "connectionStringSetting": "AzureCosmosDBConnectionString",
      "databaseName": "stocksdb",
      "collectionName": "stocks",
      "createLeaseCollectionIfNotExists": "true",
      "feedPollDelay": 500
    }
    
  6. Luego anexe la siguiente definición de enlace de salida de SignalR a la matriz bindings.

    {
      "type": "signalR",
      "name": "signalRMessages",
      "connectionString": "AzureSignalRConnectionString",
      "hubName": "stocks",
      "direction": "out"
    }
    

    Este enlace permite a la función difundir los cambios a los clientes.

  7. Actualice el archivo stocksChanged/index.js para reflejar el código siguiente. La belleza de toda la configuración es que el código de la función es sencillo.

    module.exports = async function (context, documents) {
        const updates = documents.map(stock => ({
            target: 'updated',
            arguments: [stock]
        }));
    
        context.bindings.signalRMessages = updates;
        context.done();
    }
    

    Se prepara una matriz de cambios mediante la creación de un objeto con formato para que SignalR lo lea. Se proporciona cada acción actualizada a la matriz arguments junto con una propiedad target establecida en updated.

    El valor de la propiedad target se usa en el cliente al escuchar mensajes concretos difundidos por SignalR.

Actualizar la aplicación web

Abra public/index.html y pegue el código siguiente en lugar de la etiqueta DIV actual cuyo identificador es app.

<div id="app" class="container">
    <h1 class="title">Stocks</h1>
    <div id="stocks">
        <div v-for="stock in stocks" class="stock">
            <transition name="fade" mode="out-in">
                <div class="list-item" :key="stock.price">
                    <div class="lead">{{ stock.symbol }}: ${{ stock.price }}</div>
                    <div class="change">Change:
                        <span
                            :class="{ 'is-up': stock.changeDirection === '+', 'is-down': stock.changeDirection === '-' }">
                            {{ stock.changeDirection }}{{ stock.change }}
                        </span>
                    </div>
                </div>
            </transition>
        </div>
    </div>
</div>

Este marcado agrega un elemento de transición, lo que permite a Vue.js ejecutar una animación sutil a medida que los datos de las acciones cambian. Cuando se actualiza una acción, el icono hace un fundido de salida y rápidamente vuelve a verse. De este modo, si la página está llena de datos de acciones, los usuarios pueden ver fácilmente qué acciones han cambiado.

Luego agregue el siguiente bloque de script justo encima de la referencia a index.html.js.

<script src="https://cdn.jsdelivr.net/npm/@aspnet/signalr@1.1.0/dist/browser/signalr.js"></script>

Este script agrega una referencia al SDK de SignalR.

Ahora abra public/index.html.js y reemplace el archivo por el código siguiente.

const LOCAL_BASE_URL = 'http://localhost:7071';
const REMOTE_BASE_URL = '<FUNCTION_APP_ENDPOINT>';

const getAPIBaseUrl = () => {
    const isLocal = /localhost/.test(window.location.href);
    return isLocal ? LOCAL_BASE_URL : REMOTE_BASE_URL;
}

const app = new Vue({
    el: '#app',
    data() {
        return {
            stocks: []
        }
    },
    methods: {
        async getStocks() {
            try {
                const apiUrl = `${getAPIBaseUrl()}/api/getStocks`;
                const response = await axios.get(apiUrl);
                app.stocks = response.data;
            } catch (ex) {
                console.error(ex);
            }
        }
    },
    created() {
        this.getStocks();
    }
});

const connect = () => {
    const connection = new signalR.HubConnectionBuilder()
                            .withUrl(`${getAPIBaseUrl()}/api`)
                            .build();

    connection.onclose(()  => {
        console.log('SignalR connection disconnected');
        setTimeout(() => connect(), 2000);
    });

    connection.on('updated', updatedStock => {
        const index = app.stocks.findIndex(s => s.id === updatedStock.id);
        app.stocks.splice(index, 1, updatedStock);
    });

    connection.start().then(() => {
        console.log("SignalR connection established");
    });
};

connect();

Los cambios que acaba de realizar han logrado dos objetivos: han quitado toda la lógica de sondeo del cliente y han agregado controladores para escuchar mensajes procedentes del servidor.

Se ha incorporado una nueva función auxiliar que facilita el trabajo de la aplicación en los contextos locales e implementados.

const LOCAL_BASE_URL = 'http://localhost:7071';
const REMOTE_BASE_URL = '<FUNCTION_APP_ENDPOINT>';

const getAPIBaseUrl = () => {
    const isLocal = /localhost/.test(window.location.href);
    return isLocal ? LOCAL_BASE_URL : REMOTE_BASE_URL;
}

La función getAPIBaseUrl devuelve la dirección URL apropiada según si la aplicación se ejecuta localmente o está implementada en Azure. En un ejercicio próximo, el punto de conexión de la cuenta de almacenamiento reemplaza el marcador de posición <REMOTE_BASE_URL> al implementar esta aplicación en la nube.

El código relacionado con Vue.js se simplifica ahora que los cambios se han insertado en el cliente. Vea este segmento del código que ha pegado en el archivo de script:

const app = new Vue({
    el: '#app',
    data() {
        return {
            stocks: []
        }
    },
    methods: {
        async getStocks() {
            try {
                const apiUrl = `${getAPIBaseUrl()}/api/getStocks`;
                const response = await axios.get(apiUrl);
                app.stocks = response.data;
            } catch (ex) {
                console.error(ex);
            }
        }
    },
    created() {
        this.getStocks();
    },
});

Aquí se usa la misma matriz de acciones que en la implementación anterior, pero todo el código de sondeo se ha quitado y la lógica de getStocks permanece inalterada. Aún se llama a la función getStocks cuando se crea el componente.

Luego vea este segmento del código de cliente:

const connect = () => {
    const connection = new signalR.HubConnectionBuilder()
                            .withUrl(`${getAPIBaseUrl()}/api`)
                            .build();

    connection.onclose(()  => {
        console.log('SignalR connection disconnected');
        setTimeout(() => connect(), 2000);
    });

    connection.on('updated', updatedStock => {
        const index = app.stocks.findIndex(s => s.id === updatedStock.id);
        app.stocks.splice(index, 1, updatedStock);
    });

    connection.start().then(() => {
        console.log("SignalR connection established");
    });
};

connect();

Cuando la página se carga, se llama a la función connect. En el cuerpo de la función connect, la primera acción es usar el SDK de SignalR para crear una conexión mediante una llamada a HubConnectionBuilder. El resultado es una conexión de SignalR al servidor.

Para recuperar gradualmente después de que el servidor haya agotado el tiempo de espera, el controlador onclose restablece una conexión dos segundos después de que se haya cerrado mediante una nueva llamada a connect.

Cuando el cliente recibe mensajes del servidor, los escucha a través de la sintaxis on('updated',.... Cuando se recibe una actualización, se llevan a cabo las acciones siguientes:

  • Se busca la acción modificada en la matriz.
  • Se quita la versión anterior.
  • Se inserta la versión nueva en la misma posición de índice de la matriz.

La manipulación de la matriz de esta manera permite a Vue detectar cambios en los datos y desencadenar efectos de animación para notificar los cambios a los usuarios.

Ejecutar la aplicación

Ahora puede ver la nueva versión de la aplicación que se ejecuta localmente.

Presione F5 para comenzar a depurar la aplicación de función.

Luego abra una nueva ventana de terminal en Visual Studio Code y ejecute npm start:

npm start

El script abre automáticamente el explorador y va a http://localhost:8080.. Si el explorador no se abre automáticamente, puede ir a http://localhost:8080 de forma manual.

Observar actualizaciones automáticas

Ahora puede cambiar los datos de la aplicación y observar cómo se actualiza la interfaz de usuario automáticamente.

  1. Disponga Visual Studio Code en un lado de la pantalla y el explorador web en el otro. De este modo, puede ver cómo se actualiza la interfaz de usuario a medida que se realizan cambios en la base de datos.

  2. Vuelva a Visual Studio Code y escriba el siguiente comando en un nuevo terminal integrado. Nuevamente, observe a medida que la aplicación actualiza automáticamente las acciones de ABC.

    npm run update-data
    

Una vez actualizada la base de datos, la interfaz de usuario tiene un aspecto similar a la captura de pantalla siguiente:

End state of serverless web app.

Cuando haya terminado, detenga los procesos en ejecución:

  • Para detener el servidor web, seleccione Eliminar proceso (icono de papelera) en la ventana de terminal que ejecuta el servidor web.

  • Para detener la aplicación de función, seleccione el botón Detener o presione Mayús + F5.