.NET Aspire + Data API Builder (Single-File Orchestration)
This skill provides a minimal workflow for running SQL Server, Data API Builder (DAB), SQL Commander, and MCP Inspector using .NET Aspire's single-file AppHost pattern for local development.
Core Mental Model
- •One
apphost.csfile orchestrates everything — no project files, no Docker Compose - •
aspire runstarts all services with health checks, dependencies, and telemetry - •Containers talk by service name, not localhost
- •DAB reads config from bind-mounted file — mount
dab-config.jsonread-only - •SQL Server must be healthy before DAB starts — use
.WaitFor()and health checks - •Aspire Dashboard provides visual monitoring, logs, and metrics at
http://localhost:15888
Prerequisites
- •.NET 10+ SDK — verify with
dotnet --version - •Aspire CLI 13+ — install:
dotnet tool install -g aspire— verify:aspire --version - •Docker Desktop running — SQL Server runs in container
- •
dab-config.jsonin workspace root using@env('MSSQL_CONNECTION_STRING')
MVP rule: Only add database schema/seed scripts if user explicitly asks.
Quick Workflow (Checklist)
- •Create
.envwith connection string (gitignored). - •Create
apphost.csusing the template below. - •Run with
aspire run. - •Access services via Aspire Dashboard.
Templates
.env (example)
# Never commit this file SA_PASSWORD=YourStrong@Passw0rd MSSQL_CONNECTION_STRING=Server=sql-server;Database=MyDb;User Id=sa;Password=YourStrong@Passw0rd;TrustServerCertificate=true
Critical: Add
.env,**\bin, and**\objto.gitignorebefore creating secrets.
apphost.cs (minimal template)
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
#:sdk Aspire.AppHost.Sdk@13.0.0
#:package Aspire.Hosting.SqlServer@13.0.0
#:package CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects@9.8.1-beta.420
#:package CommunityToolkit.Aspire.Hosting.Azure.DataApiBuilder@9.8.1-beta.420
#:package CommunityToolkit.Aspire.Hosting.McpInspector@9.8.0
var builder = DistributedApplication.CreateBuilder(args);
var options = new
{
SqlDatabase = "MyDb",
DabConfig = "dab-config.json",
DabImage = "1.7.83-rc",
SqlCmdrImage = "latest"
};
var sqlServer = builder
.AddSqlServer("sql-server")
.WithDataVolume("sql-data")
.WithEnvironment("ACCEPT_EULA", "Y")
.WithLifetime(ContainerLifetime.Persistent);
var sqlDatabase = sqlServer
.AddDatabase(options.SqlDatabase);
var dabServer = builder
.AddContainer("data-api", image: "azure-databases/data-api-builder", tag: options.DabImage)
.WithImageRegistry("mcr.microsoft.com")
.WithBindMount(source: new FileInfo(options.DabConfig).FullName, target: "/App/dab-config.json", isReadOnly: true)
.WithHttpEndpoint(targetPort: 5000, name: "http")
.WithEnvironment("MSSQL_CONNECTION_STRING", sqlDatabase)
.WithUrls(context =>
{
context.Urls.Clear();
context.Urls.Add(new() { Url = "/graphql", DisplayText = "GraphQL", Endpoint = context.GetEndpoint("http") });
context.Urls.Add(new() { Url = "/swagger", DisplayText = "Swagger", Endpoint = context.GetEndpoint("http") });
context.Urls.Add(new() { Url = "/health", DisplayText = "Health", Endpoint = context.GetEndpoint("http") });
})
.WithOtlpExporter()
.WithParentRelationship(sqlDatabase)
.WithHttpHealthCheck("/health")
.WaitFor(sqlDatabase);
var sqlCommander = builder
.AddContainer("sql-cmdr", "jerrynixon/sql-commander", options.SqlCmdrImage)
.WithImageRegistry("docker.io")
.WithHttpEndpoint(targetPort: 8080, name: "http")
.WithEnvironment("ConnectionStrings__db", sqlDatabase)
.WithUrls(context =>
{
context.Urls.Clear();
context.Urls.Add(new() { Url = "/", DisplayText = "Commander", Endpoint = context.GetEndpoint("http") });
})
.WithParentRelationship(sqlDatabase)
.WithHttpHealthCheck("/health")
.WaitFor(sqlDatabase);
var mcpInspector = builder
.AddMcpInspector("mcp-inspector")
.WithMcpServer(dabServer)
.WithParentRelationship(dabServer)
.WithEnvironment("NODE_TLS_REJECT_UNAUTHORIZED", "0")
.WaitFor(dabServer)
.WithUrls(context =>
{
context.Urls.First().DisplayText = "Inspector";
});
await builder.Build().RunAsync();
dab-config.json (minimal)
{
"$schema": "https://dataapibuilder.azureedge.net/schemas/latest/dab.draft.schema.json",
"data-source": {
"database-type": "mssql",
"connection-string": "@env('MSSQL_CONNECTION_STRING')"
},
"runtime": {
"rest": {
"enabled": true,
"path": "/api"
},
"graphql": {
"enabled": true,
"path": "/graphql"
},
"host": {
"mode": "development",
"cors": {
"origins": ["*"],
"allow-credentials": false
}
},
"mcp": {
"enabled": true
}
},
"entities": {}
}
Running the Stack
Start Services
aspire run
This command:
- •Builds and restores packages
- •Starts SQL Server container
- •Waits for SQL health check
- •Starts DAB container with bind-mounted config
- •Starts SQL Commander
- •Starts MCP Inspector
- •Opens Aspire Dashboard
Aspire Dashboard: http://localhost:15888
Access Services
| Service | URL | Purpose |
|---|---|---|
| Aspire Dashboard | http://localhost:15888 | Monitor all services, logs, metrics |
| DAB GraphQL | Click "GraphQL" link in dashboard | Query data via GraphQL |
| DAB Swagger | Click "Swagger" link in dashboard | REST API documentation |
| DAB Health | Click "Health" link in dashboard | Service health status |
| SQL Commander | Click "Commander" link in dashboard | SQL query tool |
| MCP Inspector | Click "Inspector" link in dashboard | Test MCP endpoints |
Note: Aspire assigns dynamic ports. Use dashboard links instead of hardcoded URLs.
Stop Services
Press Ctrl+C in terminal where aspire run is active, or:
docker stop $(docker ps -q)
Adding Database Schema (Optional)
To deploy a SQL schema on startup, add this before var dabServer:
var sqlDatabaseWithSchema = sqlDatabase
.WithSqlProject(new FileInfo("database.sql").FullName);
Then change dabServer to wait for sqlDatabaseWithSchema instead of sqlDatabase:
.WaitFor(sqlDatabaseWithSchema);
Troubleshooting
aspire: command not found
Fix:
dotnet tool install -g aspire
SQL Server container fails to start
Check Docker:
docker ps -a docker logs <container-id>
DAB can't connect to SQL Server
Verify connection string in .env uses service name sql-server, not localhost:
MSSQL_CONNECTION_STRING=Server=sql-server;Database=MyDb;...
Port conflicts
Aspire auto-assigns ports. If dashboard doesn't open, check terminal output for actual URL.
Aspire vs Docker Compose
| Feature | Aspire | Docker Compose |
|---|---|---|
| Definition | Single C# file | YAML file |
| Language | C# | YAML |
| Dependencies | .WaitFor() + health checks | depends_on + healthcheck |
| Monitoring | Built-in dashboard with logs, metrics, traces | External tools required |
| Service Discovery | Automatic | Manual with service names |
| Hot Reload | Yes with --watch | No |
| Debugging | Native .NET debugging | Container debugging only |
Use Aspire when:
- •You're already in a .NET project
- •You want integrated telemetry and monitoring
- •You need complex startup orchestration
Use Docker Compose when:
- •You need multi-language support
- •You want standard container orchestration
- •Aspire isn't installed
Configuration Details
Service Names (DNS)
Containers reference each other by service name:
- •SQL Server:
sql-server - •DAB:
data-api - •SQL Commander:
sql-cmdr - •MCP Inspector:
mcp-inspector
Volumes
SQL Server data persists in Docker volume sql-data:
docker volume ls | Select-String sql-data
To reset database:
aspire run # Stop with Ctrl+C docker volume rm <volume-name> aspire run # Restart
Health Checks
- •SQL Server: Default SQL health check
- •DAB:
GET /health(must return 200) - •SQL Commander:
GET /health
Services won't start until dependencies are healthy.