This document describes the .NET Aspire integration for the Horscht music catalog application.
The Horscht application now includes .NET Aspire for local development orchestration. Aspire provides:
- Service orchestration - Run all services together from a single entry point
- Local Azure Storage emulation - Uses Azurite containers instead of real Azure resources
- Service discovery - Automatic configuration of service endpoints
- Telemetry - OpenTelemetry integration for logs, metrics, and traces
- Health checks - Built-in health monitoring for all services
- Resilience patterns - Automatic retry and circuit breaker patterns
The AppHost is the orchestration entry point for the application. It defines:
- Azure Storage Emulator (Azurite) - Docker container providing local Blob, Queue, and Table storage with persistent data
- Horscht.Importer - The background import service
- Horscht.Web - The Blazor WebAssembly frontend
Key Features:
- Data persistence: Azurite data is stored in a Docker volume and persists between runs
- CORS enabled: Allows browser-based access from Blazor WebAssembly
- Automatic initialization: Required queues (
import) and tables (songs) are created automatically on startup
The ServiceDefaults project provides common configuration used by all services:
- OpenTelemetry configuration (logs, metrics, traces)
- Health check endpoints
- Service discovery
- HTTP client resilience (retry, circuit breaker, timeout)
- Visual Studio 2022 (17.8+) or Rider with .NET Aspire workload installed
- Docker Desktop running
- .NET 10 SDK
- Set
Horscht.AppHostas the startup project - Press F5 to run
- The Aspire Dashboard will open automatically showing all services
cd Horscht.AppHost
dotnet runThe Aspire Dashboard will be available at the URL shown in the console output (typically https://localhost:17145).
The Importer service now uses Aspire Azure Storage client integration:
BlobServiceClient- Injected via DI, configured to connect to AzuriteQueueServiceClient- Injected via DI, configured to connect to AzuriteTableServiceClient- Injected via DI, configured to connect to Azurite
The StorageClientProvider has been updated to use these injected clients instead of creating clients from connection strings.
Storage Initialization: On startup, the Importer service automatically creates the required storage resources:
- Queue:
import- For processing uploaded music files - Table:
songs- For storing song metadata
This ensures the storage structure matches the Azure deployment and allows the application to work immediately after starting.
The Web project (Blazor WebAssembly) has been updated to support both local development with Azurite and production deployment with Azure Storage:
- Local Development: Uses
appsettings.Development.jsonwhich includes the Azurite connection string for anonymous access to the local emulator - Production: Uses
appsettings.jsonwith Azure AD authentication for secure access to real Azure Storage
The StorageClientProvider in Horscht.App automatically detects whether a connection string is available:
- If
ConnectionStringis configured (local development), it uses connection string authentication - If not (production), it uses Azure AD token-based authentication
Since Blazor WebAssembly runs in the browser, it needs to make cross-origin requests to Azurite. CORS is automatically configured on startup by the StorageInitializer service in the Importer:
// Configure CORS for Blob, Queue, and Table services
var blobServiceProperties = new BlobServiceProperties
{
Cors = new List<BlobCorsRule>
{
new BlobCorsRule
{
AllowedOrigins = "*",
AllowedMethods = "GET,PUT,POST,DELETE,OPTIONS",
AllowedHeaders = "*",
ExposedHeaders = "*",
MaxAgeInSeconds = 3600
}
}
};
await _blobServiceClient.SetPropertiesAsync(blobServiceProperties);
// Table service CORS is configured via REST API since SDK doesn't support itImportant: The wildcard (*) CORS configuration is only safe for local development with Azurite. Production Azure Storage should use specific allowed origins configured in the Azure Portal.
When running via Aspire, the Blazor WebAssembly app uses configuration from wwwroot/appsettings.Development.json:
{
"Storage": {
"ConnectionString": "DefaultEndpointsProtocol=http;AccountName=devstoreaccount1;..."
}
}This connection string is the well-known Azurite development storage account, which:
- Only works locally against the Azurite emulator
- Is publicly documented and safe for local development
- Allows the browser-based app to connect without Azure AD authentication
Production uses appsettings.json without a connection string, triggering Azure AD authentication:
{
"Storage": {
"BlobUri": "https://youraccountname.blob.core.windows.net/",
"TableUri": "https://youraccountname.table.core.windows.net/",
...
}
}Aspire automatically configures connection strings for server-side services (Horscht.Importer). The connection names defined in the AppHost (blobs, queues, tables) are automatically mapped to the client registrations in the services.
For production deployments, you can switch from Azurite to real Azure Storage by:
- Removing the
.RunAsEmulator()configuration in AppHost - Providing Azure Storage connection strings via configuration
- Faster development - No need to provision real Azure resources for local development
- Better debugging - See all services and their logs in one dashboard
- Easier onboarding - New developers can run the entire stack with one command
- Production-like - Uses the same Azure SDK clients as production
- Free - No Azure costs for local development
- Ensure Docker Desktop is running
- Check that ports 10000, 10001, 10002 are not in use
- Check the Aspire Dashboard for service status
- Verify that the Azurite container is running and healthy
- Check service logs in the Aspire Dashboard
If you see errors like "No 'Access-Control-Allow-Origin' header is present":
- Verify that Azurite is configured with CORS in
AppHost.cs(see CORS Configuration section) - Check that the Azurite container started with the CORS arguments
- Ensure you're accessing Azurite via the correct URLs (127.0.0.1 with ports 10000/10001/10002)
- Clear browser cache and retry
- Check that the dashboard ports (17145, 15182) are not in use
- Try running from Visual Studio instead of command line