File: AppHost.cs
Web Access
Project: src\playground\TestShop\TestShop.AppHost\TestShop.AppHost.csproj (TestShop.AppHost)
using Aspire.Hosting.Yarp.Transforms;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
 
#pragma warning disable ASPIREPERSISTENCE001 // Resource lifetime APIs are experimental.
#pragma warning disable ASPIRETERMINAL001 // Terminal APIs are experimental.
 
var builder = DistributedApplication.CreateBuilder(args);
 
// Parameters have the same two tags as the resource that uses them, so grouping the dashboard by tags shows them
// together: Frontend/Storefront for the frontend, Backend/API for the catalog service, and Backend/Checkout for the
// basket service.
var storefrontPromotion = builder.AddParameter("storefront-promotion", "Free shipping over $50")
                                .WithTags("Frontend", "Storefront");
var lowStockThreshold = builder.AddParameter("low-stock-threshold", "5")
                         .WithTags("Backend", "API");
var checkoutSigningKey = builder.AddParameter("checkout-signing-key", () => Guid.NewGuid().ToString(), secret: true)
                          .WithTags("Backend", "Checkout");
builder.AddParameter("support-contact-email")
       .WithDescription("The customer support email address displayed by the storefront.")
       .WithTags("Frontend", "Storefront");
builder.AddParameter("storefront-theme", "system")
       .WithDescription("Choose the storefront color theme.")
       .WithTags("Frontend", "Storefront")
       .WithCustomInput(parameter => new()
       {
           Name = parameter.Name,
           InputType = InputType.Choice,
           Label = parameter.Name,
           Description = parameter.Description,
           Options =
           [
               KeyValuePair.Create("system", "System"),
               KeyValuePair.Create("light", "Light"),
               KeyValuePair.Create("dark", "Dark")
           ]
       });
builder.AddParameter("storefront-accent", "blue")
       .WithDescription("Choose a suggested accent or enter a custom value.")
       .WithTags("Frontend", "Storefront")
       .WithCustomInput(parameter => new()
       {
           Name = parameter.Name,
           InputType = InputType.Choice,
           Label = parameter.Name,
           Description = parameter.Description,
           AllowCustomChoice = true,
           Options =
           [
               KeyValuePair.Create("blue", "Blue"),
               KeyValuePair.Create("green", "Green"),
               KeyValuePair.Create("orange", "Orange")
           ]
       });
builder.AddParameter("recommendation-limit", "12")
       .WithDescription("The maximum number of recommendations to display.")
       .WithTags("Backend", "API")
       .WithCustomInput(parameter => new()
       {
           Name = parameter.Name,
           InputType = InputType.Number,
           Label = parameter.Name,
           Description = parameter.Description,
           Placeholder = "Enter a number"
       });
builder.AddParameter("maintenance-mode", "false")
       .WithDescription("Temporarily show the storefront maintenance experience.")
       .WithTags("Frontend", "Storefront")
       .WithCustomInput(parameter => new()
       {
           Name = parameter.Name,
           InputType = InputType.Boolean,
           Label = parameter.Name,
           Description = parameter.Description
       });
builder.AddParameter("storefront-certificate")
       .WithDescription("Upload the certificate used by the storefront.")
       .WithTags("Frontend", "Storefront")
       .WithCustomInput(parameter => new()
       {
           Name = parameter.Name,
           InputType = InputType.File,
           Label = parameter.Name,
           Description = parameter.Description,
           Placeholder = "Choose certificate",
           FileFilter = ".pem,.pfx,.crt"
       });
 
var catalogDb = builder.AddPostgres("postgres")
                       .WithDataVolume()
                       .WithPgAdmin(resource =>
                       {
                           resource.WithUrlForEndpoint("http", u => u.DisplayText = "PG Admin");
                       })
                       .AddDatabase("catalogdb");
 
var basketCache = builder.AddRedis("basketcache")
                         .WithDataVolume();
 
