Uso de la inserción de dependencias en Azure Functions con .NET

Azure Functions admite el modelo de diseño de software de inserción de dependencias (DI), que es una técnica para lograr la inversión de control (IoC) entre las clases y sus dependencias.

  • La inserción de dependencias en Azure Functions se basa en las características de inserción de dependencias de .NET Core. Se recomienda estar familiarizado con la inserción de dependencias de .NET Core. Hay diferencias en el modo en que se invalidan las dependencias y cómo se leen los valores de configuración con Azure Functions en el plan de consumo.

  • La compatibilidad con la inserción de dependencias comienza con Azure Functions 2.x.

  • Los patrones de inserción de dependencias varían en función de si las funciones de C# se ejecutan en proceso o fuera de proceso.

Importante

La guía de este artículo se aplica solo a las funciones de la biblioteca de clases de C# que se ejecutan en proceso con el entorno de ejecución. Este modelo de inserción de dependencias personalizado no se aplica a las funciones aisladas de .NET, lo que permite ejecutar funciones de .NET 5.0 fuera de proceso. El modelo de procesos de trabajo aislados de .NET se basa en los patrones normales de inserción de dependencias de ASP.NET Core. Para más información, consulte Inserción de dependencias en la guía de procesos de trabajo aislados de .NET.

Requisitos previos

Para poder usar la inserción de dependencias, debe instalar los siguientes paquetes NuGet:

Registro de los servicios

Para registrar los servicios, cree un método para configurar y agregar componentes a una instancia de IFunctionsHostBuilder. El host de Azure Functions crea una instancia de IFunctionsHostBuilder y la pasa directamente al método.

Advertencia

En el caso de las aplicaciones de funciones que se ejecuten en los planes de consumo o Premium, las modificaciones de los valores de configuración utilizados en los desencadenadores pueden provocar problemas de escalabilidad. Cualquier cambio que se haga en estas propiedades mediante la clase FunctionsStartup generará un error de inicio en la aplicación de funciones.

La inyección de IConfiguration puede producir un comportamiento inesperado. Para saber más sobre agregar orígenes de configuración, consulte Personalización de orígenes de configuración.

Para registrar el método, agregue el atributo de ensamblado FunctionsStartup que especifica el nombre del tipo usado durante el inicio.

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

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

namespace MyNamespace;

public class Startup : FunctionsStartup
{
    public override void Configure(IFunctionsHostBuilder builder)
    {
        builder.Services.AddHttpClient();

        builder.Services.AddSingleton<IMyService>((s) => {
            return new MyService();
        });

        builder.Services.AddSingleton<ILoggerProvider, MyLoggerProvider>();
    }
}

En este ejemplo se usa el paquete Microsoft.Extensions.Http para registrar un HttpClient en el inicio.

Advertencias

Una serie de pasos de registro se ejecuta antes y después de que el entorno de ejecución procese la clase de inicio. Por lo tanto, tenga en cuenta los elementos siguientes:

  • La clase de inicio está pensada únicamente para la instalación y el registro. Evite el uso de servicios registrados en el inicio durante el proceso de inicio. Por ejemplo, no intente registrar un mensaje en un registrador que se está registrando durante el inicio. Este punto del proceso de registro es demasiado pronto para que los servicios estén disponibles para su uso. Una vez ejecutado el método Configure, el entorno de ejecución de Functions continúa registrando otras dependencias, lo que puede afectar a cómo funcionan los servicios.

  • El contenedor de inserción de dependencias solo contiene tipos registrados explícitamente. Los únicos servicios disponibles como tipos inyectables son los que se configuran en el método Configure. Como resultado, los tipos específicos de Functions como BindingContext y ExecutionContext no están disponibles durante la instalación ni como tipos insertables.

  • No se admite la configuración de la autenticación de ASP.NET. El host de Functions configura ASP.NET servicios de autenticación para exponer correctamente las API para las operaciones básicas del ciclo de vida. Otras configuraciones de una clase Startup personalizada pueden invalidar esta configuración, lo que provoca consecuencias no deseadas. Por ejemplo, llamar a builder.Services.AddAuthentication() puede interrumpir la autenticación entre el portal y el host, lo que conduce a mensajes como tiempo de ejecución de Azure Functions es inaccesible.

Uso de dependencias insertadas

La inserción de constructores se usa para que las dependencias estén disponibles en una función. El uso de la inserción de constructores requiere que no use clases estáticas para los servicios insertados o para las clases de función.

El ejemplo siguiente muestra cómo las dependencias IMyService y HttpClient se insertan en una función desencadenada por HTTP.

using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.Extensions.Logging;
using System.Net.Http;
using System.Threading.Tasks;

