The .NET 6 runtime for AWS Lambda is here! With it comes many new features such as an improved ILambdaLogger
, support for C# 10, top-level statements and many more. But one new cool feature, originally added in .NET 5, is the ability to create minimal APIs. AWS doesn’t provide a managed runtime for .NET 5 so this is the first time you can use this feature in Lambda without using a custom runtime or container image. We’ll use AWS API Gateway HTTP API to trigger the Lambda.
This solution is great for an API that isn’t hit very often, or has spikes in request every so often. When there are no requests, it doesn’t cost anything. However this would not be ideal for an API that is very latency sensitive, as cold starts will add 200ms+, depending on your memory configuration. Additionally, this solution would not be suited for a very large application because there is a limit on the deployment package size.
Before continuing, be sure you have the .NET 6 SDK installed.
Creating the API project
Initialize the project, and then add the Amazon.Lambda.AspNetCoreServer.Hosting package. This package is what allows us to process incoming Lambda events as HTTP requests.
dotnet new webapi --name LambdaAPI
cd LambdaAPI
dotnet add package Amazon.Lambda.AspNetCoreServer.Hosting
Inside Program.cs
, add this line where the services are being defined:
builder.Services.AddAWSLambdaHosting(LambdaEventSource.HttpApi);
When running in Lambda, this method will swap Kestrel with Amazon.Lambda.AspNetCoreServer which converts the request from API Gateway into the class ASP.NET Core expects and likewise converts the response in the opposite direction. When running locally this method will do nothing, and you will be able to run/debug just like a normal ASP.NET Core API. This method supports 3 lambda event sources; HttpApi
, RestApi
, and ApplicationLoadBalancer
. We’ll be using HttpApi
for this guide.
Now, we’ll set up a couple routes using the new minimal API syntax. Add these two lines:
app.MapGet("/", () => "Hello, world!");
app.MapGet("/route", () => "Hello from /route!");
Your Program.cs
file should look similar to this:
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddAWSLambdaHosting(LambdaEventSource.HttpApi);
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.MapGet("/", () => "Hello, world!");
app.MapGet("/route", () => "Hello from /route!");
app.Run();
For a ‘cold start’, the API will start up normally and serve the request, but if the lambda is warm, app.Run();
will already have been ran, and the latency will be much lower.
Notice that we’re leaving in the weather forecast controller that came with the template. This is to show that this will work with both minimal APIs and classic MVC controllers.
The app is already ready to deploy to AWS Lambda, but first let’s test it locally. Run dotnet run
and test the endpoints.
> dotnet run
Building...
info: Microsoft.Hosting.Lifetime[14]
Now listening on: https://localhost:7058
info: Microsoft.Hosting.Lifetime[14]
Now listening on: https://localhost:5043
info: Microsoft.Hosting.Lifetime[0]
Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
Hosting environment: Development
info: Microsoft.Hosting.Lifetime[0]
Content root path: C:\Users\kyle.hickman\repos\lambda-api\LambdaAPI\
> curl https://localhost:7058
Hello, world!
> curl https://localhost:7058/route
Hello from /route!
> curl https://localhost:7058/weatherforecast
[{"date":"2022-03-26T12:10:15.9580999-05:00","temperatureC":35,"temperatureF":94,"summary":"Cool"},{"date":"2022-03-27T12:10:15.9582436-05:00","temperatureC":-3,"temperatureF":27,"summary":"Sweltering"},{"date":"2022-03-28T12:10:15.9582456-05:00","temperatureC":8,"temperatureF":46,"summary":"Sweltering"},{"date":"2022-03-29T12:10:15.9582458-05:00","temperatureC":-11,"temperatureF":13,"summary":"Chilly"},{"date":"2022-03-30T12:10:15.958246-05:00","temperatureC":48,"temperatureF":118,"summary":"Cool"}]
Note: The ports that your project’s launchSettings.json was initialized with may be different.
Deploying to AWS Lambda
Navigate to the AWS Console and go to Lambda. Click Create function, then Author from scratch, give it a name and select .NET 6 for the runtime, and then click Create function.
Now under Runtime settings click Edit and change the handler to the name of the assembly, “LambdaAPI” if you’re following along.
Lastly for the Lambda, we need to package the assemblies and upload them to AWS. Go back to your project directory and run:
dotnet publish -c Release --self-contained false -r linux-x64 -o publish
Zip up the contents of the /publish directory. Then back on the AWS Console, under Code source click Upload from and upload your .zip file.
Now navigate to API Gateway and create a new HTTP API. Configure the integrations to use the Lambda we just created and give it a name.
Set up the routes manually. We’ll test all 3 routes we have configured.
Leave the stages configured as they are and click Next, click Create, and then that’s it, it should be deployed and functioning. Take the provided Invoke URL and try it out!
> curl https://7ph2opocs1.execute-api.us-east-1.amazonaws.com
Hello, world!
> curl https://7ph2opocs1.execute-api.us-east-1.amazonaws.com/route
Hello from /route!
> curl https://7ph2opocs1.execute-api.us-east-1.amazonaws.com/weatherforecast
[{"date":"2022-03-26T17:50:07.4082337+00:00","temperatureC":34,"temperatureF":93,"summary":"Cool"},{"date":"2022-03-27T17:50:07.4271252+00:00","temperatureC":-9,"temperatureF":16,"summary":"Scorching"},{"date":"2022-03-28T17:50:07.427134+00:00","temperatureC":-18,"temperatureF":0,"summary":"Warm"},{"date":"2022-03-29T17:50:07.4271343+00:00","temperatureC":-15,"temperatureF":6,"summary":"Mild"},{"date":"2022-03-30T17:50:07.4271345+00:00","temperatureC":-19,"temperatureF":-2,"summary":"Sweltering"}]
ILambdaContext
If you’ve worked with lambdas on .NET before you might be familiar with the ILambdaContext
and be wondering how you access it. If you aren’t familiar, the ILambdaContext
contains some meta information about the Lambda itself such as its name, memory limit, RequestId, as well as a logger object you can use to easily log to CloudWatch. Both the original request object and the ILambdaContext
can be accessed from the HttpContext.Items
collection provided with every request. However, when run locally these will be null, so if you want to test locally you will have to account for that.
Here’s a simple example of how to access the ILambdaContext and use its built in logger to log to CloudWatch. Of course, this would work inside of a controller as well.
app.MapGet("hello", async httpContext =>
{
var lambdaContext = httpContext.Items[AbstractAspNetCoreFunction.LAMBDA_CONTEXT] as ILambdaContext;
lambdaContext.Logger.LogInformation("Hello from ILambdaContext!");
});