

# 使用 C\$1 构建 Lambda 函数
<a name="lambda-csharp"></a>

您可以使用托管的 .NET 8 运行时系统、自定义运行时系统或容器映像，在 Lambda 中运行您的 .NET 应用程序。编译应用程序代码后，您可以将其作为 .zip 文件或容器映像部署到 Lambda。Lambda 为.NET 语言提供以下运行时系统：


| 名称 | 标识符 | 操作系统 | 弃用日期 | 阻止函数创建 | 阻止函数更新 | 
| --- | --- | --- | --- | --- | --- | 
|  .NET 10  |  `dotnet10`  |  Amazon Linux 2023  |   2028 年 11 月 14 日   |   2028 年 12 月 14 日   |   2029 年 1 月 15 日   | 
|  .NET 9（仅限容器）  |  `dotnet9`  |  Amazon Linux 2023  |   2026 年 11 月 10 日   |   未计划   |   未计划   | 
|  .NET 8  |  `dotnet8`  |  Amazon Linux 2023  |   2026 年 11 月 10 日   |   2026 年 12 月 10 日   |   2027 年 1 月 11 日   | 

## 设置 .NET 开发环境
<a name="csharp-dev-env"></a>

要开发和构建 Lambda 函数，您可以使用任何常用的 .NET 集成式开发环境（IDE），包括 Microsoft Visual Studio、Visual Studio Code 和 JetBrains Rider。为了简化您的开发体验，AWS 提供了一组 .NET 项目模板以及 `Amazon.Lambda.Tools` 命令行界面（CLI）。

运行以下 .NET CLI 命令来安装这些项目模板和命令行工具。

### 安装 .NET 项目模板
<a name="csharp-dev-env-templates"></a>

要安装项目模板，请运行以下命令：

```
dotnet new install Amazon.Lambda.Templates
```

### 安装和更新 CLI 工具
<a name="csharp-dev-env-cli-tools"></a>

运行以下命令来安装、更新和卸载 `Amazon.Lambda.Tools` CLI。

要安装命令行工具：

```
dotnet tool install -g Amazon.Lambda.Tools
```

要更新命令行工具：

```
dotnet tool update -g Amazon.Lambda.Tools
```

要卸载命令行工具：

```
dotnet tool uninstall -g Amazon.Lambda.Tools
```

# 定义采用 C\$1 的 Lambda 函数处理程序
<a name="csharp-handler"></a>

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

本页介绍如何将 C\$1 Lambda 函数处理程序与 .NET 托管运行时配合使用，包括项目设置选项、命名约定和最佳实践。本页还包括 C\$1 Lambda 函数的示例，在示例中该函数接收订单信息，生成文本文件收据，然后将此文件放入 Amazon Simple Storage Service（S3）存储桶中。有关如何在编写函数后部署函数的信息，请参阅[使用 .zip 文件归档构建和部署 C\$1 Lambda 函数](csharp-package.md)或[使用容器映像部署 .NET Lambda 函数](csharp-image.md)。

