定義 C# 格式的 Lambda 函數處理常式 - AWS Lambda

本文為英文版的機器翻譯版本,如內容有任何歧義或不一致之處,概以英文版為準。

定義 C# 格式的 Lambda 函數處理常式

Lambda 函數處理常式是您的函數程式碼中處理事件的方法。當有人呼叫您的函數時,Lambda 會執行處理常式方法。函數會執行,直到處理常式傳回回應、結束或逾時為止。

此頁面說明如何在 C# 中使用 Lambda 函數處理常式來使用 .NET 受管執行期,包括專案設定、命名慣例和最佳實務的選項。此頁面也包含 C# Lambda 函數的範例,該函數會取得訂單的相關資訊、產生文字檔案接收,並將此檔案放入 Amazon Simple Storage Service (S3) 儲存貯體。如需編寫函數後如何部署函數的詳細資訊,請參閱使用 .zip 封存檔建置和部署 C# Lambda 函數使用容器映像部署 .NET Lambda 函數

設定 C# 處理常式專案

在 C# 中使用 Lambda 函數時,程序涉及編寫程式碼,然後將程式碼部署到 Lambda。在 .NET 中部署 Lambda 函數有兩種不同的執行模型:類別程式庫方法和可執行的組合方法。

在類別程式庫方法中,您可以將函數程式碼封裝為 .NET 組件 (.dll),並使用 .NET 受管執行時間 () 將其部署至 Lambdadotnet8。對於處理常式名稱,Lambda 預期字串的格式為 AssemblyName::Namespace.Classname::Methodname。在函數的初始化階段,系統會初始化函數的類別,並執行建構函數中的任何程式碼。

在可執行組件方法中,您可以使用 C# 9 中首次推出的最上層陳述式功能。此方法會產生一個可執行組件,每當 Lambda 收到函數的調用命令時,就會執行該組件。在此方法中,您也會使用 .NET 受管執行時間 (dotnet8)。對於處理常式名稱,您可以向 Lambda 提供要執行的可執行組件名稱。

本頁的主要範例說明類別程式庫方法。您可以透過各種方式初始化 C# Lambda 專案,但最簡單的方法是搭配 CLI 使用 .NET Amazon.Lambda.Tools CLI。依照中的步驟設定 Amazon.Lambda.Tools CLI設定您的 .NET 開發環境。然後,使用以下命令初始化您的專案:

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# Lambda 函數程式碼

下列範例 C# Lambda 函數程式碼會取得訂單的相關資訊、產生文字檔案接收,並將此檔案放入 Amazon S3 儲存貯體。

範例 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# 類別。

  • [assembly: LambdaSerializer(...)]LambdaSerializer 是組件屬性,可告知 Lambda 在將 JSON 事件承載傳遞至函數之前,自動將它們轉換為 C# 物件。

  • namespace ExampleLambda:這會定義命名空間。在 C# 中,命名空間名稱不需要符合檔案名稱。

  • public class Order {...}:這會定義預期輸入事件的形狀。

  • public class OrderHandler {...}:這會定義您的 C# 類別。其中,您將定義主要處理常式方法和任何其他協助程式方法。

  • private static readonly AmazonS3Client s3Client = new();:這會初始化具有預設登入資料提供者鏈的 Amazon S3 用戶端,位於主處理常式方法之外。這會導致 Lambda 在初始化階段執行此程式碼。

  • 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檔案不包含函數的 profileregion資訊。此外,請將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" }

若要讓此函數正常運作,其執行角色必須允許 s3:PutObject 動作。如果您使用 dotnet lambda deploy-function命令 (即 dotnet lambda deploy-function ExampleCS),CLI 提示中的AWSLambdaExecute政策會包含成功叫用此函數的必要許可。

此外,請確保

最後,請確定您定義RECEIPT_BUCKET環境變數。成功調用後,Amazon S3 儲存貯體應包含收據檔案。

類別庫處理常式