namespace MyNamespace;

public class MyHttpTrigger
{
    private readonly HttpClient _client;
    private readonly IMyService _service;

    public MyHttpTrigger(IHttpClientFactory httpClientFactory, IMyService service)
    {
        this._client = httpClientFactory.CreateClient();
        this._service = service;
    }

    [FunctionName("MyHttpTrigger")]
    public async Task<IActionResult> Run(
        [HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req,
        ILogger log)
    {
        var response = await _client.GetAsync("https://microsoft.com");
        var message = _service.GetMessage();

        return new OkObjectResult("Response from function with injected dependencies.");
    }
}

En este ejemplo se usa el paquete Microsoft.Extensions.Http para registrar un HttpClient en el inicio.

Vigencia de los servicios

Las aplicaciones de Azure Functions proporcionan la misma vigencia de servicio que la inserción de dependencias de ASP.NET. En el caso de una aplicación de Functions, las distintas vigencias del servicio se comportan de la manera siguiente:

  • Transitorio: los servicios transitorios se crean en cada resolución del servicio.
  • De ámbito: una vigencia de servicio de ámbito coincide con una vigencia de ejecución de la función. Los servicios de ámbito se crean una vez por ejecución de la función. Las solicitudes posteriores para ese servicio durante la ejecución vuelven a usar la instancia de servicio existente.
  • Singleton: la vigencia singleton del servicio coincide con la vigencia del host y se reutiliza en ejecuciones de la función en esa instancia. Los servicios de vigencia singleton se recomiendan para conexiones y clientes, por ejemplo, para instancias DocumentClient o HttpClient.

Consulte o descargue un ejemplo de vigencia de servicio diferente en GitHub.

Registro de servicios

Si necesita su propio proveedor de registro, registre un tipo personalizado como instancia de ILoggerProvider, disponible mediante el paquete NuGet Microsoft.Extensions.Logging.Abstractions.

Azure Functions agrega Application Insights automáticamente.

Advertencia

  • No agregue AddApplicationInsightsTelemetry() a la colección de servicios, lo que registra los servicios que entran en conflicto con los servicios proporcionados por el entorno.
  • No registre su propio TelemetryConfiguration ni TelemetryClient si usa la funcionalidad de Application Insights integrada. Si tiene que configurar su propia instancia de TelemetryClient, cree una a través de la TelemetryConfiguration insertada, tal como se muestra en Registrar telemetría personalizada en funciones de C#.

ILogger<T> y ILoggerFactory

El host insertará los servicios ILogger<T> e ILoggerFactory en constructores. Sin embargo, de forma predeterminada, estos nuevos filtros de registro se filtran de los registros de función. Debe modificar el archivo host.json para participar en filtros y categorías adicionales.

En el ejemplo siguiente se muestra cómo agregar un elemento ILogger<HttpTrigger> con registros que se exponen al host.

namespace MyNamespace;

public class HttpTrigger
{
    private readonly ILogger<HttpTrigger> _log;

    public HttpTrigger(ILogger<HttpTrigger> log)
    {
        _log = log;
    }

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

        // ...
}

En el ejemplo siguiente, el archivo host.json se agrega al filtro de seguridad.

{
    "version": "2.0",
    "logging": {
        "applicationInsights": {
            "samplingSettings": {
                "isEnabled": true,
                "excludedTypes": "Request"
            }
        },
        "logLevel": {
            "MyNamespace.HttpTrigger": "Information"
        }
    }
}

Para más información sobre los niveles de registro, consulte Configuración de niveles de registro.

Servicios proporcionados por la aplicación de funciones

El host de la función registra muchos servicios. Los siguientes servicios son seguros para tomar como dependencia en la aplicación:

Tipo de servicio Período de duración Descripción
Microsoft.Extensions.Configuration.IConfiguration Singleton Configuración del entorno en tiempo de ejecución
Microsoft.Azure.WebJobs.Host.Executors.IHostIdProvider Singleton Responsable de proporcionar el id. de la instancia de host

Si hay otros servicios en los que quiere tomar una dependencia, cree un problema y propóngalo en GitHub.

Invalidación de los servicios de host

La invalidación de los servicios proporcionados por el host no se admite. Si hay otros servicios que quiere invalidar, cree un problema y propóngalo en GitHub.

Trabajo con opciones y configuraciones

Los valores definidos en la configuración de la aplicación están disponibles en una instancia de IConfiguration, lo que permite leer los valores de la configuración de la aplicación en la clase de inicio.

Puede extraer valores de la instancia de IConfiguration a un tipo personalizado. Copiar los valores de configuración de la aplicación en un tipo personalizado facilita la prueba de los servicios, ya que permite que estos valores se inserten. Los valores leídos en la instancia de configuración deben ser pares de clave/valor simples. En el caso de las funciones que se ejecutan en un plan de Elastic Premium, los nombres de configuración de la aplicación solo pueden contener letras, números (0-9), puntos (.), dos puntos (:) y caracteres de subrayado (_). Para obtener más información, vea Consideraciones de configuración de la aplicación.

Considere la clase siguiente que incluye una propiedad con nombre coherente con una configuración de aplicación:

public class MyOptions
{
    public string MyCustomSetting { get; set; }
}

Y un archivo local.settings.json que puede estructurar la configuración personalizada de la manera siguiente:

{
  "IsEncrypted": false,
  "Values": {
    "MyOptions:MyCustomSetting": "Foobar"
  }
}

Desde dentro del método Startup.Configure, puede extraer valores de la instancia de IConfiguration en el tipo personalizado mediante el código siguiente:

builder.Services.AddOptions<MyOptions>()
    .Configure<IConfiguration>((settings, configuration) =>
    {
        configuration.GetSection("MyOptions").Bind(settings);
    });

Llamar a Bind copia los valores con nombres de propiedad coincidentes de la configuración en la instancia personalizada. La instancia de opciones ahora está disponible en el contenedor de IoC para su inserción en una función.

El objeto de opciones se inserta en la función como una instancia de la interfaz IOptions genérica. Use la propiedad Value para acceder a los valores encontrados en su configuración.

using System;
using Microsoft.Extensions.Options;

public class HttpTrigger
{
    private readonly MyOptions _settings;

