Referencia para desarrolladores de scripts de C# de Azure Functions (.csx)

Este artículo es una introducción al desarrollo de Azure Functions mediante el uso de scripts de C# ( .csx).

Azure Functions permite desarrollar funciones con C# de una de las siguientes maneras:

Tipo Proceso de ejecución Extensión de código Entorno de desarrollo Referencia
Script de C# en el mismo proceso .csx Portal
Core Tools
Este artículo
Biblioteca de clases de C# en el mismo proceso .cs Visual Studio
Visual Studio Code
Core Tools
Funciones de biblioteca de clases de C# en proceso
Biblioteca de clases C# (procesos de trabajo aislados) en un proceso de trabajo aislado .cs Visual Studio
Visual Studio Code
Core Tools
Funciones de proceso de trabajo aislado de .NET

En este artículo se supone que ya ha leído la guía para desarrolladores de Azure Functions.

Funcionamiento de .csx

Los datos fluyen en la función de C# a través de los argumentos de método. Los nombres de los argumentos se especifican en un archivo function.json, y hay nombres predefinidos para acceder a cosas como el registrador de funciones y los tokens de cancelación.

El formato .csx permite escribir menos "texto reutilizable" y centrarse en escribir solo una función de C#. En lugar de encapsular todo en un espacio de nombres y una clase, defina solamente un método Run. Incluya las referencias a ensamblados y los espacios de nombres al principio del archivo como de costumbre.

Los archivos .csx de una aplicación de función se compilan cuando se inicializa una instancia. Este paso de compilación puede conllevar, por ejemplo, que un arranque en frío pueda tardar más para las funciones de script de C# en comparación con las bibliotecas de clases de C#. En este paso de compilación también se plantea la pregunta de por qué las funciones de script de C# se pueden editar en Azure Portal y las bibliotecas de clases de C# no.

Estructura de carpetas

La estructura de carpetas para un proyecto de script de C# tiene el siguiente aspecto:

FunctionsProject
 | - MyFirstFunction
 | | - run.csx
 | | - function.json
 | | - function.proj
 | - MySecondFunction
 | | - run.csx
 | | - function.json
 | | - function.proj
 | - host.json
 | - extensions.csproj
 | - bin

Hay un archivo host.json compartido que se puede usar para configurar la aplicación de función. Cada función tiene su propio archivo de código (.csx) y archivo de configuración de enlace (function.json).

Las extensiones de enlace necesarias en la versión 2.x y posteriores del entorno en tiempo de ejecución de Functions se definen en el archivo extensions.csproj, con los archivos de biblioteca reales de la carpeta bin. Al desarrollar de forma local, debe registrar las extensiones de enlace. Al desarrollar funciones en Azure Portal, este registro se realiza automáticamente.

Enlace a argumentos

Los datos de entrada o salida está enlazados a un parámetro de función de script de C# mediante la propiedad name en el archivo de configuración function.json. En el siguiente ejemplo se muestra un archivo function.json y otro run.csx para una función desencadenada por la cola. El parámetro que recibe los datos del mensaje de cola se llama myQueueItem porque ese es el valor de la propiedad name.

{
    "disabled": false,
    "bindings": [
        {
            "type": "queueTrigger",
            "direction": "in",
            "name": "myQueueItem",
            "queueName": "myqueue-items",
            "connection":"MyStorageConnectionAppSetting"
        }
    ]
}
#r "Microsoft.WindowsAzure.Storage"

using Microsoft.Extensions.Logging;
using Microsoft.WindowsAzure.Storage.Queue;
using System;

public static void Run(CloudQueueMessage myQueueItem, ILogger log)
{
    log.LogInformation($"C# Queue trigger function processed: {myQueueItem.AsString}");
}

La instrucción #r se explica más adelante en este artículo.

Tipos compatibles para los enlaces

Cada enlace tiene sus propios tipos compatibles; por ejemplo, se puede utilizar un desencadenador de blobs con un parámetro de cadena, un parámetro POCO, un parámetro CloudBlockBlob o cualquiera de los demás tipos compatibles. En el artículo de referencia sobre los enlaces de blobs se enumeran todos los tipos de parámetros compatibles para los desencadenadores de blobs. Para obtener más información, vea el artículo sobre desencadenadores y enlaces y los documentos de referencia sobre enlaces para cada tipo de enlace.

