Introduction to Using .NET with AWS Lambda Functions


AWS Lambda functions are a serverless computing model that allows you to run code without servers. These are commonly written using languages ​​like JavaScript and Python, but AWS now supports many different runtimes, including .NET for C#.

Why use .NET for Lambda?

There are now many different languages ​​available for Lambda, so you are spoiled for choice. In general, JavaScript and Python are used for simple automation functions that care about fast startup times. However, they are not the best performers for heavy processing, and being dynamically typed scripting languages ​​is a major drawback for complex applications.

If C# is your language of choice, there aren’t many downsides to using it for Lambda, especially if switching to Python or JavaScript is too much of a hassle. The tools provided by AWS are also good, and you have access to the entire AWS SDK, which means you can search services like Lambda and DynamoDB with ease.

Additionally, AWS supports the full .NET runtime, which means you can use languages ​​other than C# that also compile to .NET binaries. C# is overwhelmingly the most popular, but you can also write Lambda functions in F# or VB.NET.

How does it work?

Languages ​​like Java and C# are generally much nicer, but there is a downside to using them. Both are compiled into bytecode that must be compiled on startup, so they have longer startup times, especially when cold-booted. “Cold starts” are when AWS hasn’t run the function in the last few minutes, so it won’t cache it and you’ll need to do the just-in-time build again to get it up and running. This process can cause your functions to take a second or more to respond, which is not good for web applications.

However, this problem is greatly mitigated if you use Lambda very frequently. You can also reduce cold start times entirely with provisioned concurrency. Regular response times for .NET are very high, and performance is on par with fully compiled languages ​​like Go and Rust.

If you currently use Java functions for Lambda, C# may be a viable replacement, as the modern .NET 6 runtime uses less memory and starts faster than the JVM in most cases.

Configuring C# Lambda Functions

First, you’ll need .NET installed. AWS supports .NET Core 3.1 and .NET 6, so either runtime will work, but more importantly, you’ll need the dotnet CLI installed so you can install the Lambda templates. You can get .NET from the Microsoft documentation portal.

You will need to install the Lambda templates and global Lambda tools.

dotnet new -i Amazon.Lambda.Templates
dotnet tool install -g Amazon.Lambda.Tools

There are many options that this installs; You can list them all with:

dotnet new --list

This tool is quite nice as it comes with many pre-configured pre-packaged templates for different use cases. Typically, you’ll want one function per project to keep build sizes small, but you can have multiple functions in one DLL if you use AWS serverless templates, which are implemented using CloudFormation templates. These are much more complicated to manage, so only use them if you are benefiting from them.

However, with .NET solution files, you can have multiple projects in parallel that reference common assemblies, so this isn’t a big deal.

For now, we’ll use the simple empty function template, which generates a project using .NET 6. You can create this from the command line or from your editor’s new project screen.

dotnet new lambda.EmptyFunction --name SimpleLambdaFunction --profile default --region us-east-1

This generates a very simple function: it takes a string as input and also passes a ILambdaContext. This is the Main() entry point function for your Lambda and will be called by the runtime each time the Lambda function is invoked. This particular function returns a stringbut you can also async and return a Task<string?>.

At the top, you’ll see an assembly attribute that configures a JSON serializer. Internally, Lambda will take care of deserializing the input content for you and then calling your function. Later, if it returns something, it will be written to the response stream. The Lambda libraries handle this model for you, and the code that wraps your function is in HandlerWrapper.

Essentially, it will handle all kinds of method signatures, and if your function takes input, it will deserialize it for you. If your function returns an output, it will serialize that output for you. You don’t actually have to do any of this, since you can write functions that operate raw Stream classes, but this is a nice wrapper class to make things easier.

What this means is that you are free to define your own models for the inputs and outputs passed to and from the function, one of the benefits of handling JSON with C#.

In this function, you deserialize the InputModel class, waits asynchronously for a second and then returns a OutputModel class. This class is re-serialized in the output stream so that Lambda can handle it.

Running Lambda Functions

Running the function once you’ve created it is quite simple, as the Lambda .NET CLI provides a method to implement it. just run deploy-function with

dotnet lambda deploy-function SimpleNETFunction

You’ll need to select an IAM role or create a new one, and you may need to add permissions to this new role. You should now see the function in your console:

Lambda provides a built-in tester that you can pass JSON to.

This will run and show you all the details about the run. In this case, with a very small minimal function, the cold start time was less than 500ms, which is pretty decent for .NET and for Lambda in general. Once it’s hot, the billed duration drops to just a few milliseconds.

In this case, this function did not use much memory and reducing the function to 128 MB did not cause any problems.