.NET Aspire vs Docker.
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!
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:
-
DemoDbInitializer
- runs migrations and seeds the database on startup. -
DemoDbInitializerHealthCheck
- sets up a health check to report on the status of the database initializer.
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.
Comments
4 responses