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. Si el controlador existe o devuelve una respuesta, pasará a estar disponible para gestionar otro evento.
Un controlador de función de Lambda se define como una instancia o un método estático en una clase. Para acceder al objeto de contexto Lambda, puede definir un parámetro de método de tipo ILambdaContext. Puede utilizar esta opción para acceder a información sobre la invocación actual, como el nombre de la función, el límite de memoria, el tiempo de ejecución restante y el registro.
returnType
handler-name
(inputType
input, ILambdaContext context) { ... }
En la sintaxis, tenga en cuenta lo siguiente:
-
InputType
: el primer parámetro del controlador es la entrada del controlador. Esta entrada puede consistir en datos de eventos (publicados por un origen de eventos) o por una entrada personalizada que proporcione, como una cadena o cualquier objeto de datos personalizado. -
returnType
: si piensa invocar la función de Lambda de forma síncrona (utilizando el tipo de invocaciónRequestResponse
), puede devolver la salida de la función utilizando cualquiera de los tipos de datos admitidos. Por ejemplo, si utiliza una función de Lambda como backend de aplicaciones móviles, la invocación se realiza de forma síncrona. El tipo de datos de salida se serializa en JSON.Si piensa invocar la función de Lambda de forma asíncrona (utilizando el tipo de invocación
Event
),returnType
debería servoid
. Por ejemplo, si utiliza Lambda con orígenes de eventos como Amazon Simple Storage Service (Amazon S3) o Amazon Simple Notification Service (Amazon SNS), estos orígenes de eventos invocan la función de Lambda mediante el tipo de invocaciónEvent
. -
ILambdaContext context
: el segundo argumento de la firma del controlador es opcional. Proporciona acceso al objeto context que tiene información sobre la función y la solicitud.
Control de flujos
De manera predeterminada, Lambda solo es compatible con el tipo System.IO.Stream
como parámetro de entrada.
Por ejemplo, fíjese en el siguiente código de ejemplo de C#.
using System.IO; namespace Example { public class Hello { public Stream MyHandler(Stream stream) { //function logic } } }
En el código C# de ejemplo, el primer parámetro del controlador es la entrada del controlador (MyHandler). Esta entrada puede estar formada por datos de eventos (publicados por un origen de eventos, como Simple Storage Service (Amazon S3)) o por una entrada personalizada que proporcione, como Stream
o cualquier objeto de datos personalizado. La salida es de tipo Stream
.
Control de tipos de datos estándar
Para todos los demás tipos, tendrá que especificar un serializador:
-
Tipos primitivos de .NET (como string o int)
-
Colecciones y mapas: IList, IEnumerable, IList<T>, Array, IDictionary, IDictionary<TKey, TValue>
-
Tipos POCO (objetos CLR estándar simples, o Plain old CLR objects)
-
Tipos de eventos predefinidos de AWS
-
Para las invocaciones asíncronas, Lambda ignora el tipo de retorno. El tipo de retorno se puede establecer en void en estos casos.
-
Si está utilizando la programación asíncrona de .NET, el tipo de retorno puede ser Task y Task<T>, y utilizar las palabras clave
async
yawait
. Para obtener más información, consulte Uso de async en funciones C# con Lambda.
A menos que los parámetros de entrada y salida de la función sean del tipo System.IO.Stream
, debe serializarlos. Lambda proporciona un serializador predeterminado que se puede aplicar en el nivel de ensamblado o de método de la aplicación, o puede definir uno implementando la interfaz ILambdaSerializer
proporcionada por la biblioteca Amazon.Lambda.Core
. Para obtener más información, consulte Implementar funciones de Lambda C# con archivos de archivo .zip.
Para añadir el atributo serializador predeterminado a un método, primero añada una dependencia de Amazon.Lambda.Serialization.SystemTextJson
en el archivo .csproj
.
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <TargetFramework>net6.0</TargetFramework> <ImplicitUsings>enable</ImplicitUsings> <Nullable>enable</Nullable> <GenerateRuntimeConfigurationFiles>true</GenerateRuntimeConfigurationFiles> <AWSProjectType>Lambda</AWSProjectType> <!-- Makes the build directory similar to a publish directory and helps the AWS .NET Lambda Mock Test Tool find project dependencies. --> <CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies> <!-- Generate ready to run images during publishing to improve cold start time. --> <PublishReadyToRun>true</PublishReadyToRun> </PropertyGroup> <ItemGroup> <PackageReference Include="Amazon.Lambda.Core" Version="2.1.0 " /> <PackageReference Include="Amazon.Lambda.Serialization.SystemTextJson" Version="2.2.0" /> </ItemGroup> </Project>
El ejemplo siguiente ilustra la flexibilidad que puede obtenerse especificando el serializador predeterminado de System.Text.Json en un método y otro elegido por el usuario en otro método:
public class ProductService { [LambdaSerializer(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))] public Product DescribeProduct(DescribeProductRequest request) { return catalogService.DescribeProduct(request.Id); } [LambdaSerializer(typeof(MyJsonSerializer))] public Customer DescribeCustomer(DescribeCustomerRequest request) { return customerService.DescribeCustomer(request.Id); } }
Generación de fuentes para serialización JSON
C# 9 proporciona generadores de origen que permiten la generación de código durante la compilación. A partir de .NET 6, la biblioteca JSON nativa System.Text.Json
puede utilizar generadores de orígenes, de modo que permite el análisis JSON sin necesidad de API de reflejo. Esto puede ayudar a mejorar el rendimiento del arranque en frío.
Para utilizar el generador de orígenes
-
En el proyecto, defina una clase vacía y parcial que se derive de
System.Text.Json.Serialization.JsonSerializerContext
. -
Agregue el atributo de
JsonSerializable
para cada tipo .NET para el que el generador de origen debe generar código de serialización.
ejemplo Integración de API Gateway que aprovecha la generación de orígenes
using System.Collections.Generic; using System.Net; using System.Text.Json.Serialization; using Amazon.Lambda.Core; using Amazon.Lambda.APIGatewayEvents; using Amazon.Lambda.Serialization.SystemTextJson; [assembly: LambdaSerializer(typeof(SourceGeneratorLambdaJsonSerializer<SourceGeneratorExa mple.HttpApiJsonSerializerContext>))] namespace SourceGeneratorExample; [JsonSerializable(typeof(APIGatewayHttpApiV2ProxyRequest))] [JsonSerializable(typeof(APIGatewayHttpApiV2ProxyResponse))] public partial class HttpApiJsonSerializerContext : JsonSerializerContext { } public class Functions { public APIGatewayProxyResponse Get(APIGatewayHttpApiV2ProxyRequest request, ILambdaContext context) { context.Logger.LogInformation("Get Request"); var response = new APIGatewayHttpApiV2ProxyResponse { StatusCode = (int)HttpStatusCode.OK, Body = "Hello AWS Serverless", Headers = new Dictionary<string, string> { { "Content-Type", "text/plain" } } }; return response; } }
Cuando invoca la función, Lambda utiliza el código de serialización JSON generado por el origen para gestionar la serialización de los eventos y respuestas de Lambda.
Signaturas del controlador
Cuando cree funciones de Lambda, debe proporcionar una cadena de controlador que indique a Lambda dónde debe buscar el código que va a invocar. En C#, tiene este formato:
ENSAMBLADO::TIPO::MÉTODO
, donde:
-
ENSAMBLADO
es el nombre del archivo de ensamblado de .NET para la aplicación. Cuando utilice la CLI de .NET Core para compilar la aplicación, si no ha establecido el nombre del ensamblado con la propiedadAssemblyName
en el archivo .csproj, el nombre deENSAMBLADO
será el nombre de la carpeta que contiene el archivo .csproj. Para obtener más información, consulte CLI de .NET Core. En este caso, supongamos que el archivo .csproj esHelloWorldApp.csproj
. -
TIPO
es el nombre completo del tipo de controlador, que consta delEspacioDeNombres
y delNombreDeLaClase
. En este casoExample.Hello
. -
MÉTODO
es el nombre del controlador de la función, en este caso,MyHandler
.
En definitiva, la signatura tendrá este formato: Ensamblado::EspacioDeNombres.NombreDeLaClase::NombreDelMétodo
Considere el siguiente ejemplo:
using System.IO; namespace Example { public class Hello { public Stream MyHandler(Stream stream) { //function logic } } }
La cadena del controlador sería: HelloWorldApp::Example.Hello::MyHandler
.
Si el método especificado en la cadena de controlador está sobrecargado, debe proporcionar la signatura exacta del método que Lambda debe invocar. Si la resolución requeriría seleccionar entre varias signaturas (sobrecargadas), Lambda rechazará una signatura que de otra forma sería válida.
Uso de sentencias de nivel superior
A partir de .NET 6, puede escribir funciones mediante declaraciones de nivel superior. Las instrucciones de nivel superior eliminan parte del código reutilizable requerido para los proyectos .NET, lo cual reduce el número de líneas de código que escribe. Por ejemplo, puede volver a escribir el ejemplo anterior utilizando instrucciones de nivel superior:
using Amazon.Lambda.RuntimeSupport; var handler = (Stream stream) => { //function logic }; await LambdaBootstrapBuilder.Create(handler).Build().RunAsync();
Al utilizar sentencias de nivel superior, solo incluye el nombre de ASSEMBLY
al proporcionar la firma del controlador. Continuando con el ejemplo anterior, la cadena del controlador sería HelloWorldApp
.
Al configurar el controlador en el nombre del ensamblaje, Lambda tratará el ensamblaje como un ejecutable y lo ejecutará al inicio. Debe agregar el paquete de NuGet Amazon.Lambda.RuntimeSupport
al proyecto para que el ejecutable que se ejecuta al inicio pueda iniciar el cliente en el tiempo de ejecución de Lambda.
Serialización de las funciones de Lambda
Para 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 hacerlo de las siguientes maneras:
-
Utilice el paquete
Amazon.Lambda.Serialization.SystemTextJson
de NuGet. Esta biblioteca utiliza el serializador JSON de .NET Core nativo para gestionar la serialización. Este paquete proporciona una mejora de rendimiento sobreAmazon.Lambda.Serialization.Json
, pero tenga en cuenta las limitaciones descritas en la documentación de Microsoft. Esta biblioteca está disponible para .NET Core 3.1 y en tiempos de ejecución posteriores. -
Utilice el paquete
Amazon.Lambda.Serialization.Json
de NuGet. Esta biblioteca utiliza JSON.NET para gestionar la serialización. -
Creando su propia biblioteca de serialización mediante la implementación de la interfaz
ILambdaSerializer
, que está disponible formando parte de la bibliotecaAmazon.Lambda.Core
. La 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
.
-
Para utilizar el serializador, debe agregar una dependencia a su archivo MyProject.csproj
.
... <ItemGroup> <PackageReference Include="Amazon.Lambda.Serialization.SystemTextJson" Version="2.1.0" /> <!-- or --> <PackageReference Include="Amazon.Lambda.Serialization.Json" Version="2.0.0" /> </ItemGroup>
A continuación, deberá definir el serializador. El siguiente ejemplo define el serializador
Amazon.Lambda.Serialization.SystemTextJson
en el archivo AssemblyInfo.cs.
[assembly: LambdaSerializer(typeof(DefaultLambdaJsonSerializer))]
El siguiente ejemplo define el serializador Amazon.Lambda.Serialization.Json
en el archivo AssemblyInfo.cs.
[assembly: LambdaSerializer(typeof(JsonSerializer))]
Puede definir un atributo de serialización personalizado en el nivel de método, que sustituye al serializador predeterminado especificado en el nivel de ensamblado.
public class ProductService{ [LambdaSerializer(typeof(JsonSerializer))] public Product DescribeProduct(DescribeProductRequest request) { return catalogService.DescribeProduct(request.Id); } [LambdaSerializer(typeof(MyJsonSerializer))] public Customer DescribeCustomer(DescribeCustomerRequest request) { return customerService.DescribeCustomer(request.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 contextounsafe
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 utilizarArgIterator
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
.
Uso de async en funciones C# con Lambda
Si sabe que la función de Lambda requerirá un proceso de larga duración, como cargar archivos grandes en Amazon Simple Storage Service (Amazon S3) o leer una gran cantidad de registros desde Amazon DynamoDB, puede utilizar el patrón async/await. Cuando se utiliza esta firma, Lambda invoca la función de forma síncrona y espera a que devuelva una respuesta o a que se agote el tiempo de espera de ejecución.
public async Task<Response> ProcessS3ImageResizeAsync(SimpleS3Event input) { var response = await client.DoAsyncWork(input); return response; }
Si utiliza este patrón, tenga en cuenta lo siguiente:
-
Lambda no admite métodos
async void
. -
Si crea una función de Lambda asíncrona sin implementar el operador
await
, .NET emitirá una advertencia de compilación y se producirá un comportamiento inesperado. Por ejemplo, algunas acciones asíncronas se ejecutarán, mientras que otras no. O algunas acciones asíncronas no se completarán antes de que finalice la invocación de la función.public async Task ProcessS3ImageResizeAsync(SimpleS3Event event) // Compiler warning { client.DoAsyncWork(input); }
-
La función de Lambda puede incluir varias llamadas asíncronas, que pueden invocarse en paralelo. Puede utilizar los métodos
Task.WhenAll
yTask.WhenAny
para trabajar con varias tareas. Para utilizar el métodoTask.WhenAll
, pase una lista de las operaciones en una matriz al método. En el ejemplo siguiente, tenga en cuenta que si olvida incluir cualquier operación en la matriz, la llamada puede volver antes de que finalice su operación.public async Task DoesNotWaitForAllTasks1() { // In Lambda, Console.WriteLine goes to CloudWatch Logs. var task1 = Task.Run(() => Console.WriteLine("Test1")); var task2 = Task.Run(() => Console.WriteLine("Test2")); var task3 = Task.Run(() => Console.WriteLine("Test3")); // Lambda may return before printing "Test2" since we never wait on task2. await Task.WhenAll(task1, task3); }
Para utilizar el método
Task.WhenAny
, también se pasa una lista de las operaciones en una matriz al método. La llamada vuelve tan pronto como finaliza la primera operación, incluso si las demás siguen ejecutándose.public async Task DoesNotWaitForAllTasks2() { // In Lambda, Console.WriteLine goes to CloudWatch Logs. var task1 = Task.Run(() => Console.WriteLine("Test1")); var task2 = Task.Run(() => Console.WriteLine("Test2")); var task3 = Task.Run(() => Console.WriteLine("Test3")); // Lambda may return before printing all tests since we're waiting for only one to finish. await Task.WhenAny(task1, task2, task3); }