Definir el controlador de funciones de Lambda en C# - AWS Lambda

Definir el controlador de funciones de Lambda en C#

El controlador de la función de Lambda es el método del código de la función que procesa eventos. Cuando se invoca una función, Lambda ejecuta el método del controlador. La función se ejecuta hasta que el controlador devuelve una respuesta, se cierra o se agota el tiempo de espera.

Cuando se invoca la función y Lambda ejecuta el método controlador de la función, pasa dos argumentos a la función. El primer argumento es el objeto de event. Cuando otro Servicio de AWS invoca la función, el objeto event contiene datos sobre el evento que provocó la invocación de la función. Por ejemplo, un objeto event de API Gateway contiene información sobre la ruta, el método HTTP y los encabezados HTTP. La estructura exacta del evento varía en función del Servicio de AWS que esté invocando la función. Consulte Invocar Lambda con eventos de otros servicios de AWS para obtener más información sobre los formatos de eventos para los servicios individuales.

Lambda también pasa un objeto context a la función. El objeto proporciona información sobre la invocación, la función y el entorno de ejecución. Para obtener más información, consulte Objeto context de AWS Lambda en C#.

El formato nativo de todos los eventos de Lambda son secuencias de bytes que representan el evento con formato JSON. A menos que los parámetros de entrada y salida de la función sean del tipo System.IO.Stream, debe serializarlos. Especifique el serializador que quiere usar configurando el atributo de conjunto LambdaSerializer. Para obtener más información, consulte Serialización de las funciones de Lambda.

Modelos de ejecución de .NET para Lambda

Existen dos modelos de ejecución diferentes para ejecutar funciones de Lambda en .NET: el enfoque de biblioteca de clases y el enfoque de conjunto ejecutable.

En el enfoque de biblioteca de clases, se proporciona a Lambda una cadena que indica el AssemblyName, ClassName y Method de la función que se va a invocar. Para obtener más información sobre el formato de esta cadena, consulte Controladores de bibliotecas de clases. Durante la fase de inicialización de la función, se inicializa la clase de la función y se ejecuta cualquier código del constructor.

En el enfoque de conjunto ejecutable, se utiliza la característica de declaraciones de nivel superior de C# 9. Este enfoque genera un conjunto ejecutable que Lambda ejecuta cada vez que recibe un comando invoke para su función. Solo debe proporcionar a Lambda el nombre del conjunto ejecutable que se va a ejecutar.

En las siguientes secciones se ofrece un ejemplo de código de función para estos dos enfoques.

Controladores de bibliotecas de clases

El siguiente código de función de Lambda muestra un ejemplo de un método de controlador (FunctionHandler) para una función de Lambda que utiliza el enfoque de biblioteca de clases. En esta función de ejemplo, Lambda recibe un evento de API Gateway que invoca la función. La función lee un registro de una base de datos y lo devuelve como parte de la respuesta de API Gateway.

[assembly: LambdaSerializer(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))] namespace GetProductHandler; public class Function { private readonly IDatabaseRepository _repo; public Function() { this._repo = new DatabaseRepository(); } public async Task<APIGatewayProxyResponse> FunctionHandler(APIGatewayProxyRequest request) { var id = request.PathParameters["id"]; var databaseRecord = await this._repo.GetById(id); return new APIGatewayProxyResponse { StatusCode = (int)HttpStatusCode.OK, Body = JsonSerializer.Serialize(databaseRecord) }; } }

Al crear una función de Lambda, debe proporcionar a Lambda información sobre el controlador de la función en forma de cadena de controlador. Esto le indica a Lambda qué método del código debe ejecutar cuando se invoque la función. En C#, el formato de la cadena del controlador cuando se utiliza el enfoque de biblioteca de clases es el siguiente:

ASSEMBLY::TYPE::METHOD, donde:

  • ASSEMBLY es el nombre del archivo de conjunto de .NET para la aplicación. Si utiliza la CLI de Amazon.Lambda.Tools para compilar la aplicación y no establece el nombre del ensamblado con la propiedad AssemblyName del archivo .csproj, ASSEMBLY será simplemente el nombre de la carpeta que contiene el archivo .csproj.

  • TYPE es el nombre completo del tipo de controlador, que consta de Namespace y ClassName.

  • METHOD es el nombre del método de controlador en el código de la función.

En el código de ejemplo que se muestra, si se nombra el conjunto como GetProductHandler, entonces la cadena del controlador sería GetProductHandler::GetProductHandler.Function::FunctionHandler.