Sugerencia

Si planea usar los enlaces HTTP o WebHook, debe evitar el agotamiento de puertos que puede deberse a la creación incorrecta de instancias de HttpClient. Para más información, consulte How to manage connections in Azure Functions (Administración de conexiones en Azure Functions).

Referencia a clases personalizadas

Si tiene que utilizar una clase objeto CRL estándar (POCO) personalizada, puede incluir la definición de clase en el mismo archivo o colocarla en un archivo independiente.

En el ejemplo siguiente se muestra un ejemplo de run.csx que incluye una definición de clase POCO.

public static void Run(string myBlob, out MyClass myQueueItem)
{
    log.Verbose($"C# Blob trigger function processed: {myBlob}");
    myQueueItem = new MyClass() { Id = "myid" };
}

public class MyClass
{
    public string Id { get; set; }
}

Una clase POCO debe tener un captador y un establecedor definidos para cada propiedad.

Reutilización del código .csx

Puede usar las clases y los métodos definidos en otros archivos .csx con el archivo run.csx. Para ello, utilice directivas #load en el archivo run.csx. En el siguiente ejemplo, una rutina de registro denominada MyLogger se comparte en myLogger.csx y se carga en run.csx mediante la directiva #load:

Archivo run.csxde ejemplo:

#load "mylogger.csx"

using Microsoft.Extensions.Logging;

public static void Run(TimerInfo myTimer, ILogger log)
{
    log.LogInformation($"Log by run.csx: {DateTime.Now}");
    MyLogger(log, $"Log by MyLogger: {DateTime.Now}");
}

Archivo mylogger.csxde ejemplo:

public static void MyLogger(ILogger log, string logtext)
{
    log.LogInformation(logtext);
}

El uso de un archivo .csx compartido es un patrón común para asignar rigurosamente los datos transferidos entre funciones mediante un objeto POCO. En el siguiente ejemplo simplificado, un desencadenador HTTP y un desencadenador de cola comparten un objeto POCO denominado Order para tipar fuertemente los datos del pedido:

Por ejemplo, run.csx para el desencadenador HTTP:

#load "..\shared\order.csx"

using System.Net;
using Microsoft.Extensions.Logging;

public static async Task<HttpResponseMessage> Run(Order req, IAsyncCollector<Order> outputQueueItem, ILogger log)
{
    log.LogInformation("C# HTTP trigger function received an order.");
    log.LogInformation(req.ToString());
    log.LogInformation("Submitting to processing queue.");

    if (req.orderId == null)
    {
        return new HttpResponseMessage(HttpStatusCode.BadRequest);
    }
    else
    {
        await outputQueueItem.AddAsync(req);
        return new HttpResponseMessage(HttpStatusCode.OK);
    }
}

Por ejemplo, run.csx para el desencadenador de cola:

#load "..\shared\order.csx"

using System;
using Microsoft.Extensions.Logging;

public static void Run(Order myQueueItem, out Order outputQueueItem, ILogger log)
{
    log.LogInformation($"C# Queue trigger function processed order...");
    log.LogInformation(myQueueItem.ToString());

    outputQueueItem = myQueueItem;
}

Por ejemplo, order.csx:

public class Order
{
    public string orderId {get; set; }
    public string custName {get; set;}
    public string custAddress {get; set;}
    public string custEmail {get; set;}
    public string cartId {get; set; }

    public override String ToString()
    {
        return "\n{\n\torderId : " + orderId +
                  "\n\tcustName : " + custName +
                  "\n\tcustAddress : " + custAddress +
                  "\n\tcustEmail : " + custEmail +
                  "\n\tcartId : " + cartId + "\n}";
    }
}

Puede usar una ruta de acceso relativa con la directiva #load :

  • #load "mylogger.csx" carga un archivo que se encuentra en la carpeta de la función.
  • #load "loadedfiles\mylogger.csx" carga un archivo ubicado en una carpeta dentro de la carpeta de la función.
  • #load "..\shared\mylogger.csx" carga un archivo ubicado en una carpeta del mismo nivel que la carpeta de la función, es decir, directamente en wwwroot.

La directiva #load solo funciona con archivos .csx, no con archivos .cs.

Enlace al valor devuelto del método

Puede usar el valor devuelto de un método para un enlace de salida, mediante el nombre $return en function.json. Para ver ejemplos, consulte Desencadenadores y enlaces.

