Lambda was the first serverless platform to support .NET Core for production workloads. What’s more, .NET Core is considered a first-class citizen of Lambda, unlike other Microsoft technologies available on AWS.
The goal of this article is to fill in the gap for .NET developers who are already hosting their apps on EC2 or Beanstalk. Both are perfectly fine ways to run ASP.NET applications but feel bulky for smaller applications. That’s where Lambda enters the scene, exposing the bare minimum from a runtime perspective so that most of the operational work can be done for you behind the curtains.
There are tons of code samples around for building Lambdas with .NET Core, and for that reason, I chose not to show any code in this article. I’ll instead focus on explaining the programming model, the particularities of the .NET runtime on Lambda, and wrap up with some recommendations on tooling.
The easiest way to explain Lambda to long-time .NET developers is to imagine each Lambda function as a Console application that responds to cloud events rather than command line parameters. Those cloud events can be an API call, an SQS message, a file uploaded to S3, or any other number of invocable events.
When an event triggers a Lambda function, it executes the code that you wrote, passing along the relevant event data. The function executes in an isolated environment with little access to the underlying platform. Lambda is optimized for short-running processes, and its fifteen-minute maximum timeout makes sure that this rule is respected.
Functions are deployed, run, scaled and billed on an individual basis. You configure Lambdas though CloudFormation and the Serverless Application Model (SAM for short). Moving to Lambda frees you from the work that goes into managing a full-blown .NET environment running on EC2 instances. We all have painful memories of things like failed Windows updates, incorrect IIS configurations, or throughput issues. These are all things that you can kiss goodbye with Lambda.
Lambda and .NET Core
AWS added support for .NET Core 1.0 shortly after its launch. At that time, there were still some doubts about whether .NET Core would achieve the level of adoption it has today. But it was a smart way to attract .NET developers to a product that had no true competitor on Azure.
As of today, Lambda supports .NET Core 2.1. The next update to Lambda’s .NET runtime won’t come until a version of is designated as Long Term Support by Microsoft. That’s likely to be .NET Core 3 or one of its descendants. The bottom line here is that .NET Core 2.1 will be the way to build Lambdas for some time still.
There are two types of Lambdas that you can build with .NET Core: Functions and Serverless Applications. There are also two flavours of Serverless Applications that can be written. The diagram below summarizes the differences between them.
Functions are the most basic Lambda that you can write. They are geared towards building individual functions that respond to predefined events. Each Lambda defines the amount of memory it needs and the runtime it’s using (.NET Core 1.0/2.0/2.1) as part of its SAM template. Everything else, from choosing CPU performance to spinning up the right number of instances of a function, is handled automatically by the Lambda platform.
The AWS Lambda Project template in Visual Studio creates a single function based on the trigger that you choose. More functions can be added to the project, but for cases where many functions need to work together to accomplish a larger task, Serverless Applications might be a better fit.
The idea behind Serverless Applications is to provide pre-built Lambda solutions to common problems. It’s as easy as finding an app that suits your needs on the Serverless Apps Repository and deploying it to your account. Of course, if you can’t find what you’re looking for, it also lets you build your own Serverless Applications.
The most impressive feature of Serverless Applications, at least from a .NET perspective, is that it’ll run an entire ASP.NET Core application from a Lambda function. Traffic flows through API Gateway and on to the routes specified in the application’s Controllers. It behaves like a “normal” ASP.NET Core app, but with all the advantages of Lambda like availability and auto-scaling.
I see it as an ideal way to put together and deploy a prototype into production, without worrying too much about how the application will be hosted in the long term. And because it’s nothing more than an ASP.NET Core app with a bit of AWS magic sauce sprinkled on top, you’re never more than a couple clicks away from moving it to a container as needs change.
There are a few downsides to squeezing an ASP.NET Core application into a Lambda function. The entire application runs within a single Lambda function, so a large application is likely to need more memory than a smaller, singularly focused function would. You should still have plenty of breathing room with the 3GB maximum, but it’s still something to keep in mind.
On top of that, there’s a matter of scale. One highly requested endpoint will cause the entire application, including less frequently called Controllers, to scale together as demand increases. The solution is to isolate each Controller to its own Lambda function. Thanks to Norm Johanson for pointing out how to do this in the serverless template.
The alternative to a full-fledged ASP.NET Core application is to build a Serverless Application that is composed of many small functions. Each function can be hooked to API Gateway with its routes defined in the SAM template. This does require more work than the ASP.NET Core template, but it will provide you with a finer level of control as to the configuration of each function and endpoint.
In the past, compiled languages didn’t fare as well on Lambda as other, more dynamic languages like NodeJs and Python. So I came into this expecting to downplay the performance of .NET Core versus those languages. But much to my surprise, some significant improvements have been made over the last year to .NET Core’s performance numbers. Recent tests put its average duration metric ahead of many other Lambda languages. That’s a huge gain from .NET Core 1.0 and bodes well for writing highly responsive Lambdas with C#.
However, there are still some concerns about cold-start times. A cold start occurs whenever Lambda spins up the first instance of a function. It can’t be avoided, but it can be mitigated to a certain extent by increasing the amount of memory that the function can use.
Each bump in memory has an equivalent bump in cost, so try to find a balance for your functions. You’ll want to give functions that need to be highly responsive, like APIs, more memory to do their thing. On the other hand, less time-sensitive functions, like background workers, aren’t as sensitive to cold-starts, so only give them what they need to get their work done.
What You’ll Need To Get Started
It goes without saying that the first requirement for building Lambdas is an AWS account. You can get free tier access for 12 months that will cost you next to nothing to start playing around with Lambda.
The best experience for developing .NET Core Lambdas is through Visual Studio 2017 with the AWS Toolkit extension. The provided templates get the boilerplate out of the way so you can focus on the code for your functions. There’s also a convenient test application to invoke your functions locally before deploying them to the cloud. And of course, running an ASP.NET Core Serverless Application locally is as simple as pressing F5.
You can also write .NET Core functions on Linux or Mac with Visual Studio Code. I only recommend this path to developers who are already familiar with the .NET CLI. The learning curve of the CLI can be pretty steep for someone coming from the safe confines of Visual Studio. Couple that with the Lambda learning curve, and you’ve got a recipe for frustration.
Developers coming to Lambda from the .NET Framework should set aside some time to familiarize themselves with .NET Core’s programming model. There are some significant differences between Framework and Core that take a bit of getting used to.
With all that sorted, you’re ready to write your first .NET Core function in Lambda. There are tons of samples on the GitHub page for AWS Lambda and .NET Core, so get to it!
Did you get any value from this post? Would you like to see more like it? Please consider donating through PayPal. Your hard-earned money also gives you access to me for a fifteen minute discussion where I’ll answer any questions you have with regards to building production-grade applications with Azure Functions, serverless, or .NET in general.
Did you find this post useful? If so, please consider retweeting it. If it helped you, it could help someone else too.