diff --git a/docs/app-host-overview.md b/docs/app-host-overview.md index e78febfa3..d6fd4a8e1 100644 --- a/docs/app-host-overview.md +++ b/docs/app-host-overview.md @@ -1,7 +1,7 @@ --- title: .NET Aspire orchestration overview description: Learn the fundamental concepts of .NET Aspire orchestration and explore the various APIs to express resource references. -ms.date: 11/15/2023 +ms.date: 12/05/2023 ms.topic: overview --- @@ -11,7 +11,7 @@ ms.topic: overview Before continuing, consider some common terminology used in .NET Aspire: -- **App model**: A collection of resources that make up your distributed application (`DistributedApplication`). For a more formal definition, see [Define the app model](#define-the-app-model). +- **App model**: A collection of resources that make up your distributed application (). For a more formal definition, see [Define the app model](#define-the-app-model). - **App host/Orchestrator project**: The .NET project that orchestrates the _app model_, named with the _*.AppHost_ suffix (by convention). - **Resource**: A [resource](#built-in-resource-types) represents a part of an application whether it be a .NET project, container, or executable, or some other resource like a database, cache, or cloud service (such as a storage service). - **Reference**: A reference defines a connection between resources, expressed as a dependency. For more information, see [Reference resources](#reference-resources). diff --git a/docs/components-overview.md b/docs/components-overview.md index ac4275b00..7cf53ca7a 100644 --- a/docs/components-overview.md +++ b/docs/components-overview.md @@ -1,15 +1,13 @@ --- title: .NET Aspire components overview description: Explore the fundamental concepts of .NET Aspire components and learn how to integrate them into your apps. -ms.date: 12/03/2023 +ms.date: 12/05/2023 ms.topic: conceptual --- # .NET Aspire components overview -.NET Aspire components are a curated suite of NuGet packages specifically selected to facilitate the integration of cloud-native applications with prominent services and platforms, including but not limited to Redis and PostgreSQL. Each component furnishes essential cloud-native functionalities through either automatic provisioning or standardized configuration patterns. - -For more information on working with .NET Aspire components in Visual Studio, see [Visual Studio tooling](setup-tooling.md#visual-studio-tooling). +.NET Aspire components are a curated suite of NuGet packages specifically selected to facilitate the integration of cloud-native applications with prominent services and platforms, including but not limited to Redis and PostgreSQL. Each component furnishes essential cloud-native functionalities through either automatic provisioning or standardized configuration patterns. .NET Aspire components can be used without an orchestrator project, but they're designed to work best with the [.NET Aspire app host](app-host-overview.md). ## Available components @@ -33,11 +31,13 @@ The following table lists the .NET Aspire components currently available for use | [SQL Server Entity Framework Core](database/sql-server-entity-framework-component.md) | [Aspire.Microsoft.EntityFrameworkCore.SqlServer](https://www.nuget.org/packages/Aspire.Microsoft.EntityFrameworkCore.SqlServer) | A library for accessing [SQL Server databases using Entity Framework Core](/ef/core/providers/sql-server/). | | [SQL Server](database/sql-server-component.md) | [Aspire.Microsoft.Data.SqlClient](https://www.nuget.org/packages/Aspire.Microsoft.Data.SqlClient) | A library for accessing [SQL Server](/sql/sql-server/) databases. | +For more information on working with .NET Aspire components in Visual Studio, see [Visual Studio tooling](setup-tooling.md#visual-studio-tooling). + ## Explore a sample component workflow -.NET Aspire components streamline the process of consuming popular services and platforms. For example, you could use the .NET Aspire PostgreSQL component to connect to and utilize a PostgreSQL database. The database could be hosted on-prem or in a cloud service such as Azure, AWS, or GCP. The following steps demonstrate how to integrate this component into your app: +.NET Aspire components streamline the process of consuming popular services and platforms. For example, consider the **.NET Aspire Application** template. With this template, you get the [AppHost](app-host-overview.md) and [ServiceDefaults](service-defaults.md) projects. Imagine that you have a need for a worker service to perform some database processing. You could use the [.NET Aspire PostgreSQL component](database/postgresql-component.md) to connect to and utilize a PostgreSQL database. The database could be hosted on-prem or in a cloud service such as Azure, AWS, or GCP. The following steps demonstrate how to integrate this component into your app: -1. Install the [Aspire.Npgsql](https://www.nuget.org/packages/Aspire.Npgsql) NuGet package. +1. In the component consuming (worker service) project, install the [Aspire.Npgsql](https://www.nuget.org/packages/Aspire.Npgsql) NuGet package. # [.NET CLI](#tab/dotnet-cli) @@ -48,42 +48,44 @@ The following table lists the .NET Aspire components currently available for use # [PackageReference](#tab/package-reference) ```xml - + ``` --- For more information, see [dotnet add package](/dotnet/core/tools/dotnet-add-package) or [Manage package dependencies in .NET applications](/dotnet/core/tools/dependencies). -1. In the _Program.cs_ file of your project, call the extension method to register a `NpgsqlDataSource` for use via the dependency injection container. The method expects a connection name parameter. +1. In the _Program.cs_ file of your worker service project, call the extension method to register `NpgsqlDataSource` as a service. - ```csharp - builder.AddNpgsqlDataSource("PostgreSqlConnection"); - ``` + :::code source="snippets/components/AspireApp/WorkerService/Program.cs" highlight="5"::: - > [!NOTE] - > Components that are designed to connect to Azure services also support passwordless authentication and authorization using Azure RBAC, which is the recommended approach for production apps. + The preceding code adds the `NpgsqlDataSource` to the dependency injection container with the connection name of `"customers"`. The connection name is later used by the orchestrator project, when expressing resource dependencies. -1. Add a connection string with a matching name in your _appsettings.json_ file. + > [!TIP] + > Components that are designed to connect to Azure services also support passwordless authentication and authorization using [Azure RBAC](/azure/role-based-access-control/overview), which is the recommended approach for production apps. - ```json - { - "ConnectionStrings": { - "PostgreSqlConnection": "Host=myserver;Database=test" - } - } - ``` +1. In your orchestrator project (the project with the _*.AppHost_ suffix), add a reference to the worker service project. If you're using Visual Studio, you can use the [**Add .NET Aspire Orchestrator Support**](setup-tooling.md#add-orchestration-projects) project context menu item to add the reference automatically. The following code snippet shows the project reference of the _AspireApp.AppHost.csproj_: -1. Inject the `NpgsqlDataSource` object into your controllers or service endpoints to run commands against the database: + :::code language="xml" source="snippets/components/AspireApp/AspireApp.AppHost/AspireApp.AppHost.csproj" highlight="16"::: - ```csharp - public class ExampleService(NpgsqlDataSource dataSource) - { - } - ``` + After the worker service is referenced by the orchestrator project, the worker service project has its _Program.cs_ file updated to call the `AddServiceDefaults` method. For more information on service defaults, see [Service defaults](service-defaults.md). + +1. In the orchestrator project, update the _Program.cs_ file with the following code: + + :::code source="snippets/components/AspireApp/AspireApp.AppHost/Program.cs" highlight="3-4,6-8"::: + + The preceding code: + + - Calls and chains a call to , adding a PostgreSQL database container to the app model with a database named `"customers"`. + - Chains calls on the result of the from the worker service project: + - Calls to add a reference to the `database`. + - Calls to set the number of replicas to `3`. + +1. Inject the `NpgsqlDataSource` object into the `Worker` to run commands against the database: + + :::code source="snippets/components/AspireApp/WorkerService/Worker.cs" highlight="7,13"::: -After four steps, you now have a fully configured PostgreSQL database component integrated into your app. This component also configured health checks, logging, tracing, metrics, retries, and other useful capabilities for you behind the scenes. .NET Aspire components provide various options to configure each of these features. +You now have a fully configured PostgreSQL database component and corresponding container with connection integrated into your app! This component also configured health checks, logging, metrics, retries, and other useful capabilities for you behind the scenes. .NET Aspire components provide various options to configure each of these features. ## Configure .NET Aspire components diff --git a/docs/database/postgresql-component.md b/docs/database/postgresql-component.md index 6995068c0..1ee4092de 100644 --- a/docs/database/postgresql-component.md +++ b/docs/database/postgresql-component.md @@ -1,7 +1,7 @@ --- title: .NET Aspire PostgreSQL component description: This article describes the .NET Aspire PostgreSQL component. -ms.date: 11/15/2023 +ms.date: 12/05/2023 ms.topic: how-to --- @@ -22,8 +22,7 @@ dotnet add package Aspire.Npgsql --prerelease ### [PackageReference](#tab/package-reference) ```xml - + ``` --- @@ -43,7 +42,7 @@ After adding `NpgsqlDataSource` to the builder, you can get the `NpgsqlDataSourc ```csharp public class ExampleService(NpgsqlDataSource dataSource) { - // Use datasource... + // Use dataSource... } ``` @@ -77,9 +76,11 @@ The following example shows an _appsettings.json_ file that configures some of t ```json { - "Aspire.PostgreSql.Npgsql": { - "ConnectionString": "YOUR_CONNECTIONSTRING", - "Metrics": false + "Aspire": { + "Npgsql": { + "HealthChecks": false, + "Tracing": false + } } } ``` @@ -106,7 +107,7 @@ var exampleProject = builder.AddProject() .WithReference(postgresdb); ``` -The method configures a connection in the `ExampleProject` named `postgresdb`. In the _Program.cs_ file of the `ExampleService` project, the database connection can be consumed using: +The method configures a connection in the `ExampleProject` named `postgresdb`. In the _Program.cs_ file of the `ExampleService` project, the database connection can be consumed using: ```csharp builder.AddNpgsqlDataSource("postgresdb"); diff --git a/docs/snippets/components/AspireApp/AspireApp.AppHost/AspireApp.AppHost.csproj b/docs/snippets/components/AspireApp/AspireApp.AppHost/AspireApp.AppHost.csproj new file mode 100644 index 000000000..7a8f6eac7 --- /dev/null +++ b/docs/snippets/components/AspireApp/AspireApp.AppHost/AspireApp.AppHost.csproj @@ -0,0 +1,19 @@ + + + + Exe + net8.0 + enable + enable + true + + + + + + + + + + + diff --git a/docs/snippets/components/AspireApp/AspireApp.AppHost/Program.cs b/docs/snippets/components/AspireApp/AspireApp.AppHost/Program.cs new file mode 100644 index 000000000..f2b0914df --- /dev/null +++ b/docs/snippets/components/AspireApp/AspireApp.AppHost/Program.cs @@ -0,0 +1,10 @@ +var builder = DistributedApplication.CreateBuilder(args); + +var database = builder.AddPostgresContainer("postgresql") + .AddDatabase("customers"); + +builder.AddProject("workerservice") + .WithReference(database) + .WithReplicas(3); + +builder.Build().Run(); diff --git a/docs/snippets/components/AspireApp/AspireApp.AppHost/Properties/launchSettings.json b/docs/snippets/components/AspireApp/AspireApp.AppHost/Properties/launchSettings.json new file mode 100644 index 000000000..3694cf70b --- /dev/null +++ b/docs/snippets/components/AspireApp/AspireApp.AppHost/Properties/launchSettings.json @@ -0,0 +1,16 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "http://localhost:15021", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development", + "DOTNET_ENVIRONMENT": "Development", + "DOTNET_DASHBOARD_OTLP_ENDPOINT_URL": "http://localhost:16244" + } + } + } +} diff --git a/docs/snippets/components/AspireApp/AspireApp.AppHost/appsettings.Development.json b/docs/snippets/components/AspireApp/AspireApp.AppHost/appsettings.Development.json new file mode 100644 index 000000000..a34cd70c5 --- /dev/null +++ b/docs/snippets/components/AspireApp/AspireApp.AppHost/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/docs/snippets/components/AspireApp/AspireApp.AppHost/appsettings.json b/docs/snippets/components/AspireApp/AspireApp.AppHost/appsettings.json new file mode 100644 index 000000000..27a39347d --- /dev/null +++ b/docs/snippets/components/AspireApp/AspireApp.AppHost/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning", + "Aspire.Hosting.Dcp": "Warning" + } + } +} diff --git a/docs/snippets/components/AspireApp/AspireApp.ServiceDefaults/AspireApp.ServiceDefaults.csproj b/docs/snippets/components/AspireApp/AspireApp.ServiceDefaults/AspireApp.ServiceDefaults.csproj new file mode 100644 index 000000000..00e0d1f1f --- /dev/null +++ b/docs/snippets/components/AspireApp/AspireApp.ServiceDefaults/AspireApp.ServiceDefaults.csproj @@ -0,0 +1,24 @@ + + + + Library + net8.0 + enable + enable + true + + + + + + + + + + + + + + + + diff --git a/docs/snippets/components/AspireApp/AspireApp.ServiceDefaults/Extensions.cs b/docs/snippets/components/AspireApp/AspireApp.ServiceDefaults/Extensions.cs new file mode 100644 index 000000000..c59308d50 --- /dev/null +++ b/docs/snippets/components/AspireApp/AspireApp.ServiceDefaults/Extensions.cs @@ -0,0 +1,119 @@ +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/docs/snippets/components/AspireApp/AspireApp.sln b/docs/snippets/components/AspireApp/AspireApp.sln new file mode 100644 index 000000000..f359e57a0 --- /dev/null +++ b/docs/snippets/components/AspireApp/AspireApp.sln @@ -0,0 +1,36 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.9.34310.174 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AspireApp.AppHost", "AspireApp.AppHost\AspireApp.AppHost.csproj", "{5ECB1120-CA00-4E6B-B58B-119AC1BD7EAB}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AspireApp.ServiceDefaults", "AspireApp.ServiceDefaults\AspireApp.ServiceDefaults.csproj", "{D70B1822-D354-41BC-932F-F38A390E1A95}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WorkerService", "WorkerService\WorkerService.csproj", "{31496392-228C-4C0C-97FC-A311A82723CB}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {5ECB1120-CA00-4E6B-B58B-119AC1BD7EAB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5ECB1120-CA00-4E6B-B58B-119AC1BD7EAB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5ECB1120-CA00-4E6B-B58B-119AC1BD7EAB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5ECB1120-CA00-4E6B-B58B-119AC1BD7EAB}.Release|Any CPU.Build.0 = Release|Any CPU + {D70B1822-D354-41BC-932F-F38A390E1A95}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D70B1822-D354-41BC-932F-F38A390E1A95}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D70B1822-D354-41BC-932F-F38A390E1A95}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D70B1822-D354-41BC-932F-F38A390E1A95}.Release|Any CPU.Build.0 = Release|Any CPU + {31496392-228C-4C0C-97FC-A311A82723CB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {31496392-228C-4C0C-97FC-A311A82723CB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {31496392-228C-4C0C-97FC-A311A82723CB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {31496392-228C-4C0C-97FC-A311A82723CB}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {77615A82-474F-4434-B928-5CF7A56A09C7} + EndGlobalSection +EndGlobal diff --git a/docs/snippets/components/AspireApp/WorkerService/Program.cs b/docs/snippets/components/AspireApp/WorkerService/Program.cs new file mode 100644 index 000000000..a97e3491e --- /dev/null +++ b/docs/snippets/components/AspireApp/WorkerService/Program.cs @@ -0,0 +1,11 @@ +using WorkerService; + +var builder = Host.CreateApplicationBuilder(args); + +builder.AddNpgsqlDataSource("customers"); + +builder.AddServiceDefaults(); +builder.Services.AddHostedService(); + +var host = builder.Build(); +host.Run(); diff --git a/docs/snippets/components/AspireApp/WorkerService/Properties/launchSettings.json b/docs/snippets/components/AspireApp/WorkerService/Properties/launchSettings.json new file mode 100644 index 000000000..1db52499c --- /dev/null +++ b/docs/snippets/components/AspireApp/WorkerService/Properties/launchSettings.json @@ -0,0 +1,12 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "profiles": { + "WorkerService": { + "commandName": "Project", + "dotnetRunMessages": true, + "environmentVariables": { + "DOTNET_ENVIRONMENT": "Development" + } + } + } +} diff --git a/docs/snippets/components/AspireApp/WorkerService/Worker.cs b/docs/snippets/components/AspireApp/WorkerService/Worker.cs new file mode 100644 index 000000000..a203f7225 --- /dev/null +++ b/docs/snippets/components/AspireApp/WorkerService/Worker.cs @@ -0,0 +1,18 @@ +using Npgsql; + +namespace WorkerService; + +public sealed class Worker( + ILogger logger, + NpgsqlDataSource dataSource) : BackgroundService +{ + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + while (!stoppingToken.IsCancellationRequested) + { + // Use the dataSource + + await Task.Delay(15_000, stoppingToken); + } + } +} diff --git a/docs/snippets/components/AspireApp/WorkerService/WorkerService.csproj b/docs/snippets/components/AspireApp/WorkerService/WorkerService.csproj new file mode 100644 index 000000000..944adf4f8 --- /dev/null +++ b/docs/snippets/components/AspireApp/WorkerService/WorkerService.csproj @@ -0,0 +1,18 @@ + + + + net8.0 + enable + enable + dotnet-WorkerService-9b27f862-501e-46ec-982c-ad843ccf73a8 + + + + + + + + + + + diff --git a/docs/snippets/components/AspireApp/WorkerService/appsettings.Development.json b/docs/snippets/components/AspireApp/WorkerService/appsettings.Development.json new file mode 100644 index 000000000..b2dcdb674 --- /dev/null +++ b/docs/snippets/components/AspireApp/WorkerService/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.Hosting.Lifetime": "Information" + } + } +} diff --git a/docs/snippets/components/AspireApp/WorkerService/appsettings.json b/docs/snippets/components/AspireApp/WorkerService/appsettings.json new file mode 100644 index 000000000..b2dcdb674 --- /dev/null +++ b/docs/snippets/components/AspireApp/WorkerService/appsettings.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.Hosting.Lifetime": "Information" + } + } +}