定義 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 受管執行時期 (dotnet8) 將其部署至 Lambda。對於處理常式名稱,Lambda 期望字串的格式為 AssemblyName::Namespace.Classname::Methodname。在函數的初始化階段,系統會初始化函數的類別,並執行建構函數中的任何程式碼。
在可執行組件方法中,您可以使用 C# 9 中首次推出的頂層陳述式功能dotnet8)。對於處理常式名稱,您需要將要執行的可執行組件名稱提供給 Lambda。
本頁上的主要範例示範了類別庫方法。您可以透過各種方式初始化 C# Lambda 專案,但最簡單的方法是搭配 Amazon.Lambda.Tools CLI 使用 .NET CLI。依照中設定您的 .NET 開發環境的步驟設定 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# 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 檔案不包含函式的 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" }
若要讓此函數正常運作,其執行角色必須允許 s3:PutObject 動作。此外,請確保定義 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.ToolsCLI 建置應用程式,而沒有使用.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 文件網站上的 unsafe (C# reference)。 -
處理常式不得使用
params關鍵字,也不得將ArgIterator作為輸入或傳回參數。這些關鍵字支援可變數量的參數。處理常式最多能接受兩個引數。 -
處理常式不可為泛型方法。換言之,其無法使用泛型類型參數,例如
<T>。 -
Lambda 不支援簽章中包含
async void的非同步處理常式。
處理常式命名慣例
C# 中的 Lambda 處理常式沒有嚴格的命名限制。不過,在部署函式時,必須確保向 Lambda 提供正確的處理常式字串。正確的處理常式字串視您要部署的是類別庫處理常式還是可執行組件處理常式而定。
雖然您可以為處理常式使用任何名稱,但 C# 中的函式名稱通常採用 PascalCase 命名方式。此外,雖然檔案名稱無需與類別名稱或處理常式名稱相符,但最佳實務通常是讓二者保持一致:例如類別名稱為 OrderHandler,檔案名稱則為 OrderHandler.cs。舉例而言,您可以將此範例中的檔案名稱從 Function.cs 修改為 OrderHandler.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);若實作此方法,會將請求承載從
InvokeAPI 還原序列化至傳遞到 Lambda 函數處理常式的物件。 -
T Serialize<T>(T response, Stream responseStream);若實作此方法,會將從 Lambda 函數處理常式傳回的結果,序列化至
InvokeAPI 作業傳回的回應承載中。
本頁上的主要範例使用反射式序列化。反射式序列化與 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 使用原生預先編譯 (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# 函數資訊。
在處理常式中使用 適用於 .NET 的 SDK v3
通常,您會使用 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# 程式碼中匯入相依項:
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
如需使用 Lambda Annotations 的完整應用程式範例,請參閱 awsdocs/aws-doc-sdk-examples GitHub 儲存庫中的 PhotoAssetManagerPamApiAnnotations 目錄中的主要 Function.cs 檔案使用 Lambda Annotations。為了進行比較,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 的內部 API。這些內部 API 更新可能是向後不相容的,這會導致意外結果,例如若您的函數依賴於這些非公有 API,則叫用失敗。請參閱 API 參考查看公開可用 API 的清單。
撰寫等冪程式碼。為函數撰寫等冪程式碼可確保採用相同方式來處理重複事件。程式碼應正確驗證事件並正常處理重複的事件。如需詳細資訊,請參閱 How do I make my Lambda function idempotent?