Re[mark]able.net

My journey with .NET, Azure and Security related bits

What is .NET Aspire?

If you are a .NET developer you will have probably heard of Aspire. What is Aspire? Yet another framework or a better way of working? I have been using and testing Aspire since the previews and will share some insight into how it has been helpful to me on different projects already. Even though it is still in public preview.

What is .NET Aspire all about?

So what is Aspire all about? According to Microsoft Learn:

“NET Aspire is designed to improve the experience of building .NET cloud-native apps. It provides a consistent, opinionated set of tools and patterns that help you build and run distributed apps.”

You will get 3 things right out of the box:

  • CLI tools and Project templates for getting started
  • Orchestration for local dev only
  • Components for consuming external services (databases, caching, messaging)

Keep in mind that everything is opt-in with Aspire. If you want to use a component you can but you are not required to. You can always set things up manually if you need to. This helped me to make the switch to Aspire in small steps instead of changing the entire project at once.

How to start in an existing project?

I get no fun out of copying the docs here on how to start. So for a full explanation of how to get started look at the Microsoft Learn docs. Install the tooling. Create a standard Aspire project with the provided template and all the information below will make sense.

For all the JetBrains Rider users there is an awesome plugin available that also handles debugging for you out of the box. You can visit one of the following three links to get more info:

Thats all nice and fun for a demo app but how to I add this to my current project? Asuming your project is in a single repository you can do one of the following depending on the tools that you have:

  • Visual studio: Right click > Add Aspire Orchestration.
  • CLI: execute
    dotnet new aspire-apphost
    

    &

    dotnet new aspire-servicedefaults
    
  • Rider: Add new project and go to the Aspire templates to add Orchestration and ServiceDefaults projects.

For other templates you can have a look here

Orchestration, getting rid of docker compose?

The orchestration part is only meant for local development. It is the replacement for your docker compose file that links everything together. If you follow the default template then this is the Aspire.AppHost project. This project will tie everything together. One of the best features of Aspire is that you don’t need to think about ports anymore. Let’s consider the following orchestration in the Program.cs of the Aspire AppHost project and I will explain it afterwards.

var builder = DistributedApplication.CreateBuilder(args);

// My Redis for caching stuff
var myRedis = builder.AddRedis("myRedisCache");

// My API
var myApi = builder.AddProject<Projects.MyApi>("myApi")
 .WithReference(myRedis);

// React frontend
builder.AddNpmApp("frontend", "../frontend", "start")
 .WithReference(myApi)
 .WithEndpoint(targetPort: 3000, port: 3000, isProxied: false, scheme: "http", env: "PORT")
 .PublishAsDockerFile();

builder.Build().Run();

This orchestration consists of 3 parts:

  • Redis cache
  • .NET Web API project
  • React frontend

Yes, that is correct, any Node project will work with this because Aspire is not .NET only! Simply give the npm run command that is needed to start your frontend. In my case, it is “start”.

First I added a Redis cache. This will launch a Redis container in docker to host it. Although we don’t use a compose or docker file anymore. Aspire still requires docker to run things it cannot run in-process (dotnet, npm, etc) like databases, Redis, messaging services, etc. Also, notice the “myRedisCache” name I gave it. This will come back in the configuration later in this blog.

Now by adding the dotnet project as “myApi” I also add a reference to myRedis. This will inject myRedis environment variables into MyApi. Therefore my API will know on my local dev where this redis cache is running. It can be a different port every time I run the apphost.

In your react frontend you will probably have an env.localhost or env.yourenvironment file. Here you can define environment variables and use them to call your API. Aspire will make sure that those environment variables are available. The format is always the same.

MY_API_ENDPOINT=$services__myApi__http__0/api/v2.0/my/call

These few lines of code will be the orchestration of the solution and tie three components together. Let’s take a look at how we can connect to the Redis cache from our API. We do this by using components.

Components and a strong opinionated approach

Aspire components are Nuget packages that simplify your life when connecting to external sources like databases such as Postgres, Cosmos, Redis, etc. There is currently, even though it is still in preview, a big list of already supported components. Now let’s look into how we can use Aspire in our “MyApi” project and how we initialize the Redis cache.

