Serverless Functions in C#: AWS Lambda — From Basics to Advanced Level
\n\nServerless architecture has firmly entered the world of modern development. If you have already mastered the basic syntax of creating AWS Lambda functions in C#, it's time to move to the next level. In this article, we will cover advanced techniques: dependency injection (DI), creating Middleware, working with secrets, and asynchronous patterns. This knowledge will help you write more flexible, testable, and performant code for cloud solutions.
\n\n1. Dependency Injection (DI) in AWS Lambda with C#
\n\nThe standard Lambda function template often creates a new instance of the handler class on each invocation. This leads to recreating "heavy" objects (e.g., database clients or HTTP clients). Using a dependency injection (DI) container solves this problem by allowing instance reuse and managing their lifetimes.
\n\nTo implement DI in .NET 8+ for Lambda, the Amazon.Lambda.AspNetCoreServer.Hosting package is used. Let's look at an example of configuring DI using IServiceCollection.
Example: Configuring DI in Lambda
\n\nusing Amazon.Lambda.Core;\nusing Amazon.Lambda.RuntimeSupport;\nusing Amazon.Lambda.Serialization.SystemTextJson;\nusing Microsoft.Extensions.DependencyInjection;\nusing Microsoft.Extensions.Hosting;\n\n// Service interface and implementation\npublic interface IWeatherService\n{\n string GetForecast(string city);\n}\n\npublic class WeatherService : IWeatherService\n{\n public string GetForecast(string city) => $"Weather in {city}: +25°C, sunny";\n}\n\n// Lambda handler\npublic class Function\n{\n private readonly IWeatherService _weatherService;\n\n public Function() : this(Startup.CreateHostBuilder().Build()) { }\n\n public Function(IHost host)\n {\n _weatherService = host.Services.GetRequiredService<IWeatherService>();\n }\n\n public async Task<string> FunctionHandler(string input, ILambdaContext context)\n {\n return _weatherService.GetForecast(input);\n }\n}\n\n// Configuration class\npublic static class Startup\n{\n public static IHostBuilder CreateHostBuilder() =>\n Host.CreateDefaultBuilder()\n .ConfigureServices((context, services) =>\n {\n services.AddSingleton<IWeatherService, WeatherService>();\n // Add other services here\n });\n}\n\nImportant: Use AddSingleton for AWS SDK clients (S3, DynamoDB) and AddTransient for lightweight services. This reduces cold start time and saves resources.
2. Middleware in AWS Lambda: Request Processing Chain
\n\nThe Middleware concept, familiar from ASP.NET Core, applies well to Lambda functions as well. Middleware allows adding cross-cutting logic: logging, error handling, authentication, response compression — without changing the main handler code.
\n\nTo implement Middleware, the Amazon.Lambda.AspNetCoreServer package and the ILambdaMiddleware interface (or a custom implementation) are used. Below is an example of creating a simple Middleware for logging execution time.
Example: Logging Middleware
\n\nusing Amazon.Lambda.Core;\nusing System.Diagnostics;\n\npublic class LoggingMiddleware : ILambdaMiddleware\n{\n public async Task<TResponse> InvokeAsync<TRequest, TResponse>(TRequest request, \n Func<TRequest, Task<TResponse>> next, ILambdaContext context)\n {\n var sw = Stopwatch.StartNew();\n context.Logger.LogInformation($"Request received: {request}");\n\n try\n {\n var response = await next(request);\n sw.Stop();\n context.Logger.LogInformation($"Response sent in {sw.ElapsedMillis