Utilice el valor devuelto solo si una ejecución de función correcta siempre da como resultado un valor devuelto para pasar al enlace de salida. En caso contrario, use ICollector o IAsyncCollector, como se muestra en la sección siguiente.

Escribir varios valores de salida

Para escribir varios valores en un enlace de salida, o si una invocación de función correcta podría no dar nada como resultado para pasar al enlace de salida, use los tipos ICollector o IAsyncCollector. Estos tipos son colecciones de solo escritura que se escriben en el enlace de salida cuando se completa el método.

En este ejemplo se escriben varios mensajes en cola en la misma cola mediante ICollector:

public static void Run(ICollector<string> myQueue, ILogger log)
{
    myQueue.Add("Hello");
    myQueue.Add("World!");
}

Registro

Para grabar la salida en los registros de streaming de C#, incluya un argumento de tipo ILogger. Es recomendable que lo denomine log. Evite el uso de Console.Write en Azure Functions.

public static void Run(string myBlob, ILogger log)
{
    log.LogInformation($"C# Blob trigger function processed: {myBlob}");
}

Nota

Para obtener información sobre un marco de registro más reciente que se pueda usar en lugar de TraceWriter, vea la documentación de ILogger en la guía para desarrolladores de la biblioteca de clases .NET.

Registro de métricas personalizadas

Puede usar el método de extensión LogMetric en ILogger para crear métricas personalizadas en Application Insights. Este es un ejemplo de una llamada de método:

logger.LogMetric("TestMetric", 1234);

Este código es una alternativa a llamar a TrackMetric con la API de Application Insights para .NET.

Async

Para convertir una función en asincrónica, use la palabra clave y devuelva un objeto Task.

public async static Task ProcessQueueMessageAsync(
        string blobName,
        Stream blobInput,
        Stream blobOutput)
{
    await blobInput.CopyToAsync(blobOutput, 4096);
}

No puede usar parámetros out en funciones asincrónicas. Para los enlaces de salida, utilice el valor devuelto de función o un objeto recopilador en su lugar.

Tokens de cancelación

Una función puede aceptar un parámetro CancellationToken que permite que el sistema operativo notifique al código cuando la función esté a punto de finalizar. Puede utilizar esta notificación para asegurarse de que la función no se termina inesperadamente en una forma que deje los datos en un estado incoherente.

En el ejemplo siguiente se muestra cómo comprobar la finalización inminente de la función.

using System;
using System.IO;
using System.Threading;

public static void Run(
    string inputText,
    TextWriter logger,
    CancellationToken token)
{
    for (int i = 0; i < 100; i++)
    {
        if (token.IsCancellationRequested)
        {
            logger.WriteLine("Function was cancelled at iteration {0}", i);
            break;
        }
        Thread.Sleep(5000);
        logger.WriteLine("Normal processing for queue message={0}", inputText);
    }
}

Importación de espacios de nombres

Si necesita importar espacios de nombres, puede hacerlo de la manera habitual, con la cláusula using .

using System.Net;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;

public static Task<HttpResponseMessage> Run(HttpRequestMessage req, ILogger log)

Los siguientes espacios de nombres se importan automáticamente y, por tanto, son opcionales:

  • System
  • System.Collections.Generic
  • System.IO
  • System.Linq
  • System.Net.Http
  • System.Threading.Tasks
  • Microsoft.Azure.WebJobs
  • Microsoft.Azure.WebJobs.Host

Referencia a ensamblados externos

Para los ensamblados de marco, agregue referencias mediante la directiva #r "AssemblyName" .

#r "System.Web.Http"

using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;

public static Task<HttpResponseMessage> Run(HttpRequestMessage req, ILogger log)

El entorno de hospedaje de Azure Functions agrega automáticamente los siguientes ensamblados:

  • mscorlib
  • System
  • System.Core
  • System.Xml
  • System.Net.Http
  • Microsoft.Azure.WebJobs
  • Microsoft.Azure.WebJobs.Host
  • Microsoft.Azure.WebJobs.Extensions
  • System.Web.Http
  • System.Net.Http.Formatting

Se puede hacer referencia a los ensamblados siguientes con nombre simple por versión de runtime:

  • Newtonsoft.Json
  • Microsoft.WindowsAzure.Storage*

