采用 C# 的 Lambda 函数处理程序 - Amazon Lambda
Amazon Web Services 文档中描述的 Amazon Web Services 服务或功能可能因区域而异。要查看适用于中国区域的差异,请参阅 中国的 Amazon Web Services 服务入门 (PDF)

本文属于机器翻译版本。若本译文内容与英语原文存在差异,则一律以英文原文为准。

采用 C# 的 Lambda 函数处理程序

Lambda 函数处理程序是函数代码中处理事件的方法。当调用函数时,Lambda 运行处理程序方法。您的函数会一直运行,直到处理程序返回响应、退出或超时。

当您的函数被调用且 Lambda 运行了函数的处理程序方法时,它会向您的函数传递两个参数。第一个参数是 event 对象。当另一个 Amazon Web Service 调用您的函数时,event 对象包含有关导致您的函数被调用的事件的数据。例如,来自 API Gateway 的 event 对象包含有关路径、HTTP 方法和 HTTP 标头的信息。确切的事件结构因调用函数的 Amazon Web Service 不同而有所不同。有关各个服务的事件格式的更多信息,请参阅 将 Amazon Lambda 与其他服务一起使用

Lambda 还会将 context 对象传递给函数。此对象包含有关调用、函数和执行环境的信息。有关更多信息,请参阅Amazon LambdaC# 中的 上下文对象

所有 Lambda 事件的本机格式都是表示 JSON 格式的事件的字节流。除非函数输入和输出参数的类型为 System.IO.Stream,否则您需要对这些参数执行序列化。通过设置程序 LambdaSerializer 集属性来指定要使用的序列化器。有关更多信息,请参阅Lambda 函数中的序列化

Lambda 的 .NET 执行模型

在 .NET 中运行 Lambda 函数有两种不同的执行模型:类库方法和可执行程序集方法。

类库方法中,您可以向 Lambda 提供一个字符串,以指示要调用的函数的 AssemblyNameClassNameMethod。有关此字符串的格式的更多信息,请参阅 类库处理程序。在函数的初始化阶段,会初始化函数的类,构造函数中的所有代码都会运行。

可执行程序集方法中,使用 C# 9 的顶级语句功能。这种方法会生成一个可执行程序集,每当 Lambda 收到函数的调用命令时,它就会运行该程序集。您仅向 Lambda 提供要运行的可执行程序集的名称。

以下各节给出了这两种方法的示例函数代码。

类库处理程序

以下 Lambda 函数代码显示了使用类库方法的 Lambda 函数的处理方法(FunctionHandler)的示例。在此示例函数中,Lambda 从 API Gateway 接收了一个调用该函数的事件。该函数从数据库读取了一条记录,并将该记录作为 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) }; } }

创建 Lambda 函数时,您需要以处理程序字符串的形式向 Lambda 提供有关您的函数处理程序的信息。这会告知 Lambda 在函数被调用时运行代码中的哪个方法。在 C# 中,使用类库方法时,处理程序字符串的格式如下所示:

ASSEMBLY::TYPE::METHOD,其中:

  • ASSEMBLY 是您的应用程序的 .NET 程序集文件的名称。如果您使用 Amazon.Lambda.Tools CLI 来构建应用程序,但未使用 .csproj 文件中的 AssemblyName 属性设置程序集名称,则 ASSEMBLY 只是 .csproj 文件的名称。

  • TYPE 是包含 NamespaceClassName 的处理程序类型的全名。

  • METHOD 中您的代码中的函数处理程序方法的名称。

对于所示的示例代码,如果程序集被命名为 GetProductHandler,则处理程序字符串将为 GetProductHandler::GetProductHandler.Function::FunctionHandler

可执行程序集处理程序

在以下示例中,Lambda 函数被定义为了可执行程序集。此代码中的处理程序方法被命名为 Handler。使用可执行程序集时,必须引导 Lambda 运行时系统。为此,请使用 LambdaBootstrapBuilder.Create 方法。此方法将您的函数用作处理程序的方法和要使用的 Lambda 序列化程序作为输入。

有关使用顶级语句的更多信息,请参阅 Amazon 计算博客上的推出适用于 Amazon Lambda 的 .NET 6 运行时系统

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) }; };

使用可执行程序集时,告知 Lambda 如何运行代码的处理程序字符串是程序集的名称。在本示例中,这将为 GetProductHandler

