Skip to content

.NET on AWS Lambda in 20 minutes

Published:

Feels Like ASP.NET, Runs on Lambda: Under 10 Minutes to Serverless APIs with .NET Aspire

The line between the familiar and the future in cloud-native .NET

Staring at a greenfield project, most engineers face that twinge of anxiety. You know the feeling: you want to move fast, lean on your experience, but there’s this wall—deploying your .NET API to serverless without losing the developer ergonomics you love from ASP.NET. That’s the heart of the issue. Serverless is supposed to save us operational headaches, but how do you keep your flow when the abstraction starts to diverge from your muscle memory?

Recently, the rise of .NET Aspire and the Lambda Annotations framework has begun to close that psychological (and technical) gap. With the right approach, you can build .NET APIs for AWS Lambda with the confidence and patterns you’d expect from mature ASP.NET—without losing momentum or getting trapped in YAML purgatory. And, if you’re fast, all of this can be live in less than ten minutes.

The Familiarity Principle: Serverless Without Sacrificing Syntax

Software engineers love muscle memory. That’s why so many engineers bounce off early serverless: the friction of learning new SDKs, puzzling over cryptic handler strings, and wrestling with unfamiliar deployment models. What if you didn’t have to?

That’s where Lambda Annotations come in. You set up your project as you would with an ordinary ASP.NET API—no need to break old habits. Here’s a taste:

public class Functions {

    private readonly ICalculatorService _calculatorService;

    public Functions(ICalculatorService calculatorService)
        => _calculatorService = calculatorService;

    [LambdaFunction]
    [HttpApi(HttpMethod = "GET", Path = "/add/{x}/{y}")]
    public int Add(int x, int y) => _calculatorService.Add(x, y);

}

You’re reading it right. Endpoints are just class methods, annotated with [LambdaFunction] and [HttpApi], mapping HTTP paths and verbs just like classic ASP.NET. Path parameters? Handled through names—you don’t fumble with routing tables or hand-rolled API Gateway event parsing.

Want to accept a typed payload? Add a POST endpoint, use a [FromBody] parameter, and let the framework handle parsing:

[LambdaFunction]
[HttpApi(HttpMethod = "POST", Path = "/post")]
public string Post([FromBody] MyData data) => $"You said: {data.Hello}";

The result: refactoring, DI, and endpoint development feel exactly like an old-school ASP.NET project. And all that complexity AWS used to force on you—the ugly handler strings, the cloudformation glue—is boiled down to a thin, readable abstraction.

Configuration Done Right: Dependency Injection and That Startup.CS Nostalgia

For those who’ve grown up with .NET, the Startup.cs file is almost sacred ground. It’s the heart of your app’s configuration, and it keeps your engineering fast and organized. Even as new patterns like minimal APIs have taken hold, there’s value in having a central place for dependency injection, configuration builders, and service registration.

Lambda Annotations respects that. Your Startup.cs lives on:

public class Startup {

public void ConfigureServices(IServiceCollection services)
{
    services.AddSingleton<ICalculatorService, CalculatorService>();

    var config = new ConfigurationBuilder()
        .AddEnvironmentVariables()
        .Build();

    services.AddSingleton<IConfiguration>(config);
}

}

Not quite as magic as DI in classic ASP.NET Core—there’s a bit of hands-on work for configuration—but it’s recognizable. Adding AWS SDKs or custom secrets logic goes here, and IConfiguration flows everywhere it needs to go.

The upshot: moving your codebase from POC to production won’t mean undoing months of “serverless special-case” hacks. Changes stay predictable, understandable, and close to conventional engineering wisdom.

The Reality Under the Hood: Source Generators and Infrastructure Simplified

Here’s the deal nobody talks about with serverless: the glue code is exhausting. Most Lambda guides ask you to hand-craft handler functions, stubbing out JSON parsing of APIGateway Proxy events until your eyes cross. But with .NET 10 and the Lambda Annotations framework, the ceremony is—finally—gone.

