Building with .NET Aspire

Image of aspire dashboard

What Is .NET Aspire?

.NET Aspire is a set of tools, packages, and templates aimed at making app development easier - especially in the context of distributed, observable apps. Over the years, it has grown into an ecosystem in its own right, providing ways to build many different types of apps and offering a great local development experience.

While development-time orchestration is the main benefit of Aspire, it also provides the ability to generate deployment scripts/artifacts from your Aspire setup. I’ll be exploring that in the future, but for now, here’s a bit more about how I’m using Aspire.

.NET Aspire & AWS

AWS Loves .NET

As mentioned in a previous post on building Parentime, I’m using AWS to host the app. This is primarily a way to learn more about using AWS resources, rather than focusing on the local developer experience of building apps for AWS. Enter Aspire and the AWS Aspire integrations available which help bridge that gap.

These packages and templates have made it very straightforward to start building AWS Lambda applications locally while emulating what that traffic might look like through an AWS API Gateway - all without having to worry about managing connection strings and settings between apps, APIs, and databases.

.NET Native AOT & AWS Lambda

You may be wondering - .NET on AWS Lambda? Aren’t you going to hit some expensive cold starts since .NET code is JIT-compiled? Typically, yes! However, you can opt into .NET Native AOT compilation, which is natively supported on AWS Lambda (at least, for .NET 8 at the time of writing). This creates a native binary that eliminates the need for extra compilation steps and should result in consistently high-performing Lambda functions written in .NET.

I’ll return to performance once I actually deploy this, but for now, let’s focus on the dev experience. I used one of the default .NET Native Lambda templates provided by the AWS .NET Aspire toolset and simply added them to my .NET Aspire AppHost using the Aspire.Hosting.AWS.Lambda package, like so:

var getRoutinesFunc = builder.AddAWSLambdaFunction<ParenTime_GetRoutinesFunction>("GetRoutinesFunction", lambdaHandler: "")
    .WithEnvironment(ENV_CONNECTIONSTRING, parentimedb);

Note the WithEnvironment method, which allows the lambda function depend on a previously configured database setup in Aspire,, and automatically pass in the connection string as an environment variable.

Similarly, I plan to consume these lambda functions via an AWS API Gateway, it’s also trivial to emulate an API Gateway in Aspire using something like the below:

builder.AddAWSAPIGatewayEmulator("APIGatewayEmulator", APIGatewayType.HttpV2)
    .WithReference(getRoutinesFunc, Method.Get, "/routines")
    .WithReference(addRoutineForChildFunc, Method.Post, "/child-routines")
    .WithReference(getActivitiesFunc, Method.Get, "/children/{childId}/activities/today");

Without having to manually configure local settings, endpoints and ports, I can have a dev environment with everything correctly configured which just a few declarative lines of code!

Sound too good to be true? Well, it doesn’t come without it’s caveats

Caveat 1 - EF Core on Native AOT

If you want to use EF Core, and like to use the reflection-happy entity type configuration builders, you may have to opt out of the native AOT compilation. There are experimental ways to get this to work with source-generated code, and this may get official support in the future, but for now, I’d recommend steering away from using something like this in your natively compiled .NET apps - especially an AWS lambda function.

Caveat 2 - AWS Lambda Annotations and Aspire

The Aspire integration with natively compiled lambda functions work reasonably well, you have to write your lambda functions in a specific way. There needs to be static Main and FunctionHandlers defined that bootstrap your lambda, and that prevents you from using the amazing AWS Lambda Annotations package.

With the package, you can write native AOT lambdas in a very similar way to how you would write ASP.NET API endpoints (without needing to actually use ASP.NET libraries at all) resulting in a much leaner Lambda with an exceptional dev experience. However, at the time of writing this, I could not get this to work within Aspire. It appears the Aspire AWS Hosting library is not compatible with the way the Lambdas bootstrap in an Annotation and source generated model.

What’s Next? Deployment!

In my next post on .NET Aspire, I’ll explore how deployment into AWS looks with this app, and see if the hype around the dev experience in Aspire continues to live up to the expectations on the deployment side of things.