Lambda 函数中的序列化

如果您的 Lambda 函数使用 Stream 对象以外的输入或输出类型,您必须向应用程序中添加一个序列化库。您可以使用 System.Text.JsonNewtonsoft.Json 提供的基于标准反射的序列化来实现序列化,也可以使用源代码生成的序列化来实现序列化。

使用源代码生成的序列化

源代码生成的序列化是 .NET 版本 6 及更高版本的一项功能,它允许在编译时生成序列化代码。它消除了反射需求,可以提高函数性能。要在函数中使用源代码生成的序列化,请执行以下操作:

  • 创建一个继承自 JsonSerializerContext 的新部分类,为所有需要序列化或反序列化的类型添加 JsonSerializable 属性。

  • 配置 LambdaSerializer 以使用 SourceGeneratorLambdaJsonSerializer<T>

  • 更新应用程序代码中的任何手动序列化或反序列化,以使用新创建的类。

以下代码显示了使用源代码生成的序列化的示例函数。

[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 { }
注意

如果您想在 Lambda 中使用本机提前编译(AOT),则必须使用源代码生成的序列化。

使用基于反射的序列化

Amazon 提供了预先构建的库,使您可以快速向应用程序添加序列化。您可以使用 Amazon.Lambda.Serialization.SystemTextJsonAmazon.Lambda.Serialization.Json NuGet 包对此进行配置。在后台,Amazon.Lambda.Serialization.SystemTextJson 使用 System.Text.Json 执行序列化任务,Amazon.Lambda.Serialization.Json 使用 Newtonsoft.Json 程序包。

您还可以通过实施 ILambdaSerializer 接口(作为 Amazon.Lambda.Core 库的一部分提供)创建您自己的序列化库。该接口定义了两种方法:

  • T Deserialize<T>(Stream requestStream);

    通过实施此方法,您可以将请求负载从 Invoke API 反序列化至传递到 Lambda 函数处理程序的对象中。

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

    通过实施此方法,您可以将从 Lambda 函数处理程序中返回的结果序列化到 Invoke API 操作返回的响应负载中。

使用 Lambda 注释框架简化函数代码

Lambda 注释是一个适用于 .NET 6 和 .NET 8 的框架,它简化了使用 C# 编写 Lambda 函数的过程。使用注释框架,您可以替换使用常规编程模型所编写的 Lambda 函数中的大部分代码。使用该框架所编写的代码使用了更简单的表达式,使您可以专注于业务逻辑。

以下示例代码展示了使用注释框架可以如何简化 Lambda 函数的编写。第一个示例显示了使用常规 Lambda 程序模型编写的代码,第二个示例显示了使用注解框架编写的等效代码。

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; }

有关使用 Lambda 注释如何简化代码的另一个示例,请参阅 awsdocs/aws-doc-sdk-examples GitHub 存储库中的此跨服务示例应用程序。文件夹 PamApiAnnotations 在主 function.cs 文件中使用 Lambda 注释。相比之下,PamApi 文件夹包含使用常规 Lambda 编程模型编写的等效文件。

注释框架使用源代码生成器来生成从 Lambda 编程模型转换为第二个示例中所显示代码的代码。

有关如何使用适用于 .NET 的 Lambda 注释的更多信息,请参阅以下资源:

使用 Lambda 注释框架进行依赖关系注入

您还可以使用 Lambda 注释框架,使用您熟悉的语法向 Lambda 函数添加依赖关系注入。当您向 Startup.cs 文件添加 [LambdaStartup] 属性时,Lambda 注释框架将在编译时生成所需的代码。

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

您的 Lambda 函数可以使用构造函数注入或使用 [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); } }

Lambda 函数处理程序限制

请注意,处理程序签名存在一些限制。

  • 它不能是 unsafe 并且不得在处理程序签名中使用指针类型,但您可以在处理程序方法及其依赖项中使用 unsafe 上下文。有关更多信息,请参阅 Microsoft 文档网站上的不安全(C# 参考)

  • 处理程序签名不得使用 params 关键字传递可变数量的参数,也不得将用于支持可变数量参数的 ArgIterator 用作输入或返回参数。

  • 处理程序不能是泛型方法,例如,IList<T> Sort<T>(IList<T> input)。

  • 不支持使用签名 async void 的 Async 处理程序。