#if !SKIP_DASHBOARD_REFERENCE
basketCache.WithRedisCommander(c =>
            {
                c.WithHostPort(33801);
                c.WithUrlForEndpoint("http", u => u.DisplayText = "Redis Commander");
            })
           .WithRedisInsight(c =>
            {
                c.WithHostPort(33802);
                c.WithUrlForEndpoint("http", u => u.DisplayText = "Redis Insight");
            });
#endif
 
var catalogDbApp = builder.AddDotnetProject("catalogdbapp", "../CatalogDb/CatalogDb.csproj")
                          .WithReference(catalogDb)
                          .WaitFor(catalogDb)
                          .WithHttpHealthCheck("/health");
 
if (builder.Environment.IsDevelopment() && builder.ExecutionContext.IsRunMode)
{
    var resetDbKey = Guid.NewGuid().ToString();
    catalogDbApp.WithEnvironment("DatabaseResetKey", resetDbKey)
                .WithHttpCommand("/reset-db", "Reset Database",
                    commandOptions: new()
                    {
                        Description = "Reset the catalog database to its initial state. This will delete and recreate the database.",
                        ConfirmationMessage = "Are you sure you want to reset the catalog database?",
                        IconName = "DatabaseLightning",
                        PrepareRequest = requestContext =>
                        {
                            requestContext.Request.Headers.Add("Authorization", $"Key {resetDbKey}");
                            return Task.CompletedTask;
                        }
                    });
}
 
var catalogService = builder.AddDotnetProject("catalogservice", "../CatalogService/CatalogService.csproj")
                            .WithTags("Backend", "API")
                            .WithTrait("Owner", "Finance")
                            .WithTrait("Owner", "Platform")
                            .WithEnvironment("Inventory__LowStockThreshold", lowStockThreshold)
                            .WithDashboardPosition(10)
                            .WithReference(catalogDb)
                            .WaitFor(catalogDb)
                            .WaitFor(catalogDbApp)
                            // Modify the endpoint URL
                            .WithUrlForEndpoint("https", u =>
                            {
                                u.Url = "/";
                                u.DisplayText = "Catalog API";
                            })
                            // Add an endpoint URL
                            .WithUrlForEndpoint("https", _ => new()
                            {
                                Url = "/swagger",
                                DisplayText = "Swagger UI"
                            })
                            // Hide the http URL
                            .WithUrlForEndpoint("http", u => u.DisplayLocation = UrlDisplayLocation.DetailsOnly)
                            .WithHttpHealthCheck("/health")
                            .WithReplicas(2);
 
var messaging = builder.AddRabbitMQ("messaging")
                       .WithDataVolume()
                       .WithPersistentLifetime()
                       .WithManagementPlugin()
                       .PublishAsContainer();
 
var basketService = builder.AddDotnetProject("basketservice", "../BasketService/BasketService.csproj")
                           .WithTags("Backend", "Checkout")
                           .WithEnvironment("Checkout__SigningKey", checkoutSigningKey)
                           .WithDashboardPosition(20)
                           .WithReference(basketCache)
                           .WaitFor(basketCache)
                           .WithReference(messaging)
                           .WaitFor(messaging);
 
var frontend = builder.AddDotnetProject("frontend", "../MyFrontend/MyFrontend.csproj")
    .WithTags("Frontend", "Storefront")
    .WithEnvironment("Storefront__Promotion", storefrontPromotion)
    .WithDashboardPosition(0)
    .WithExternalHttpEndpoints()
    .WithReference(basketService)
    .WaitFor(basketService)
    .WithReference(catalogService)
    .WaitFor(catalogService)
    // Modify the display text of the URLs
    .WithUrls(c => c.Urls.ForEach(u => u.DisplayText = $"Online store ({u.Endpoint?.EndpointName})"))
    // Don't show the non-HTTPS link on the resources page (details only)
    .WithUrlForEndpoint("http", url => url.DisplayLocation = UrlDisplayLocation.DetailsOnly)
    // Add health relative URL (show in details only)
    .WithUrlForEndpoint("https", ep => new() { Url = "/health", DisplayText = "Health", DisplayLocation = UrlDisplayLocation.DetailsOnly })
    .WithHttpHealthCheck("/health");
 
