Skip to content

Commit

Permalink
Add sample showing how to use the support in Aspire.Hosting for datab…
Browse files Browse the repository at this point in the history
…ase containers (#61)

* Updating samples for preview.2

* Skip sign check on workload install for now

* Update NuGet.config

* Update to latest preview.2 build

* Update to latest preview.2 build

* Fix node sample

* Update the version of preview2 to the latest

* Updated the dapr sample and instructions

* Update Dapr README to call out dapr init (#66)

* Update to latest package versions

* WIP

* Added DatabaseContainers sample

Fixes #60

* Tweaks

* Inject connection from DI instead of DataSource

* Update samples/DatabaseContainers/README.md

Co-authored-by: Bradley Grainger <[email protected]>

* Prepare for release

---------

Co-authored-by: David Fowler <[email protected]>
Co-authored-by: Balamurugan Chirtsabesan <[email protected]>
Co-authored-by: Bradley Grainger <[email protected]>
  • Loading branch information
4 people authored Dec 20, 2023
1 parent f67105c commit 04c1f43
Show file tree
Hide file tree
Showing 23 changed files with 650 additions and 1 deletion.
2 changes: 2 additions & 0 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
<PackageVersion Include="Aspire.Hosting" Version="$(AspireVersion)" />
<PackageVersion Include="Aspire.Hosting.Dapr" Version="$(AspireVersion)" />
<PackageVersion Include="Aspire.Npgsql" Version="$(AspireVersion)" />
<PackageVersion Include="Aspire.MySqlConnector" Version="$(AspireVersion)" />
<PackageVersion Include="Aspire.Npgsql.EntityFrameworkCore.PostgreSQL" Version="$(AspireVersion)" />
<PackageVersion Include="Aspire.Microsoft.Data.SqlClient" Version="$(AspireVersion)" />
<PackageVersion Include="Aspire.Microsoft.EntityFrameworkCore.SqlServer" Version="$(AspireVersion)" />
Expand Down Expand Up @@ -65,5 +66,6 @@
<PackageVersion Include="Dapr.AspNetCore" Version="1.12.0" />
<PackageVersion Include="Swashbuckle.AspNetCore" Version="6.5.0" />
<PackageVersion Include="Yarp.ReverseProxy" Version="2.1.0" />
<PackageVersion Include="Dapper" Version="2.1.24" />
</ItemGroup>
</Project>
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,16 @@ Samples for .NET Aspire.

## Official Samples

Official samples can be accessed via the [Samples browser](https://learn.microsoft.com/samples/browse/?expanded=dotnet&products=dotnet-aspire).
Official samples hosted in this repo can be accessed via the [Samples browser](https://learn.microsoft.com/samples/browse/?expanded=dotnet&products=dotnet-aspire).

Sample highlights include:

- [eShop Lite](./samples/eShopLite/)
- [Metrics with OpenTelemetry, Prometheus & Grafana](./samples/Metrics)
- [Integrating a Node.js app](./samples/AspireWithNode)
- [Integrating DAPR](./samples/AspireWithDapr)
- [Persisting data in composed containers using volume mounts](./samples/VolumeMount)
- [Working with database containers](./samples/DatabaseContainers)

## eShop

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
using Dapper;
using Microsoft.Data.SqlClient;
using MySqlConnector;
using Npgsql;

namespace DatabaseContainers.ApiService;

public static class ApiEndpoints
{
public static WebApplication MapTodosApi(this WebApplication app)
{
app.MapGet("/todos", async (NpgsqlConnection db) =>
{
const string sql = """
SELECT Id, Title, IsComplete
FROM Todos
""";

return await db.QueryAsync<Todo>(sql);
});

app.MapGet("/todos/{id}", async (int id, NpgsqlConnection db) =>
{
const string sql = """
SELECT Id, Title, IsComplete
FROM Todos
WHERE Id = @id
""";

return await db.QueryFirstOrDefaultAsync<Todo>(sql, new { id }) is { } todo
? Results.Ok(todo)
: Results.NotFound();
});

return app;
}

public static WebApplication MapCatalogApi(this WebApplication app)
{
app.MapGet("/catalog", async (MySqlConnection db) =>
{
const string sql = """
SELECT Id, Name, Description, Price
FROM catalog
""";

return await db.QueryAsync<CatalogItem>(sql);
});

app.MapGet("/catalog/{id}", async (int id, MySqlConnection db) =>
{
const string sql = """
SELECT Id, Name, Description, Price
FROM catalog
WHERE Id = @id
""";

return await db.QueryFirstOrDefaultAsync<CatalogItem>(sql, new { id }) is { } item
? Results.Ok(item)
: Results.NotFound();
});

return app;
}

public static WebApplication MapAddressBookApi(this WebApplication app)
{
app.MapGet("/addressbook", async (SqlConnection db) =>
{
const string sql = """
SELECT Id, FirstName, LastName, Email, Phone
FROM Contacts
""";

return await db.QueryAsync<Contact>(sql);
});

app.MapGet("/addressbook/{id}", async (int id, SqlConnection db) =>
{
const string sql = """
SELECT Id, FirstName, LastName, Email, Phone
FROM Contacts
WHERE Id = @id
""";

return await db.QueryFirstOrDefaultAsync<Contact>(sql, new { id }) is { } contact
? Results.Ok(contact)
: Results.NotFound();
});

return app;
}
}

public record Todo(int Id, string Title, bool IsComplete);

public record CatalogItem(int Id, string Name, string Description, decimal Price);

public record Contact(int Id, string FirstName, string LastName, string Email, string? Phone);
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<Project Sdk="Microsoft.NET.Sdk.Web">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\DatabaseContainers.ServiceDefaults\DatabaseContainers.ServiceDefaults.csproj" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Aspire.Microsoft.Data.SqlClient" />
<PackageReference Include="Aspire.Npgsql" />
<PackageReference Include="Aspire.MySqlConnector" />
<PackageReference Include="Dapper" />
<PackageReference Include="Swashbuckle.AspNetCore" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
using DatabaseContainers.ApiService;

var builder = WebApplication.CreateBuilder(args);

// Add service defaults & Aspire components.
builder.AddServiceDefaults();

builder.AddNpgsqlDataSource("Todos");
builder.AddMySqlDataSource("Catalog");
builder.AddSqlServerClient("AddressBook");

// Add services to the container.
builder.Services.AddProblemDetails();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var app = builder.Build();

// Configure the HTTP request pipeline.
app.UseExceptionHandler();

app.UseSwagger();
if (app.Environment.IsDevelopment())
{
app.UseSwaggerUI();
}

app.MapTodosApi();
app.MapCatalogApi();
app.MapAddressBookApi();

app.MapDefaultEndpoints();

app.Run();
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"$schema": "http://json.schemastore.org/launchsettings.json",
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"launchUrl": "swagger",
"applicationUrl": "http://localhost:5468",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
-- MySql init script

-- NOTE: MySql database and table names are case-sensitive on non-Windows platforms!
-- Column names are always case-insensitive.

-- Create the Catalog table
CREATE TABLE IF NOT EXISTS `catalog`
(
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) NOT NULL,
`description` varchar(255) NOT NULL,
`price` DECIMAL(18,2) NOT NULL,
PRIMARY KEY (`id`)
);

-- Insert some sample data into the Catalog table only if the table is empty
INSERT INTO catalog (name, description, price)
SELECT *
FROM (
SELECT '.NET Bot Black Hoodie', 'This hoodie will keep you warm while looking cool and representing .NET!', 19.5 UNION ALL
SELECT '.NET Black & White Mug', 'The perfect place to keep your favorite beverage while you code.', 8.5 UNION ALL
SELECT 'Prism White T-Shirt', "It's a t-shirt, it's white, and it can be yours.", 12
) data
-- This clause ensures the rows are only inserted if the table is empty
WHERE NOT EXISTS (SELECT NULL FROM catalog)
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
-- Postgres init script

-- Create the Todos table
CREATE TABLE IF NOT EXISTS Todos
(
Id SERIAL PRIMARY KEY,
Title text UNIQUE NOT NULL,
IsComplete boolean NOT NULL DEFAULT false
);

-- Insert some sample data into the Todos table
INSERT INTO Todos (Title, IsComplete)
VALUES
('Give the dog a bath', false),
('Wash the dishes', false),
('Do the groceries', false)
ON CONFLICT DO NOTHING;
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
-- SQL Server init script

-- Create the AddressBook database
IF NOT EXISTS (SELECT * FROM sys.databases WHERE name = N'AddressBook')
BEGIN
CREATE DATABASE AddressBook;
END;
GO

USE AddressBook;
GO

-- Create the Contacts table
IF OBJECT_ID(N'Contacts', N'U') IS NULL
BEGIN
CREATE TABLE Contacts
(
Id INT PRIMARY KEY IDENTITY(1,1) ,
FirstName VARCHAR(255) NOT NULL,
LastName VARCHAR(255) NOT NULL,
Email VARCHAR(255) NULL,
Phone VARCHAR(255) NULL
);
END;
GO

-- Ensure that either the Email or Phone column is populated
IF OBJECT_ID(N'chk_Contacts_Email_Phone', N'C') IS NULL
BEGIN
ALTER TABLE Contacts
ADD CONSTRAINT chk_Contacts_Email_Phone CHECK
(
Email IS NOT NULL OR Phone IS NOT NULL
);
END;
GO

-- Insert some sample data into the Contacts table
IF (SELECT COUNT(*) FROM Contacts) = 0
BEGIN
INSERT INTO Contacts (FirstName, LastName, Email, Phone)
VALUES
('John', 'Doe', '[email protected]', '555-123-4567'),
('Jane', 'Doe', '[email protected]', '555-234-5678');
END;
GO
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsAspireHost>true</IsAspireHost>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\DatabaseContainers.ApiService\DatabaseContainers.ApiService.csproj" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Aspire.Hosting" />
</ItemGroup>

</Project>
42 changes: 42 additions & 0 deletions samples/DatabaseContainers/DatabaseContainers.AppHost/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
var builder = DistributedApplication.CreateBuilder(args);

// PostgreSQL container is configured to use trust authentication by default so no password is required
// and supports setting the default database name via an environment variable & running *.sql/*.sh scripts in a bind mount.
var todosDbName = "Todos";
var todosDb = builder.AddPostgresContainer("postgres")
// Set the name of the default database to auto-create on container startup.
.WithEnvironment("POSTGRES_DB", todosDbName)
// Mount the SQL scripts directory into the container so that the init scripts run.
.WithVolumeMount("../DatabaseContainers.ApiService/data/postgres", "/docker-entrypoint-initdb.d", VolumeMountType.Bind)
// Add the default database to the application model so that it can be referenced by other resources.
.AddDatabase(todosDbName);

// MySql container is configured with an auto-generated password by default
// and supports setting the default database name via an environment variable & running *.sql/*.sh scripts in a bind mount.
var catalogDbName = "catalog"; // MySql database & table names are case-sensitive on non-Windows.
var catalogDb = builder.AddMySqlContainer("mysql")
// Set the name of the database to auto-create on container startup.
.WithEnvironment("MYSQL_DATABASE", catalogDbName)
// Mount the SQL scripts directory into the container so that the init scripts run.
.WithVolumeMount("../DatabaseContainers.ApiService/data/mysql", "/docker-entrypoint-initdb.d", VolumeMountType.Bind)
// Add the database to the application model so that it can be referenced by other resources.
.AddDatabase(catalogDbName);

// SQL Server container is configured with an auto-generated password by default
// but doesn't support any auto-creation of databases or running scripts on startup so we have to do it manually.
var addressBookDb = builder.AddSqlServerContainer("sqlserver")
// Mount the init scripts directory into the container.
.WithVolumeMount("./sqlserverconfig", "/usr/config", VolumeMountType.Bind)
// Mount the SQL scripts directory into the container so that the init scripts run.
.WithVolumeMount("../DatabaseContainers.ApiService/data/sqlserver", "/docker-entrypoint-initdb.d", VolumeMountType.Bind)
// Run the custom entrypoint script on startup.
.WithArgs("/usr/config/entrypoint.sh")
// Add the database to the application model so that it can be referenced by other resources.
.AddDatabase("AddressBook");

builder.AddProject<Projects.DatabaseContainers_ApiService>("apiservice")
.WithReference(todosDb)
.WithReference(catalogDb)
.WithReference(addressBookDb);

builder.Build().Run();
Loading

0 comments on commit 04c1f43

Please sign in to comment.