This is a follow-up to my previous post where I compared .NET Aspire to NuGet. In that post, I promised I would follow up with a comparison of using .NET Aspire to add a service dependency to a project versus using Docker. And looky here, I’m following through for once!

Docker vs Aspire

The goal of these examples is to look at how much “ceremony” there is to add a service dependency to a .NET project using .NET Aspire versus using Docker. Even though it may not be the “best” example, I chose PostgreSql because it’s often the first service dependency I add to a new project. The example would be stronger if I chose another service dependency in addition to Postgres, but I think you can extrapolate that as well. And I have another project I’m working on that will have more dependencies.

I won’t include installing the pre-requisite tooling as part of the “ceremony” because that’s a one-time thing. I’ll focus on the steps to add the service dependency to a project.

Tooling

I wrote this so that you can follow along and create the projects yourself on your own computer. If you want to follow along, you’ll need the following tools installed.

Once you have these installed, you’ll also need to install the Aspire .NET workloads.

dotnet workload update
dotnet workload install aspire

Examples

This section contains two step-by-step walk throughs to create the example project, once with Docker and once with PostgreSql.

The example project is a simple Blazor web app with a PostgreSQL database. I’ll use Entity Framework Core to interact with the database. I’ll also use the dotnet-ef command line tool to create migrations.

Since we’re creating the same project twice, I’ll put the common code we’ll need right here since both walkthroughs will refer to it.

Both projects will make use of a custom DbContext derived class and a simple User entity with an Id and a Name.

using Microsoft.EntityFrameworkCore;

namespace HaackDemo.Web;

public class DemoDbContext(DbContextOptions<DemoDbContext> options)
  : DbContext(options)
{
    public DbSet<User> Users { get; set; }
}

public class User
{
    public int Id { get; set; }

    public string Name { get; set; }
}

Also, both projects will have a couple of background services that run on startup:

I used to run my migrations in Program.cs on startup, but I saw this example in the Aspire samples and thought I’d try it out. I also copied their health check initializer.

Both of these need to be registered in Program.cs.

builder.Services.AddSingleton<DemoDbInitializer>();
builder.Services.AddHostedService(sp => sp.GetRequiredService<DemoDbInitializer>());
builder.Services.AddHealthChecks()
    .AddCheck<DemoDbInitializerHealthCheck>("DbInitializer", null);

With that in place, let’s begin.

Docker

From your root development directory, the following commands will create a new Blazor project and solution.

md docker-efcore-postgres-demo && cd docker-efcore-postgres-demo`
dotnet new blazor -n DockerDemo -o DockerDemo.Web`
dotnet new sln --name DockerDemo`
dotnet sln add DockerDemo.Web`

Npgqsl is the PostgreSql provider for Entity Framework Core. We need to add it to the web project.

cd DockerDemo.Web
dotnet add package Npgsql.EntityFrameworkCore.PostgreSQL

We also need the EF Core Design package to support the dotnet-ef command line tool we’re going to use to create migrations.

dotnet add package Microsoft.EntityFrameworkCore.Design

Now we add the docker file to the root directory.

cd ..
touch docker-compose.yml

And paste the following in

version: '3.7'

services:
  postgres:
    container_name: 'postgres'
    image: postgres
    environment:
      # change this for a "real" app!
      POSTGRES_PASSWORD: password

Note that the container_name could conflict with other containers on your system. You may need to change it to something unique.

Add the postgres connection string to your appsettings.json.

    "ConnectionStrings": {
      "postgresdb": "User ID=postgres;Password=password;Server=postgres;Port=5432;Database=POSTGRES_USER;Integrated Security=true;Pooling=true;"
    }

Now we can add our custom DbContext derived class and User entity mentioned earlier. We also need to register DemoDbInitializer and DemoDbInitializerHealthCheck in Program.cs as mentioned before.

Next create the initial migration.

cd ../DockerDemo.Web
dotnet ef migrations add InitialMigration

We’re ready to run the app. First, we need to start the Postgres container.

docker-compose build
docker-compose up

Finally, we can hit hit F5 in Visual Studio/Rider or run dotnet run in the terminal and run our app locally.

Aspire

Once again, from your root development directory, the following commands will create a new Blazor project and solution. But this time, we’ll use the Aspire starter template.

md aspire-efcore-postgres-demo && cd aspire-efcore-postgres-demo
dotnet new aspire-starter -n AspireDemo -o .

This creates three projects:

  • AspireDemo.AppHost - The host project that configures the application.
  • AspireDemo.Web - The web application project.
  • AspireDemo.ApiService - An example web service to get the weather.

We don’t need AspireDemo.ApiService for this example, so we can remove it.

The first thing we want to do is configure the PostgreSql service in the AspireDemo.AppHost project. In a way, this is analogous to how we configured Postgres in the docker-compose.yml file in the Docker example.

Switch to the App Host project and install the Aspire.Hosting.PostgreSQL package.

cd ../AspireDemo.AppHost
dotnet add package Aspire.Hosting.PostgreSQL

Add this snippet after the builder is created in Program.cs.

var postgres = builder.AddPostgres("postgres");
var postgresdb = postgres.AddDatabase("postgresdb");

This creates a Postgres service named postgres and a database named postgresdb. We’ll use the postgresdb reference when we want to connect to the database in the consuming project.

Finally, we update the existing line to include the reference to the database.

  builder.AddProject<Projects.AspireDemo_Web>("webfrontend")
-     .WithExternalHttpEndpoints();
+     .WithExternalHttpEndpoints()
+     .WithReference(postgresdb);

That completes the configuration of the PostgreSql service in the App Host project. Now we can consume this from our web project.

Add the PostgreSQL component to the consuming project, aka the web application.

cd ../AspireDemo.Web
dotnet add package Aspire.Npgsql.EntityFrameworkCore.PostgreSQL

We also need the EF Core Design package to support the ef command line tool we’re going to use to create migrations.

dotnet add package Microsoft.EntityFrameworkCore.Design

Once again, we add our custom DbContext derived class, DemoDbContext, along with the User entity to the project. Once we do that, we configure the DemoDbContext in the Program.cs file. Note that we use the postgresdb reference we created in the App Host project.

builder.AddNpgsqlDbContext<DemoDbContext>("postgresdb");

Then we can create the migrations using the dotnet-ef cli tool.

cd ../AspireDemo.Web
dotnet ef migrations add InitialMigration

Don’t forget to add the DemoDbInitializer and DemoDbInitializerHealthCheck to the project and register them in Program.cs as mentioned before.

Now to run the app, I can hit F5 in Visual Studio/Rider or run dotnet run in the terminal. If you use F5, make sure the AppHost project is selected as the run project.`

Conclusions

At the end of both walkthroughs we end up with a simple Blazor web app that uses a PostgreSQL database. Personally, I like the .NET Aspire approach because I didn’t have to mess with connection strings and the F5 to run experience is preserved.

As I mentioned before, I have another project I’m working on that has more dependencies. When I’m done with that port, I think it’ll be a better example of the ceremony surrounding cloud dependencies when using .NET Aspire.

In any case, you can see both of these projects I created on GitHub.