Depuración de Azure Functions en Visual Studio

Azure ofrece varias alternativas para la creación de microservicios. Probablemente la más poderosa sea Azure Functions, que nos permite crear funciones que ejecutan tareas muy concretas y que se disparan como consecuencia de un desencadenante (trigger). Normalmente ese desencadenante es una llamada HTTP, pero puede ser una interacción con alguno de los componentes de Azure, como las colas de Azure Storage o una inserción en Azure CosmosDB.

Dado que podemos crear estas funciones en lenguajes diversos (C#, Javascript, Python, Powershell…) su creación no está limitada a Visual Studio, y de hecho probablemente la forma más flexible de crearlas sea en el propio interfaz web que ofrece Azure, y que nos permite editar el código desde un navegador cualquiera.

En este post sin embargo vamos a abordar la creación y depuración de Azure Functions desde Visual Studio.

Preparación del entorno

A partir de la versión 2019, la instalación de Visual Studio nos permite escoger con gran granularidad los módulos que vamos a necesitar en nuestro equipo de desarrollo. Uno de esos módulos es el de desarrollo en Azure. Para instalarlo hemos de escogerlo entre las opciones de cargas de trabajo que aparecen al instalar o modificar Visual Studio:

Otra opción es abrir la sección de componentes individuales e instalar tan solo lo que necesitemos. Si somos usuarios avanzados y tenemos claro qué tipo de proyecto de Azure queremos abordar, tal vez esta sea la opción más eficiente:

Sea como sea, al finalizar nuestra instancia de Visual Studio estará preparada para crear diversos tipos de proyectos destinados a Azure, entre ellos las Azure Functions.

Creación del proyecto

Una vez el entorno está preparado podemos crear un proyecto del tipo adecuado. Lo más rápido es seleccionar “Azure” en la lista desplegable de plataformas para buscar el tipo de proyecto que nos interesa. Esto nos permite comprobar también la gran cantidad de proyectos de Azure que están disponibles en Visual Studio, desde aplicaciones web con Node.js hasta lectores de eventos de Event Hub o aplicaciones para web alojadas en el gestor de contenedores Kubernetes.

Tras seleccionar el directorio de la solución Visual Studio nos pregunta qué tipo de Azure Function vamos a crear. En realidad no hay diferencia sustancial entre ellas, pero la selección del tipo de función nos permitirá comenzar con una plantilla que expone los aspectos básicos de la función. También cada tipo de proyecto posee diversos puntos de entrada i/o salida, que también se crearán a partir de la plantilla.

Dos aspectos importantes hemos de tener en cuenta a la hora de crear nuestro proyecto:

  1. La selección de autenticación: Esta selección determina el modo en que los clientes se comunicarán con la función. Es un aspecto que puede cambiarse más adelante, pero conviene tenerlo claro desde el principio. Las opciones son:
    • Anónimo: Cualquier cliente podrá hacer una llamada a la funcion
    • Function: El cliente ha de pasarle un token de autenticación a la función. Dicho token sólo sirve para esta función.
    • Master: El cliente ha de utilizar un token de autorización a la aplicación de funciones que contiene esta función. Dicho token puede utilizarse en cualquier función que posea este sistema de autenticación.
  2. El uso de una cuenta emulada de Azure Storage: No todas las funciones requieren el uso de una cuenta de almacenamiento, pero si nuestra función lo requiere podemos emplear un emulador de cuentas de almacenamiento de Azure Storage que se ejecutará localmente. Este es un aspecto que puede fallar así que nos ocuparemos de él más adelante.

Ejecución y depuración

En este caso hemos utilizado como disparador una llamada a través de HTTP. La función recibirá un parámetro a través del QueryString o bien a través del Body de una petición POST. El código simplemente recoge esa petición:


using System;
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;

namespace FunctionApp3
{
     public static class Function1
     {
     [FunctionName("Function1")]
     public static async Task Run(
     [HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req,
       ILogger log)
        {
           log.LogInformation("El disparador de HTTP se ha ejecutado y estamos procesando una llamada.");

           string name = req.Query["nombre"];

           string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
           dynamic data = JsonConvert.DeserializeObject(requestBody);
           name = name ?? data?.name;

           string responseMessage = string.IsNullOrEmpty(name)
           ? "La función se ha ejecutado bien, pero debes pasar un valor en el QueryString para que se muestre algún resultado."
           : $"¡Hola, {name}, desde el blog de Certia!.";

           return new OkObjectResult(responseMessage);
        }
     }
}

Para ejecutar basta con pulsar F5. En estos momentos nuestra ejecución puede fallar. Recordemos que hemos determinado que es necesario un emulador de Azure Storage. Ese emulador se inicia en este momento, y para ello busca una instalación local de LocalDB o una instancia de SQLExpress por defecto. Si no encuentra ninguna de las dos la inicializacion del emulador de Azure Storage fallará y no podremos depurar.

Para solucionar el problema podemos bien instalar LocalDB o SQL Express, o bien, si tenemos alguna instancia local de SQL Server, decirle al emulador que se inicialice ahí. Para ello podemos ejecutar el emulador manualmente con un parámetro que le indica que ha de buscar cualquier instancia de SQL Server presente en la máquina.

El emulador se halla en el directorio de instalación del SDK de Azure y se llama AzureStorageEmulator.exe. Podemos forzar su inicialización en una instancia determinada de SQL Server o decirle que busque cualquiera de las instancias presentes en local:


AzureStorageEmulator.exe init /server 

AzureStorageEmulator.exe init /*.*

Una vez hecho esto el emulador creará una base de datos en la instancia especificada en la que guardará los elementos que nuestra función requiera. A partir de ese momento la ejecución y la depuración ya se resolverá sin problemas.

El resto es sencillo: basta copiar la URL local que nos propone la salida de la compilación y realizar la llamada desde cualquier navegador. El resultado puede verse en la imagen que sirve de cabecera a este post. La llamada es capturada por la Azure Function, que se ejecuta normalmente. Podemos poner puntos de interrupción en nuestro código y trabajar con ella cómodamente.

En el siguiente post veremos cómo publicar una Azure Function en Azure desde Visual Studio.