*Eliminado en la versión 4.x de runtime.

En el código, se hace referencia a los ensamblados como en el ejemplo siguiente:

#r "AssemblyName"

Hacer referencia a ensamblados personalizados

Para hacer referencia a un ensamblado personalizado, puede usar un ensamblado compartido o un ensamblado privado:

  • Los ensamblados compartidos se comparten en todas las funciones dentro de una aplicación de función. Para hacer referencia a un ensamblado personalizado, cargue el ensamblado en una carpeta denominada bin en su carpeta raíz de aplicaciones de función (wwwroot).

  • Los ensamblados privados forman parte del contexto de una función determinada y admiten la instalación de prueba de versiones diferentes. Los ensamblados privados se deben cargar en una carpeta bin en el directorio de la función. Haga referencia a ellos con el nombre de archivo, como #r "MyAssembly.dll".

Para más información sobre cómo cargar archivos en su carpeta de función, consulte la sección sobre administración de paquetes.

Directorios inspeccionados

El directorio que contiene el archivo de script de función se inspecciona automáticamente para buscar cambios en los ensamblados. Para inspeccionar los cambios de los ensamblado en otros directorios, agréguelos a la lista watchDirectories en host.json.

Uso de paquetes NuGet

La forma en que los paquetes de extensión de enlace y otros paquetes de NuGet se agregan a la aplicación de funciones depende de la versión de destino del entorno de ejecución de Functions.

De forma predeterminada, el conjunto admitido de paquetes de extensión de Functions NuGet está disponible para la aplicación de funciones de script de C# mediante agrupaciones de extensiones. Para obtener más información, consulte Conjuntos de extensiones.

Si por alguna razón no puede usar agrupaciones de extensiones en el proyecto, también puede usar Azure Functions Core Tools para instalar extensiones basadas en enlaces definidos en los archivos function.json de la aplicación. Al usar Core Tools para registrar extensiones, asegúrese de usar la opción --csx. Para obtener más información, consulte Instalación de extensiones.

De forma predeterminada, Core Tools lee los archivos function.json y agrega los paquetes necesarios a un archivo de proyecto de biblioteca de clases de C# extensions.csproj en la raíz del sistema de archivos de la aplicación de funciones (wwwroot). Dado que Core Tools usa dotnet.exe, puede usarlo para agregar cualquier referencia de paquete NuGet a este archivo de extensiones. Durante la instalación, Core Tools compila extensions.csproj para instalar las bibliotecas necesarias. Este es un ejemplo del archivo extensions.csproj en el que se agrega una referencia a la versión 1.1.0 de Microsoft.ProjectOxford.Face:

<Project Sdk="Microsoft.NET.Sdk">
    <PropertyGroup>
        <TargetFramework>netstandard2.0</TargetFramework>
    </PropertyGroup>
    <ItemGroup>
        <PackageReference Include="Microsoft.ProjectOxford.Face" Version="1.1.0" />
    </ItemGroup>
</Project>

Nota

Para el script de C# (.csx), debe establecer TargetFramework en el valor netstandard2.0. No se admiten otras plataformas de destino, como net6.0.

Para usar una fuente NuGet personalizada, especifique la fuente en un archivo Nuget.Config en la carpeta raíz de la aplicación de funciones. Para más información, vea Configuring NuGet behavior (Configuración del comportamiento de NuGet).

Si solo está trabajando en el proyecto en el portal, deberá crear manualmente el archivo extensions.csproj o un archivo Nuget.Config directamente en el sitio. Para obtener más información, consulte Instalación manual de extensiones.

Variables de entorno

Para obtener una variable de entorno o un valor de configuración de aplicación, use System.Environment.GetEnvironmentVariable, como se muestra en el ejemplo de código siguiente:

public static void Run(TimerInfo myTimer, ILogger log)
{
    log.LogInformation($"C# Timer trigger function executed at: {DateTime.Now}");
    log.LogInformation(GetEnvironmentVariable("AzureWebJobsStorage"));
    log.LogInformation(GetEnvironmentVariable("WEBSITE_SITE_NAME"));
}

public static string GetEnvironmentVariable(string name)
{
    return name + ": " +
        System.Environment.GetEnvironmentVariable(name, EnvironmentVariableTarget.Process);
}

Enlace en tiempo de ejecución