**Topics**
+ [

## 设置 C\$1 处理程序项目
](#csharp-handler-setup)
+ [

## 示例 C\$1 Lambda 函数代码
](#csharp-example-code)
+ [

## 类库处理程序
](#csharp-class-library-handlers)
+ [

## 可执行程序集处理程序
](#csharp-executable-assembly-handlers)
+ [

## C\$1 函数的有效处理程序签名
](#csharp-handler-signatures)
+ [

## 处理程序命名约定
](#csharp-handler-naming)
+ [

## C\$1 Lambda 函数中的序列化
](#csharp-handler-serializer)
+ [

## 基于文件的函数
](#csharp-file-based-functions)
+ [

## 访问和使用 Lambda 上下文对象
](#csharp-example-context)
+ [

## 在处理程序中使用 适用于 .NET 的 SDK v3
](#csharp-example-sdk-usage)
+ [

## 评估环境变量
](#csharp-example-envvars)
+ [

## 使用全局状态
](#csharp-handler-state)
+ [

## 使用 Lambda 注释框架简化函数代码
](#csharp-handler-annotations)
+ [

## C\$1 Lambda 函数的代码最佳实践
](#csharp-best-practices)

## 设置 C\$1 处理程序项目
<a name="csharp-handler-setup"></a>

使用 C\$1 Lambda 函数时，此过程包括编写代码，然后将代码部署到 Lambda。在 .NET 中部署 Lambda 函数有两种不同的执行模型：类库方法和可执行程序集方法。

在类库方法中，您可以将函数代码打包为 .NET 程序集（`.dll`），然后使用 .NET 托管运行时（`dotnet8`）将其部署到 Lambda。对于处理程序名称，Lambda 需要采用格式为 `AssemblyName::Namespace.Classname::Methodname` 的字符串。在函数的初始化阶段，会初始化函数的类，构造函数中的所有代码都会运行。

在可执行程序集方法中，您可以使用 C\$1 9 中首次推出的[顶级语句功能](https://learn.microsoft.com/en-us/dotnet/csharp/tutorials/top-level-statements)。这种方法会生成一个可执行程序集，每当 Lambda 收到函数的调用命令时，它就会运行该程序集。在这种方法中，您还可以使用 .NET 托管运行时（`dotnet8`）。对于处理程序名称，您可以向 Lambda 提供要运行的可执行程序集的名称。

本页的主要示例说明了类库方法。您可以通过多种方式初始化 C\$1 Lambda 项目，但最简单的方法是将 .NET CLI 与 `Amazon.Lambda.Tools` CLI 结合使用。按照 [设置 .NET 开发环境](lambda-csharp.md#csharp-dev-env) 中的步骤设置 `Amazon.Lambda.Tools` CLI。然后使用以下命令来初始化您的项目：

```
dotnet new lambda.EmptyFunction --name ExampleCS
```

此命令会生成以下文件结构：

```
/project-root 
    └ src
        └ ExampleCS
            └ Function.cs (contains main handler)
            └ Readme.md
            └ aws-lambda-tools-defaults.json
            └ ExampleCS.csproj          
    └ test
         └ ExampleCS.Tests
            └ FunctionTest.cs (contains main handler)
            └ ExampleCS.Tests.csproj
```

在此文件结构中，函数的主处理程序逻辑位于 `Function.cs` 文件中。

## 示例 C\$1 Lambda 函数代码
<a name="csharp-example-code"></a>

以下示例 C\$1 Lambda 函数代码接收有关订单的信息，生成文本文件接收，并将此文件放入 Amazon S3 存储桶中。

**Example `Function.cs` Lambda 函数**  

```
using System;
using System.Text;
using System.Threading.Tasks;
using Amazon.Lambda.Core;
using Amazon.S3;
using Amazon.S3.Model;

// Assembly attribute to enable Lambda function logging
[assembly: LambdaSerializer(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))]

namespace ExampleLambda;

public class Order
{
    public string OrderId { get; set; } = string.Empty;
    public double Amount { get; set; }
    public string Item { get; set; } = string.Empty;
}

public class OrderHandler
{
    private static readonly AmazonS3Client s3Client = new();

    public async Task<string> HandleRequest(Order order, ILambdaContext context)
    {
        try
        {
            string? bucketName = Environment.GetEnvironmentVariable("RECEIPT_BUCKET");
            if (string.IsNullOrWhiteSpace(bucketName))
            {
                throw new ArgumentException("RECEIPT_BUCKET environment variable is not set");
            }

            string receiptContent = $"OrderID: {order.OrderId}\nAmount: ${order.Amount:F2}\nItem: {order.Item}";
            string key = $"receipts/{order.OrderId}.txt";

            await UploadReceiptToS3(bucketName, key, receiptContent);

            context.Logger.LogInformation($"Successfully processed order {order.OrderId} and stored receipt in S3 bucket {bucketName}");
            return "Success";
        }
        catch (Exception ex)
        {
            context.Logger.LogError($"Failed to process order: {ex.Message}");
            throw;
        }
    }

    private async Task UploadReceiptToS3(string bucketName, string key, string receiptContent)
    {
        try
        {
            var putRequest = new PutObjectRequest
            {
                BucketName = bucketName,
                Key = key,
                ContentBody = receiptContent,
                ContentType = "text/plain"
            };

            await s3Client.PutObjectAsync(putRequest);
        }
        catch (AmazonS3Exception ex)
        {
            throw new Exception($"Failed to upload receipt to S3: {ex.Message}", ex);
        }
    }
}
```

此 `Function.cs` 文件包含以下代码部分：
+ `using` 语句：使用这些语句可导入 Lambda 函数所需的 C\$1 类。
+ `[assembly: LambdaSerializer(...)]`：`LambdaSerializer` 是一个程序集属性，告诉 Lambda 在将 JSON 事件负载传递给函数之前自动将其转换为 C\$1 对象。
+ `namespace ExampleLambda`：用于定义命名空间。在 C\$1 中，命名空间名称不必与文件名一致。
+ `public class Order {...}`：用于定义预期输入事件的形状。
+ `public class OrderHandler {...}`：用于定义您的 C\$1 类。在其中，您可以定义主处理程序方法和任何其他帮助程序方法。
+ `private static readonly AmazonS3Client s3Client = new();`：用于在主处理程序方法之外使用默认凭证提供程序链来初始化 Amazon S3 客户端。这会导致 Lambda 在[初始化阶段](lambda-runtime-environment.md#runtimes-lifecycle-ib)运行此代码。
+ `public async ... HandleRequest (Order order, ILambdaContext context)`：这是包含主应用程序逻辑的**主处理程序方法**。
+ `private async Task UploadReceiptToS3(...) {}`：这是主 `handleRequest` 处理程序方法引用的帮助程序方法。

由于此函数需要 Amazon S3 SDK 客户端，因此您必须将其添加到项目的依赖项中。您可以导航到 `src/ExampleCS` 并使用以下命令来执行此操作：

```
dotnet add package AWSSDK.S3
```

### 将元数据信息添加到 aws-lambda-tools-defaults.json
<a name="csharp-metadata-example"></a>

默认情况下，生成的 `aws-lambda-tools-defaults.json` 文件不包含函数的 `profile` 或 `region` 信息。此外，请将 `function-handler` 字符串更新为正确的值（`ExampleCS::ExampleLambda.OrderHandler::HandleRequest`）。您可以手动进行此更新并添加必要的元数据，以便将特定的凭证配置文件和区域用于您的函数。例如，您的 `aws-lambda-tools-defaults.json` 文件应如下所示：

```
{
  "Information": [
    "This file provides default values for the deployment wizard inside Visual Studio and the AWS Lambda commands added to the .NET Core CLI.",
    "To learn more about the Lambda commands with the .NET Core CLI execute the following command at the command line in the project root directory.",
    "dotnet lambda help",
    "All the command line options for the Lambda command can be specified in this file."
  ],
  "profile": "default",
  "region": "us-east-1",
  "configuration": "Release",
  "function-architecture": "x86_64",
  "function-runtime": "dotnet8",
  "function-memory-size": 512,
  "function-timeout": 30,
  "function-handler": "ExampleCS::ExampleLambda.OrderHandler::HandleRequest"
}
```

要使此函数正常运行，其[执行角色](lambda-intro-execution-role.md)必须允许 `s3:PutObject` 操作。此外，请确保您定义了 `RECEIPT_BUCKET` 环境变量。成功调用后，Amazon S3 存储桶应包含接收文件。

## 类库处理程序
<a name="csharp-class-library-handlers"></a>

本页的主要[示例代码](#csharp-example-code)说明了类库处理程序。类库处理程序具有以下结构：

```
[assembly: LambdaSerializer(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))]

namespace NAMESPACE;

...

public class CLASSNAME {
    public async Task<string> METHODNAME (...) {
    ...
    }
}
```

创建 Lambda 函数时，您需要在[处理程序字段](https://docs.aws.amazon.com/lambda/latest/api/API_CreateFunction.html#lambda-CreateFunction-request-Handler)中以字符串的形式向 Lambda 提供有关您的函数处理程序的信息。这会告知 Lambda 在函数被调用时运行代码中的哪个方法。在 C\$1 中，对于类库处理程序，处理程序字符串的格式为 `ASSEMBLY::TYPE::METHOD`，其中：
+ `ASSEMBLY` 是您的应用程序的 .NET 程序集文件的名称。如果您使用 `Amazon.Lambda.Tools` CLI 来构建应用程序，但未使用 `.csproj` 文件中的 `AssemblyName` 属性来设置程序集名称，则 `ASSEMBLY` 只是 `.csproj` 文件的名称。
+ `TYPE` 是处理程序类型的全称，即 `NAMESPACE.CLASSNAME`。
+ `METHOD` 是代码中主处理程序方法的名称，即 `METHODNAME`。

对于本页上的主要示例代码，如果程序集被命名为 `ExampleCS`，则完整的处理程序字符串将为 `ExampleCS::ExampleLambda.OrderHandler::HandleRequest`。

## 可执行程序集处理程序
<a name="csharp-executable-assembly-handlers"></a>

您也可以在 C\$1 中将 Lambda 函数定义为可执行程序集。可执行程序集处理程序利用了 C\$1 的顶级语句功能，在该功能中，编译器会生 成`Main()` 方法并将您的函数代码放入其中。使用可执行程序集时，必须引导 Lambda 运行时系统。为此，请在代码中使用 `LambdaBootstrapBuilder.Create` 方法。此方法的输入是要使用的主处理程序函数和 Lambda 序列化程序。以下是 C\$1 中可执行程序集处理程序的示例：

```
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 = apigProxyEvent.PathParameters["id"];
    var databaseRecord = await this.repo.GetById(id);
    
    return new APIGatewayProxyResponse 
    {
        StatusCode = (int)HttpStatusCode.OK,
        Body = JsonSerializer.Serialize(databaseRecord)
    };
};
```

在可执行程序集处理程序的[处理程序字段](https://docs.aws.amazon.com/lambda/latest/api/API_CreateFunction.html#lambda-CreateFunction-request-Handler)中，告知 Lambda 如何运行代码的处理程序字符串是程序集的名称。在此示例中为 `GetProductHandler`。

## C\$1 函数的有效处理程序签名
<a name="csharp-handler-signatures"></a>

在 C\$1 中，有效的 Lambda 处理程序签名需要 0 到 2 个参数。通常，处理程序签名具有两个参数，如主示例所示：

```
public async Task<string> HandleRequest(Order order, ILambdaContext context)
```

提供两个参数时，第一个参数必须是事件输入，第二个参数则必须是 Lambda 上下文对象。两个参数都为选填。例如，以下示例也是 C\$1 中有效的 Lambda 处理程序签名：
+ `public async Task<string> HandleRequest()`
+ `public async Task<string> HandleRequest(Order order)`
+ `public async Task<string> HandleRequest(ILambdaContext context)`

除了处理程序签名的基本语法外，还有一些额外限制：
+ 您不能在处理程序签名中使用 `unsafe` 关键字。但是，您可以在处理程序方法及其依赖项中使用 `unsafe` 上下文。有关更多信息，请参阅 Microsoft 文档网站上的 [unsafe（C\$1 参考）](https://msdn.microsoft.com/en-us/library/chfa2zb8.aspx)。
+ 处理程序不得使用 `params` 关键字，也不能使用 `ArgIterator` 作为输入或返回参数。这些关键字支持可变数量的参数。处理程序可以接受的参数最多为两个。
+ 处理程序不能是泛型方法。换句话说，它不能使用泛型类型参数，例如 `<T>`。
+ Lambda 不支持签名中带有 `async void` 的异步处理程序。

## 处理程序命名约定
<a name="csharp-handler-naming"></a>

C\$1 中的 Lambda 处理程序并没有严格的命名限制。但是，您必须确保在部署函数时向 Lambda 提供正确的处理程序字符串。正确的处理程序字符串取决于您部署的是[类库处理程序](#csharp-class-library-handlers)还是[可执行程序集处理程序](#csharp-executable-assembly-handlers)。

尽管处理程序可以使用任意名称，但 C\$1 中的函数名称通常采用 PascalCase。此外，尽管文件名不需要与类名或处理程序名称一致，但通常情况下，如果类名是 `OrderHandler`，最佳做法是使用像 `OrderHandler.cs` 这样的文件名。例如，您可以将本示例中的文件名从 `Function.cs` 修改为 `OrderHandler.cs`。

## C\$1 Lambda 函数中的序列化
<a name="csharp-handler-serializer"></a>

JSON 是 Lambda 函数最常用且最标准的输入格式。在此示例中，该函数需要类似于下方的输入：

```
{
    "orderId": "12345",
    "amount": 199.99,
    "item": "Wireless Headphones"
}
```

在 C\$1 中，您可以在类中定义预期输入事件的形状。在此示例中，我们定义了 `Order` 类来为此输入建模：

```
public class Order
{
    public string OrderId { get; set; } = string.Empty;
    public double Amount { get; set; }
    public string Item { get; set; } = string.Empty;
}
```

如果您的 Lambda 函数使用 `Stream` 对象以外的输入或输出类型，您必须向应用程序中添加一个序列化库。这样您就可以将 JSON 输入转换为您定义的类的实例。Lambda 中的 C\$1 函数有两种序列化方法：基于反射的序列化和源生成的序列化。

### 基于反射的序列化
<a name="csharp-reflection-based-serialization"></a>

AWS 提供了预先构建的库，您可以快速将其添加到您的应用程序中。这些库使用[反射](https://learn.microsoft.com/en-us/dotnet/csharp/advanced-topics/reflection-and-attributes/)来实现序列化。使用以下程序包之一可实现基于反射的序列化：
+ `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 操作返回的响应负载中。

本页的主要示例使用了基于反射的序列化。基于反射的序列化与 AWS Lambda 一起开箱即用，无需进行额外设置，因此，如果想要操作简单，这是一个不错的选择。但是，它确实需要使用更多的函数内存。由于运行时反射，您可能还会遭遇更高的函数延迟。

### 源生成的序列化
<a name="csharp-source-generated-serialization"></a>

使用源生成的序列化，序列化代码会在编译时生成。这种序列化消除了反射需求，可以提高函数性能。要在函数中使用源生成的序列化，请执行以下操作：
+ 创建一个继承自 `JsonSerializerContext` 的新部分类，为所有需要序列化或反序列化的类型添加 `JsonSerializable` 属性。
+ 配置 `LambdaSerializer` 以使用 `SourceGeneratorLambdaJsonSerializer<T>`。
+ 更新应用程序代码中的任何手动序列化和反序列化，以使用新创建的类。

以下示例说明了如何修改本页上的主要示例（该示例使用了基于反射的序列化），改为使用源生成的序列化。

```
using System.Text.Json;
using System.Text.Json.Serialization;

...

public class Order
{
    public string OrderId { get; set; } = string.Empty;
    public double Amount { get; set; }
    public string Item { get; set; } = string.Empty;
}

[JsonSerializable(typeof(Order))]
public partial class OrderJsonContext : JsonSerializerContext {}

public class OrderHandler
{

    ...

    public async Task<string> HandleRequest(string input, ILambdaContext context)
    {
    
    var order = JsonSerializer.Deserialize(input, OrderJsonContext.Default.Order);
    
    ...
    
    }

}
```

与基于反射的序列化相比，源生成的序列化需要进行更多设置。但是，由于编译时生成代码，使用源生成的函数往往会占用更少的内存并具备更好的性能。要消除函数[冷启动](lambda-runtime-environment.md#cold-start-latency)，可以考虑切换到源生成的序列化。

**注意**  
如果您想将本机[提前编译（AOT）](dotnet-native-aot.md)与 Lambda 结合使用，则必须使用源生成的序列化。

## 基于文件的函数
<a name="csharp-file-based-functions"></a>

基于文件的应用程序是在 .NET 10 中引入的，它允许从单个 `.cs` 文件构建 .NET 应用程序，而无需 `.csproj` 文件或目录结构。Lambda 从 .NET 10 开始支持基于文件的函数。它们提供了一种简化的轻量级方法来采用 C\$1 构建 Lambda 函数。

开始创建 C\$1 基于文件的 Lambda 函数的最快方法是使用 `Amazon.Lambda.Templates` 包。要安装此程序包，请运行以下命令：

```
dotnet new install Amazon.Lambda.Templates
```

接下来，创建一个 C\$1 基于文件的 Lambda 示例函数：

```
dotnet new lambda.FileBased -n MyLambdaFunction
```

基于文件的函数使用[可执行程序集处理程序](#csharp-executable-assembly-handlers)。因此，您必须包含 `Amazon.Lambda.RuntimeSupport` NuGet 包，并使用 `LambdaBootstrapBuilder.Create` 方法为事件类型注册 .NET 处理程序函数，并启动 .NET Lambda 运行时客户端。

基于文件的函数默认使用 .NET 本机 AOT，这需要源生成的序列化。您可以通过在源文件中指定 `#:property PublishAot=false` 来禁用本机 AOT。有关在 Lambda 中使用本机 AOT 的更多信息，请参阅[将 .NET Lambda 函数代码编译为本地运行时格式](dotnet-native-aot.md)。

## 访问和使用 Lambda 上下文对象
<a name="csharp-example-context"></a>

Lambda [上下文对象](csharp-context.md)包含有关调用、函数和执行环境的信息。在此示例中，上下文对象的类型为 `Amazon.Lambda.Core.ILambdaContext`，是主处理程序函数的第二个参数。

```
public async Task<string> HandleRequest(Order order, ILambdaContext context) {
    ...
}
```

上下文对象是可选输入。有关有效接受处理程序签名的更多信息，请参阅[C\$1 函数的有效处理程序签名](#csharp-handler-signatures)。

上下文对象对于向 Amazon CloudWatch 生成函数日志非常有用。您可以使用 `context.getLogger()` 方法获取用于日志记录的 `LambdaLogger` 对象。在此示例中，如果由于任何原因处理失败，我们可以使用记录器记录错误消息：

```
context.Logger.LogError($"Failed to process order: {ex.Message}");
```

除了日志记录之外，您还可以使用上下文对象进行函数监控。有关上下文对象的更多信息，请参阅[使用 Lambda 上下文对象检索 C\$1 函数信息](csharp-context.md)。

## 在处理程序中使用 适用于 .NET 的 SDK v3
<a name="csharp-example-sdk-usage"></a>

通常，您将使用 Lambda 函数与其他 AWS 资源进行交互或对其进行更新。与此类资源最简单的交互方法是使用 适用于 .NET 的 SDK v3。

**注意**  
适用于 .NET 的 SDK（v2）已弃用。建议您只使用 适用于 .NET 的 SDK v3。

您可以使用以下 `Amazon.Lambda.Tools` 命令将 SDK 依赖项添加到您的项目中：

```
dotnet add package <package_name>
```

例如，在本页的主要示例中，我们需要使用 Amazon S3 API 将收据上传到 S3。我们可以使用以下命令导入 Amazon S3 SDK 客户端：

```
dotnet add package AWSSDK.S3
```

此命令可将依赖项添加到您的项目中。您还会在项目的 `.csproj` 文件中看到如下所示的行：

```
<PackageReference Include="AWSSDK.S3" Version="3.7.2.18" />
```

然后，直接在 C\$1 代码中导入这些依赖项：

```
using Amazon.S3;
using Amazon.S3.Model;
```

然后，示例代码将初始化 Amazon S3 客户端（使用[默认凭证提供程序链](https://docs.aws.amazon.com/sdkref/latest/guide/standardized-credentials.html)），如下所示：

```
private static readonly AmazonS3Client s3Client = new();
```

在此示例中，我们在主处理程序函数之外初始化了 Amazon S3 客户端，以免每次调用函数时都必须对其进行初始化。初始化 SDK 客户端后，您可以使用该客户端与其他 AWS 服务进行交互。示例代码按如下方式调用 Amazon S3 `PutObject` API：

```
var putRequest = new PutObjectRequest
{
    BucketName = bucketName,
    Key = key,
    ContentBody = receiptContent,
    ContentType = "text/plain"
};

await s3Client.PutObjectAsync(putRequest);
```

## 评估环境变量
<a name="csharp-example-envvars"></a>

在处理程序代码中，您可以使用 `System.Environment.GetEnvironmentVariable` 方法引用任何[环境变量](configuration-envvars.md)。在此示例中，我们使用以下代码行来引用已定义的 `RECEIPT_BUCKET` 环境变量：

```
string? bucketName = Environment.GetEnvironmentVariable("RECEIPT_BUCKET");
if (string.IsNullOrWhiteSpace(bucketName))
{
    throw new ArgumentException("RECEIPT_BUCKET environment variable is not set");
}
```

## 使用全局状态
<a name="csharp-handler-state"></a>

在首次调用您的函数之前，Lambda 会在[初始化阶段](lambda-runtime-environment.md#runtimes-lifecycle-ib)运行您的静态代码和类构造函数。初始化期间创建的资源在两次调用之间保留在内存中，因此您可以避免每次调用函数时都必须创建这些资源的情况。

在示例代码中，S3 客户端初始化代码位于主处理程序方法之外。运行时会在函数处理其第一个事件之前初始化客户端，这可能会导致更长的处理时间。后续事件的处理速度则要快得多，因为 Lambda 不需要再次初始化客户端。

## 使用 Lambda 注释框架简化函数代码
<a name="csharp-handler-annotations"></a>

[Lambda 注释](https://www.nuget.org/packages/Amazon.Lambda.Annotations)是一个适用于 .NET 8 的框架，它简化了使用 C\$1 编写 Lambda 函数的过程。注释框架使用[源生成器](https://learn.microsoft.com/en-us/dotnet/csharp/roslyn-sdk/source-generators-overview)来生成从 Lambda 编程模型转换为简化代码所的代码。使用注释框架，您可以替换使用常规编程模型所编写的 Lambda 函数中的大部分代码。使用该框架所编写的代码使用了更简单的表达式，使您可以专注于业务逻辑。有关示例，请参阅 nuget 文档中的 [Amazon.Lambda.Annotations](https://www.nuget.org/packages/Amazon.Lambda.Annotations)。

有关使用 Lambda 注释的完整应用示例，请参阅 `awsdocs/aws-doc-sdk-examples` GitHub 存储库中的 [PhotoAssetManager](https://github.com/awsdocs/aws-doc-sdk-examples/tree/main/dotnetv3/cross-service/PhotoAssetManager) 示例。`PamApiAnnotations` 目录中的 主 `Function.cs` 文件中使用了 Lambda 注释。相比之下，`PamApi` 目录包含使用常规 Lambda 编程模型编写的等效文件。

### 使用 Lambda 注释框架进行依赖关系注入
<a name="csharp-handler-annotations-injection"></a>

您还可以使用 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);
    }
}
```

## C\$1 Lambda 函数的代码最佳实践
<a name="csharp-best-practices"></a>

在构建 Lambda 函数时，请遵循以下列表中的指南，采用最佳编码实践：
+ **从核心逻辑中分离 Lambda 处理程序。**这样您可以创建更容易进行单元测试的函数。
+ **控制函数部署包中的依赖关系。**AWS Lambda 执行环境包含许多库。Lambda 会定期更新这些库，以支持最新的功能组合和安全更新。这些更新可能会使 Lambda 函数的行为发生细微变化。要完全控制您的函数所用的依赖项，请使用部署包来打包所有依赖项。
+ **将依赖关系的复杂性降至最低。**首选在[执行环境](lambda-runtime-environment.md)启动时可以快速加载的更简单的框架。
+ **将部署包大小精简为只包含运行时必要的部分。**这样会减少调用前下载和解压缩部署包所需的时间。对于用 .NET 编写的函数，请不要将整个 AWS SDK 库作为部署包的一部分上传。而是要根据所需的模块有选择地挑选软件开发工具包中的组件（例如 DynamoDB、Simple Storage Service (Amazon S3) 软件开发工具包模块和 Lambda 核心库）。

**利用执行环境重用来提高函数性能。**连接软件开发工具包 (SDK) 客户端和函数处理程序之外的数据库，并在 `/tmp` 目录中本地缓存静态资产。由函数的同一实例处理的后续调用可重用这些资源。这样就可以通过缩短函数运行时间来节省成本。

为了避免调用之间潜在的数据泄露，请不要使用执行环境来存储用户数据、事件或其他具有安全影响的信息。如果您的函数依赖于无法存储在处理程序的内存中的可变状态，请考虑为每个用户创建单独的函数或单独的函数版本。

**使用 keep-alive 指令来维护持久连接。**Lambda 会随着时间的推移清除空闲连接。在调用函数时尝试重用空闲连接会导致连接错误。要维护您的持久连接，请使用与运行时关联的 keep-alive 指令。有关示例，请参阅[在 Node.js 中通过 Keep-Alive 重用连接](https://docs.aws.amazon.com/sdk-for-javascript/v3/developer-guide/node-reusing-connections.html)。

**使用[环境变量](configuration-envvars.md)将操作参数传递给函数。**例如，您在写入 Amazon S3 存储桶时，不应对要写入的存储桶名称进行硬编码，而应将存储桶名称配置为环境变量。

**避免在 Lambda 函数中使用递归调用**，在这种情况下，函数会调用自己或启动可能再次调用该函数的进程。这可能会导致意想不到的函数调用量和升级成本。如果您看到意外的调用量，请立即将函数保留并发设置为 `0` 来限制对函数的所有调用，同时更新代码。

Lambda 函数代码中**不要使用非正式的非公有 API**。对于 AWS Lambda 托管式运行时，Lambda 会定期为 Lambda 的内部 API 应用安全性和功能更新。这些内部 API 更新可能不能向后兼容，会导致意外后果，例如，假设您的函数依赖于这些非公有 API，则调用会失败。请参阅 [API 参考](https://docs.aws.amazon.com/lambda/latest/api/welcome.html)以查看公开发布的 API 列表。

**编写幂等代码。**为您的函数编写幂等代码可确保以相同的方式处理重复事件。您的代码应该正确验证事件并优雅地处理重复事件。有关更多信息，请参阅[如何使我的 Lambda 函数具有幂等性？](https://aws.amazon.com/premiumsupport/knowledge-center/lambda-function-idempotent/)。

# 使用 .zip 文件归档构建和部署 C\$1 Lambda 函数
<a name="csharp-package"></a>

.NET 部署包（.zip 文件存档），包含您的函数的已编译程序集以及其所有程序集依赖项。该程序包还包含一个 `proj.deps.json` 文件。这将向 .NET 运行时系统告知您的所有函数的依赖项和 `proj.runtimeconfig.json` 文件，后者用于配置运行时系统。

要部署单个 Lambda 函数，您可以使用 `Amazon.Lambda.Tools` .NET Lambda Global CLI。使用 `dotnet lambda deploy-function` 命令会自动创建.zip 部署包并将其部署到 Lambda。但是，建议您使用类似 AWS Serverless Application Model（AWS SAM）或 AWS Cloud Development Kit (AWS CDK) 等框架来将 .NET 应用程序部署到 AWS。

无服务器应用程序通常由 Lambda 函数和其他托管 AWS 服务 组合而成，它们共同执行特定的业务任务。AWS SAM 和 AWS CDK 简化了使用其他 AWS 服务 大规模构建和部署 Lambda 函数的过程。[AWS SAM 模板规范](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-specification.html)提供了一种简单而干净的语法，用于描述构成无服务器应用程序的 Lambda 函数、API、权限、配置和其他 AWS 资源。使用 [AWS CDK](https://docs.aws.amazon.com/cdk/v2/guide/home.html)，您可以将云基础设施定义为代码，以帮助您借助 .NET 等现代编程语言和框架，在云中构建可靠、可扩展且成本高效的应用程序。AWS CDK 和 AWS SAM 都使用 .NET Lambda Global CLI 来打包您的函数。

尽管可以[使用 .NET Core CLI](csharp-package-cli.md#csharp-layers) 将 [Lambda 层](chapter-layers.md)与 C\$1 中的函数结合使用，但我们不建议这样做。C\$1 中使用层的函数会在 [Init 阶段](lambda-runtime-environment.md#runtimes-lifecycle-ib) 期间将共享程序集手动加载到内存中，而这可能会增加冷启动时间。相反，在编译时包含所有共享代码，以避免在运行时加载程序集对性能造成影响。

您可以在以下各节中找到有关使用 AWS SAM、AWS CDK 和 .NET Lambda Global CLI 构建和部署 .NET Lambda 函数的说明。

**Topics**
+ [

# 使用 .NET Lambda Global CLI
](csharp-package-cli.md)
+ [

# 使用 AWS SAM 部署 C\$1 Lambda 函数
](csharp-package-sam.md)
+ [

# 使用 AWS CDK 部署 C\$1 Lambda 函数
](csharp-package-cdk.md)
+ [

# 部署 ASP.NET 应用程序
](csharp-package-asp.md)

# 使用 .NET Lambda Global CLI
<a name="csharp-package-cli"></a>

.NET CLI 和.NET Lambda 全球工具扩展（`Amazon.Lambda.Tools`）提供了一种跨平台的方式来创建基于 .NET 的 Lambda 应用程序、将其打包并部署到 Lambda。在本节中，您将学习如何使用 .NET CLI 和 Amazon Lambda 模板创建新的 Lambda .NET 项目，以及如何使用 `Amazon.Lambda.Tools` 对其进行打包和部署

**Topics**
+ [

## 先决条件
](#csharp-package-cli-prerequisites)
+ [

## 使用 .NET CLI 创建 .NET 项目
](#csharp-package-cli-create)
+ [

## 使用 .NET CLI 部署 .NET 项目
](#csharp-package-cli-deploy)
+ [

## 将 Lambda 层与 .NET CLI 结合使用
](#csharp-layers)

## 先决条件
<a name="csharp-package-cli-prerequisites"></a>

**.NET 8 SDK**  
如果您尚未安装 [.NET 8](https://dotnet.microsoft.com/en-us/download/dotnet/8.0) SDK 和运行时系统，则请安装。

**AWS Amazon.Lambda.Templates .NET 项目模板**  
要生成您的 Lambda 函数代码，请使用 [https://www.nuget.org/packages/Amazon.Lambda.Templates](https://www.nuget.org/packages/Amazon.Lambda.Templates) NuGet 包。要安装此模板包，请运行以下命令：  

```
dotnet new install Amazon.Lambda.Templates
```

**AWS Amazon.Lambda.Tools .NET Global CLI 工具**  
要创建您的 Lambda 函数，请使用 [https://www.nuget.org/packages/Amazon.Lambda.Tools](https://www.nuget.org/packages/Amazon.Lambda.Tools) [.NET 全球工具扩展](https://aws.amazon.com/blogs/developer/net-core-global-tools-for-aws/)。要安装 Amazon.Lambda.Tools，请运行以下命令：  

```
dotnet tool install -g Amazon.Lambda.Tools
```
有关 Amazon.Lambda.Tools .NET CLI 扩展的更多信息，请参阅 GitHub 上的[适用于 .NET CLI 的 AWS 扩展](https://github.com/aws/aws-extensions-for-dotnet-cli)存储库。

## 使用 .NET CLI 创建 .NET 项目
<a name="csharp-package-cli-create"></a>

在 .NET CLI 中，您可以使用 `dotnet new` 命令从命令行创建 .NET 项目。Lambda 使用 [https://www.nuget.org/packages/Amazon.Lambda.Templates](https://www.nuget.org/packages/Amazon.Lambda.Templates) NuGet 软件包提供了其他模板。

安装此软件包后，运行以下命令，以查看可用模板的列表。

```
dotnet new list
```

要检查有关模板的详细信息，请使用 `help` 选项。例如，要查看有关 `lambda.EmptyFunction` 模板的详细信息，请运行以下命令。

```
dotnet new lambda.EmptyFunction --help
```

要为 .NET Lambda 函数创建基本模板，请使用 `lambda.EmptyFunction` 模板。这将创建一个简单的函数，该函数将字符串作为输入，并使用 `ToUpper` 方法将其转换为大写。此模板支持以下选项：
+ `--name` – 函数的名称。
+ `--region` – 要在其中创建函数的 AWS 区域。
+ `--profile` – 适用于 .NET 的 AWS SDK 凭证文件中配置文件的名称。要详细了解 .NET 中的凭证配置文件，请参阅《适用于 .NET 的 AWS SDK 开发人员指南》**中的[配置 AWS 凭证](https://docs.aws.amazon.com/sdk-for-net/v3/developer-guide/net-dg-config-creds.html)。

在此示例中，我们使用默认配置文件和 AWS 区域 设置创建了一个名为 `myDotnetFunction` 的新空函数：

```
dotnet new lambda.EmptyFunction --name myDotnetFunction
```

此命令在您的项目目录中创建了以下文件和目录。

```
└── myDotnetFunction
    ├── src
    │   └── myDotnetFunction
    │       ├── Function.cs
    │       ├── Readme.md
    │       ├── aws-lambda-tools-defaults.json
    │       └── myDotnetFunction.csproj
    └── test
        └── myDotnetFunction.Tests
            ├── FunctionTest.cs
            └── myDotnetFunction.Tests.csproj
```

在 `src/myDotnetFunction` 目录下，检查以下文件：
+ **aws-lambda-tools-defaults.json**：这是您部署 Lambda 函数时指定命令行选项的位置。例如：

  ```
    "profile" : "default",
    "region" : "us-east-2",
    "configuration" : "Release",
    "function-architecture": "x86_64",
    "function-runtime":"dotnet8",
    "function-memory-size" : 256,
    "function-timeout" : 30,
    "function-handler" : "myDotnetFunction::myDotnetFunction.Function::FunctionHandler"
  ```
+ **Function.cs**：您的 Lambda 处理程序函数代码。它是一个 C\$1 模板，该模板包含默认 `Amazon.Lambda.Core` 库和默认 `LambdaSerializer` 属性。有关序列化要求和选项的更多信息，请参阅[C\$1 Lambda 函数中的序列化](csharp-handler.md#csharp-handler-serializer)。它还包含一个示例函数，您可以编辑该函数以应用您的 Lambda 函数代码。

  ```
  using Amazon.Lambda.Core;
  
  // Assembly attribute to enable the Lambda function's JSON input to be converted into a .NET class.
  [assembly: LambdaSerializer(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))]
  
  namespace myDotnetFunction;
  
  public class Function
  {
  
      /// <summary>
      /// A simple function that takes a string and does a ToUpper
      /// </summary≫
      /// <param name="input"></param>
      /// <param name="context"></param>
      /// <returns></returns>
      public string FunctionHandler(string input, ILambdaContext context)
      {
          return input.ToUpper();
      }
  }
  ```
+ **myDotnetFunction.csproj**：列出构成您的应用程序的文件和程序集的 [MSBuild](https://msdn.microsoft.com/en-us/library/dd393574.aspx) 文件。

  ```
  <Project Sdk="Microsoft.NET.Sdk">
    <PropertyGroup>
      <TargetFramework>net8.0</TargetFramework>
      <ImplicitUsings>enable</ImplicitUsings>
      <Nullable>enable</Nullable>
      <GenerateRuntimeConfigurationFiles>true</GenerateRuntimeConfigurationFiles>
      <AWSProjectType>Lambda</AWSProjectType>
      <!-- This property 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.2.0" />
      <PackageReference Include="Amazon.Lambda.Serialization.SystemTextJson" Version="2.4.0" />
    </ItemGroup>
  </Project>
  ```
+ **Readme**：使用此文件记录您的 Lambda 函数。

在 `myfunction/test` 目录下，检查以下文件：
+ **myDotnetFunction.Tests.csproj**：如前所述，这是一个 [MSBuild](https://msdn.microsoft.com/en-us/library/dd393574.aspx) 文件，其中列出了构成您的测试项目的文件和程序集。另请注意，它包含 `Amazon.Lambda.Core` 库，因此您可以无缝集成测试函数所需的任何 Lambda 模板。

  ```
  <Project Sdk="Microsoft.NET.Sdk">
     ... 
  
      <PackageReference Include="Amazon.Lambda.Core" Version="2.2.0 " />
     ...
  ```
+ **FunctionTest.cs**：`src` 目录中包含的相同 C\$1 代码模板文件。编辑此文件，以镜像您函数的生产代码并对其进行测试，然后将您的 Lambda 函数上载到生产环境。

  ```
  using Xunit;
  using Amazon.Lambda.Core;
  using Amazon.Lambda.TestUtilities;
  
  using MyFunction;
  
  namespace MyFunction.Tests
  {
      public class FunctionTest
      {
          [Fact]
          public void TestToUpperFunction()
          {
  
              // Invoke the lambda function and confirm the string was upper cased.
              var function = new Function();
              var context = new TestLambdaContext();
              var upperCase = function.FunctionHandler("hello world", context);
  
              Assert.Equal("HELLO WORLD", upperCase);
          }
      }
  }
  ```

## 使用 .NET CLI 部署 .NET 项目
<a name="csharp-package-cli-deploy"></a>

要构建您的部署包并将其部署到 Lambda，您可以使用 `Amazon.Lambda.Tools` CLI 工具。要使用您在前面步骤中创建的文件部署函数，请先导航到包含函数的 `.csproj` 文件的文件夹。

```
cd myDotnetFunction/src/myDotnetFunction
```

要将您的代码作为 .zip 部署包部署到 Lambda，请运行以下命令。选择您自己的函数名称。

```
dotnet lambda deploy-function myDotnetFunction
```

在部署过程中，向导会要求您选择 [使用执行角色定义 Lambda 函数权限](lambda-intro-execution-role.md)。在本示例中，选择 `lambda_basic_role`。

部署函数后，您可以在云中使用 `dotnet lambda invoke-function` 命令对其进行测试。对于 `lambda.EmptyFunction` 模板中的示例代码，您可以通过使用 `--payload` 选项传递字符串来测试您的函数。

```
dotnet lambda invoke-function myDotnetFunction --payload "Just checking if everything is OK"
```

如果您的函数已成功部署，则应看到与以下内容相似的输出。

```
dotnet lambda invoke-function myDotnetFunction --payload "Just checking if everything is OK"
Amazon Lambda Tools for .NET Core applications (5.8.0)
Project Home: https://github.com/aws/aws-extensions-for-dotnet-cli, https://github.com/aws/aws-lambda-dotnet

Payload:
"JUST CHECKING IF EVERYTHING IS OK"

Log Tail:
START RequestId: id Version: $LATEST
END RequestId: id
REPORT RequestId: id  Duration: 0.99 ms       Billed Duration: 1 ms         Memory Size: 256 MB     Max Memory Used: 12 MB
```

## 将 Lambda 层与 .NET CLI 结合使用
<a name="csharp-layers"></a>

**注意**  
尽管可以将[层](chapter-layers.md)与 .NET 中的函数结合使用，但我们不建议这样做。.NET 中使用层的函数会在 `Init` 阶段将共享程序集手动加载到内存中，而这可能会增加冷启动时间。您可以改为在编译时包含所有共享代码，以利用 .NET 编译器的内置优化。

.NET CLI 支持帮助您发布层并部署使用层的 C\$1 函数的命令。要将层发布到指定的 Amazon S3 存储桶，请在 `.csproj` 文件所在的同一目录中运行以下命令：

```
dotnet lambda publish-layer <layer_name> --layer-type runtime-package-store --s3-bucket <s3_bucket_name>
```

然后，当您使用 .NET CLI 部署函数时，请在以下命令中指定要使用的层 ARN：

```
dotnet lambda deploy-function <function_name> --function-layers arn:aws:lambda:us-east-1:123456789012:layer:layer-name:1
```

有关 Hello World 函数的完整示例，请参阅 [blank-csharp-with-layer](https://github.com/awsdocs/aws-lambda-developer-guide/tree/main/sample-apps/blank-csharp-with-layer) 示例。

# 使用 AWS SAM 部署 C\$1 Lambda 函数
<a name="csharp-package-sam"></a>

AWS Serverless Application Model（AWS SAM）是一个工具包，可帮助简化在 AWS 上构建和运行无服务器应用程序的过程。您可以在 YAML 或 JSON 模板中为应用程序定义资源，并使用 AWS SAM 命令行界面（AWS SAM CLI）构建、打包和部署应用程序。当您通过 AWS SAM 模板构建 Lambda 函数时，AWS SAM 会使用您的函数代码和您指定的任何依赖项自动创建 .zip 部署包或容器映像。然后，AWS SAM 使用 [CloudFormation 堆栈](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/stacks.html)部署您的函数。要了解有关使用 AWS SAM 构建和部署 Lambda 函数的更多信息，请参阅《AWS Serverless Application Model 开发人员指南**》中的 [AWS SAM 入门](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-getting-started.html)。

以下示例向您显示如何使用 AWS SAM 下载、构建和部署示例 Hellow World .NET 应用程序。此示例应用程序使用 Lambda 函数和 Amazon API Gateway 端点来实现基本的 API 后端。当您向 API Gateway 端点发送 HTTP GET 请求时，API Gateway 会调用您的 Lambda 函数。该函数返回了一条“hello world”消息，以及处理您请求的 Lambda 函数实例的 IP 地址。

当您使用 AWS SAM 构建和部署应用程序时，AWS SAM CLI 会在后台使用 `dotnet lambda package` 命令来打包各个 Lambda 函数代码包。

## 先决条件
<a name="csharp-package-sam-prerequisites"></a>

**.NET 8 SDK**  
安装 [.NET 8](https://dotnet.microsoft.com/en-us/download/dotnet/8.0) SDK 和运行时系统。

**AWS SAM CLI 版本 1.39 或更高版本**  
要了解如何安装 AWS SAM CLI 的最新版本，请参阅[安装 AWS SAM CLI](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/install-sam-cli.html)。

## 部署示例 AWS SAM 应用程序
<a name="csharp-package-sam-deploy"></a>

1. 使用以下命令，通过 Hello world .NET 模板初始化应用程序。

   ```
   sam init --app-template hello-world --name sam-app \
   --package-type Zip --runtime dotnet8
   ```

   此命令在您的项目目录中创建了以下文件和目录。

   ```
   └── sam-app
       ├── README.md
       ├── events
       │   └── event.json
       ├── omnisharp.json
       ├── samconfig.toml
       ├── src
       │   └── HelloWorld
       │       ├── Function.cs
       │       ├── HelloWorld.csproj
       │       └── aws-lambda-tools-defaults.json
       ├── template.yaml
       └── test
           └── HelloWorld.Test
               ├── FunctionTest.cs
               └── HelloWorld.Tests.csproj
   ```

1. 导航到包含 `template.yaml file` 的目录。此文件是一个模板，用于定义应用程序的 AWS 资源，包括您的 Lambda 函数和 API Gateway API。

   ```
   cd sam-app
   ```

1. 要生成应用程序的来源，请运行以下命令。

   ```
   sam build
   ```

1. 要将应用程序部署到 AWS，请运行以下命令。

   ```
   sam deploy --guided
   ```

   此命令使用以下一系列提示打包您的应用程序，并进行部署。要接受默认选项，请按 Enter 键。
**注意**  
对于 **HelloWorldFunction 可能没有定义授权，确定执行此操作吗？**，确保输入 `y`。
   + **堆栈名称**：要部署到 CloudFormation 的堆栈的名称。该名称必须是您的 AWS 账户 和 AWS 区域 的唯一名称。
   + **AWS 区域**：您要将应用程序部署到的 AWS 区域。
   + **部署前确认更改**：选择“是”可在 AWS SAM 部署应用程序更改之前手动查看所有更改集。如果选择“否”，AWS SAM CLI 会自动部署应用程序更改。
   + **允许创建 SAM CLI IAM 角色**：许多 AWS SAM 模板，包括本示例中的 Hello world 模板，都会创建 AWS Identity and Access Management（IAM）角色来授予您的 Lambda 函数访问其他 AWS 服务 的权限。选择“是”以提供部署用于创建或修改 IAM 角色的 CloudFormation 堆栈的权限。
   + **禁用回滚**：默认情况下，如果 AWS SAM 在创建或部署堆栈的过程中遇到错误，它会将堆栈回滚到以前的版本。选择“否”接受此默认值。
   + **HelloWorldFunction 可能没有定义授权，确定执行此操作吗**，输入 `y`。
   + **将参数保存到 samconfig.toml**：选择“是”以保存您的配置选择。将来，您可以在没有参数的情况下重新运行 `sam deploy`，以将更改部署到您的应用程序。

1. 应用程序部署完成后，CLI 会返回 Hello World Lambda 函数的 Amazon 资源名称（ARN）以及为其创建的 IAM 角色。它还会显示您的 API Gateway API 的端点。要测试应用程序，请在浏览器中打开端点。您可以看到类似以下内容的响应。

   ```
   {"message":"hello world","location":"34.244.135.203"}
   ```

1. 要删除您的资源，请运行以下命令。请注意，您创建的 API 端点是可通过互联网访问的公共端点。我们建议您在测试后删除该端点。

   ```
   sam delete
   ```

## 后续步骤
<a name="csharp-package-sam-next"></a>

要了解有关使用 AWS SAM 借助 .NET 构建和部署 Lambda 函数的更多信息，请参阅以下资源：
+ [https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/what-is-sam.html](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/what-is-sam.html)
+ [使用 AWS Lambda 和 SAM CLI 构建无服务器 .NET 应用程序](https://aws.amazon.com/blogs/dotnet/building-serverless-net-applications-with-aws-lambda-and-the-sam-cli/)

# 使用 AWS CDK 部署 C\$1 Lambda 函数
<a name="csharp-package-cdk"></a>

AWS Cloud Development Kit (AWS CDK) 是一个开源软件开发框架，用于将云基础设施定义为使用现代编程语言和框架（如 .NET）的代码。执行 AWS CDK 项目是为了生成 CloudFormation 模板，然后使用这些模板来部署您的代码。

要使用 AWS CDK 构建和部署示例 Hello world .NET 应用程序，请按照以下各节中的说明进行操作。示例应用程序实现了一个基本 API 后端，该后端由 API Gateway 端点和 Lambda 函数组成。在向端点发送 HTTP GET 请求时，API Gateway 会调用 Lambda 函数。该函数返回一条“Hello world”消息，以及处理您的请求的 Lambda 实例的 IP 地址。

## 先决条件
<a name="csharp-package-cdk-prereqs"></a>

**.NET 8 SDK**  
安装 [.NET 8](https://dotnet.microsoft.com/en-us/download/dotnet/8.0) SDK 和运行时系统。

**AWS CDK 版本 2**  
要了解如何安装最新版本的 AWS CDK，请参阅 *AWS Cloud Development Kit (AWS CDK) v2 开发人员指南*中的 [AWS CDK 入门](https://docs.aws.amazon.com/cdk/v2/guide/getting_started.html)。

## 部署示例 AWS CDK 应用程序
<a name="csharp-package-cdk-deploy"></a>

1. 为示例应用程序创建项目目录，并导航到该项目目录。

   ```
   mkdir hello-world
   cd hello-world
   ```

1. 通过运行以下命令来初始化新的 AWS CDK 应用程序。

   ```
   cdk init app --language csharp
   ```

   此命令在您的项目目录中创建以下文件和目录

   ```
   ├── README.md
   ├── cdk.json
   └── src
       ├── HelloWorld
       │   ├── GlobalSuppressions.cs
       │   ├── HelloWorld.csproj
       │   ├── HelloWorldStack.cs
       │   └── Program.cs
       └── HelloWorld.sln
   ```

1. 打开 `src` 目录并使用 .NET CLI. 创建新的 Lambda 函数。这是您将使用 AWS CDK 部署的函数。在此示例中，您将使用 `lambda.EmptyFunction` 模板创建名为 `HelloWorldLambda` 的 Hello world 函数。

   ```
   cd src
   dotnet new lambda.EmptyFunction -n HelloWorldLambda
   ```

   完成此步骤后，您的项目目录内的目录结构应如下所示。

   ```
   ├── README.md
   ├── cdk.json
   └── src
       ├── HelloWorld
       │   ├── GlobalSuppressions.cs
       │   ├── HelloWorld.csproj
       │   ├── HelloWorldStack.cs
       │   └── Program.cs
       ├── HelloWorld.sln
       └── HelloWorldLambda
           ├── src
           │   └── HelloWorldLambda
           │       ├── Function.cs
           │       ├── HelloWorldLambda.csproj
           │       ├── Readme.md
           │       └── aws-lambda-tools-defaults.json
           └── test
               └── HelloWorldLambda.Tests
                   ├── FunctionTest.cs
                   └── HelloWorldLambda.Tests.csproj
   ```

1. 打开 `src/HelloWorld` 目录中的 `HelloWorldStack.cs` 文件。将文件的内容替换为以下代码。

   ```
   using Amazon.CDK;
   using Amazon.CDK.AWS.Lambda;
   using Amazon.CDK.AWS.Logs;
   using Constructs;
   
   namespace CdkTest
   {
       public class HelloWorldStack : Stack
       {
           internal HelloWorldStack(Construct scope, string id, IStackProps props = null) : base(scope, id, props)
           {
               var buildOption = new BundlingOptions()
               {
                   Image = Runtime.DOTNET_8.BundlingImage,
                   User = "root",
                   OutputType = BundlingOutput.ARCHIVED,
                   Command = new string[]{
               "/bin/sh",
                   "-c",
                   " dotnet tool install -g Amazon.Lambda.Tools"+
                   " && dotnet build"+
                   " && dotnet lambda package --output-package /asset-output/function.zip"
                   }
               };
   
                var helloWorldLambdaFunction = new Function(this, "HelloWorldFunction", new FunctionProps
               {
                   Runtime = Runtime.DOTNET_8,
                   MemorySize = 1024,
                   LogRetention = RetentionDays.ONE_DAY,
                   Handler = "HelloWorldLambda::HelloWorldLambda.Function::FunctionHandler",
                   Code = Code.FromAsset("./src/HelloWorldLambda/src/HelloWorldLambda", new Amazon.CDK.AWS.S3.Assets.AssetOptions
                   {
                       Bundling = buildOption
                   }),
               });
           }
       }
   }
   ```

   这是用于编译和捆绑应用程序代码的代码，也是 Lambda 函数本身的定义。`BundlingOptions` 对象允许创建 zip 格式文件以及一组用于生成 zip 格式文件内容的命令。在这种情况下，`dotnet lambda package` 命令用于编译和生成 zip 格式文件。

1. 要部署应用程序，请运行以下命令。

   ```
   cdk deploy
   ```

1. 使用 .NET Lambda CLI 调用已部署的 Lambda 函数。

   ```
   dotnet lambda invoke-function HelloWorldFunction -p "hello world"
   ```

1. 除非您想要保留您创建的资源，否则在您完成测试后，可立即将其删除。请运行以下命令以删除您的资源。

   ```
   cdk destroy
   ```

## 后续步骤
<a name="csharp-package-cdk-next"></a>

要了解有关使用 AWS CDK 借助 .NET 构建和部署 Lambda 函数的更多信息，请参阅以下资源：
+ [在 C\$1 中使用 AWS CDK](https://docs.aws.amazon.com/cdk/v2/guide/work-with-cdk-csharp.html)
+ [使用 AWS CDK 构建、打包和发布 .NET C\$1 Lambda 函数](https://aws.amazon.com/blogs/modernizing-with-aws/build-package-publish-dotnet-csharp-lambda-functions-aws-cdk/)

# 部署 ASP.NET 应用程序
<a name="csharp-package-asp"></a>

除了托管事件驱动型函数外，您还可以将 .NET 与 Lambda 结合使用来托管轻量级 ASP.NET 应用程序。您可以使用 `Amazon.Lambda.AspNetCoreServer` NuGet 包构建和部署 ASP.NET 应用程序。在本节中，您将学习如何使用 .NET Lambda CLI 工具将 ASP.NET Web API 部署到 Lambda。

**Topics**
+ [

## 先决条件
](#csharp-package-asp-prerequisites)
+ [

## 将 ASP.NET Web API 部署到 Lambda
](#csharp-package-asp-deploy-api)
+ [

## 将 ASP.NET 最小 API 部署到 Lambda
](#csharp-package-asp-deploy-minimal)

## 先决条件
<a name="csharp-package-asp-prerequisites"></a>

**.NET 8 SDK**  
安装 [.NET 8](https://dotnet.microsoft.com/en-us/download/dotnet/8.0) SDK 和 ASP.NET Core 运行时系统。

**Amazon.Lambda.Tools**  
要创建您的 Lambda 函数，请使用 [https://www.nuget.org/packages/Amazon.Lambda.Tools](https://www.nuget.org/packages/Amazon.Lambda.Tools) [.NET 全球工具扩展](https://aws.amazon.com/blogs/developer/net-core-global-tools-for-aws/)。要安装 Amazon.Lambda.Tools，请运行以下命令：  

```
dotnet tool install -g Amazon.Lambda.Tools
```
有关 Amazon.Lambda.Tools .NET CLI 扩展的更多信息，请参阅 GitHub 上的[适用于 .NET CLI 的 AWS 扩展](https://github.com/aws/aws-extensions-for-dotnet-cli)存储库。

**Amazon.Lambda.Templates**  
要生成您的 Lambda 函数代码，请使用 [https://www.nuget.org/packages/Amazon.Lambda.Templates](https://www.nuget.org/packages/Amazon.Lambda.Templates) NuGet 包。要安装此模板包，请运行以下命令：  

```
dotnet new --install Amazon.Lambda.Templates
```

## 将 ASP.NET Web API 部署到 Lambda
<a name="csharp-package-asp-deploy-api"></a>

要使用 ASP.NET 部署 Web API，您可以使用 .NET Lambda 模板创建新的 Web API 项目。使用以下命令初始化新的 ASP.NET Web API 项目。在示例命令中，我们将项目命名为 `AspNetOnLambda`。

```
dotnet new serverless.AspNetCoreWebAPI -n AspNetOnLambda
```

此命令在您的项目目录中创建了以下文件和目录。

```
.
└── AspNetOnLambda
    ├── src
    │   └── AspNetOnLambda
    │       ├── AspNetOnLambda.csproj
    │       ├── Controllers
    │       │   └── ValuesController.cs
    │       ├── LambdaEntryPoint.cs
    │       ├── LocalEntryPoint.cs
    │       ├── Readme.md
    │       ├── Startup.cs
    │       ├── appsettings.Development.json
    │       ├── appsettings.json
    │       ├── aws-lambda-tools-defaults.json
    │       └── serverless.template
    └── test
        └── AspNetOnLambda.Tests
            ├── AspNetOnLambda.Tests.csproj
            ├── SampleRequests
            │   └── ValuesController-Get.json
            ├── ValuesControllerTests.cs
            └── appsettings.json
```

当 Lambda 调用您的函数时，它使用的入口点是 `LambdaEntryPoint.cs` 文件。由 .NET Lambda 模板创建的文件包含以下代码。

```
namespace AspNetOnLambda;

public class LambdaEntryPoint : Amazon.Lambda.AspNetCoreServer.APIGatewayProxyFunction
{
    protected override void Init(IWebHostBuilder builder)
    {
        builder
            .UseStartup≪Startup≫();
    }

    protected override void Init(IHostBuilder builder)
    {
    }
}
```

Lambda 使用的入口点必须继承自 `Amazon.Lambda.AspNetCoreServer` 包中的三个基类之一。这三个基类为：
+ `APIGatewayProxyFunction`
+ `APIGatewayHttpApiV2ProxyFunction`
+ `ApplicationLoadBalancerFunction`

使用提供的 .NET Lambda 模板创建 `LambdaEntryPoint.cs` 文件时使用的默认类是 `APIGatewayProxyFunction`。您在函数中所使用的基类取决于您的 Lambda 函数前面是哪个 API 层。

三个基类中的每一个都包含一个名为 `FunctionHandlerAsync` 的公共方法。此方法的名称将构成 Lambda 用于调用函数的[处理程序字符串](csharp-handler.md#csharp-class-library-handlers)的一部分。`FunctionHandlerAsync` 方法将入站事件有效负载转换为正确的 ASP.NET 格式，将 ASP.NET 响应转换回 Lambda 响应有效负载。对于所示的示例 `AspNetOnLambda` 项目，处理程序字符串将如下所示。

```
AspNetOnLambda::AspNetOnLambda.LambdaEntryPoint::FunctionHandlerAsync
```

要将 API 部署到 Lambda，请运行以下命令导航到包含源代码文件的目录并使用 CloudFormation 部署您的函数。

```
cd AspNetOnLambda/src/AspNetOnLambda
dotnet lambda deploy-serverless
```

**提示**  
使用 `dotnet lambda deploy-serverless` 命令部署 API 时，CloudFormation 会根据您在部署期间指定的堆栈名称为您的 Lambda 函数命名。要为您的 Lambda 函数指定自定义名称，请编辑 `serverless.template` 文件以向 `AWS::Serverless::Function` 资源添加 `FunctionName` 属性。要了解更多信息，请参阅《CloudFormation 用户指南》**中的[名称类型](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-name.html)。

## 将 ASP.NET 最小 API 部署到 Lambda
<a name="csharp-package-asp-deploy-minimal"></a>

要将 ASP.NET 最小 API 部署到 Lambda，您可以使用 .NET Lambda 模板创建新的 API 项目。使用以下命令初始化新的最小 API 项目。在此示例中，我们将项目命名为 `MinimalApiOnLambda`。

```
dotnet new serverless.AspNetCoreMinimalAPI -n MinimalApiOnLambda
```

此命令在您的项目目录中创建以下文件和目录。

```
└── MinimalApiOnLambda
    └── src
        └── MinimalApiOnLambda
            ├── Controllers
            │   └── CalculatorController.cs
            ├── MinimalApiOnLambda.csproj
            ├── Program.cs
            ├── Readme.md
            ├── appsettings.Development.json
            ├── appsettings.json
            ├── aws-lambda-tools-defaults.json
            └── serverless.template
```

`Program.cs` 文件包含以下代码。

```
var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddControllers();

// Add AWS Lambda support. When application is run in Lambda Kestrel is swapped out as the web server with Amazon.Lambda.AspNetCoreServer. This
// package will act as the webserver translating request and responses between the Lambda event source and ASP.NET Core.
builder.Services.AddAWSLambdaHosting(LambdaEventSource.RestApi);

var app = builder.Build();


app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();

app.MapGet("/", () => "Welcome to running ASP.NET Core Minimal API on AWS Lambda");

app.Run();
```

要将您的最小 API 配置为在 Lambda 上运行，您可能需要对此代码进行编辑，以便正确翻译 Lambda 和 ASP.NET Core 之间的请求和响应。默认情况下，该函数是针对 REST API 事件源进行配置的。对于 HTTP API 或应用程序负载均衡器，请将 `(LambdaEventSource.RestApi)` 替换为以下选项之一：
+ `(LambdaEventSource.HttpAPi)`
+ `(LambdaEventSource.ApplicationLoadBalancer)`

要将您的最小 API 部署到 Lambda，请运行以下命令导航到包含源代码文件的目录并使用 CloudFormation 部署您的函数。

```
cd MinimalApiOnLambda/src/MinimalApiOnLambda
dotnet lambda deploy-serverless
```

# 使用 .NET Lambda 函数的层
<a name="dotnet-layers"></a>

不建议使用[层](chapter-layers.md)来管理用 .NET 编写的 Lambda 函数的依赖项。这是因为 .NET 是一种编译语言，并且您的函数仍然必须在 [Init](lambda-runtime-environment.md#runtimes-lifecycle-ib) 阶段手动将任何共享程序集加载到内存中，而这可能会增加冷启动时间。使用层不仅会使部署过程变得复杂，而且还会阻止您利用内置编译器优化。

要将外部依赖项与 .NET 处理程序一起使用，请在编译时将它们直接包含在部署包中。这样就可以简化部署过程，还可以利用内置 .NET 编译器优化。有关如何在函数中导入和使用依赖项（如 NuGet 包）的示例，请参阅 [定义采用 C\$1 的 Lambda 函数处理程序](csharp-handler.md)。

# 使用容器映像部署 .NET Lambda 函数
<a name="csharp-image"></a>

有三种方法可以为 .NET Lambda 函数构建容器映像：
+ [使用 .NET 的 AWS 基本映像](#csharp-image-instructions)

  [AWS 基本映像](images-create.md#runtimes-images-lp)会预加载一个语言运行时系统、一个用于管理 Lambda 和函数代码之间交互的运行时系统接口客户端，以及一个用于本地测试的运行时系统接口仿真器。
+ [使用 AWS 仅限操作系统的基础镜像](images-create.md#runtimes-images-provided)

  [AWS 仅限操作系统的运行时系统](https://gallery.ecr.aws/lambda/provided)包含 Amazon Linux 发行版和[运行时系统接口模拟器](https://github.com/aws/aws-lambda-runtime-interface-emulator/)。这些镜像通常用于为编译语言（例如 [Go](go-image.md#go-image-provided) 和 [Rust](lambda-rust.md)）以及 Lambda 未提供基础映像的语言或语言版本（例如 Node.js 19）创建容器镜像。您也可以使用仅限操作系统的基础映像来实施[自定义运行时系统](runtimes-custom.md)。要使映像与 Lambda 兼容，您必须在映像中包含 [.NET 的运行时系统接口客户端](#csharp-image-clients)。
+ [使用非 AWS 基本映像](#csharp-image-clients)

  您还可以使用其他容器注册表的备用基本映像，例如 Alpine Linux 或 Debian。您还可以使用您的组织创建的自定义映像。要使映像与 Lambda 兼容，您必须在映像中包含 [.NET 的运行时系统接口客户端](#csharp-image-clients)。

**提示**  
要缩短 Lambda 容器函数激活所需的时间，请参阅 Docker 文档中的[使用多阶段构建](https://docs.docker.com/build/building/multi-stage/)。要构建高效的容器映像，请遵循[编写 Dockerfiles 的最佳实践](https://docs.docker.com/develop/develop-images/dockerfile_best-practices/)。

此页面介绍了如何为 Lambda 构建、测试和部署容器映像。

**Topics**
+ [

## AWS.NET 的基本映像
](#csharp-image-base)
+ [

## 使用 .NET 的 AWS 基本映像
](#csharp-image-instructions)
+ [

## 将备用基本映像与运行时系统接口客户端配合使用
](#csharp-image-clients)

## AWS.NET 的基本映像
<a name="csharp-image-base"></a>

AWS 为 .NET 提供了以下基本映像：


| 标签 | 运行时 | 操作系统 | Dockerfile | 弃用 | 
| --- | --- | --- | --- | --- | 
| 10 | .NET 10 | Amazon Linux 2023 | [GitHub 上适用于 .NET 10 的 Dockerfile](https://github.com/aws/aws-lambda-base-images/blob/dotnet10/Dockerfile.dotnet10) |   2028 年 11 月 14 日   | 
| 9 | .NET 9 | Amazon Linux 2023 | [GitHub 上适用于 .NET 9 的 Dockerfile](https://github.com/aws/aws-lambda-base-images/blob/dotnet9/Dockerfile.dotnet9) |   2026 年 11 月 10 日   | 
| 8 | .NET 8 | Amazon Linux 2023 | [GitHub 上适用于 .NET 8 的 Dockerfile](https://github.com/aws/aws-lambda-base-images/blob/dotnet8/Dockerfile.dotnet8) |   2026 年 11 月 10 日   | 

Amazon ECR 存储库：[gallery.ecr.aws/lambda/dotnet](https://gallery.ecr.aws/lambda/dotnet)

## 使用 .NET 的 AWS 基本映像
<a name="csharp-image-instructions"></a>

### 先决条件
<a name="dotnet-csharp-image-prerequisites"></a>

要完成本节中的步骤，您必须满足以下条件：
+ [.NET 开发工具包](https://dotnet.microsoft.com/download) – 以下步骤使用 .NET 8 基本映像。确保 .NET 版本与您在 Dockerfile 中指定的[基本映像](https://gallery.ecr.aws/lambda/dotnet)版本相符。
+ [Docker](https://docs.docker.com/get-docker)（最低版本 25.0.0）
+ Docker [buildx 插件](https://github.com/docker/buildx/blob/master/README.md)。

### 使用基本映像创建和部署映像
<a name="dotnet-image-create"></a>

在以下步骤中，您将使用 [Amazon.Lambda.Templates](https://github.com/aws/aws-lambda-dotnet#dotnet-cli-templates) 和 [Amazon.Lambda.Tools](https://github.com/aws/aws-extensions-for-dotnet-cli#aws-lambda-amazonlambdatools) 创建 .NET 项目。然后，构建 Docker 映像，将该映像上传到 Amazon ECR，并将其部署到 Lambda 函数。

1. 安装 [Amazon.Lambda.Templates](https://github.com/aws/aws-lambda-dotnet#dotnet-cli-templates) NuGet 程序包。

   ```
   dotnet new install Amazon.Lambda.Templates
   ```

1. 使用 `lambda.image.EmptyFunction` 模板创建 .NET 项目。

   ```
   dotnet new lambda.image.EmptyFunction --name MyFunction --region us-east-1
   ```

   项目文件存储在 `MyFunction/src/MyFunction` 目录中：
   + **aws-lambda-tools-defaults.json**：指定用于部署 Lambda 函数的命令行选项。
   + **Function.cs**：您的 Lambda 处理程序函数代码。这是一个 C\$1 模板，该模板包含默认 `Amazon.Lambda.Core` 库和默认 `LambdaSerializer` 属性。有关序列化要求和选项的更多信息，请参阅 [C\$1 Lambda 函数中的序列化](csharp-handler.md#csharp-handler-serializer)。您可以使用提供的代码进行测试，也可以将其替换为您自己的代码。
   + **MyFunction.csproj**：.NET [项目文件](https://learn.microsoft.com/en-us/dotnet/core/project-sdk/overview#project-files)，其中列出了构成您的应用程序的文件和程序集。
   + **Dockerfile**：您可以使用提供的 Dockerfile 进行测试，也可以将其替换为您自己的 Dockerfile。如果您使用自己的 Dockerfile，请确保：
     + 将 `FROM` 属性设置为[基本映像的 URI](https://gallery.ecr.aws/lambda/dotnet)。基本映像和 `MyFunction.csproj` 文件中的 `TargetFramework` 必须使用相同的 .NET 版本。例如，要使用 .NET 9：
       + Dockerfile：`FROM public.ecr.aws/lambda/dotnet:9`
       + MyFunction.csproj：`<TargetFramework>net9.0</TargetFramework>`
     + 将 `CMD` 参数设置为 Lambda 函数处理程序。这应与 `aws-lambda-tools-defaults.json` 中的 `image-command` 相符。

1. 安装 Amazon.Lambda.Tools [.NET Global Tool](https://aws.amazon.com/blogs/developer/net-core-global-tools-for-aws/).

   ```
   dotnet tool install -g Amazon.Lambda.Tools
   ```

   如果已安装 Amazon.Lambda.Tools，请确保您使用的是最新版本。

   ```
   dotnet tool update -g Amazon.Lambda.Tools
   ```

1. 如果尚未安装，请将目录更改为 `MyFunction/src/MyFunction`。

   ```
   cd src/MyFunction
   ```

1. 使用 Amazon.Lambda.Tools 构建 Docker 映像，将其推送到新的 Amazon ECR 存储库，然后部署 Lambda 函数。

   对于 `--function-role`，指定函数[执行角色](lambda-intro-execution-role.md)的角色名称，而不是 Amazon 资源名称（ARN）。例如 `lambda-role`。

   ```
   dotnet lambda deploy-function MyFunction --function-role lambda-role
   ```

   有关 Amazon.Lambda.Tools .NET Global Tool 的更多信息，请参阅 GitHub 上的[AWS适用于 .NET CLI 的扩展程序](https://github.com/aws/aws-extensions-for-dotnet-cli)存储库。

1. 调用函数。

   ```
   dotnet lambda invoke-function MyFunction --payload "Testing the function"
   ```

   如果一切成功，您将看到类似以下内容的响应：

   ```
   Payload:
   {"Lower":"testing the function","Upper":"TESTING THE FUNCTION"}
   
   Log Tail:
   INIT_REPORT Init Duration: 9999.81 ms   Phase: init     Status: timeout
   START RequestId: 12378346-f302-419b-b1f2-deaa1e8423ed Version: $LATEST
   END RequestId: 12378346-f302-419b-b1f2-deaa1e8423ed
   REPORT RequestId: 12378346-f302-419b-b1f2-deaa1e8423ed  Duration: 3173.06 ms    Billed Duration: 3174 ms        Memory Size: 512 MB     Max Memory Used: 24 MB
   ```

1. 删除 Lambda 函数。

   ```
   dotnet lambda delete-function MyFunction
   ```

## 将备用基本映像与运行时系统接口客户端配合使用
<a name="csharp-image-clients"></a>

如果使用[仅限操作系统的基础映像](images-create.md#runtimes-images-provided)或者备用基础映像，则必须在映像中包括运行时系统接口客户端。运行时系统接口客户端可扩展 [运行时 API](runtimes-api.md)，用于管理 Lambda 和函数代码之间的交互。

以下示例演示如何使用非 AWS 基础映像构建 .NET 的容器映像，以及如何添加 [Amazon.Lambda.RuntimeSupport 程序包](https://github.com/aws/aws-lambda-dotnet/blob/master/Libraries/src/Amazon.Lambda.RuntimeSupport/README.md#using-amazonlambdaruntimesupport-as-a-class-library)，该程序包是 .NET 的 Lambda 运行时系统接口客户端。示例 Dockerfile 使用 Microsoft .NET 8 基本映像。

### 先决条件
<a name="dotnet-csharp-alt-prerequisites"></a>

要完成本节中的步骤，您必须满足以下条件：
+ [.NET SDK](https://dotnet.microsoft.com/download) – 以下步骤使用 .NET 9 基本映像。确保 .NET 版本与您在 Dockerfile 中指定的基本映像版本相符。
+ [Docker](https://docs.docker.com/get-docker)（最低版本 25.0.0）
+ Docker [buildx 插件](https://github.com/docker/buildx/blob/master/README.md)。

### 使用备用基本映像创建和部署映像
<a name="dotnet-alt-create"></a>

1. 安装 [Amazon.Lambda.Templates](https://github.com/aws/aws-lambda-dotnet#dotnet-cli-templates) NuGet 程序包。

   ```
   dotnet new install Amazon.Lambda.Templates
   ```

1. 使用 `lambda.CustomRuntimeFunction` 模板创建 .NET 项目。此模板包括 [Amazon.Lambda.RuntimeSupport](https://github.com/aws/aws-lambda-dotnet/blob/master/Libraries/src/Amazon.Lambda.RuntimeSupport/README.md#using-amazonlambdaruntimesupport-as-a-class-library) 程序包。

   ```
   dotnet new lambda.CustomRuntimeFunction --name MyFunction --region us-east-1
   ```

1. 导航到 `MyFunction/src/MyFunction` 目录。这是存储项目文件的位置。检查以下文件：
   + **aws-lambda-tools-defaults.json** – 此文件是您部署 Lambda 函数时指定命令行选项的位置。
   + **Function.cs** – 该代码包含一个类，其 `Main` 方法将 `Amazon.Lambda.RuntimeSupport` 库初始化为引导。`Main` 方法是函数进程的入口点。`Main` 方法将函数处理程序封装在引导可以使用的包装器中。有关更多信息，请参阅 GitHub 存储库中的[使用 Amazon.Lambda.RuntimeSupport 作为类库](https://github.com/aws/aws-lambda-dotnet/blob/master/Libraries/src/Amazon.Lambda.RuntimeSupport/README.md#using-amazonlambdaruntimesupport-as-a-class-library)。
   + **MyFunction.csproj** – 列出构成您应用程序的文件和程序集的 .NET [项目文件](https://learn.microsoft.com/en-us/dotnet/core/project-sdk/overview#project-files)。
   + **Readme.md** – 此文件包含有关示例 Lambda 函数的更多信息。

1. 打开 `aws-lambda-tools-defaults.json` 文件，然后添加以下各行：

   ```
     "package-type": "image",
     "docker-host-build-output-dir": "./bin/Release/lambda-publish"
   ```
   + **package-type**：将部署包定义为容器映像。
   + **docker-host-build-output-dir**：设置构建过程的输出目录。  
**Example aws-lambda-tools-defaults.json**  

   ```
   {
     "Information": [
       "This file provides default values for the deployment wizard inside Visual Studio and the AWS Lambda commands added to the .NET Core CLI.",
       "To learn more about the Lambda commands with the .NET Core CLI execute the following command at the command line in the project root directory.",
       "dotnet lambda help",
       "All the command line options for the Lambda command can be specified in this file."
     ],
     "profile": "",
     "region": "us-east-1",
     "configuration": "Release",
     "function-runtime": "provided.al2023",
     "function-memory-size": 256,
     "function-timeout": 30,
     "function-handler": "bootstrap",
     "msbuild-parameters": "--self-contained true",
     "package-type": "image",
     "docker-host-build-output-dir": "./bin/Release/lambda-publish"
   }
   ```

1. 在 `MyFunction/src/MyFunction` 目录中创建一个 Dockerfile。以下示例 Dockerfile 使用 Microsoft .NET 基本映像而不是 [AWS 基本映像](#csharp-image-base)。
   + 将 `FROM` 属性设置为基本映像标识符。基本映像和 `MyFunction.csproj` 文件中的 `TargetFramework` 必须使用相同的 .NET 版本。
   + 使用 `COPY` 命令将函数复制到 `/var/task` 目录中。
   + 将 `ENTRYPOINT` 设置为您希望 Docker 容器在启动时运行的模块。在这种情况下，该模块是引导，它会初始化 `Amazon.Lambda.RuntimeSupport` 库。

   请注意，示例 Dockerfile 不包含 [USER 指令](https://docs.docker.com/reference/dockerfile/#user)。当您将容器映像部署到 Lambda 时，Lambda 会自动定义具有最低权限的默认 Linux 用户。这与标准 Docker 行为不同，标准 Docker 在未提供 `USER` 指令时默认为 `root` 用户。  
**Example Dockerfile**  

   ```
   # You can also pull these images from DockerHub amazon/aws-lambda-dotnet:8
   FROM mcr.microsoft.com/dotnet/runtime:9.0
   
   # Set the image's internal work directory
   WORKDIR /var/task
     
   # Copy function code to Lambda-defined environment variable
   COPY "bin/Release/net9.0/linux-x64"  .
     
   # Set the entrypoint to the bootstrap
   ENTRYPOINT ["/usr/bin/dotnet", "exec", "/var/task/bootstrap.dll"]
   ```

1. 安装 Amazon.Lambda.Tools [.NET Global Tools 扩展](https://aws.amazon.com/blogs/developer/net-core-global-tools-for-aws/)。

   ```
   dotnet tool install -g Amazon.Lambda.Tools
   ```

   如果已安装 Amazon.Lambda.Tools，请确保您使用的是最新版本。

   ```
   dotnet tool update -g Amazon.Lambda.Tools
   ```

1. 使用 Amazon.Lambda.Tools 构建 Docker 映像，将其推送到新的 Amazon ECR 存储库，然后部署 Lambda 函数。

   对于 `--function-role`，指定函数[执行角色](lambda-intro-execution-role.md)的角色名称，而不是 Amazon 资源名称（ARN）。例如 `lambda-role`。

   ```
   dotnet lambda deploy-function MyFunction --function-role lambda-role
   ```

   有关 Amazon.Lambda.Tools .NET CLI 扩展程序的更多信息，请参阅 GitHub 上的[适用于 .NET CLI 的 AWS 扩展程序](https://github.com/aws/aws-extensions-for-dotnet-cli)存储库。

1. 调用函数。

   ```
   dotnet lambda invoke-function MyFunction --payload "Testing the function"
   ```

   如果所有操作成功，您将看到以下内容：

   ```
   Payload:
   "TESTING THE FUNCTION"
   
   Log Tail:
   START RequestId: id Version: $LATEST
   END RequestId: id
   REPORT RequestId: id  Duration: 0.99 ms       Billed Duration: 1 ms         Memory Size: 256 MB     Max Memory Used: 12 MB
   ```

1. 删除 Lambda 函数。

   ```
   dotnet lambda delete-function MyFunction
   ```

# 将 .NET Lambda 函数代码编译为本地运行时格式
<a name="dotnet-native-aot"></a>

.NET 8 支持本机的提前（AOT）编译。通过本机 AOT，您可以将 Lambda 函数代码编译为本机运行时格式，从而无需在运行时编译 .NET 代码。本机 AOT 编译可以减少您在.NET 中编写的 Lambda 函数的冷启动时间。有关更多信息，请参阅 AWS 计算博客上的[适用于 AWS Lambda 的 .NET 8 运行时系统简介](https://aws.amazon.com/blogs/compute/introducing-the-net-8-runtime-for-aws-lambda/)。

**Topics**
+ [

## Lambda 运行时
](#dotnet-native-aot-runtime)
+ [

## 先决条件
](#dotnet-native-aot-prerequisites)
+ [

## 开始使用
](#dotnet-native-aot-getting-started)
+ [

## 序列化
](#dotnet-native-aot-serialization)
+ [

## 修剪
](#dotnet-native-aot-trimming)
+ [

## 故障排除
](#dotnet-native-aot-troubleshooting)

## Lambda 运行时
<a name="dotnet-native-aot-runtime"></a>

要部署使用本机 AOT 编译构建的 Lambda 函数，请使用托管的 .NET 8 Lambda 运行时系统。该运行时系统支持使用 x86\$164 和 arm64 结构。

当您在不使用 AOT 的情况下部署 .NET Lambda 函数时，您的应用程序首先将编译为中间语言（IL）代码。在运行时，Lambda 运行时系统中的即时（JIT）编译器获取 IL 代码并根据需要将其编译为机器代码。借助使用原生 AOT 提前编译的 Lambda 函数，您可以在部署函数时将代码编译成机器代码，这样您就不必依赖 .NET 运行时系统或 Lambda 运行时系统中的 SDK 来在代码运行之前对其进行编译。

AOT 的一个限制是，您的应用程序代码必须在 Amazon Linux 2023（AL2023）操作系统与 .NET 8 运行时系统所用操作系统相同的环境中编译。.NET Lambda CLI 提供了使用 AL2023 映像在 Docker 容器中编译应用程序的功能。

为避免跨架构兼容性方面的潜在问题，我们强烈建议您在与您为功能配置的处理器架构相同的环境中编译代码。要详细了解跨架构编译的局限性，请参阅 Microsoft .NET 文档中的[交叉编译](https://learn.microsoft.com/en-us/dotnet/core/deploying/native-aot/cross-compile)。

## 先决条件
<a name="dotnet-native-aot-prerequisites"></a>

**Docker**  
要使用本机 AOT，必须在与 .NET 8 运行时系统具有相同 AL2023 操作系统的环境中进行编译。以下各节中的 .NET CLI 命令使用 Docker 在 AL2023 环境中开发和构建 Lambda 函数。

**.NET 8 SDK**  
本机 AOT 编译是 .NET 8 的一项功能。您必须在生成计算机上安装 [.NET 8 SDK](https://dotnet.microsoft.com/en-us/download/dotnet/8.0)，而不仅仅是在运行时系统上。

**Amazon.Lambda.Tools**  
要创建您的 Lambda 函数，请使用 [https://www.nuget.org/packages/Amazon.Lambda.Tools](https://www.nuget.org/packages/Amazon.Lambda.Tools) [.NET 全球工具扩展](https://aws.amazon.com/blogs/developer/net-core-global-tools-for-aws/)。要安装 Amazon.Lambda.Tools，请运行以下命令：  

```
dotnet tool install -g Amazon.Lambda.Tools
```
有关 Amazon.Lambda.Tools .NET CLI 扩展的更多信息，请参阅 GitHub 上的[适用于 .NET CLI 的 AWS 扩展](https://github.com/aws/aws-extensions-for-dotnet-cli)存储库。

**Amazon.Lambda.Templates**  
要生成您的 Lambda 函数代码，请使用 [https://www.nuget.org/packages/Amazon.Lambda.Templates](https://www.nuget.org/packages/Amazon.Lambda.Templates) NuGet 包。要安装此模板包，请运行以下命令：  

```
dotnet new install Amazon.Lambda.Templates
```

## 开始使用
<a name="dotnet-native-aot-getting-started"></a>

.NET 全局 CLI 和 AWS Serverless Application Model（AWS SAM）都提供了使用本机 AOT 构建应用程序的入门模板。要构建您的第一个本机 AOT Lambda 函数，请执行以下说明中的步骤。

**要初始化和部署本机 AOT 编译的 Lambda 函数**

1. 使用本机 AOT 模板初始化新项目，然后导航到包含已创建的 `.cs` 和 `.csproj` 文件的目录。在此示例中，我们将函数命名为 `NativeAotSample`。

   ```
   dotnet new lambda.NativeAOT -n NativeAotSample
   cd ./NativeAotSample/src/NativeAotSample
   ```

   由本机 AOT 模板创建的 `Function.cs` 文件包含以下函数代码。

   ```
   using Amazon.Lambda.Core;
   using Amazon.Lambda.RuntimeSupport;
   using Amazon.Lambda.Serialization.SystemTextJson;
   using System.Text.Json.Serialization;
   
   namespace NativeAotSample;
   
   public class Function
   {
       /// <summary>
       /// The main entry point for the Lambda function. The main function is called once during the Lambda init phase. It
       /// initializes the .NET Lambda runtime client passing in the function handler to invoke for each Lambda event and
       /// the JSON serializer to use for converting Lambda JSON format to the .NET types.
       /// </summary>
       private static async Task Main()
       {
           Func<string, ILambdaContext, string> handler = FunctionHandler;
           await LambdaBootstrapBuilder.Create(handler, new SourceGeneratorLambdaJsonSerializer<LambdaFunctionJsonSerializerContext>())
               .Build()
               .RunAsync();
       }
   
       /// <summary>
       /// A simple function that takes a string and does a ToUpper.
       ///
       /// To use this handler to respond to an AWS event, reference the appropriate package from
       /// https://github.com/aws/aws-lambda-dotnet#events
       /// and change the string input parameter to the desired event type. When the event type
       /// is changed, the handler type registered in the main method needs to be updated and the LambdaFunctionJsonSerializerContext
       /// defined below will need the JsonSerializable updated. If the return type and event type are different then the
       /// LambdaFunctionJsonSerializerContext must have two JsonSerializable attributes, one for each type.
       ///
       // When using Native AOT extra testing with the deployed Lambda functions is required to ensure
       // the libraries used in the Lambda function work correctly with Native AOT. If a runtime
       // error occurs about missing types or methods the most likely solution will be to remove references to trim-unsafe
       // code or configure trimming options. This sample defaults to partial TrimMode because currently the AWS
       // SDK for .NET does not support trimming. This will result in a larger executable size, and still does not
       // guarantee runtime trimming errors won't be hit.
       /// </summary>
       /// <param name="input"></param>
       /// <param name="context"></param>
       /// <returns></returns>
       public static string FunctionHandler(string input, ILambdaContext context)
       {
           return input.ToUpper();
       }
   }
   
   /// <summary>
   /// This class is used to register the input event and return type for the FunctionHandler method with the System.Text.Json source generator.
   /// There must be a JsonSerializable attribute for each type used as the input and return type or a runtime error will occur
   /// from the JSON serializer unable to find the serialization information for unknown types.
   /// </summary>
   [JsonSerializable(typeof(string))]
   public partial class LambdaFunctionJsonSerializerContext : JsonSerializerContext
   {
       // By using this partial class derived from JsonSerializerContext, we can generate reflection free JSON Serializer code at compile time
       // which can deserialize our class and properties. However, we must attribute this class to tell it what types to generate serialization code for.
       // See https://docs.microsoft.com/en-us/dotnet/standard/serialization/system-text-json-source-generation
   ```

   本机 AOT 将您的应用程序编译成单个本机二进制文件。该二进制文件的入口点是 `static Main` 方法。在 `static Main` 内，引导 Lambda 运行时系统并设置 `FunctionHandler` 方法。作为运行时系统引导程序的一部分，使用 `new SourceGeneratorLambdaJsonSerializer<LambdaFunctionJsonSerializerContext>()` 配置源代码生成的序列化器

1. 要将您的应用程序部署到 Lambda，请确保 Docker 在您的本地环境中运行，并运行以下命令。

   ```
   dotnet lambda deploy-function
   ```

   在后台，.NET 全局 CLI 会下载 AL2023 Docker 映像，并在正在运行的容器中编译您的应用程序代码。在部署到 Lambda 之前，编译后的二进制文件会输出回您的本地文件系统。

1. 通过运行以下命令来测试您的函数。将 `<FUNCTION_NAME>` 替换为您在部署向导中为函数选择的名称。

   ```
   dotnet lambda invoke-function <FUNCTION_NAME> --payload "hello world"
   ```

   来自 CLI 的响应包括冷启动的性能详细信息（初始化持续时间）以及函数调用的总运行时间。

1. 要删除按照上述步骤创建的 AWS 资源，请运行以下命令。将 `<FUNCTION_NAME>` 替换为您在部署向导中为函数选择的名称。通过删除您不再使用的 AWS 资源，可防止您的 AWS 账户 产生不必要的费用。

   ```
   dotnet lambda delete-function <FUNCTION_NAME>
   ```

## 序列化
<a name="dotnet-native-aot-serialization"></a>

要使用本机 AOT 将函数部署到 Lambda，您的函数代码必须使用[源代码生成的序列化](https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json/source-generation-modes?pivots=dotnet-8-0)。源代码生成器不是使用运行时系统反射来收集访问对象属性以进行序列化所需的元数据，而是生成在构建应用程序时编译的 C\$1 源文件。要正确配置源生成的序列化器，请确保包含了函数使用的任何输入和输出对象，以及任何自定义类型。例如，从 API Gateway REST API 接收事件并返回自定义 `Product` 类型的 Lambda 函数将包括一个定义如下的序列化器。

```
[JsonSerializable(typeof(APIGatewayProxyRequest))]
[JsonSerializable(typeof(APIGatewayProxyResponse))]
[JsonSerializable(typeof(Product))]
public partial class CustomSerializer : JsonSerializerContext
{
}
```

## 修剪
<a name="dotnet-native-aot-trimming"></a>

本机 AOT 会在编译过程中对应用程序代码进行修剪，以确保二进制文件尽可能小。与以前的 .NET 版本相比，适用于 Lambda 的 .NET 8 提供了改进的修剪支持。已增加对 [Lambda 运行时系统库](https://github.com/aws/aws-lambda-dotnet/pull/1596)、[AWS .NET SDK](https://github.com/aws/aws-sdk-net/pulls?q=is%3Apr+trimming)、[.NET Lambda 注释](https://github.com/aws/aws-lambda-dotnet/pull/1610)和 .NET 8 本身的支持。

这些改进有可能消除生成时修剪警告，但是 .NET 永远不会完全安全。这意味着在编译步骤中，您的函数所依赖的部分库可能会被剪掉。您可以通过将 `TrimmerRootAssemblies` 定义为 `.csproj` 文件的一部分来进行管理，如以下示例所示。

```
<ItemGroup>
    <TrimmerRootAssembly Include="AWSSDK.Core" />
    <TrimmerRootAssembly Include="AWSXRayRecorder.Core" />
    <TrimmerRootAssembly Include="AWSXRayRecorder.Handlers.AwsSdk" />
    <TrimmerRootAssembly Include="Amazon.Lambda.APIGatewayEvents" />
    <TrimmerRootAssembly Include="bootstrap" />
    <TrimmerRootAssembly Include="Shared" />
</ItemGroup>
```

请注意，当您收到修剪警告时，添加生成警告的类到 `TrimmerRootAssembly` 中可能无法解决问题。修剪警告表示，该类正在尝试访问一些直到运行时才能确定的其他类。为避免运行时系统错误，请将第二个类添加到 `TrimmerRootAssembly`。

要了解有关管理修剪警告的更多信息，请参阅 Microsoft .NET 文档中的[修剪警告简介](https://learn.microsoft.com/en-us/dotnet/core/deploying/trimming/fixing-warnings)。

## 故障排除
<a name="dotnet-native-aot-troubleshooting"></a>

**错误：不支持跨操作系统本机编译。**  
您的 Amazon.Lambda.Tools .NET Core Global Tool 版本已过期。更新到最新版本，并重新尝试。

**Docker：映像操作系统“linux”不能在此平台上使用。**  
系统上的 Docker 配置为使用 Windows 容器。切换到 Linux 容器以运行本机 AOT 构建环境。

有关常见错误的更多信息，请参阅 GitHub 上的 [AWS NativeAOT for .NET](https://github.com/awslabs/dotnet-nativeaot-labs#common-errors) 存储库。

# 使用 Lambda 上下文对象检索 C\$1 函数信息
<a name="csharp-context"></a>

Lambda 运行您的函数时，会将 context 对象传递到[处理程序](csharp-handler.md)。此对象提供的属性包含有关调用、函数和执行环境的信息。

**上下文属性**
+ `FunctionName` – Lambda 函数的名称。
+ `FunctionVersion` – 函数的[版本](configuration-versions.md)
+ `InvokedFunctionArn` – 用于调用函数的 Amazon Resource Name (ARN)。表明调用者是否指定了版本号或别名。
+ `MemoryLimitInMB` – 为函数分配的内存量。
+ `AwsRequestId` – 调用请求的标识符。
+ `LogGroupName` – 函数的日志组。
+ `LogStreamName` – 函数实例的日志流。
+ `RemainingTime` (`TimeSpan`) – 执行超时前剩余的毫秒数。
+ `Identity` – （移动应用程序）授权请求的 Amazon Cognito 身份的相关信息。
+ `ClientContext` – （移动应用程序）客户端应用程序提供给 Lambda 的客户端上下文。
+ `Logger` 函数的[记录器对象](csharp-logging.md)。

您可以使用 `ILambdaContext` 对象中的信息输出有关函数调用的信息，以便进行监控。以下代码提供了如何向结构化日志记录框架添加上下文信息的示例。在此示例中，该函数将 `AwsRequestId` 添加到日志输出中。如果即将到达 Lambda 函数超时，该函数还使用 `RemainingTime` 属性取消正在进行的任务。

```
[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, ILambdaContext context)
    {
        Logger.AppendKey("AwsRequestId", context.AwsRequestId);
        
        var id = request.PathParameters["id"];

        using var cts = new CancellationTokenSource();
        
        try
        {
            cts.CancelAfter(context.RemainingTime.Add(TimeSpan.FromSeconds(-1)));
            
            var databaseRecord = await this._repo.GetById(id, cts.Token);
            
            return new APIGatewayProxyResponse 
            {
                StatusCode = (int)HttpStatusCode.OK,
                Body = JsonSerializer.Serialize(databaseRecord)
            };
        }
        catch (Exception ex)
        {
            return new APIGatewayProxyResponse 
            {
                StatusCode = (int)HttpStatusCode.InternalServerError,
                Body = JsonSerializer.Serialize(new { error = ex.Message })
            };
        }
        finally
        {
            cts.Cancel();
        }
    }
}
```

# C\$1 Lambda 函数日志记录和监控
<a name="csharp-logging"></a>

AWS Lambda 将自动监控 Lambda 函数并将日志条目发送到 Amazon CloudWatch。您的 Lambda 函数带有一个 CloudWatch Logs 日志组以及函数的每个实例的日志流。Lambda 运行时系统环境会将每次调用的详细信息以及函数代码的其他输出发送到该日志流。有关 CloudWatch Logs 的更多信息，请参阅[将 Lambda 函数日志发送到 CloudWatch Logs](monitoring-cloudwatchlogs.md)。

**Topics**
+ [

## 创建返回日志的函数
](#csharp-logging-output)
+ [

## 将 Lambda 高级日志记录控件与 .NET 结合使用
](#csharp-logging-advanced)
+ [

## 其他日志记录工具和库
](#csharp-tools-libraries)
+ [

## 将 Powertools for AWS Lambda（.NET）和 AWS SAM 用于结构化日志记录
](#dotnet-logging-sam)
+ [

## 在 Lambda 控制台中查看日志
](#csharp-logging-console)
+ [

## 在 CloudWatch 控制台中查看日志
](#csharp-logging-cwconsole)
+ [

## 使用 AWS Command Line Interface（AWS CLI）查看日志
](#csharp-logging-cli)
+ [

## 删除日志
](#csharp-logging-delete)

## 创建返回日志的函数
<a name="csharp-logging-output"></a>

要从函数代码输出日志，您可以使用上下文对象上的 [ILambdaLogger](https://github.com/aws/aws-lambda-dotnet/blob/master/Libraries/src/Amazon.Lambda.Core/ILambdaLogger.cs)、[控制台类](https://docs.microsoft.com/en-us/dotnet/api/system.console)上的方法或写入到 `stdout` 或 `stderr` 的任何日志记录库。

.NET 运行时记录每次调用的 `START`、`END` 和 `REPORT` 行。报告行提供了以下详细信息：

**REPORT 行数据字段**
+ **RequestId** – 调用的唯一请求 ID。
+ **Duration**（持续时间）– 函数的处理程序方法处理事件所花费的时间。
+ **Billed Duration**（计费持续时间）– 针对调用计费的时间量。
+ **Memory Size**（内存大小）– 分配给函数的内存量。
+ **Max Memory Used**（最大内存使用量）– 函数使用的内存量。如果调用共享执行环境，Lambda 会报告所有调用使用的最大内存。此行为可能会导致报告值高于预期。
+ **Init Duration**（初始持续时间）– 对于提供的第一个请求，为运行时在处理程序方法外部加载函数和运行代码所花费的时间。
+ **XRAY TraceId** – 对于追踪的请求，为 [AWS X-Ray 追踪 ID](services-xray.md)。
+ **SegmentId** – 对于追踪的请求，为 X-Ray 分段 ID。
+ **Sampled**（采样）– 对于追踪的请求，为采样结果。

## 将 Lambda 高级日志记录控件与 .NET 结合使用
<a name="csharp-logging-advanced"></a>

为了让您更好地控制如何捕获、处理和使用函数日志，您可以为支持的 .NET 运行时系统配置以下日志记录选项：
+ **日志格式** - 为函数日志选择纯文本或结构化的 JSON 格式
+ **日志级别** - 对于 JSON 格式的日志，选择 Lambda 发送到 CloudWatch 的日志的详细信息级别，例如 ERROR、DEBUG 或 INFO
+ **日志组** - 选择您的函数发送日志的目标 CloudWatch 日志组

有关这些日志记录选项的更多信息以及如何通过配置来使用函数的说明，请参阅 [为 Lambda 函数配置高级日志记录控件](monitoring-logs.md#monitoring-cloudwatchlogs-advanced)。

要将日志格式和日志级别选项与 .NET Lambda 函数结合使用，请参阅以下各节中的指南。

### 将结构化的 JSON 日志格式与 .NET 结合使用
<a name="csharp-logging-advanced-JSON"></a>

如果您为函数的日志格式选择 JSON，Lambda 将使用 [ILambdaLogger](https://github.com/aws/aws-lambda-dotnet/blob/master/Libraries/src/Amazon.Lambda.Core/ILambdaLogger.cs) 作为结构化 JSON 发送日志输出。每个 JSON 日志对象包含至少五个键值对和以下键：
+ `"timestamp"` - 生成日志消息的时间
+ `"level"` - 分配给消息的日志级别
+ `"requestId"` - 函数调用的唯一请求 ID
+ `"traceId"`：`_X_AMZN_TRACE_ID` 环境变量
+ `"message"` - 日志消息的内容

`ILambdaLogger` 实例可以添加其他键值对，例如在日志记录异常时。您也可以提供自己的其他参数，如[由客户提供的日志参数](#csharp-logging-advanced-JSON-user-supplied)一节所述。

**注意**  
如果您的代码已经使用其他日志记录库来生成 JSON 格式的日志，则请确保将函数的日志格式设置为纯文本。将日志格式设置为 JSON 将导致您的日志输出采用双重编码。

以下示例日志记录命令显示了如何写入级别为 `INFO` 的日志消息。

**Example .NET 日志记录代码**  

```
context.Logger.LogInformation("Fetching cart from database");
```

您也可以使用通用日志方法，该方法将日志级别作为参数，如以下示例所示。

```
context.Logger.Log(LogLevel.Information, "Fetching cart from database");
```

这些示例代码片段输出的日志将在 CloudWatch Logs 中被捕获，如下所示：

**Example JSON 日志记录**  

```
{
    "timestamp": "2025-09-07T01:30:06.977Z",
    "level": "Information",
    "requestId": "8f711428-7e55-46f9-ae88-2a65d4f85fc5",
    "traceId": "1-6408af34-50f56f5b5677a7d763973804",
    "message": "Fetching cart from database"
}
```

**注意**  
如果您将函数的日志格式配置为使用纯文本而不是 JSON，则消息中捕获的日志级别将遵循使用四字符的标签的 Microsoft 约定。例如，日志级别 `Debug` 在消息中表示为 `dbug`。  
当您将函数配置为使用 JSON 格式的日志时，日志中捕获的日志级别会使用完整的标签，如示例 JSON 日志记录所示。

如果您没有为日志输出分配级别，Lambda 将自动为其分配 INFO 级别。

#### 在 JSON 中记录异常
<a name="csharp-logging-advanced-JSON-exceptions"></a>

将结构化 JSON 日志记录与 `ILambdaLogger` 结合使用时，可以在代码中记录异常，如以下示例所示。

**Example 异常日志记录的使用**  

```
try
{
    connection.ExecuteQuery(query);
}
catch(Exception e)
{
    context.Logger.LogWarning(e, "Error executing query");
}
```

此代码输出的日志格式如以下示例 JSON 中所示。请注意，JSON 中的 `message` 属性是使用 `LogWarning` 调用中提供的消息参数填充的，而 `errorMessage` 属性来自异常本身的 `Message` 属性。

**Example JSON 日志记录**  

```
{
    "timestamp": "2025-09-07T01:30:06.977Z",
    "level": "Warning",
    "requestId": "8f711428-7e55-46f9-ae88-2a65d4f85fc5",
    "traceId": "1-6408af34-50f56f5b5677a7d763973804",
    "message": "Error executing query",
    "errorType": "System.Data.SqlClient.SqlException",
    "errorMessage": "Connection closed",
    "stackTrace": ["<call exception.StackTrace>"]
}
```

如果您的函数的日志记录格式设置为 JSON，则当您的代码抛出未捕获的异常时，Lambda 还会输出 JSON 格式的日志消息。以下示例代码片段和日志消息显示了如何记录未捕获的异常。

**Example 异常代码**  

```
throw new ApplicationException("Invalid data");
```

**Example JSON 日志记录**  

```
{
    "timestamp": "2025-09-07T01:30:06.977Z",
    "level": "Error",
    "requestId": "8f711428-7e55-46f9-ae88-2a65d4f85fc5",
    "traceId": "1-6408af34-50f56f5b5677a7d763973804",
    "message": "Invalid data",
    "errorType": "System.ApplicationException",
    "errorMessage": "Invalid data",
    "stackTrace": ["<call exception.StackTrace>"]
}
```

#### 由客户提供的日志参数
<a name="csharp-logging-advanced-JSON-user-supplied"></a>

使用 JSON 格式的日志消息，您可以提供其他日志参数并将其包含在日志 `message` 中。以下代码片段示例显示了添加两个标记为 `retryAttempt` 和 `uri` 的用户所提供参数的命令。在示例中，这些参数的值来自传递给日志记录命令的 `retryAttempt` 和 `uriDestination` 参数。

**Example 带有其他参数的 JSON 日志记录命令**  

```
context.Logger.LogInformation("Starting retry {retryAttempt} to make GET request to {uri}", retryAttempt, uriDestination);
```

此命令输出的日志消息如以下示例 JSON 中所示。

**Example JSON 日志记录**  

```
{
    "timestamp": "2025-09-07T01:30:06.977Z",
    "level": "Information",
    "requestId": "8f711428-7e55-46f9-ae88-2a65d4f85fc5",
    "traceId": "1-6408af34-50f56f5b5677a7d763973804",
    "message": "Starting retry 1 to make GET request to http://example.com/",
    "retryAttempt": 1,
    "uri": "http://example.com/"
}
```

**提示**  
指定其他参数时，您也可以使用位置属性来代替名称。例如，也可以如下所示编写上一个示例中的日志记录命令：  

```
context.Logger.LogInformation("Starting retry {0} to make GET request to {1}", retryAttempt, uriDestination);
```

请注意，当您提供其他日志记录参数时，Lambda 会将这些参数捕获为 JSON 日志记录中的顶级属性。这种方法不同于一些流行的 .NET 日志记录库（例如 `Serilog`），后者在单独的子对象中捕获其他参数。

如果您为其他参数提供的参数是一个复杂对象，则默认情况下 Lambda 使用 `ToString()` 方法来提供值。要指示参数应以 JSON 形式序列化，请使用 `@` 前缀，如以下代码片段所示。在此示例中，`User` 是一个具有 `FirstName` 和 `LastName` 属性的对象。

**Example 具有以 JSON 形式序列化的对象的 JSON 日志记录命令**  

```
context.Logger.LogInformation("User {@user} logged in", User);
```

此命令输出的日志消息如以下示例 JSON 中所示。

**Example JSON 日志记录**  

```
{
    "timestamp": "2025-09-07T01:30:06.977Z",
    "level": "Information",
    "requestId": "8f711428-7e55-46f9-ae88-2a65d4f85fc5",
    "traceId": "1-6408af34-50f56f5b5677a7d763973804",
    "message": "User {@user} logged in",
    "user": 
    {
        "FirstName": "John",
        "LastName": "Doe"
    }
}
```

如果其他参数的参数是数组或实施 `IList` 或 `IDictionary`，则 Lambda 会将该参数作为数组添加到 JSON 日志消息中，如以下示例 JSON 日志记录所示。在此示例中，`{users}` 采用一个包含 `User` 属性的实例的 `IList` 参数，其格式与上一个示例相同。Lambda 会将此 `IList` 转换为数组，每个值都使用 `ToString` 方法创建。

**Example 具有 `IList` 参数的 JSON 日志记录**  

```
{
    "timestamp": "2025-09-07T01:30:06.977Z",
    "level": "Information",
    "requestId": "8f711428-7e55-46f9-ae88-2a65d4f85fc5",
    "traceId": "1-6408af34-50f56f5b5677a7d763973804",
    "message": "{users} have joined the group",
    "users": 
    [
        "Rosalez, Alejandro",
        "Stiles, John"       
    ] 
}
```

您也可以使用日志记录命令中的 `@` 前缀以 JSON 形式序列化列表。在以下示例 JSON 日志记录中，`users` 属性以 JSON 形式序列化。

**Example 具有以 JSON 形式序列化的 `IList` 参数的 JSON 日志记录**  

```
{
    "timestamp": "2025-09-07T01:30:06.977Z",
    "level": "Information",
    "requestId": "8f711428-7e55-46f9-ae88-2a65d4f85fc5",
    "traceId": "1-6408af34-50f56f5b5677a7d763973804",
    "message": "{@users} have joined the group",
    "users": 
    [
        {
            "FirstName": "Alejandro",
            "LastName": "Rosalez"
        },
        {
            "FirstName": "John",
            "LastName": "Stiles"
        }        
    ] 
}
```

### 将日志级别筛选与 .NET 结合使用
<a name="csharp-logging-advanced-levels"></a>

通过配置日志级别筛选，您可以选择仅将特定详细信息级别或更低级别的日志发送到 CloudWatch Logs。要了解如何为您的函数配置日志级别筛选，请参阅 [日志级别筛选](monitoring-cloudwatchlogs-log-level.md)。

为使 AWS Lambda 按日志级别筛选日志消息，您可以使用 JSON 格式的日志或使用 .NET `Console` 方法来输出日志消息。要创建 JSON 格式的日志，请[将函数的日志类型配置为 JSON](monitoring-cloudwatchlogs-logformat.md#monitoring-cloudwatchlogs-set-format) 并使用 `ILambdaLogger` 实例。

借助 JSON 格式的日志，Lambda 使用 [将结构化的 JSON 日志格式与 .NET 结合使用](#csharp-logging-advanced-JSON) 中所述的 JSON 对象中的“级别”键值对筛选您的日志输出。

如果您使用 .NET `Console` 方法向 CloudWatch Logs 写入消息，则 Lambda 会对您的消息应用日志级别，如下所示：
+ **Console.WriteLine 方法**：Lambda 应用日志级别 `INFO`
+ **Console.Error 方法**：Lambda 应用日志级别 `ERROR`

将函数配置为使用日志级别筛选时，您必须从以下选项中选择希望 Lambda 发送到 CloudWatch Logs 的日志级别。请注意 Lambda 使用的日志级别与 .NET `ILambdaLogger` 使用的标准 Microsoft 级别的映射。


| Lambda 日志级别 | 等效 Microsoft 级别 | 标准使用情况 | 
| --- | --- | --- | 
| TRACE（最详细） | 跟踪 | 用于跟踪代码执行路径的最精细信息 | 
| 调试 | Debug | 系统调试的详细信息 | 
| INFO | 信息 | 记录函数正常运行情况的消息 | 
| 警告 | 警告 | 有关潜在错误的消息，如果不加以解决，这些错误可能会导致意外行为 | 
| ERROR | 错误 | 有关会阻碍代码按预期执行的问题的消息 | 
| FATAL（最简略） | 重大 | 有关导致应用程序停止运行的严重错误的消息 | 

Lambda 仅将选定详细信息级别及更低级别的日志发送到 CloudWatch。例如，如果您将日志级别配置为 WARN，Lambda 将发送与 WARN、ERROR 和 FATAL 级别相对应的日志。

## 其他日志记录工具和库
<a name="csharp-tools-libraries"></a>

[Powertools for AWS Lambda（.NET）](https://docs.aws.amazon.com/powertools/dotnet/)是一个开发人员工具包，用于实施无服务器最佳实践并提高开发人员速度。[日志记录实用程序](https://docs.aws.amazon.com/powertools/dotnet/core/logging/)提供经优化的 Lambda 日志记录程序，其中包含有关所有函数的函数上下文的附加信息，输出结构为 JSON。请使用该实用程序执行以下操作：
+ 从 Lambda 上下文中捕获关键字段，冷启动并将日志记录输出结构化为 JSON
+ 根据指示记录 Lambda 调用事件（默认情况下禁用）
+ 通过日志采样仅针对一定百分比的调用输出所有日志（默认情况下禁用）
+ 在任何时间点将其他键附加到结构化日志
+ 使用自定义日志格式设置程序（自带格式设置程序），从而在与组织的日志记录 RFC 兼容的结构中输出日志

## 将 Powertools for AWS Lambda（.NET）和 AWS SAM 用于结构化日志记录
<a name="dotnet-logging-sam"></a>

请按照以下步骤使用 AWS SAM，通过集成的 [Powertools for AWS Lambda（.NET）](https://docs.powertools.aws.dev/lambda-dotnet)模块来下载、构建和部署示例 Hello World C\$1 应用程序。此应用程序实现了基本的 API 后端，并使用 Powertools 发送日志、指标和跟踪。它由 Amazon API Gateway 端点和 Lambda 函数组成。在向 API Gateway 端点发送 GET 请求时，Lambda 函数会使用嵌入式指标格式向 CloudWatch 调用、发送日志和指标，并向 AWS X-Ray 发送跟踪。该函数将返回一条 `hello world` 消息。

**先决条件**

要完成本节中的步骤，您必须满足以下条件：
+ .NET 8
+ [AWS CLI 版本 2](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html)
+ [AWS SAM CLI 版本 1.75 或更高版本](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html)。如果您使用的是旧版本的 AWS SAM CLI，请参阅[升级 AWS SAM CLI](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/manage-sam-cli-versions.html#manage-sam-cli-versions-upgrade)。

**部署示例 AWS SAM 应用程序**

1. 使用 Hello World TypeScript 模板初始化该应用程序。

   ```
   sam init --app-template hello-world-powertools-dotnet --name sam-app --package-type Zip --runtime dotnet6 --no-tracing
   ```

1. 构建应用程序。

   ```
   cd sam-app && sam build
   ```

1. 部署应用程序。

   ```
   sam deploy --guided
   ```

1. 按照屏幕上的提示操作。要在交互式体验中接受提供的默认选项，请按 `Enter`。
**注意**  
对于 **HelloWorldFunction 可能没有定义授权，确定执行此操作吗？**，确保输入 `y`。

1. 获取已部署应用程序的 URL：

   ```
   aws cloudformation describe-stacks --stack-name sam-app --query 'Stacks[0].Outputs[?OutputKey==`HelloWorldApi`].OutputValue' --output text
   ```

1. 调用 API 端点：

   ```
   curl -X GET <URL_FROM_PREVIOUS_STEP>
   ```

   如果成功，您将会看到如下响应：

   ```
   {"message":"hello world"}
   ```

1. 要获取该函数的日志，请运行 [sam logs](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-cli-command-reference-sam-logs.html)。有关更多信息，请参阅《AWS Serverless Application Model 开发人员指南》中的 [使用日志](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-logging.html)**。

   ```
   sam logs --stack-name sam-app
   ```

   该日志输出类似于以下示例：

   ```
   2025/02/20/[$LATEST]4eaf8445ba7a4a93b999cb17fbfbecd8 2025-09-20T14:15:27.988000 INIT_START Runtime Version: dotnet:6.v13        Runtime Version ARN: arn:aws:lambda:ap-southeast-2::runtime:699f346a05dae24c58c45790bc4089f252bf17dae3997e79b17d939a288aa1ec
   2025/02/20/[$LATEST]4eaf8445ba7a4a93b999cb17fbfbecd8 2025-09-20T14:15:28.229000 START RequestId: bed25b38-d012-42e7-ba28-f272535fb80e Version: $LATEST
   2025/02/20/[$LATEST]4eaf8445ba7a4a93b999cb17fbfbecd8 2025-09-20T14:15:29.259000 2025-09-20T14:15:29.201Z        bed25b38-d012-42e7-ba28-f272535fb80e    info   {"_aws":{"Timestamp":1676902528962,"CloudWatchMetrics":[{"Namespace":"sam-app-logging","Metrics":[{"Name":"ColdStart","Unit":"Count"}],"Dimensions":[["FunctionName"],["Service"]]}]},"FunctionName":"sam-app-HelloWorldFunction-haKIoVeose2p","Service":"PowertoolsHelloWorld","ColdStart":1}
   2025/02/20/[$LATEST]4eaf8445ba7a4a93b999cb17fbfbecd8 2025-09-20T14:15:30.479000 2025-09-20T14:15:30.479Z        bed25b38-d012-42e7-ba28-f272535fb80e    info   {"ColdStart":true,"XrayTraceId":"1-63f3807f-5dbcb9910c96f50742707542","CorrelationId":"d3d4de7f-4ccc-411a-a549-4d67b2fdc015","FunctionName":"sam-app-HelloWorldFunction-haKIoVeose2p","FunctionVersion":"$LATEST","FunctionMemorySize":256,"FunctionArn":"arn:aws:lambda:ap-southeast-2:123456789012:function:sam-app-HelloWorldFunction-haKIoVeose2p","FunctionRequestId":"bed25b38-d012-42e7-ba28-f272535fb80e","Timestamp":"2025-09-20T14:15:30.4602970Z","Level":"Information","Service":"PowertoolsHelloWorld","Name":"AWS.Lambda.Powertools.Logging.Logger","Message":"Hello world API - HTTP 200"}
   2025/02/20/[$LATEST]4eaf8445ba7a4a93b999cb17fbfbecd8 2025-09-20T14:15:30.599000 2025-09-20T14:15:30.599Z        bed25b38-d012-42e7-ba28-f272535fb80e    info   {"_aws":{"Timestamp":1676902528922,"CloudWatchMetrics":[{"Namespace":"sam-app-logging","Metrics":[{"Name":"ApiRequestCount","Unit":"Count"}],"Dimensions":[["Service"]]}]},"Service":"PowertoolsHelloWorld","ApiRequestCount":1}
   2025/02/20/[$LATEST]4eaf8445ba7a4a93b999cb17fbfbecd8 2025-09-20T14:15:30.680000 END RequestId: bed25b38-d012-42e7-ba28-f272535fb80e
   2025/02/20/[$LATEST]4eaf8445ba7a4a93b999cb17fbfbecd8 2025-09-20T14:15:30.680000 REPORT RequestId: bed25b38-d012-42e7-ba28-f272535fb80e  Duration: 2450.99 ms   Billed Duration: 2692 ms Memory Size: 256 MB     Max Memory Used: 74 MB  Init Duration: 240.05 ms
   XRAY TraceId: 1-63f3807f-5dbcb9910c96f50742707542       SegmentId: 16b362cd5f52cba0
   ```

1. 这是一个可以通过互联网访问的公有 API 端点。我们建议您在测试后删除该端点。

   ```
   sam delete
   ```

### 管理日志保留日期
<a name="csharp-log-retention"></a>

删除函数时，日志组不会自动删除。要避免无限期存储日志，请删除日志组，或配置一个保留期，在该保留期结束后，日志将自动删除。要设置日志保留日期，请将以下内容添加到您的 AWS SAM 模板中：

```
Resources:
  HelloWorldFunction:
    Type: AWS::Serverless::Function
    Properties:
      # Omitting other properties

  LogGroup:
    Type: AWS::Logs::LogGroup
    Properties:
      LogGroupName: !Sub "/aws/lambda/${HelloWorldFunction}"
      RetentionInDays: 7
```

## 在 Lambda 控制台中查看日志
<a name="csharp-logging-console"></a>

调用 Lambda 函数后，您可以使用 Lambda 控制台查看日志输出。

如果可以在嵌入式**代码**编辑器中测试代码，则可以在**执行结果**中找到日志。使用控制台测试功能调用函数时，可以在**详细信息**部分找到**日志输出**。

## 在 CloudWatch 控制台中查看日志
<a name="csharp-logging-cwconsole"></a>

您可以使用 Amazon CloudWatch 控制台查看所有 Lambda 函数调用的日志。

**使用 CloudWatch 控制台查看日志**

1. 打开 CloudWatch 控制台的 [Log groups](https://console.aws.amazon.com/cloudwatch/home?#logs:)（日志组页面）。

1. 选择您的函数 (**/aws/lambda/*your-function-name***) 的日志组。

1. 创建日志流。

每个日志流对应一个[函数实例](lambda-runtime-environment.md)。日志流会在您更新 Lambda 函数以及创建更多实例来处理并发调用时显示。要查找特定调用的日志，建议您使用 AWS X-Ray 检测函数。X-Ray 会在追踪中记录有关请求和日志流的详细信息。

## 使用 AWS Command Line Interface（AWS CLI）查看日志
<a name="csharp-logging-cli"></a>

AWS CLI 是一种开源工具，让您能够在命令行 Shell 中使用命令与 AWS 服务进行交互。要完成本节中的步骤，您必须拥有 [AWS CLI 版本 2](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html)。

您可以通过 [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-welcome.html)，使用 `--log-type` 命令选项检索调用的日志。响应包含一个 `LogResult` 字段，其中包含多达 4KB 来自调用的 base64 编码日志。

**Example 检索日志 ID**  
以下示例说明如何从 `LogResult` 字段中检索名为 `my-function` 的函数的*日志 ID*。  

```
aws lambda invoke --function-name my-function out --log-type Tail
```
您应看到以下输出：  

```
{
    "StatusCode": 200,
    "LogResult": "U1RBUlQgUmVxdWVzdElkOiA4N2QwNDRiOC1mMTU0LTExZTgtOGNkYS0yOTc0YzVlNGZiMjEgVmVyc2lvb...",
    "ExecutedVersion": "$LATEST"
}
```

**Example 解码日志**  
在同一命令提示符下，使用 `base64` 实用程序解码日志。以下示例说明如何为 `my-function` 检索 base64 编码的日志。  

```
aws lambda invoke --function-name my-function out --log-type Tail \
--query 'LogResult' --output text --cli-binary-format raw-in-base64-out | base64 --decode
```
如果使用 **cli-binary-format** 版本 2，则 AWS CLI 选项是必需的。要将其设为默认设置，请运行 `aws configure set cli-binary-format raw-in-base64-out`。有关更多信息，请参阅*版本 2 的 AWS Command Line Interface 用户指南*中的 [AWS CLI 支持的全局命令行选项](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-options.html#cli-configure-options-list)。  
您应看到以下输出：  

```
START RequestId: 57f231fb-1730-4395-85cb-4f71bd2b87b8 Version: $LATEST
"AWS_SESSION_TOKEN": "AgoJb3JpZ2luX2VjELj...", "_X_AMZN_TRACE_ID": "Root=1-5d02e5ca-f5792818b6fe8368e5b51d50;Parent=191db58857df8395;Sampled=0"",ask/lib:/opt/lib",
END RequestId: 57f231fb-1730-4395-85cb-4f71bd2b87b8
REPORT RequestId: 57f231fb-1730-4395-85cb-4f71bd2b87b8  Duration: 79.67 ms      Billed Duration: 80 ms         Memory Size: 128 MB     Max Memory Used: 73 MB
```
`base64` 实用程序在 Linux、macOS 和 [Ubuntu on Windows](https://docs.microsoft.com/en-us/windows/wsl/install-win10) 上可用。macOS 用户可能需要使用 `base64 -D`。

**Example get-logs.sh 脚本**  
在同一命令提示符下，使用以下脚本下载最后五个日志事件。此脚本使用 `sed` 从输出文件中删除引号，并休眠 15 秒以等待日志可用。输出包括来自 Lambda 的响应，以及来自 `get-log-events` 命令的输出。  
复制以下代码示例的内容并将其作为 `get-logs.sh` 保存在 Lambda 项目目录中。  
如果使用 **cli-binary-format** 版本 2，则 AWS CLI 选项是必需的。要将其设为默认设置，请运行 `aws configure set cli-binary-format raw-in-base64-out`。有关更多信息，请参阅*版本 2 的 AWS Command Line Interface 用户指南*中的 [AWS CLI 支持的全局命令行选项](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-options.html#cli-configure-options-list)。  

```
#!/bin/bash
aws lambda invoke --function-name my-function --cli-binary-format raw-in-base64-out --payload '{"key": "value"}' out
sed -i'' -e 's/"//g' out
sleep 15
aws logs get-log-events --log-group-name /aws/lambda/my-function --log-stream-name stream1 --limit 5
```

**Example macOS 和 Linux（仅限）**  
在同一命令提示符下，macOS 和 Linux 用户可能需要运行以下命令以确保脚本可执行。  

```
chmod -R 755 get-logs.sh
```

**Example 检索最后五个日志事件**  
在同一命令提示符下，运行以下脚本以获取最后五个日志事件。  

```
./get-logs.sh
```
您应看到以下输出：  

```
{
    "StatusCode": 200,
    "ExecutedVersion": "$LATEST"
}
{
    "events": [
        {
            "timestamp": 1559763003171,
            "message": "START RequestId: 4ce9340a-b765-490f-ad8a-02ab3415e2bf Version: $LATEST\n",
            "ingestionTime": 1559763003309
        },
        {
            "timestamp": 1559763003173,
            "message": "2019-06-05T19:30:03.173Z\t4ce9340a-b765-490f-ad8a-02ab3415e2bf\tINFO\tENVIRONMENT VARIABLES\r{\r  \"AWS_LAMBDA_FUNCTION_VERSION\": \"$LATEST\",\r ...",
            "ingestionTime": 1559763018353
        },
        {
            "timestamp": 1559763003173,
            "message": "2019-06-05T19:30:03.173Z\t4ce9340a-b765-490f-ad8a-02ab3415e2bf\tINFO\tEVENT\r{\r  \"key\": \"value\"\r}\n",
            "ingestionTime": 1559763018353
        },
        {
            "timestamp": 1559763003218,
            "message": "END RequestId: 4ce9340a-b765-490f-ad8a-02ab3415e2bf\n",
            "ingestionTime": 1559763018353
        },
        {
            "timestamp": 1559763003218,
            "message": "REPORT RequestId: 4ce9340a-b765-490f-ad8a-02ab3415e2bf\tDuration: 26.73 ms\tBilled Duration: 27 ms \tMemory Size: 128 MB\tMax Memory Used: 75 MB\t\n",
            "ingestionTime": 1559763018353
        }
    ],
    "nextForwardToken": "f/34783877304859518393868359594929986069206639495374241795",
    "nextBackwardToken": "b/34783877303811383369537420289090800615709599058929582080"
}
```

## 删除日志
<a name="csharp-logging-delete"></a>

删除函数时，日志组不会自动删除。要避免无限期存储日志，请删除日志组，或[配置一个保留期](https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/Working-with-log-groups-and-streams.html#SettingLogRetention)，在该保留期之后，日志将自动删除。

# 在 AWS Lambda 中检测 C\$1 代码
<a name="csharp-tracing"></a>

Lambda 与 AWS X-Ray 集成，以帮助您跟踪、调试和优化 Lambda 应用程序。您可以在某个请求遍历应用程序中的资源（其中可能包括 Lambda 函数和其他 AWS 服务）时，使用 X-Ray 跟踪该请求。

要将跟踪数据发送到 X-Ray，您可以使用以下三个开发工具包库之一：
+ [适用于 OpenTelemetry 的 AWS 发行版 (ADOT)](https://aws.amazon.com/otel) – 一种安全、可供生产、支持 AWS 的 OpenTelemetry (OTel) SDK 的分发版本。
+ [AWS X-Ray SDK for .NET](https://docs.aws.amazon.com/xray/latest/devguide/xray-sdk-dotnet.html) – 用于生成跟踪数据并将其发送到 X-Ray 的 SDK
+ [Powertools for AWS Lambda（.NET）](https://docs.aws.amazon.com/powertools/dotnet/)– 一个开发人员工具包，用于实施无服务器最佳实践并提高开发人员速度。

每个开发工具包均提供了将遥测数据发送到 X-Ray 服务的方法。然后，您可以使用 X-Ray 查看、筛选和获得对应用程序性能指标的洞察，从而发现问题和优化机会。

**重要**  
X-Ray 和 Powertools for AWS Lambda SDK 是 AWS 提供的紧密集成的分析解决方案的一部分。ADOT Lambda Layers 是全行业通用的跟踪分析标准的一部分，该标准通常会收集更多数据，但可能不适用于所有使用案例。您可以使用任一解决方案在 X-Ray 中实现端到端跟踪。要了解有关如何在两者之间进行选择的更多信息，请参阅[在 AWS Distro for Open Telemetry 和 X-Ray 开发工具包之间进行选择](https://docs.aws.amazon.com/xray/latest/devguide/xray-instrumenting-your-app.html#xray-instrumenting-choosing)。

**Topics**
+ [

## 将 Powertools for AWS Lambda（.NET）和 AWS SAM 用于跟踪
](#dotnet-tracing-sam)
+ [

## 使用 X-Ray SDK 分析 .NET 函数
](#dotnet-xray-sdk)
+ [

## 使用 Lambda 控制台激活跟踪
](#dotnet-tracing-console)
+ [

## 使用 Lambda API 激活跟踪
](#dotnet-tracing-api)
+ [

## 使用 CloudFormation 激活跟踪
](#dotnet-tracing-cloudformation)
+ [

## 解释 X-Ray 跟踪
](#dotnet-tracing-interpretation)

## 将 Powertools for AWS Lambda（.NET）和 AWS SAM 用于跟踪
<a name="dotnet-tracing-sam"></a>

请按照以下步骤使用 AWS SAM，通过集成的 [Powertools for AWS Lambda（.NET）](https://docs.powertools.aws.dev/lambda-dotnet)模块来下载、构建和部署示例 Hello World C\$1 应用程序。此应用程序实现了基本的 API 后端，并使用 Powertools 发送日志、指标和跟踪。它由 Amazon API Gateway 端点和 Lambda 函数组成。在向 API Gateway 端点发送 GET 请求时，Lambda 函数会使用嵌入式指标格式向 CloudWatch 调用、发送日志和指标，并向 AWS X-Ray 发送跟踪。该函数将返回一条 hello world 消息。

**先决条件**

要完成本节中的步骤，您必须满足以下条件：
+ .NET 8
+ [AWS CLI 版本 2](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html)
+ [AWS SAM CLI 版本 1.75 或更高版本](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html)。如果您使用的是旧版本的 AWS SAM CLI，请参阅[升级 AWS SAM CLI](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/manage-sam-cli-versions.html#manage-sam-cli-versions-upgrade)。

**部署示例 AWS SAM 应用程序**

1. 使用 Hello World TypeScript 模板初始化该应用程序。

   ```
   sam init --app-template hello-world-powertools-dotnet --name sam-app --package-type Zip --runtime dotnet6 --no-tracing
   ```

1. 构建应用程序。

   ```
   cd sam-app && sam build
   ```

1. 部署应用程序。

   ```
   sam deploy --guided
   ```

1. 按照屏幕上的提示操作。要在交互式体验中接受提供的默认选项，请按 `Enter`。
**注意**  
对于 **HelloWorldFunction 可能没有定义授权，确定执行此操作吗？**，确保输入 `y`。

1. 获取已部署应用程序的 URL：

   ```
   aws cloudformation describe-stacks --stack-name sam-app --query 'Stacks[0].Outputs[?OutputKey==`HelloWorldApi`].OutputValue' --output text
   ```

1. 调用 API 端点：

   ```
   curl <URL_FROM_PREVIOUS_STEP>
   ```

   如果成功，您将会看到如下响应：

   ```
   {"message":"hello world"}
   ```

1. 要获取该函数的跟踪信息，请运行 [sam traces](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-cli-command-reference-sam-traces.html)。

   ```
   sam traces
   ```

   该跟踪输出类似于以下示例：

   ```
   New XRay Service Graph
     Start time: 2023-02-20 23:05:16+08:00
     End time: 2023-02-20 23:05:16+08:00
     Reference Id: 0 - AWS::Lambda - sam-app-HelloWorldFunction-pNjujb7mEoew - Edges: [1]
      Summary_statistics:
        - total requests: 1
        - ok count(2XX): 1
        - error count(4XX): 0
        - fault count(5XX): 0
        - total response time: 2.814
     Reference Id: 1 - AWS::Lambda::Function - sam-app-HelloWorldFunction-pNjujb7mEoew - Edges: []
      Summary_statistics:
        - total requests: 1
        - ok count(2XX): 1
        - error count(4XX): 0
        - fault count(5XX): 0
        - total response time: 2.429
     Reference Id: 2 - (Root) AWS::ApiGateway::Stage - sam-app/Prod - Edges: [0]
      Summary_statistics:
        - total requests: 1
        - ok count(2XX): 1
        - error count(4XX): 0
        - fault count(5XX): 0
        - total response time: 2.839
     Reference Id: 3 - client - sam-app/Prod - Edges: [2]
      Summary_statistics:
        - total requests: 0
        - ok count(2XX): 0
        - error count(4XX): 0
        - fault count(5XX): 0
        - total response time: 0
   
   XRay Event [revision 3] at (2023-02-20T23:05:16.521000) with id (1-63f38c2c-270200bf1d292a442c8e8a00) and duration (2.877s)
    - 2.839s - sam-app/Prod [HTTP: 200]
      - 2.836s - Lambda [HTTP: 200]
    - 2.814s - sam-app-HelloWorldFunction-pNjujb7mEoew [HTTP: 200]
    - 2.429s - sam-app-HelloWorldFunction-pNjujb7mEoew
      - 0.230s - Initialization
      - 2.389s - Invocation
        - 0.600s - ## FunctionHandler
          - 0.517s - Get Calling IP
      - 0.039s - Overhead
   ```

1. 这是一个可以通过互联网访问的公有 API 端点。我们建议您在测试后删除该端点。

   ```
   sam delete
   ```

X-Ray 无法跟踪对应用程序的所有请求。X-Ray 将应用采样算法确保跟踪有效，同时仍会提供所有请求的一个代表性样本。采样率是每秒 1 个请求和 5% 的其他请求。您无法为函数配置此 X-Ray 采样率。

## 使用 X-Ray SDK 分析 .NET 函数
<a name="dotnet-xray-sdk"></a>

您可以使用函数代码来记录元数据并跟踪下游调用。要记录有关您的函数对其他资源和服务进行调用的详细信息，请使用 AWS X-Ray SDK for .NET。要获取开发工具包，请将 `AWSXRayRecorder` 程序包添加到您的项目文件中。

```
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net8.0</TargetFramework>
    <GenerateRuntimeConfigurationFiles>true</GenerateRuntimeConfigurationFiles>
    <AWSProjectType>Lambda</AWSProjectType>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="Amazon.Lambda.Core" Version="2.1.0" />
    <PackageReference Include="Amazon.Lambda.SQSEvents" Version="2.1.0" />
    <PackageReference Include="Amazon.Lambda.Serialization.Json" Version="2.1.0" />
    <PackageReference Include="AWSSDK.Core" Version="3.7.103.24" />
    <PackageReference Include="AWSSDK.Lambda" Version="3.7.104.3" />
    <PackageReference Include="AWSXRayRecorder.Core" Version="2.13.0" />
    <PackageReference Include="AWSXRayRecorder.Handlers.AwsSdk" Version="2.11.0" />
  </ItemGroup>
</Project>
```

有一系列 Nuget 软件包可以为 AWS SDK、实体框架和 HTTP 请求提供自动分析。要查看完整的配置选项集，请参阅《AWS X-Ray 开发人员指南》**中的[适用于 .NET 的 AWS X-Ray SDK](https://docs.aws.amazon.com/xray/latest/devguide/xray-sdk-dotnet.html)。

添加所需的 Nuget 软件包后，请配置自动分析。最佳实践是在函数的处理函数之外执行此配置。这样一来，您可以利用执行环境重用来提高函数性能。在以下代码示例中，在函数构造函数中调用 `RegisterXRayForAllServices` 方法，为所有 AWS SDK 调用添加分析。

```
[assembly: LambdaSerializer(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))]

namespace GetProductHandler;

public class Function
{
    private readonly IDatabaseRepository _repo;
    
    public Function()
    {
        // Add auto instrumentation for all AWS SDK calls
        // It is important to call this method before initializing any SDK clients
        AWSSDKHandler.RegisterXRayForAllServices();
        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 控制台激活跟踪
<a name="dotnet-tracing-console"></a>

要使用控制台切换 Lambda 函数的活动跟踪，请按照以下步骤操作：

**打开活跃跟踪**

1. 打开 Lamba 控制台的[函数](https://console.aws.amazon.com/lambda/home#/functions)页面。

1. 选择函数。

1. 选择 **Configuration**（配置），然后选择 **Monitoring and operations tools**（监控和操作工具）。

1. 在**其他监控工具**下，选择**编辑**。

1. 在 **CloudWatch 应用程序信号和 AWS X-Ray** 下，为 **Lambda 服务跟踪**选择**启用**。

1. 选择**保存**。

## 使用 Lambda API 激活跟踪
<a name="dotnet-tracing-api"></a>

借助 AWS CLI 或 AWS SDK 在 Lambda 函数上配置跟踪，请使用以下 API 操作：
+ [UpdateFunctionConfiguration](https://docs.aws.amazon.com/lambda/latest/api/API_UpdateFunctionConfiguration.html)
+ [GetFunctionConfiguration](https://docs.aws.amazon.com/lambda/latest/api/API_GetFunctionConfiguration.html)
+ [CreateFunction](https://docs.aws.amazon.com/lambda/latest/api/API_CreateFunction.html)

以下示例 AWS CLI 命令对名为 **my-function** 的函数启用活跃跟踪。

```
aws lambda update-function-configuration --function-name my-function \
--tracing-config Mode=Active
```

跟踪模式是发布函数版本时版本特定配置的一部分。您无法更改已发布版本上的跟踪模式。

## 使用 CloudFormation 激活跟踪
<a name="dotnet-tracing-cloudformation"></a>

要对 CloudFormation 模板中的 `AWS::Lambda::Function` 资源激活跟踪，请使用 `TracingConfig` 属性。

**Example [function-inline.yml](https://github.com/awsdocs/aws-lambda-developer-guide/blob/master/templates/function-inline.yml) – 跟踪配置**  

```
Resources:
  function:
    Type: [AWS::Lambda::Function](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lambda-function.html)
    Properties:
      TracingConfig:
        Mode: Active
      ...
```

对于 AWS Serverless Application Model (AWS SAM) `AWS::Serverless::Function` 资源，请使用 `Tracing` 属性。

**Example [template.yml](https://github.com/awsdocs/aws-lambda-developer-guide/tree/main/sample-apps/blank-nodejs/template.yml) – 跟踪配置**  

```
Resources:
  function:
    Type: [AWS::Serverless::Function](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-resource-function.html)
    Properties:
      Tracing: Active
      ...
```

## 解释 X-Ray 跟踪
<a name="dotnet-tracing-interpretation"></a>

您的函数需要权限才能将跟踪数据上载到 X-Ray。在 Lambda 控制台中激活跟踪后，Lambda 会将所需权限添加到函数的[执行角色](lambda-intro-execution-role.md)。如果没有，请将 [AWSXRayDaemonWriteAccess](https://console.aws.amazon.com/iam/home#/policies/arn:aws:iam::aws:policy/AWSXRayDaemonWriteAccess) 策略添加到执行角色。

在配置活跃跟踪后，您可以通过应用程序观察特定请求。[X-Ray 服务图](https://docs.aws.amazon.com/xray/latest/devguide/aws-xray.html#xray-concepts-servicegraph)将显示有关应用程序及其所有组件的信息。以下示例显示了具有两个函数的应用程序。主函数处理事件，有时会返回错误。位于顶部的第二个函数将处理第一个函数的日志组中显示的错误，并使用 AWS SDK 调用 X-Ray、Amazon Simple Storage Service (Amazon S3) 和 Amazon CloudWatch Logs。

![\[\]](http://docs.aws.amazon.com/zh_cn/lambda/latest/dg/images/sample-errorprocessor-servicemap.png)


X-Ray 无法跟踪对应用程序的所有请求。X-Ray 将应用采样算法确保跟踪有效，同时仍会提供所有请求的一个代表性样本。采样率是每秒 1 个请求和 5% 的其他请求。您无法为函数配置此 X-Ray 采样率。

在 X-Ray 中，*跟踪*记录有关由一个或多个*服务*处理的请求的信息。Lambda 会每个跟踪记录 2 个分段，这些分段将在服务图上创建两个节点。下图突出显示了这两个节点：

![\[\]](http://docs.aws.amazon.com/zh_cn/lambda/latest/dg/images/xray-servicemap-function.png)


位于左侧的第一个节点表示接收调用请求的 Lambda 服务。第二个节点表示特定的 Lambda 函数。以下示例显示了一个包含这 2 个分段的跟踪。两者都命名为 **my-function**，但其中一个函数具有 `AWS::Lambda` 源，另一个则具有 `AWS::Lambda::Function` 源。如果 `AWS::Lambda` 分段显示错误，则表示 Lambda 服务存在问题。如果 `AWS::Lambda::Function` 分段显示错误，则说明函数存在问题。

![\[\]](http://docs.aws.amazon.com/zh_cn/lambda/latest/dg/images/V2_sandbox_images/my-function-2-v1.png)


此示例将展开 `AWS::Lambda::Function` 分段，以显示其三个子分段。

**注意**  
AWS 目前正在实施对 Lambda 服务的更改。由于这些更改，您可能会看到 AWS 账户 中不同 Lambda 函数发出的系统日志消息和跟踪分段的结构和内容之间存在细微差异。  
此处显示的示例跟踪说明了旧样式函数分段。以下段落介绍了新旧样式分段之间的差异。  
这些更改将在未来几周内实施，除中国和 GovCloud 区域外，所有 AWS 区域 的函数都将过渡到使用新格式的日志消息和跟踪分段。

旧样式函数分段包含以下子分段：
+ **初始化** – 表示加载函数和运行[初始化代码](foundation-progmodel.md)所花费的时间。此子分段仅对由您的函数的每个实例处理的第一个事件显示。
+ **调用** – 表示执行处理程序代码花费的时间。
+ **开销** – 表示 Lambda 运行时为准备处理下一个事件而花费的时间。

新样式函数分段不包含 `Invocation` 子分段。而是将客户子分段直接附加到函数分段。有关新旧样式函数分段结构的更多信息，请参阅 [了解 X-Ray 跟踪](services-xray.md#services-xray-traces)。

您还可以分析 HTTP 客户端、记录 SQL 查询以及使用注释和元数据创建自定义子段。有关更多信息，请参阅 *AWS X-Ray 开发人员指南*中的 [AWS X-Ray SDK for .NET](https://docs.aws.amazon.com/xray/latest/devguide/xray-sdk-dotnet.html)。

**定价**  
作为 AWS 免费套餐的组成部分，您可以每月免费使用 X-Ray 跟踪，但不能超过一定限制。超出该阈值后，X-Ray 会对跟踪存储和检索进行收费。有关更多信息，请参阅 [AWS X-Ray 定价](https://aws.amazon.com/xray/pricing/)。

# C\$1 中的 AWS Lambda 函数测试
<a name="dotnet-csharp-testing"></a>

**注意**  
有关测试无服务器解决方案的技术和最佳实践的完整介绍，请参阅[测试函数](testing-guide.md)一章。

 测试无服务器函数使用传统的测试类型和技术，但您还必须考虑对无服务器应用程序进行整体测试。基于云的测试将为您的函数和无服务器应用程序的质量提供**最准确**的衡量。

 无服务器应用程序架构包括可通过 API 调用提供关键应用程序功能的托管服务。因此，您的开发周期应包括在函数与服务交互时验证功能的自动化测试。

 如果您不创建基于云的测试，则可能会由于本地环境和已部署环境之间的差异而遇到问题。在将代码提升到下一个部署环境（例如 QA、暂存或生产）之前，您的持续集成过程应针对在云端预置的一套资源运行测试。

 继续阅读本简短指南，了解无服务器应用程序的测试策略，或者访问[无服务器测试示例存储库](https://github.com/aws-samples/serverless-test-samples)，深入了解特定于您所选语言和运行时系统的实用示例。

 ![\[illustration showing the relationship between types of tests\]](http://docs.aws.amazon.com/zh_cn/lambda/latest/dg/images/test-type-illustration2.png) 

 对于无服务器测试，您仍需要编写*单元*、*集成*和*端到端*测试。
+ **单元测试** – 针对隔离代码块运行的测试。例如，验证业务逻辑以计算给定特定项目和目的地的配送费用。
+ **集成测试** – 涉及通常在云环境中交互的两个或更多组件或服务的测试。例如，验证函数是否会处理队列中的事件。
+ **端到端测试** – 验证整个应用程序行为的测试。例如，确保正确设置基础设施，并确保事件在服务之间按预期流动，以记录客户的订单。

## 测试无服务器应用程序
<a name="dotnet-csharp-testing-techniques-for-serverless-applications"></a>

 您通常会使用多种方法来测试无服务器应用程序代码，包括在云端进行测试、使用 Mock 进行测试，以及偶尔使用仿真器进行测试。

### 在云端进行测试
<a name="dotnet-csharp-testing-in-the-cloud"></a>

 在云端进行测试对于各个阶段的测试（包括单元测试、集成测试和端到端测试）而言都很有价值。您可以针对部署在云端并与基于云的服务进行交互的代码运行测试。这种方法可以**准确**衡量代码的质量。

 在云端调试 Lambda 函数的一种便捷方法是在控制台中使用测试事件。*测试事件*是函数的一个 JSON 输入。如果函数不需要输入，则事件可以是空 JSON 文档 `({})`。控制台为各种服务集成提供示例事件。在控制台中创建事件后，您可以将其与团队共享，以简化测试并保持一致性。

**注意**  
[在控制台中测试函数](testing-functions.md)是一种快速开始的方法，但自动化测试周期可确保应用程序质量和开发速度。

### 测试工具
<a name="dotnet-csharp-testing-tools"></a>

为了加速开发周期，您可以在测试函数时使用多种工具和技术。例如，[AWS SAM Accelerate](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/using-sam-cli-sync.html) 和 [AWS CDK 监视模式](https://docs.aws.amazon.com/cdk/v2/guide/cli.html#cli-deploy-watch)都减少了更新云环境所需的时间。

您定义 Lambda 函数代码的方式使添加单元测试变得简单。Lambda 需要公共、无参数的构造函数来初始化您的类。引入第二个内部构造函数可以让您控制应用程序使用的依赖关系。

```
[assembly: LambdaSerializer(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))]

namespace GetProductHandler;

public class Function
{
    private readonly IDatabaseRepository _repo;
    
    public Function(): this(null)
    {
    }
    
    internal Function(IDatabaseRepository repo)
    {
        this._repo = 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)
        };
    }
}
```

要为此函数编写测试，您可以初始化 `Function` 类的新实例，然后传递 `IDatabaseRepository` 的模拟实现。以下示例使用 `XUnit`、`Moq` 和 `FluentAssertions` 来编写简单的测试，以确保 `FunctionHandler` 返回 200 状态代码。

```
using Xunit;
using Moq;
using FluentAssertions;

public class FunctionTests
{
    [Fact]
    public async Task TestLambdaHandler_WhenInputIsValid_ShouldReturn200StatusCode()
    {
        // Arrange
        var mockDatabaseRepository = new Mock<IDatabaseRepository>();
        
        var functionUnderTest = new Function(mockDatabaseRepository.Object);
        
        // Act
        var response = await functionUnderTest.FunctionHandler(new APIGatewayProxyRequest());
        
        // Assert
        response.StatusCode.Should().Be(200);
    }
}
```

有关更详细的示例，包括异步测试的示例，请参阅 GitHub 上的 [.NET 测试示例存储库](https://github.com/aws-samples/serverless-test-samples/tree/main/dotnet-test-samples)。