本頁的主要範例程式碼說明類別程式庫處理常式。類別程式庫處理常式具有下列結構:

[assembly: LambdaSerializer(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))] namespace NAMESPACE; ... public class CLASSNAME { public async Task<string> METHODNAME (...) { ... } }

當您建立 Lambda 函數時,您需要以處理常式欄位中字串的形式,提供 Lambda 函數處理常式的相關資訊。目的是指示 Lambda 在調用函數時要在程式碼中執行哪種方法。在 C# 中,對於類別程式庫處理常式,處理常式字串的格式為 ASSEMBLY::TYPE::METHOD,其中:

  • ASSEMBLY 是應用程式的 .NET 組件名稱。如果您使用 Amazon.Lambda.Tools CLI 來建置應用程式,但並未使用 .csproj 檔案中的 AssemblyName 屬性來設定組件名稱,則 ASSEMBLY 就是您的.csproj檔案名稱。

  • TYPE 是處理常式類型的全名,即 NAMESPACE.CLASSNAME

  • METHOD 是程式碼中主要處理常式方法的名稱,也就是 METHODNAME

對於此頁面的主要範例程式碼,如果組件名為 ExampleCS,則完整的處理常式字串為 ExampleCS::ExampleLambda.OrderHandler::HandleRequest

可執行組件處理常式

您也可以在 C# 中將 Lambda 函數定義為可執行組件。可執行的組件處理常式會使用 C# 的頂層陳述式功能,其中編譯器會產生 Main()方法,並將函數程式碼放入其中。使用可執行組件時,必須引導 Lambda 執行期。若要這樣做,請在程式碼中使用 LambdaBootstrapBuilder.Create方法。此方法的輸入是主要處理常式函數,以及要使用的 Lambda 序列化器。以下顯示 C# 中可執行的組件處理常式範例:

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

在可執行組件處理常式的處理常式欄位中,告知 Lambda 如何執行程式碼的處理常式字串是組件的名稱。在此範例中,這是 GetProductHandler

C# 函數的有效處理常式簽章

在 C# 中,有效的 Lambda 處理常式簽章需要 0 到 2 個引數。一般而言,您的處理常式簽章有兩個引數,如主要範例所示:

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

提供兩個引數時,第一個引數必須是事件輸入,第二個引數必須是 Lambda 內容物件。這兩個引數都是選用的。例如,下列也是 C# 中有效的 Lambda 處理常式簽章:

  • public async Task<string> HandleRequest()

  • public async Task<string> HandleRequest(Order order)

  • public async Task<string> HandleRequest(ILambdaContext context)