What’s actually happening? As you write your nice, human-friendly methods, .NET Source Generators spin out the glue: handler functions, deserializers, and the bits needed to make Lambda “just work.” Even the CloudFormation serverless template is piped out. Curious? Go peek at the generated code or template if you like, but you rarely need to touch them.

Aspire and the Local Feedback Feedback Loop

Local testing is where most serverless enthusiasm dies. Mocking infrastructure, wrangling function URLs, and staring at inscrutable errors is the norm. But this new workflow changes that equation.

.NET Aspire’s Aspire.Hosting.AWS package puts your Lambda in a local development harness. You can spin up everything with:

var builder = DistributedApplication.CreateBuilder(args);

var getRootFunction = builder.AddAWSLambdaFunction<Projects.LambdaAnnotationsDemo>("GetRoot",
    lambdaHandler: "LambdaAnnotationsDemo::LambdaAnnotationsDemo.Functions_GetRoot_Generated::GetRoot");
var getItemsFunction = builder.AddAWSLambdaFunction<Projects.LambdaAnnotationsDemo>("GetItems",
    lambdaHandler: "LambdaAnnotationsDemo::LambdaAnnotationsDemo.Functions_GetItems_Generated::GetItems");
var getItemFunction = builder.AddAWSLambdaFunction<Projects.LambdaAnnotationsDemo>("GetItem",
    lambdaHandler: "LambdaAnnotationsDemo::LambdaAnnotationsDemo.Functions_GetItem_Generated::GetItem");
var createItemFunction = builder.AddAWSLambdaFunction<Projects.LambdaAnnotationsDemo>("CreateItem",
    lambdaHandler: "LambdaAnnotationsDemo::LambdaAnnotationsDemo.Functions_CreateItem_Generated::CreateItem");


builder.AddAWSAPIGatewayEmulator("APIGatewayEmulator", Aspire.Hosting.AWS.Lambda.APIGatewayType.HttpV2)
    .WithReference(getRootFunction, Method.Get, "/")
    .WithReference(getItemsFunction, Method.Get, "/items")
    .WithReference(getItemFunction, Method.Get, "/items/{id}")
    .WithReference(createItemFunction, Method.Post, "/items");

builder.Build().Run();

Fire up Aspire locally, and you’ll get a dashboard showing your Lambda functions and an API Gateway emulator. Hit endpoints; tweak methods; debug as you would a regular web app. Not only does this save hours of frustration, it returns control to the engineer—no more blind deployment-faith.

And when it’s time to ship, toss in a SAM template or the generated CloudFormation and deploy straight to AWS, no translation layer needed.

Evolving with Minimal Friction

Here’s where it all pays off. You need to evolve your API: add a DELETE endpoint, accept new parameters, or branch out to complex workflows. You don’t pay the serverless tax anymore. Just graft on another method, update the annotations, and you’re done.

[LambdaFunction]
[HttpApi(HttpMethod = "DELETE", Path = "/product/{id}")]
public string DeleteProduct(string id) => $"Deleted product {id}";

You’ve maintained a direct line between engineering intent and cloud-native result. Plus, performance is up-to-date with .NET 10—so you’re not losing anything by moving serverless.

Closing the Gap Between API Imagination and Reality

The part that engineers really care about—in my experience, at least—is flow. Not the meditative “in the zone” flow, but the deterministic, reproducible, low-surprise flow from business need to delivered feature. The new .NET serverless story, rooted in familiar ASP.NET paradigms, finally honors that.

Yes, serverless is still evolving, and yes, debugging distributed systems remains a challenge. But today you can build production-grade .NET APIs for Lambda without putting on a different hat, learning a new language, or sacrificing codebase integrity. There’s an elegance to that—a closing of a tradeoff we’ve carried too long.

If you’ve been hesitating to bring your .NET team into serverless, now’s the time: try building that next microservice the Aspire/Lambda way, and notice how little you have to re-train yourself. You might be surprised how much of your classic ASP.NET sensibility is preserved in the cloud.

Want to see this in action—code and all? Check out the video version of this blog post on YouTube.