if (OperatingSystem.IsWindows())
{
    builder.AddExecutable("frontend-shell", "cmd.exe", @"..\MyFrontend")
        .WithTags("Frontend", "Development")
        .WithTerminal();
 
    builder.AddExecutable("catalog-shell", "cmd.exe", @"..\CatalogService")
        .WithTags("Backend", "Development")
        .WithTerminal();
}
else
{
    builder.AddExecutable("frontend-shell", "/bin/bash", @"..\MyFrontend", "-i", "-l")
        .WithTags("Frontend", "Development")
        .WithTerminal();
 
    builder.AddExecutable("catalog-shell", "/bin/bash", @"..\CatalogService", "-i", "-l")
        .WithTags("Backend", "Development")
        .WithTerminal();
}
 
builder.AddDotnetProject("orderprocessor", "../OrderProcessor/OrderProcessor.csproj", options => options.LaunchProfileName = "OrderProcessor")
    .WithReference(messaging)
    .WaitFor(messaging);
 
var yarp = builder.AddYarp("apigateway");
#if YARP_USE_CONFIG_FILE
yarp.WithConfigFile("yarp.json")
    .WithReference(basketService)
    .WaitFor(basketService)
    .WithReference(catalogService)
    .WaitFor(catalogService);
#else
yarp.WithReference(basketService)
    .WaitFor(basketService)
    .WithReference(catalogService)
    .WaitFor(catalogService);
 
yarp.WithConfiguration(builder =>
{
    // catalog 
    builder.AddRoute("/catalog/{**catch-all}", catalogService.GetEndpoint("http"))
           .WithTransformPathRemovePrefix("/catalog");
    // basket
    builder.AddRoute("/basket/{**catch-all}", basketService.GetEndpoint("http"))
           .WithTransformPathRemovePrefix("/basket");
});
#endif
 
builder.AddDotnetProject("activity-generator", "../ActivityGenerator/ActivityGenerator.csproj")
    .WithReference(frontend)
    .WaitFor(frontend)
    .WithReference(yarp)
    .WaitFor(yarp)
    .WithReference(catalogService)
    .WaitFor(catalogService)
    .WithExplicitStart()
    .ExcludeFromManifest();
 
#if !SKIP_DASHBOARD_REFERENCE
// This project is only added in playground projects to support development/debugging
// of the dashboard. It is not required in end developer code. Comment out this code
// or build with `/p:SkipDashboardProjectReference=true` to test the end developer
// dashboard launch experience. The opt-out and project reference are defined in
// playground/Directory.Build.targets. The repo-root Directory.Build.props sets the
// default dashboard binary path to the Aspire.Dashboard output in the artifacts dir.
var dashboardBuilder = builder.AddDotnetProject(KnownResourceNames.AspireDashboard, "../../../src/Aspire.Dashboard/Aspire.Dashboard.csproj");
if (builder.Configuration["OTEL_EXPORTER_OTLP_ENDPOINT"] is { Length: > 0 } dashboardOtlpEndpoint)
{
    // The AppHost normally points every project at its own dashboard. Preserve an explicitly configured
    // external endpoint for dashboard self-telemetry so its activities can be inspected separately.
    dashboardBuilder
        .WithEnvironment("OTEL_EXPORTER_OTLP_ENDPOINT", dashboardOtlpEndpoint)
        .WithEnvironment("OTEL_EXPORTER_OTLP_PROTOCOL", builder.Configuration["OTEL_EXPORTER_OTLP_PROTOCOL"] ?? "grpc")
        .WithEnvironment("OTEL_EXPORTER_OTLP_HEADERS", builder.Configuration["OTEL_EXPORTER_OTLP_HEADERS"] ?? string.Empty);
}
#endif
 
builder.Build().Run();