    public HttpTrigger(IOptions<MyOptions> options)
    {
        _settings = options.Value;
    }
}

Para obtener más información, vea patrón de opciones en ASP.NET Core.

Uso de secretos de usuario de ASP.NET Core

Al desarrollar la aplicación localmente, ASP.NET Core proporciona una herramienta Secret Manager que le permite almacenar información secreta fuera de la raíz del proyecto. De este modo, es menos probable que los secretos se confirmen accidentalmente en el control de código fuente. Azure Functions Core Tools (versión 3.0.3233 o posterior) lee automáticamente los secretos que crea el administrador de secretos de ASP.NET Core.

Para configurar un proyecto de Azure Functions de .NET para que use secretos de usuario, ejecute el siguiente comando en la raíz del proyecto.

dotnet user-secrets init

A continuación, use el comando dotnet user-secrets set para crear o actualizar los secretos.

dotnet user-secrets set MySecret "my secret value"

Para acceder a los valores de secretos del usuario en el código de la aplicación de funciones, use IConfiguration o IOptions.

Personalización de orígenes de configuración

Para especificar otros orígenes de configuración, invalide el ConfigureAppConfigurationmétodo en la clase de la aplicación de funcionesStartUp.

En el ejemplo siguiente se agregan valores de configuración de archivos de configuración básicos y opcionales específicos de la aplicación.

using System.IO;
using Microsoft.Azure.Functions.Extensions.DependencyInjection;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;

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

namespace MyNamespace;

public class Startup : FunctionsStartup
{
    public override void ConfigureAppConfiguration(IFunctionsConfigurationBuilder builder)
    {
        FunctionsHostBuilderContext context = builder.GetContext();

        builder.ConfigurationBuilder
            .AddJsonFile(Path.Combine(context.ApplicationRootPath, "appsettings.json"), optional: true, reloadOnChange: false)
            .AddJsonFile(Path.Combine(context.ApplicationRootPath, $"appsettings.{context.EnvironmentName}.json"), optional: true, reloadOnChange: false)
            .AddEnvironmentVariables();
    }
    
    public override void Configure(IFunctionsHostBuilder builder)
    {
    }
}

Añada proveedores de configuración a la propiedad ConfigurationBuilder de IFunctionsConfigurationBuilder. Para obtener más información sobre el uso de proveedores de configuración, consulte Configuración en ASP.NET Core.

FunctionsHostBuilderContext se obtiene de IFunctionsConfigurationBuilder.GetContext(). Use este contexto para recuperar el nombre del entorno actual y resolver la ubicación de los archivos de configuración almacenados en la carpeta de su aplicación de funciones.

De forma predeterminada, los archivos de configuración como appsettings.json no se copian automáticamente en la carpeta de salida de la aplicación de funciones. Actualice el archivo .csproj para que coincida con el ejemplo siguiente para asegurarse de que se copian los archivos.

<None Update="appsettings.json">
    <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>      
</None>
<None Update="appsettings.Development.json">
    <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    <CopyToPublishDirectory>Never</CopyToPublishDirectory>
</None>

Pasos siguientes

Para obtener más información, consulte los siguientes recursos: