diff --git a/.editorconfig b/.editorconfig index 1e311974190..7b89a017365 100644 --- a/.editorconfig +++ b/.editorconfig @@ -476,3 +476,6 @@ dotnet_diagnostic.IDE0005.severity = silent [{*.razor.cs,src/Aspire.Dashboard/Components/**.cs}] # CA2007: Consider calling ConfigureAwait on the awaited task dotnet_diagnostic.CA2007.severity = silent + +[{src/Playgrounds/**.cs}] +dotnet_diagnostic.CA2007.severity = silent diff --git a/Aspire.sln b/Aspire.sln index 8ba2b43d1b2..2ae0159929a 100644 --- a/Aspire.sln +++ b/Aspire.sln @@ -1,4 +1,3 @@ - Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.0.31903.59 @@ -194,6 +193,16 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Aspire.Confluent.Kafka", "s EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Aspire.Confluent.Kafka.Tests", "tests\Aspire.Confluent.Kafka.Tests\Aspire.Confluent.Kafka.Tests.csproj", "{A8CB331A-1247-41D9-8118-538E5A2CC9DF}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Playgrounds", "Playgrounds", "{DF8ADBC2-5B15-422F-A703-C60711D5552D}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "CosmosEndToEnd", "CosmosEndToEnd", "{DBEDDF76-1C33-4943-8CCB-337A7D48AFF5}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CosmosEndToEnd.AppHost", "src\Playgrounds\CosmosEndToEnd\CosmosEndToEnd.AppHost\CosmosEndToEnd.AppHost.csproj", "{51DDD6BC-1D6C-466A-B509-FC49E3BD72E4}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CosmosEndToEnd.ApiService", "src\Playgrounds\CosmosEndToEnd\CosmosEndToEnd.ApiService\CosmosEndToEnd.ApiService.csproj", "{EABB20A8-CDA2-4AFE-A5B1-FB631200CD64}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Playground.ServiceDefaults", "src\Playgrounds\Playground.ServiceDefaults\Playground.ServiceDefaults.csproj", "{25208C6F-0A9D-4D60-9EDD-256C9891B1CD}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -520,6 +529,18 @@ Global {A8CB331A-1247-41D9-8118-538E5A2CC9DF}.Debug|Any CPU.Build.0 = Debug|Any CPU {A8CB331A-1247-41D9-8118-538E5A2CC9DF}.Release|Any CPU.ActiveCfg = Release|Any CPU {A8CB331A-1247-41D9-8118-538E5A2CC9DF}.Release|Any CPU.Build.0 = Release|Any CPU + {51DDD6BC-1D6C-466A-B509-FC49E3BD72E4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {51DDD6BC-1D6C-466A-B509-FC49E3BD72E4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {51DDD6BC-1D6C-466A-B509-FC49E3BD72E4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {51DDD6BC-1D6C-466A-B509-FC49E3BD72E4}.Release|Any CPU.Build.0 = Release|Any CPU + {EABB20A8-CDA2-4AFE-A5B1-FB631200CD64}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EABB20A8-CDA2-4AFE-A5B1-FB631200CD64}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EABB20A8-CDA2-4AFE-A5B1-FB631200CD64}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EABB20A8-CDA2-4AFE-A5B1-FB631200CD64}.Release|Any CPU.Build.0 = Release|Any CPU + {25208C6F-0A9D-4D60-9EDD-256C9891B1CD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {25208C6F-0A9D-4D60-9EDD-256C9891B1CD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {25208C6F-0A9D-4D60-9EDD-256C9891B1CD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {25208C6F-0A9D-4D60-9EDD-256C9891B1CD}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -610,6 +631,10 @@ Global {F7D9FA54-1F64-4A36-961A-0087F8E88D07} = {8BAF2119-8370-4E9E-A887-D92506F8C727} {174E0507-3BB0-4CDC-829E-9CA75DA66473} = {27381127-6C45-4B4C-8F18-41FF48DFE4B2} {A8CB331A-1247-41D9-8118-538E5A2CC9DF} = {4981B3A5-4AFD-4191-BF7D-8692D9783D60} + {DBEDDF76-1C33-4943-8CCB-337A7D48AFF5} = {DF8ADBC2-5B15-422F-A703-C60711D5552D} + {51DDD6BC-1D6C-466A-B509-FC49E3BD72E4} = {DBEDDF76-1C33-4943-8CCB-337A7D48AFF5} + {EABB20A8-CDA2-4AFE-A5B1-FB631200CD64} = {DBEDDF76-1C33-4943-8CCB-337A7D48AFF5} + {25208C6F-0A9D-4D60-9EDD-256C9891B1CD} = {DF8ADBC2-5B15-422F-A703-C60711D5552D} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {6DCEDFEC-988E-4CB3-B45B-191EB5086E0C} diff --git a/src/Aspire.Hosting.Azure/AzureCosmosDBDatabaseResource.cs b/src/Aspire.Hosting.Azure/AzureCosmosDBDatabaseResource.cs index 12466cad656..2eb92d0d829 100644 --- a/src/Aspire.Hosting.Azure/AzureCosmosDBDatabaseResource.cs +++ b/src/Aspire.Hosting.Azure/AzureCosmosDBDatabaseResource.cs @@ -26,5 +26,5 @@ public AzureCosmosDBDatabaseResource(string name, AzureCosmosDBResource parent) /// Gets the connection string to use for this database. /// /// The connection string to use for this database. - public string? GetConnectionString() => ConnectionString ?? Parent.GetConnectionString(); + public string? GetConnectionString() => ConnectionString ?? Parent.GetConnectionString(); // HACK: Will go away when we get rid of Azure Provisioner package. } diff --git a/src/Playgrounds/CosmosEndToEnd/CosmosEndToEnd.ApiService/CosmosEndToEnd.ApiService.csproj b/src/Playgrounds/CosmosEndToEnd/CosmosEndToEnd.ApiService/CosmosEndToEnd.ApiService.csproj new file mode 100644 index 00000000000..211dd11a47b --- /dev/null +++ b/src/Playgrounds/CosmosEndToEnd/CosmosEndToEnd.ApiService/CosmosEndToEnd.ApiService.csproj @@ -0,0 +1,15 @@ + + + + net8.0 + enable + enable + true + + + + + + + + diff --git a/src/Playgrounds/CosmosEndToEnd/CosmosEndToEnd.ApiService/CosmosEndToEnd.ApiService.http b/src/Playgrounds/CosmosEndToEnd/CosmosEndToEnd.ApiService/CosmosEndToEnd.ApiService.http new file mode 100644 index 00000000000..ebb1db4fe79 --- /dev/null +++ b/src/Playgrounds/CosmosEndToEnd/CosmosEndToEnd.ApiService/CosmosEndToEnd.ApiService.http @@ -0,0 +1,6 @@ +@CosmosEndToEnd.ApiService_HostAddress = http://localhost:5193 + +GET {{CosmosEndToEnd.ApiService_HostAddress}}/weatherforecast/ +Accept: application/json + +### diff --git a/src/Playgrounds/CosmosEndToEnd/CosmosEndToEnd.ApiService/Program.cs b/src/Playgrounds/CosmosEndToEnd/CosmosEndToEnd.ApiService/Program.cs new file mode 100644 index 00000000000..d678b1ece4d --- /dev/null +++ b/src/Playgrounds/CosmosEndToEnd/CosmosEndToEnd.ApiService/Program.cs @@ -0,0 +1,54 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.Azure.Cosmos; +using Newtonsoft.Json; + +var builder = WebApplication.CreateBuilder(args); + +builder.AddServiceDefaults(); +builder.AddAzureCosmosDB("db", settings => +{ + settings.IgnoreEmulatorCertificate = true; +}); + +var app = builder.Build(); + +app.MapGet("/", async (CosmosClient cosmosClient) => +{ + var db = (await cosmosClient.CreateDatabaseIfNotExistsAsync("db")).Database; + var container = (await db.CreateContainerIfNotExistsAsync("entries", "/Id")).Container; + + // Add an entry to the database on each request. + var newEntry = new Entry() { Id = Guid.NewGuid().ToString() }; + await container.CreateItemAsync(newEntry); + + var entries = new List(); + var iterator = container.GetItemQueryIterator(requestOptions: new QueryRequestOptions() { MaxItemCount = 5 }); + + var batchCount = 0; + while (iterator.HasMoreResults) + { + batchCount++; + var batch = await iterator.ReadNextAsync(); + foreach (var entry in batch) + { + entries.Add(entry); + } + } + + return new + { + batchCount = batchCount, + totalEntries = entries.Count, + entries = entries + }; +}); + +app.Run(); + +public class Entry +{ + [JsonProperty("id")] + public string? Id { get; set; } +} diff --git a/src/Playgrounds/CosmosEndToEnd/CosmosEndToEnd.ApiService/Properties/launchSettings.json b/src/Playgrounds/CosmosEndToEnd/CosmosEndToEnd.ApiService/Properties/launchSettings.json new file mode 100644 index 00000000000..de23e4696cf --- /dev/null +++ b/src/Playgrounds/CosmosEndToEnd/CosmosEndToEnd.ApiService/Properties/launchSettings.json @@ -0,0 +1,14 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "http://localhost:5193", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/src/Playgrounds/CosmosEndToEnd/CosmosEndToEnd.ApiService/appsettings.Development.json b/src/Playgrounds/CosmosEndToEnd/CosmosEndToEnd.ApiService/appsettings.Development.json new file mode 100644 index 00000000000..0c208ae9181 --- /dev/null +++ b/src/Playgrounds/CosmosEndToEnd/CosmosEndToEnd.ApiService/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/src/Playgrounds/CosmosEndToEnd/CosmosEndToEnd.ApiService/appsettings.json b/src/Playgrounds/CosmosEndToEnd/CosmosEndToEnd.ApiService/appsettings.json new file mode 100644 index 00000000000..10f68b8c8b4 --- /dev/null +++ b/src/Playgrounds/CosmosEndToEnd/CosmosEndToEnd.ApiService/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/src/Playgrounds/CosmosEndToEnd/CosmosEndToEnd.AppHost/CosmosEndToEnd.AppHost.csproj b/src/Playgrounds/CosmosEndToEnd/CosmosEndToEnd.AppHost/CosmosEndToEnd.AppHost.csproj new file mode 100644 index 00000000000..833f8567418 --- /dev/null +++ b/src/Playgrounds/CosmosEndToEnd/CosmosEndToEnd.AppHost/CosmosEndToEnd.AppHost.csproj @@ -0,0 +1,17 @@ + + + + Exe + net8.0 + enable + enable + true + + + + + + + + + diff --git a/src/Playgrounds/CosmosEndToEnd/CosmosEndToEnd.AppHost/Directory.Build.props b/src/Playgrounds/CosmosEndToEnd/CosmosEndToEnd.AppHost/Directory.Build.props new file mode 100644 index 00000000000..dbc16a48c05 --- /dev/null +++ b/src/Playgrounds/CosmosEndToEnd/CosmosEndToEnd.AppHost/Directory.Build.props @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/Playgrounds/CosmosEndToEnd/CosmosEndToEnd.AppHost/Directory.Build.targets b/src/Playgrounds/CosmosEndToEnd/CosmosEndToEnd.AppHost/Directory.Build.targets new file mode 100644 index 00000000000..ff4a3c400b3 --- /dev/null +++ b/src/Playgrounds/CosmosEndToEnd/CosmosEndToEnd.AppHost/Directory.Build.targets @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/Playgrounds/CosmosEndToEnd/CosmosEndToEnd.AppHost/Program.cs b/src/Playgrounds/CosmosEndToEnd/CosmosEndToEnd.AppHost/Program.cs new file mode 100644 index 00000000000..bf20951d3a2 --- /dev/null +++ b/src/Playgrounds/CosmosEndToEnd/CosmosEndToEnd.AppHost/Program.cs @@ -0,0 +1,13 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +var builder = DistributedApplication.CreateBuilder(args); + +var db = builder.AddAzureCosmosDB("cosmos") + .UseEmulator() + .AddDatabase("db"); + +builder.AddProject("api") + .WithReference(db); + +builder.Build().Run(); diff --git a/src/Playgrounds/CosmosEndToEnd/CosmosEndToEnd.AppHost/Properties/launchSettings.json b/src/Playgrounds/CosmosEndToEnd/CosmosEndToEnd.AppHost/Properties/launchSettings.json new file mode 100644 index 00000000000..539615401c1 --- /dev/null +++ b/src/Playgrounds/CosmosEndToEnd/CosmosEndToEnd.AppHost/Properties/launchSettings.json @@ -0,0 +1,28 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "http://localhost:15129", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development", + "DOTNET_ENVIRONMENT": "Development", + "DOTNET_DASHBOARD_OTLP_ENDPOINT_URL": "http://localhost:16175" + } + }, + "generate-manifest": { + "commandName": "Project", + "launchBrowser": true, + "dotnetRunMessages": true, + "commandLineArgs": "--publisher manifest --output-path aspire-manifest.json", + "applicationUrl": "http://localhost:15129", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development", + "DOTNET_ENVIRONMENT": "Development", + "DOTNET_DASHBOARD_OTLP_ENDPOINT_URL": "http://localhost:16175" + } + } + } +} diff --git a/src/Playgrounds/CosmosEndToEnd/CosmosEndToEnd.AppHost/appsettings.Development.json b/src/Playgrounds/CosmosEndToEnd/CosmosEndToEnd.AppHost/appsettings.Development.json new file mode 100644 index 00000000000..0c208ae9181 --- /dev/null +++ b/src/Playgrounds/CosmosEndToEnd/CosmosEndToEnd.AppHost/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/src/Playgrounds/CosmosEndToEnd/CosmosEndToEnd.AppHost/appsettings.json b/src/Playgrounds/CosmosEndToEnd/CosmosEndToEnd.AppHost/appsettings.json new file mode 100644 index 00000000000..31c092aa450 --- /dev/null +++ b/src/Playgrounds/CosmosEndToEnd/CosmosEndToEnd.AppHost/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning", + "Aspire.Hosting.Dcp": "Warning" + } + } +} diff --git a/src/Playgrounds/Playground.ServiceDefaults/Extensions.cs b/src/Playgrounds/Playground.ServiceDefaults/Extensions.cs new file mode 100644 index 00000000000..30ea4a2493f --- /dev/null +++ b/src/Playgrounds/Playground.ServiceDefaults/Extensions.cs @@ -0,0 +1,122 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Diagnostics.HealthChecks; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Diagnostics.HealthChecks; +using Microsoft.Extensions.Logging; +using OpenTelemetry.Logs; +using OpenTelemetry.Metrics; +using OpenTelemetry.Trace; + +namespace Microsoft.Extensions.Hosting; + +public static class Extensions +{ + public static IHostApplicationBuilder AddServiceDefaults(this IHostApplicationBuilder builder) + { + builder.ConfigureOpenTelemetry(); + + builder.AddDefaultHealthChecks(); + + builder.Services.AddServiceDiscovery(); + + builder.Services.ConfigureHttpClientDefaults(http => + { + // Turn on resilience by default + http.AddStandardResilienceHandler(); + + // Turn on service discovery by default + http.UseServiceDiscovery(); + }); + + return builder; + } + + public static IHostApplicationBuilder ConfigureOpenTelemetry(this IHostApplicationBuilder builder) + { + builder.Logging.AddOpenTelemetry(logging => + { + logging.IncludeFormattedMessage = true; + logging.IncludeScopes = true; + }); + + builder.Services.AddOpenTelemetry() + .WithMetrics(metrics => + { + metrics.AddRuntimeInstrumentation() + .AddBuiltInMeters(); + }) + .WithTracing(tracing => + { + if (builder.Environment.IsDevelopment()) + { + // We want to view all traces in development + tracing.SetSampler(new AlwaysOnSampler()); + } + + tracing.AddAspNetCoreInstrumentation() + .AddGrpcClientInstrumentation() + .AddHttpClientInstrumentation(); + }); + + builder.AddOpenTelemetryExporters(); + + return builder; + } + + private static IHostApplicationBuilder AddOpenTelemetryExporters(this IHostApplicationBuilder builder) + { + var useOtlpExporter = !string.IsNullOrWhiteSpace(builder.Configuration["OTEL_EXPORTER_OTLP_ENDPOINT"]); + + if (useOtlpExporter) + { + builder.Services.Configure(logging => logging.AddOtlpExporter()); + builder.Services.ConfigureOpenTelemetryMeterProvider(metrics => metrics.AddOtlpExporter()); + builder.Services.ConfigureOpenTelemetryTracerProvider(tracing => tracing.AddOtlpExporter()); + } + + // Uncomment the following lines to enable the Prometheus exporter (requires the OpenTelemetry.Exporter.Prometheus.AspNetCore package) + // builder.Services.AddOpenTelemetry() + // .WithMetrics(metrics => metrics.AddPrometheusExporter()); + + // Uncomment the following lines to enable the Azure Monitor exporter (requires the Azure.Monitor.OpenTelemetry.Exporter package) + // builder.Services.AddOpenTelemetry() + // .UseAzureMonitor(); + + return builder; + } + + public static IHostApplicationBuilder AddDefaultHealthChecks(this IHostApplicationBuilder builder) + { + builder.Services.AddHealthChecks() + // Add a default liveness check to ensure app is responsive + .AddCheck("self", () => HealthCheckResult.Healthy(), ["live"]); + + return builder; + } + + public static WebApplication MapDefaultEndpoints(this WebApplication app) + { + // Uncomment the following line to enable the Prometheus endpoint (requires the OpenTelemetry.Exporter.Prometheus.AspNetCore package) + // app.MapPrometheusScrapingEndpoint(); + + // All health checks must pass for app to be considered ready to accept traffic after starting + app.MapHealthChecks("/health"); + + // Only health checks tagged with the "live" tag must pass for app to be considered alive + app.MapHealthChecks("/alive", new HealthCheckOptions + { + Predicate = r => r.Tags.Contains("live") + }); + + return app; + } + + private static MeterProviderBuilder AddBuiltInMeters(this MeterProviderBuilder meterProviderBuilder) => + meterProviderBuilder.AddMeter( + "Microsoft.AspNetCore.Hosting", + "Microsoft.AspNetCore.Server.Kestrel", + "System.Net.Http"); +} diff --git a/src/Playgrounds/Playground.ServiceDefaults/Playground.ServiceDefaults.csproj b/src/Playgrounds/Playground.ServiceDefaults/Playground.ServiceDefaults.csproj new file mode 100644 index 00000000000..dde733791d9 --- /dev/null +++ b/src/Playgrounds/Playground.ServiceDefaults/Playground.ServiceDefaults.csproj @@ -0,0 +1,27 @@ + + + + Library + net8.0 + enable + enable + true + + + + + + + + + + + + + + + + + + +