Controladores de conjuntos ejecutables

En el siguiente ejemplo, la función de Lambda se define como un conjunto ejecutable. El método controlador de este código recibe el nombre Handler. Cuando se utilizan ensamblados ejecutables, se debe iniciar el tiempo de ejecución de Lambda. Para ello, se utiliza el método LambdaBootstrapBuilder.Create. Este método toma como entradas el método que su función usa como controlador y el serializador Lambda que debe usar.

Para obtener más información sobre el uso de instrucciones de nivel superior, consulte Introducción del tiempo de ejecución .NET 6 de AWS Lambda en el Blog de computación de AWS.

namespace GetProductHandler; IDatabaseRepository repo = new DatabaseRepository(); await LambdaBootstrapBuilder.Create<APIGatewayProxyRequest>(Handler, new DefaultLambdaJsonSerializer()) .Build() .RunAsync(); async Task<APIGatewayProxyResponse> Handler(APIGatewayProxyRequest apigProxyEvent, ILambdaContext context) { var id = input.PathParameters["id"]; var databaseRecord = await this.repo.GetById(id); return new APIGatewayProxyResponse { StatusCode = (int)HttpStatusCode.OK, Body = JsonSerializer.Serialize(databaseRecord) }; };

Cuando se utilizan conjuntos ejecutables, la cadena del controlador que indica a Lambda cómo ejecutar el código es el nombre del conjunto. En este ejemplo, eso sería GetProductHandler.

Serialización de las funciones de Lambda

Si las funciones de Lambda que utilizan tipos de entrada o salida distintos de un objeto Stream, debe agregar una biblioteca de serialización a la aplicación. Puede implementar la serialización mediante la serialización estándar basada en la reflexión proporcionada por System.Text.Json y Newtonsoft.Json, o mediante la serialización generada en la fuente.

Uso de la serialización generada en la fuente

La serialización generada en la fuente es una característica de las versiones 6 y posteriores de .NET que permite generar el código de serialización en tiempo de compilación. Elimina la necesidad de reflexión y puede mejorar el rendimiento de la función. Para utilizar la serialización generada en la fuente en la función, haga lo siguiente:

  • Cree una nueva clase parcial que herede de JsonSerializerContext al añadir atributos JsonSerializable para todos los tipos que requieran serialización o deserialización.

  • Configurar la LambdaSerializer para usar un SourceGeneratorLambdaJsonSerializer<T>.

  • Actualice cualquier serialización o deserialización manual del código de su aplicación para usar la clase recién creada.

En el siguiente código se muestra un ejemplo de función que utiliza la serialización generada en la fuente.

[assembly: LambdaSerializer(typeof(SourceGeneratorLambdaJsonSerializer<CustomSerializer>))] public class Function { private readonly IDatabaseRepository _repo; public Function() { this._repo = new DatabaseRepository(); } public async Task<APIGatewayProxyResponse> FunctionHandler(APIGatewayProxyRequest request) { var id = request.PathParameters["id"]; var databaseRecord = await this._repo.GetById(id); return new APIGatewayProxyResponse { StatusCode = (int)HttpStatusCode.OK, Body = JsonSerializer.Serialize(databaseRecord, CustomSerializer.Default.Product) }; } } [JsonSerializable(typeof(APIGatewayProxyRequest))] [JsonSerializable(typeof(APIGatewayProxyResponse))] [JsonSerializable(typeof(Product))] public partial class CustomSerializer : JsonSerializerContext { }
nota

Si desea utilizar la compilación anticipada (AOT) nativa con Lambda, debe usar la serialización generada en la fuente.

Uso de la serialización basada en la reflexión

AWS proporciona bibliotecas prediseñadas que le permiten añadir rápidamente la serialización a su aplicación. Esto se configura mediante los paquetes NuGet Amazon.Lambda.Serialization.SystemTextJson o Amazon.Lambda.Serialization.Json. Entre bastidores, Amazon.Lambda.Serialization.SystemTextJson utiliza System.Text.Json para realizar tareas de serialización y Amazon.Lambda.Serialization.Json utiliza el paquete Newtonsoft.Json.

También puede crear su propia biblioteca de serialización mediante la implementación de la interfaz ILambdaSerializer, que está disponible como parte de la biblioteca Amazon.Lambda.Core. Esta interfaz define dos métodos:

  • T Deserialize<T>(Stream requestStream);

    Puede implementar este método para deserializar la carga de solicitud desde la API Invoke en el objeto que se pasa al controlador de la función de Lambda.

  • T Serialize<T>(T response, Stream responseStream);

    Puede implementar este método para serializar el resultado que devuelve el controlador de la función de Lambda en la carga de respuesta que devuelve la API de operación Invoke.

