27 jul 2020

Azure Functions 1 – Dependency injection

Últimamente gracias a proyectos dentro de la compañía he tenido oportunidad de tener contacto con tecnologías en la “nube”, cloud computing como lo llaman los entendidos. Entre estas tecnologías están las azure functions que son parte de la oferta serverless de Microsoft. 

Estas funciones consisten en porciones de código que se ejecutan producto de algún trigger o evento. Estos triggers puede ser llamadas http, timers, archivos subidos a un blob storage, entre muchos otros tipos. Estas funciones en la versión 2 de las azure functions se pueden escribir utilizando .net core 3.1 (también se puede usar javascript) por lo que se pueden usar técnicas afines, por ejemplo, al web api o los sitios MVC tales como la inyección de dependencias.

Realicemos un ejercicio sencillo. Empecemos por crear nuestra azure function desde visual studio 2019:


Seleccionamos el template de las azure función y hacemos clic en “Next”


Le damos un nombre a nuestro proyecto, seleccionamos una ubicación y hacemos clic en “Create”. A continuación, se nos presentará la pantalla donde podemos seleccionar el trigger que desencadenará la ejecución de nuestra función.


Para nuestro ejercicio seleccionaremos el “HTTP trigger”. En el panel de la derecha si tuviéramos una cuenta en azure podríamos seleccionar el storage account al cual se subiría la azure función, también podemos dejarlo tal cual en el storage emulator como lo haremos en este caso. Adicionalmente podemos seleccionar el nivel de autorización (Authorization level) los cuales pueden ser:

Anonymous: no hay ningún mecanismo que restrinja el acceso.

Function: va a requerir un código o key para acceder a la función particular, este código se pude proporcionar por medio un campo llamado code en el querystring del llamado o bien en un header llamado x-functions-key.

Admin: al igual que el nivel de función el acceso es por medio de un código o key, solo que en este caso se conocen como “host key” y se define una sola llave para todas las funciones dentro de una app función mientras que en Funcion se debe definir un code para cada función.

En nuestro caso dejaremos el nivel del acceso por defecto, es decir Function.
Este template de Visual Studio nos proporciona una azure function totalmente funcional


Esta función espera un parámetro name ya sea en el querystring o bien como parte de un json en el body del llamado. Visual Studio proporciona una manera de probar nuestra función de manera local, sin necesidad de subirla a la nube de Azure. Simplemente ejecutamos nuestra función con lo cual se nos mostrará una ventana de consola como esta:


Como podemos notar en la sección Functions se nos listan las funciones que tenemos codificadas (en este caso solo tenemos una, pero podríamos tener “n”) al tiempo que nos indica, en el caso de las funciones activadas por http trigger, su endpoint. Podemos utilizar insomnia (o postman) por ejemplo para probar nuestra función


Bien, ya tenemos nuestra primera azure function, ahora hagamos esto un poco mas divertido. Añadimos un proyecto de lógica de negocios, en el cual tendremos una clase que vamos a utilizar en nuestra azure función por medio de la inyección de dependencias. Quedamos con algo similar a lo siguiente:


Ahora tenemos un proyecto BLL, donde tenemos nuestra clase RequestProcessor y su respectiva interfaz. 

IRequestProcessor: 

namespace BLL
{
    public interface IRequestProcessor
    {
        string ProcessName(string name);
    }
}

RequestProcessor: 

namespace BLL
{
    public class RequestProcessor : IRequestProcessor
    {
        public string ProcessName(string name)
        {
            return $"Hello {name} from Azure function!";
        }
    }
}


Ahora queremos utilizarla en nuestra función. Probablemente el código de nuestra función termine quedando algo más o menos así (Recordemos añadimos a nuestro proyecto AzureFuncionDI la referencia al proyecto BLL haciendo clic derecho sobre el primero y Add > Project Reference…): 

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

namespace AzureFunctionDI
{
    public class Function1
    {
        private readonly IRequestProcessor Processor;

        public Function1(IRequestProcessor Processor)
        {
            this.Processor = Processor;
        }

        [FunctionName("Function1")]
        public async Task <IActionResult> Run(
            [HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req,
            ILogger log)
        {
            log.LogInformation("C# HTTP trigger function processed a request.");

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

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

            string responseMessage = Processor.ProcessName(name);

            return new OkObjectResult(responseMessage);
        }
    }
}
¿Qué hicimos? Pues añadimos una variable del tipo interfaz y un constructor a nuestra clase para poder inyectar la clase que implementa esta interfaz y para esto debimos quitar el modificador static tanto de la clase como del método run. Esto compila, sin embargo, no se ejecuta correctamente; al realizar el llamado nos muestra algo como esto y nos devuelve un código 500 Internal Server Error:


Esto pasa porque nos hace falta una pieza muy importante que es donde realizamos la inversión del control, es decir, donde decimos que al solicitar un tipo de esa interfaz se debe insertar cierta clase en concreto. En las aplicaciones MVC esto se realiza en el Startup.cs pero aquí no tenemos este archivo, entonces, vamos a crearlo dentro del proyecto que contiene la azure function:

El código del archivo Startup sería el siguiente:

using BLL;
using Microsoft.Azure.Functions.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection;

[assembly: FunctionsStartup(typeof(AzureFunctionDI.Startup))]

namespace AzureFunctionDI
{
    public class Startup : FunctionsStartup
    {
        public override void Configure(IFunctionsHostBuilder builder)
        {
            builder.Services.AddScoped<IRequestProcessor, RequestProcessor>();
        }
    }
}
Como se mencionó al inicio, si ya hemos trabajado con el mecanismo de inyección de dependencias de .Net Core esto no parecerá muy familiar. Para que el código de arriba nos funcione debimos instalar el paquete Microsoft.Azure.Functions.Extensions ya que estamos heredando de functionStartup. En este código indicamos que al solicitar un tipo de interfaz IRequestProcessor se inyecte un objeto RequestProcessor el cual implementa dicha interfaz. Con esto ya en su lugar procedemos a ejecutar nuevamente la función.

Y he aquí que nuestra azure function con inyección de dependencias está funcionando.

En otra entrada explicare como realiza la publicación de la función puesto que esta entrada del blog ya se alargó bastante.