En C# y otros lenguajes .NET, puede usar un patrón de enlace imperativo, en contraposición a los enlaces declarativos de function.json. Los enlaces imperativos resultan útiles cuando los parámetros de enlace tienen que calcularse en tiempo de ejecución, en lugar de en el tiempo de diseño. Con este patrón, se pueden establecer enlaces compatibles de entrada y salida sobre la marcha en el código de función.

Defina un enlace imperativo como se indica a continuación:

  • No incluya una entrada en function.json para los enlaces imperativos deseados.
  • Pase un parámetro de entrada Binder binder o IBinder binder.
  • Utilice el siguiente patrón de C# para realizar el enlace de datos.
using (var output = await binder.BindAsync<T>(new BindingTypeAttribute(...)))
{
    ...
}

BindingTypeAttribute es el atributo de .NET que define el enlace y T es un tipo de entrada o de salida compatible con ese tipo de enlace. T no puede ser un tipo de parámetro out (como out JObject). Por ejemplo, el enlace de salida de la tabla de Mobile Apps admite seis tipos de salida, pero solo se puede usar ICollector<T> o IAsyncCollector<T> para T.

Ejemplo de un único atributo

El ejemplo de código siguiente crea un enlace de salida al blob de almacenamiento con la ruta de acceso al blob definida en tiempo de ejecución y, a continuación, escribe una cadena en el blob.

using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Host.Bindings.Runtime;

public static async Task Run(string input, Binder binder)
{
    using (var writer = await binder.BindAsync<TextWriter>(new BlobAttribute("samples-output/path")))
    {
        writer.Write("Hello World!!");
    }
}

BlobAttribute define el enlace de entrada o salida del blob de almacenamiento, y TextWriter es un tipo de enlace de salida admitido.

Ejemplo de varios atributos

En el ejemplo anterior se obtiene el valor de la aplicación para la cadena de conexión en la cuenta de almacenamiento principal de la aplicación de función (que es AzureWebJobsStorage). Se puede especificar una configuración personalizada de la aplicación para utilizarla para la cuenta de almacenamiento agregando el atributo StorageAccountAttribute y pasando la matriz de atributos a . Use un parámetro Binder, no IBinder. Por ejemplo:

using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Host.Bindings.Runtime;

public static async Task Run(string input, Binder binder)
{
    var attributes = new Attribute[]
    {
        new BlobAttribute("samples-output/path"),
        new StorageAccountAttribute("MyStorageAccount")
    };

    using (var writer = await binder.BindAsync<TextWriter>(attributes))
    {
        writer.Write("Hello World!");
    }
}

En la tabla siguiente, aparecen los atributos de .NET para cada tipo de enlace, así como los paquetes en los que se definen.

Enlace Atributo Agregar referencia
Azure Cosmos DB Microsoft.Azure.WebJobs.DocumentDBAttribute #r "Microsoft.Azure.WebJobs.Extensions.CosmosDB"
Event Hubs Microsoft.Azure.WebJobs.ServiceBus.EventHubAttribute, Microsoft.Azure.WebJobs.ServiceBusAccountAttribute #r "Microsoft.Azure.Jobs.ServiceBus"
Mobile Apps Microsoft.Azure.WebJobs.MobileTableAttribute #r "Microsoft.Azure.WebJobs.Extensions.MobileApps"
Notification Hubs Microsoft.Azure.WebJobs.NotificationHubAttribute #r "Microsoft.Azure.WebJobs.Extensions.NotificationHubs"
Azure Service Bus Microsoft.Azure.WebJobs.ServiceBusAttribute, Microsoft.Azure.WebJobs.ServiceBusAccountAttribute #r "Microsoft.Azure.WebJobs.ServiceBus"
Cola de almacenamiento Microsoft.Azure.WebJobs.QueueAttribute, Microsoft.Azure.WebJobs.StorageAccountAttribute
Blob de almacenamiento Microsoft.Azure.WebJobs.BlobAttribute, Microsoft.Azure.WebJobs.StorageAccountAttribute
Tabla de almacenamiento Microsoft.Azure.WebJobs.TableAttribute, Microsoft.Azure.WebJobs.StorageAccountAttribute
Twilio Microsoft.Azure.WebJobs.TwilioSmsAttribute #r "Microsoft.Azure.WebJobs.Extensions.Twilio"

Pasos siguientes