Simplifique el código de funciones con el marco de anotaciones Lambda

Las anotaciones Lambda son un marco de trabajo para .NET 6 y .NET 8 que simplifica la escritura de funciones de Lambda mediante C#. Con el marco de anotaciones, puede reemplazar gran parte del código de una función de Lambda escrita con el modelo de programación normal. El código escrito con el marco utiliza expresiones más sencillas que le permiten centrarse en la lógica empresarial.

El siguiente código de ejemplo muestra cómo el uso del marco de anotaciones puede simplificar la escritura de funciones de Lambda. El primer ejemplo muestra el código escrito con el modelo de programa Lambda normal y el segundo muestra el equivalente con el marco de anotaciones.

public APIGatewayHttpApiV2ProxyResponse LambdaMathAdd(APIGatewayHttpApiV2ProxyRequest request, ILambdaContext context) { if (!request.PathParameters.TryGetValue("x", out var xs)) { return new APIGatewayHttpApiV2ProxyResponse { StatusCode = (int)HttpStatusCode.BadRequest }; } if (!request.PathParameters.TryGetValue("y", out var ys)) { return new APIGatewayHttpApiV2ProxyResponse { StatusCode = (int)HttpStatusCode.BadRequest }; } var x = int.Parse(xs); var y = int.Parse(ys); return new APIGatewayHttpApiV2ProxyResponse { StatusCode = (int)HttpStatusCode.OK, Body = (x + y).ToString(), Headers = new Dictionary≪string, string> { { "Content-Type", "text/plain" } } }; }
[LambdaFunction] [HttpApi(LambdaHttpMethod.Get, "/add/{x}/{y}")] public int Add(int x, int y) { return x + y; }

Para ver otro ejemplo de cómo el uso de anotaciones Lambda puede simplificar su código, consulte este ejemplo de aplicación multiservicio en el repositorio de GitHub awsdocs/aws-doc-sdk-examples. La carpeta PamApiAnnotations usa anotaciones Lambda en el archivo principal function.cs. A modo de comparación, la carpeta PamApi tiene archivos equivalentes escritos con el modelo de programación normal de Lambda.

El marco de anotaciones utiliza generadores de código fuente para generar código que se traduce del modelo de programación Lambda al código que se ve en el segundo ejemplo.

Para obtener más información acerca de cómo utilizar anotaciones de Lambda para .NET, consulte los recursos siguientes:

Inyección de dependencias con el marco de anotaciones Lambda

También puede utilizar el marco de anotaciones de Lambda para añadir una inyección de dependencias a las funciones de Lambda mediante una sintaxis con la que esté familiarizado. Al añadir un atributo [LambdaStartup] a un archivo Startup.cs, la estructura de anotaciones Lambda generará el código necesario en tiempo de compilación.

[LambdaStartup] public class Startup { public void ConfigureServices(IServiceCollection services) { services.AddSingleton<IDatabaseRepository, DatabaseRepository>(); } }

La función de Lambda puede inyectar servicios mediante la inyección de un constructor o mediante la inyección en métodos individuales mediante el atributo [FromServices].

[assembly: LambdaSerializer(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))] namespace GetProductHandler; public class Function { private readonly IDatabaseRepository _repo; public Function(IDatabaseRepository repo) { this._repo = repo; } [LambdaFunction] [HttpApi(LambdaHttpMethod.Get, "/product/{id}")] public async Task<Product> FunctionHandler([FromServices] IDatabaseRepository repository, string id) { return await this._repo.GetById(id); } }

Restricciones del controlador de funciones de Lambda

Tenga en cuenta que existen algunas restricciones que afectan a la firma del controlador.

  • No puede ser unsafe ni utilizar tipos de puntero en la signatura del controlador, aunque puede utilizar el contexto unsafe dentro del método del controlador y sus dependencias. Para obtener más información, consulte inseguro (Referencia de C#) en el sitio web de Microsoft Docs.

  • No puede pasar un número variable de parámetros utilizando la palabra clave params, ni utilizar ArgIterator como parámetro de entrada o de retorno que se utiliza para admitir un número variable de parámetros.

  • El controlador no puede ser un método genérico, por ejemplo, IList<T> Sort<T(IList<T> input).

  • No se admiten los controladores asíncronos con la signatura async void.