除了處理常式簽章的基本語法之外,還有一些額外的限制:

  • 您無法在處理常式簽章中使用unsafe關鍵字。不過,您可以使用處理常式方法及其相依性中的unsafe內容。如需詳細資訊,請參閱 Microsoft 文件網站上的不安全 (C# 參考)

  • 處理常式可能不會使用 params關鍵字,或ArgIterator用作輸入或傳回參數。這些關鍵字支援變數數量的參數。您的處理常式可以接受的引數數目上限為兩個。

  • 處理常式可能不是一般方法。換句話說,它無法使用一般類型參數,例如 <T>

  • Lambda 不支援在簽章async void中使用 的非同步處理常式。

處理常式命名慣例

C# 中的 Lambda 處理常式沒有嚴格的命名限制。不過,部署函數時,您必須確保向 Lambda 提供正確的處理常式字串。正確的處理常式字串取決於您要部署類別程式庫處理常式可執行的組件處理常式

雖然您可以為處理常式使用任何名稱,但 C# 中的函數名稱通常在 PascalCase 中。此外,雖然檔案名稱不需要符合類別名稱或處理常式名稱,但通常最佳實務是使用檔案名稱,例如OrderHandler.cs您的類別名稱為 OrderHandler。例如,您可以將此範例中的檔案名稱從 修改Function.csOrderHandler.cs

C# Lambda 函數中的序列化

JSON 是 Lambda 函數最常見的標準輸入格式。在此範例中,函數預期輸入類似以下內容:

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

在 C# 中,您可以定義類別中預期輸入事件的形狀。在此範例中,我們會定義要建立此輸入模型的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# 函數的序列化有兩種方法:以反射為基礎的序列化和來源產生的序列化。

以反射為基礎的序列化

AWS 提供預先建置的程式庫,您可以快速新增至您的應用程式。這些程式庫會使用反射實作序列化。使用下列其中一個套件實作以反射為基礎的序列化:

  • 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 ,且不需要額外的設定,因此是簡化的理想選擇。不過,它確實需要更多的函數記憶體用量。由於執行時間反射,您可能也會看到較高的函數延遲。

來源產生的序列化

使用來源產生的序列化,會在編譯時產生序列化程式碼。這消除了反射的需求,並可以改善函數的效能。若要在函數中使用來源產生的序列化,您必須執行下列動作:

  • 建立一個繼承自 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 使用原生ahead-of-time編譯 (AOT),則必須使用來源產生的序列化。

存取和使用 Lambda 內容物件

Lambda 內容物件 包含有關調用、函數以及執行環境的資訊。在此範例中,內容物件的類型為 Amazon.Lambda.Core.ILambdaContext,是主要處理常式函數的第二個引數。

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

內容物件是選用的輸入。如需有效可接受處理常式簽章的詳細資訊,請參閱 C# 函數的有效處理常式簽章

內容物件有助於將函數日誌產生到 Amazon CloudWatch。您可以使用 context.getLogger()方法取得LambdaLogger物件以供記錄。在此範例中,如果處理因任何原因失敗,我們可以使用記錄器記錄錯誤訊息:

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

除了記錄之外,您也可以使用內容物件進行函數監控。如需內容物件的詳細資訊,請參閱使用 Lambda 內容物件擷取 C# 函數資訊

在處理常式中使用 AWS SDK for .NET v3

通常,您將使用 Lambda 函數與其他 AWS 資源互動或進行更新。與這些資源互動的最簡單方法是使用 AWS SDK for .NET v3。

注意

AWS SDK for .NET (v2) 已棄用。我們建議您僅使用 AWS SDK for .NET 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# 程式碼中匯入相依性:

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

然後,範例程式碼會初始化 Amazon S3 用戶端 (使用預設登入資料提供者鏈),如下所示:

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

存取環境變數

在處理常式程式碼中,您可以使用 System.Environment.GetEnvironmentVariable 方法參考任何環境變數。在此範例中,我們使用下列程式碼行來參考定義的RECEIPT_BUCKET環境變數:

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

使用全域狀態

在首次調用函數之前,Lambda 會在初始化階段執行靜態程式碼和類別建構函數。在初始化期間建立的資源會保留在叫用之間的記憶體中,因此您可以避免在每次叫用函數時建立它們。

在範例程式碼中,S3 用戶端初始化程式碼位於主處理常式方法之外。執行時間會在函數處理其第一個事件之前初始化用戶端,這可能會導致更長的處理時間。後續事件會更快,因為 Lambda 不需要再次初始化用戶端。

使用 Lambda Annotations 架構簡化函數程式碼

Lambda Annotations 是 .NET 8 的架構,可簡化使用 C# 編寫 Lambda 函數的程序。註釋架構使用來源產生器產生程式碼,從 Lambda 程式設計模型轉譯為簡化程式碼。透過 Annotations 架構,您可以取代使用一般程式設計模型編寫的大部分 Lambda 函數程式碼。使用此架構編寫的程式碼使用更簡單的表達式,讓您可專注於商業邏輯。如需範例,請參閱模組文件中的 Amazon.Lambda.Annotations

如需使用 Lambda 註釋的完整應用程式範例,請參閱 awsdocs/aws-doc-sdk-examples GitHub 儲存庫中的 PhotoAssetManager 範例。PamApiAnnotations 目錄中的主要Function.cs檔案使用 Lambda 註釋。為了比較,PamApi目錄具有使用一般 Lambda 程式設計模型寫入的同等檔案。

使用 Lambda Annotations 架構進行相依性插入

您也可以透過 Lambda Annotations 架構,使用熟悉的語法將相依性插入新增至 Lambda 函數。將 [LambdaStartup] 屬性新增至 Startup.cs 檔案時,Lambda Annotations 架構會在編譯時產生所需的程式碼。

[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# Lambda 函數的程式碼最佳實務

請遵循下列清單中的準則,在建置 Lambda 函數時使用最佳編碼實務:

  • 區隔 Lambda 處理常式與您的核心邏輯。能允許您製作更多可測單位的函式。

  • 控制函數部署套件內的相依性。 AWS Lambda 執行環境包含多個程式庫。若要啟用最新的一組功能與安全更新,Lambda 會定期更新這些程式庫。這些更新可能會為您的 Lambda 函數行為帶來細微的變更。若要完全掌控您函式所使用的相依性,請利用部署套件封裝您的所有相依性。

  • 最小化依存項目的複雜性。偏好更簡易的框架,其可快速在執行環境啟動時載入。

  • 將部署套件最小化至執行時間所必要的套件大小。這能減少您的部署套件被下載與呼叫前解壓縮的時間。對於在 .NET 中編寫的函數,請避免上傳整個 AWS SDK 程式庫,做為部署套件的一部分。或者,選擇性倚賴取得您需要的軟體開發套件元件的模組 (例如 DynamoDB、Amazon S3 開發套件模組,以及 Lambda 核心程式庫)。

  • 請利用執行環境重新使用來改看函式的效能。在函式處理常式之外初始化 SDK 用戶端和資料庫連線,並在本機快取 /tmp 目錄中的靜態資產。由您函式的相同執行個體處理的後續叫用可以重複使用這些資源。這可藉由減少函數執行時間來節省成本。

    若要避免叫用間洩漏潛在資料,請不要使用執行環境來儲存使用者資料、事件,或其他牽涉安全性的資訊。如果您的函式依賴無法存放在處理常式內記憶體中的可變狀態,請考慮為每個使用者建立個別函式或個別函式版本。

  • 使用 Keep-Alive 指令維持持續連線的狀態。Lambda 會隨著時間的推移清除閒置連線。叫用函數時嘗試重複使用閒置連線將導致連線錯誤。若要維護持續連線,請使用與執行階段相關聯的 keep-alive (保持啟用) 指令。如需範例,請參閱在 Node.js 中重複使用 Keep-Alive 的連線

  • 使用環境變數將操作參數傳遞給您的函數。例如,如果您正在寫入到 Amazon S3 儲存貯體,而非對您正在寫入的儲存貯體名稱進行硬式編碼,請將儲存貯體名稱設定為環境變數。

  • 避免在 Lambda 函數中使用遞迴調用,其中函數會調用自己或啟動可能再次調用函數的程序。這會導致意外的函式呼叫量與升高的成本。若您看到意外的調用數量,當更新程式碼時,請立刻將函數的預留並行設為 0,以調節對函數的所有調用。

  • 請勿在您的 Lambda 函數程式碼中使用未記錄的非公有 API。對於 AWS Lambda 受管執行期,Lambda 會定期將安全性和功能更新套用至 Lambda 的內部 APIs。這些內部 API 更新可能是向後不相容的,這會導致意外結果,例如若您的函數依賴於這些非公有 API,則叫用失敗。請參閱 API 參考查看公開可用 API 的清單。

  • 撰寫等冪程式碼。為函數撰寫等冪程式碼可確保採用相同方式來處理重複事件。程式碼應正確驗證事件並正常處理重複的事件。如需詳細資訊,請參閱 How do I make my Lambda function idempotent? (如何讓 Lambda 函數等冪?)。