First we need to add the so called ServiceDefaults. This is a project template given by Aspire with an opinionated way to set up logging, tracing, and metrics with OpenTelemetry. This will set up everything for you to work perfectly with the Aspire Dashboard (getting back to that later).

// Add Aspire service defaults for exception handling, observability with open telemetry
builder.AddServiceDefaults();

To add Redis we only have to add one line of code. This will add a few things:

  • Adding the StackExhange Redis connection multiplexer so that you can retrieve that with dependency injection in implementations.
  • Look for the connection string to the “myRedisCache” connection setup in the orchestration.
  • Setup specific Redis tracing, logging, and metrics.
// Use the Aspire Components that make sure everything is added in the same way and open telemetry is added
builder.AddRedisClient("myRedisCache");

If you look at the implementation you will notice that “AddRedisClient” is basically one big extension method that sets up everything for you in a certain opinionated way. The components do not include some sort of magic Aspire code. At least not in the components that I looked at. When in doubt you can always go to the components on GitHub to see the implementation.

Tip: If you want to have custom tracing working with OpenTelemetry you have to add the tracing source. This will link your custom tracing to OpenTelemetry. Everything you trace with “MyActivitySource” will then be collected, sent, and shown in the Aspire dashboard.

// Quick and dirty way to have the activity source available
public static readonly ActivitySource MyActivitySource = new("MyApi", "1.0.0");

// Manually set the activity source name so that custom Activity tracing gets propagated to open telemetry.
builder.Services.AddOpenTelemetry()
 .WithTracing(o =>
 {
 o.AddSource(MyActivitySource.Name);
 });

The same can be done with custom meters for metrics.

What to do when your specific service is not available?

Components are developed by Microsoft but also by the community. A good example of this is the development of Amazon AWS Components & Orchestration in this pull request

If you can’t wait you always have the option to start a container like below with an AWS DynamoDb. It also shows how to get the endpoint which can be referenced like in the examples above. This way you can nearly start every docker image you want.

var dynamoDbContainer = builder.AddContainer("dynamoDb", "amazon/dynamodb-local", "latest")
 .WithEndpoint(name: "dynamoDb", port: 8000, scheme: "http");
var dynamoDbEndpoint = dynamoDbContainer.GetEndpoint("dynamoDb");

Do not deploy it

When explaining Aspire to other people I hear the same question a lot. How do I deploy this? Simply put, you dont. Just like you dont deploy your docker compose file. What is often meant is that they want the insights like tracing and metrics the default dashboard is giving. This I understand because it is helping you a lot in local development.

For that reason they released the dashboard as an image for you to run anywhere if needed here

or use the docker standalone image:

docker run --rm -it -p 18888:18888 -p 4317:18889 -d --name aspire-dashboard \
 mcr.microsoft.com/dotnet/nightly/aspire-dashboard:8.0.0-preview.6

Do note that there is no authenticaiton possible at the moment of writing this blog with preview 5.

How to deploy it?

Okay so I dont deploy the app host. But the other apps frontend, myApi, and Redis cache should be deployed. When you do deploy your application with your favorite CI/CD pipelines or any other means there is one thing you should configure. Since your orchestration is not deployed you have to link the parts of your application together. Basically like your used to.

Let me give you a schematic overview of how it all links together.

Local Development localdev

Production environment production

As you can see only the left part is replaced. This is done through environment variables. Do note that this is the part of the opinionated part but as of preview 6 this can be changed if it adapts better to your existing project. For more info see this doc. Let’s continue on how the default way is set up.

For the dotnet MyApi project, you can configure it like this:

"ConnectionStrings": {
 "myRedisCache": "your-redis.url:6379"
}

If for some reason you want to link 2 API services together the config in the environment will look like this

"services": {
 "myApi2": {
 "http": "your-api.url"
 }
}

In the frontend you can again use the environment variables in the env.production or any env file you have.

MY_API_ENDPOINT=$services__myApi__http__0/api/v2.0/my/call

All in all, will Aspire help you to get up and running for local development with a distributed application. I am already seeing the benefits of using it when there are 2 or more components. We will have to see how this all develops in the future. For now, at least Aspire looks very promising to me.

This concludes this blog post. I suggest you give Aspire a try and see for yourself how remarkably easy it is to set up and use. Let me know what you think of this opinionated framework in the comments!