diff --git a/.gitignore b/.gitignore
index efff01f2..d8023b4d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -261,5 +261,5 @@ __pycache__/
*.pyc
.vscode
-
+.DS_Store
appsettings.*.json
\ No newline at end of file
diff --git a/Lectures/Lecture_13/IW5_Lecture13-Deployment_Aspire.pptx b/Lectures/Lecture_13/IW5_Lecture13-Deployment_Aspire.pptx
new file mode 100644
index 00000000..04d3c254
--- /dev/null
+++ b/Lectures/Lecture_13/IW5_Lecture13-Deployment_Aspire.pptx
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:591aef445da4a3f89ab5bd609782d8713c738707beabfede43125dee0fe2cd64
+size 3388756
diff --git a/src/CookBook/.dockerignore b/src/CookBook/.dockerignore
new file mode 100644
index 00000000..cd967fc3
--- /dev/null
+++ b/src/CookBook/.dockerignore
@@ -0,0 +1,25 @@
+**/.dockerignore
+**/.env
+**/.git
+**/.gitignore
+**/.project
+**/.settings
+**/.toolstarget
+**/.vs
+**/.vscode
+**/.idea
+**/*.*proj.user
+**/*.dbmdl
+**/*.jfm
+**/azds.yaml
+**/bin
+**/charts
+**/docker-compose*
+**/Dockerfile*
+**/node_modules
+**/npm-debug.log
+**/obj
+**/secrets.dev.yaml
+**/values.dev.yaml
+LICENSE
+README.md
\ No newline at end of file
diff --git a/src/CookBook/CookBook.Api.App/CookBook.Api.App.csproj b/src/CookBook/CookBook.Api.App/CookBook.Api.App.csproj
index 516e0ffb..70f618c5 100644
--- a/src/CookBook/CookBook.Api.App/CookBook.Api.App.csproj
+++ b/src/CookBook/CookBook.Api.App/CookBook.Api.App.csproj
@@ -2,6 +2,7 @@
70e22f70-c609-4d6a-9be8-b462915b90e5
+ Linux
@@ -20,8 +21,14 @@
+
+
+
+ .dockerignore
+
+
diff --git a/src/CookBook/CookBook.Api.App/Dockerfile b/src/CookBook/CookBook.Api.App/Dockerfile
new file mode 100644
index 00000000..ac2b4f65
--- /dev/null
+++ b/src/CookBook/CookBook.Api.App/Dockerfile
@@ -0,0 +1,31 @@
+FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
+USER $APP_UID
+WORKDIR /app
+EXPOSE 8080
+
+FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
+ARG BUILD_CONFIGURATION=Release
+WORKDIR /src
+COPY Directory.Build.props .
+COPY ["CookBook.Api.App/CookBook.Api.App.csproj", "CookBook.Api.App/"]
+COPY ["CookBook.Api.BL/CookBook.Api.BL.csproj", "CookBook.Api.BL/"]
+COPY ["CookBook.Api.DAL.Common/CookBook.Api.DAL.Common.csproj", "CookBook.Api.DAL.Common/"]
+COPY ["CookBook.Common/CookBook.Common.csproj", "CookBook.Common/"]
+COPY ["CookBook.Common.BL/CookBook.Common.BL.csproj", "CookBook.Common.BL/"]
+COPY ["CookBook.Common.Models/CookBook.Common.Models.csproj", "CookBook.Common.Models/"]
+COPY ["CookBook.Api.DAL.EF/CookBook.Api.DAL.EF.csproj", "CookBook.Api.DAL.EF/"]
+COPY ["CookBook.Api.DAL.Memory/CookBook.Api.DAL.Memory.csproj", "CookBook.Api.DAL.Memory/"]
+COPY ["CookBook.Hosting.ServiceDefaults/CookBook.Hosting.ServiceDefaults.csproj", "CookBook.Hosting.ServiceDefaults/"]
+RUN dotnet restore "CookBook.Api.App/CookBook.Api.App.csproj"
+COPY . .
+WORKDIR "/src/CookBook.Api.App"
+RUN dotnet build "CookBook.Api.App.csproj" -c $BUILD_CONFIGURATION -o /app/build
+
+FROM build AS publish
+ARG BUILD_CONFIGURATION=Release
+RUN dotnet publish "CookBook.Api.App.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false
+
+FROM base AS final
+WORKDIR /app
+COPY --from=publish /app/publish .
+ENTRYPOINT ["dotnet", "CookBook.Api.App.dll"]
diff --git a/src/CookBook/CookBook.Api.App/Program.cs b/src/CookBook/CookBook.Api.App/Program.cs
index edb4f316..9da0e3d3 100644
--- a/src/CookBook/CookBook.Api.App/Program.cs
+++ b/src/CookBook/CookBook.Api.App/Program.cs
@@ -1,8 +1,5 @@
-using System;
-using System.Collections.Generic;
-using System.Globalization;
+using System.Globalization;
using AutoMapper;
-using AutoMapper.Internal;
using CookBook.Api.App.Extensions;
using CookBook.Api.App.Processors;
using CookBook.Api.BL.Facades;
@@ -15,18 +12,13 @@
using CookBook.Common.Extensions;
using CookBook.Common.Models;
using CookBook.Common.Resources;
-using Microsoft.AspNetCore.Builder;
-using Microsoft.AspNetCore.Hosting;
-using Microsoft.AspNetCore.Http;
+using CookBook.Hosting.ServiceDefaults;
using Microsoft.AspNetCore.Http.HttpResults;
using Microsoft.AspNetCore.Localization;
-using Microsoft.AspNetCore.Routing;
-using Microsoft.Extensions.Configuration;
-using Microsoft.Extensions.DependencyInjection;
-using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Localization;
var builder = WebApplication.CreateBuilder();
+builder.AddServiceDefaults();
ConfigureCors(builder.Services);
ConfigureLocalization(builder.Services);
@@ -36,6 +28,7 @@
ConfigureAutoMapper(builder.Services);
var app = builder.Build();
+app.MapDefaultEndpoints();
ValidateAutoMapperConfiguration(app.Services);
diff --git a/src/CookBook/CookBook.API.DAL.IntegrationTests/CookBook.API.DAL.IntegrationTests.csproj b/src/CookBook/CookBook.Api.DAL.IntegrationTests/CookBook.Api.DAL.IntegrationTests.csproj
similarity index 100%
rename from src/CookBook/CookBook.API.DAL.IntegrationTests/CookBook.API.DAL.IntegrationTests.csproj
rename to src/CookBook/CookBook.Api.DAL.IntegrationTests/CookBook.Api.DAL.IntegrationTests.csproj
diff --git a/src/CookBook/CookBook.API.DAL.IntegrationTests/IDatabaseFixture.cs b/src/CookBook/CookBook.Api.DAL.IntegrationTests/IDatabaseFixture.cs
similarity index 91%
rename from src/CookBook/CookBook.API.DAL.IntegrationTests/IDatabaseFixture.cs
rename to src/CookBook/CookBook.Api.DAL.IntegrationTests/IDatabaseFixture.cs
index 237afc2c..25da565e 100644
--- a/src/CookBook/CookBook.API.DAL.IntegrationTests/IDatabaseFixture.cs
+++ b/src/CookBook/CookBook.Api.DAL.IntegrationTests/IDatabaseFixture.cs
@@ -3,7 +3,7 @@
using CookBook.Api.DAL.Common.Entities;
using CookBook.Api.DAL.Common.Repositories;
-namespace CookBook.API.DAL.IntegrationTests;
+namespace CookBook.Api.DAL.IntegrationTests;
public interface IDatabaseFixture
{
diff --git a/src/CookBook/CookBook.API.DAL.IntegrationTests/InMemoryDatabaseFixture.cs b/src/CookBook/CookBook.Api.DAL.IntegrationTests/InMemoryDatabaseFixture.cs
similarity index 98%
rename from src/CookBook/CookBook.API.DAL.IntegrationTests/InMemoryDatabaseFixture.cs
rename to src/CookBook/CookBook.Api.DAL.IntegrationTests/InMemoryDatabaseFixture.cs
index 9fb6e754..5935adde 100644
--- a/src/CookBook/CookBook.API.DAL.IntegrationTests/InMemoryDatabaseFixture.cs
+++ b/src/CookBook/CookBook.Api.DAL.IntegrationTests/InMemoryDatabaseFixture.cs
@@ -8,7 +8,7 @@
using CookBook.Common.Enums;
using Newtonsoft.Json;
-namespace CookBook.API.DAL.IntegrationTests;
+namespace CookBook.Api.DAL.IntegrationTests;
public class InMemoryDatabaseFixture : IDatabaseFixture
{
diff --git a/src/CookBook/CookBook.API.DAL.IntegrationTests/RecipeRepositoryTests.cs b/src/CookBook/CookBook.Api.DAL.IntegrationTests/RecipeRepositoryTests.cs
similarity index 99%
rename from src/CookBook/CookBook.API.DAL.IntegrationTests/RecipeRepositoryTests.cs
rename to src/CookBook/CookBook.Api.DAL.IntegrationTests/RecipeRepositoryTests.cs
index 55e52d78..0013763d 100644
--- a/src/CookBook/CookBook.API.DAL.IntegrationTests/RecipeRepositoryTests.cs
+++ b/src/CookBook/CookBook.Api.DAL.IntegrationTests/RecipeRepositoryTests.cs
@@ -5,7 +5,7 @@
using CookBook.Common.Enums;
using Xunit;
-namespace CookBook.API.DAL.IntegrationTests;
+namespace CookBook.Api.DAL.IntegrationTests;
public class RecipeRepositoryTests
{
diff --git a/src/CookBook/CookBook.Hosting.AppHost/.config/dotnet-tools.json b/src/CookBook/CookBook.Hosting.AppHost/.config/dotnet-tools.json
new file mode 100644
index 00000000..4c9769d1
--- /dev/null
+++ b/src/CookBook/CookBook.Hosting.AppHost/.config/dotnet-tools.json
@@ -0,0 +1,12 @@
+{
+ "version": 1,
+ "isRoot": false,
+ "tools": {
+ "aspirate": {
+ "version": "8.0.7",
+ "commands": [
+ "aspirate"
+ ]
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/CookBook/CookBook.Hosting.AppHost/CookBook.Hosting.AppHost.csproj b/src/CookBook/CookBook.Hosting.AppHost/CookBook.Hosting.AppHost.csproj
new file mode 100644
index 00000000..30d4c001
--- /dev/null
+++ b/src/CookBook/CookBook.Hosting.AppHost/CookBook.Hosting.AppHost.csproj
@@ -0,0 +1,22 @@
+
+
+
+ Exe
+ net8.0
+ enable
+ enable
+ true
+ 65e3f962-7661-40d1-b440-1a322d8d8dba
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/CookBook/CookBook.Hosting.AppHost/Program.cs b/src/CookBook/CookBook.Hosting.AppHost/Program.cs
new file mode 100644
index 00000000..be9f63db
--- /dev/null
+++ b/src/CookBook/CookBook.Hosting.AppHost/Program.cs
@@ -0,0 +1,92 @@
+using Microsoft.Extensions.Configuration;
+
+var builder = DistributedApplication.CreateBuilder(args);
+builder.Configuration.AddEnvironmentVariables();
+
+var prometheus = builder.AddContainer("prometheus", "prom/prometheus", "v2.53.2")
+ .WithHttpEndpoint(targetPort: 9090, isProxied: false)
+ .WithBindMount("./configs/prometheus/config.yaml", "/mnt/config/local-config.yaml", isReadOnly: true)
+ .WithArgs("--config.file=/mnt/config/local-config.yaml", "--web.enable-remote-write-receiver");
+
+var grafanaLoki = builder.AddContainer("grafanaLoki", "grafana/loki", "3.2.0")
+ .WithHttpEndpoint(targetPort: 3100, isProxied: false)
+ .WithBindMount("./configs/loki/config.yaml", "/mnt/config/local-config.yaml", isReadOnly: true)
+ .WithArgs("-config.file=/mnt/config/local-config.yaml");
+
+var grafanaTempo = builder.AddContainer("grafanaTempo", "grafana/tempo", "2.6.0")
+ .WithHttpEndpoint(targetPort: 3200, isProxied: false)
+ .WithEndpoint(targetPort: 9097, name: "grpc", isProxied: false)
+ .WithBindMount("./configs/tempo/config.yaml", "/mnt/config/local-config.yaml", isReadOnly: true)
+ .WithArgs("-config.file=/mnt/config/local-config.yaml");
+
+var grafana = builder.AddContainer("grafana", "grafana/grafana", "11.2.0")
+ .WithHttpEndpoint(targetPort: 3000)
+ .WithBindMount("./configs/grafana/config", "/etc/grafana", isReadOnly: true)
+ .WithBindMount("./configs/grafana/dashboards", "/var/lib/grafana/dashboards", isReadOnly: true)
+ .WithVolume("grafana-data", "/var/lib/grafana")
+ .WithReference(grafanaTempo.GetEndpoint("http"))
+ .WithReference(prometheus.GetEndpoint("http"))
+ .WithReference(grafanaLoki.GetEndpoint("http"));
+
+var otelCollector = builder.AddContainer("otel-collector", "otel/opentelemetry-collector", "0.109.0")
+ .WithEndpoint(targetPort: 4317, name: "grpc", scheme: "http", isProxied: false)
+ .WithEndpoint(targetPort: 4318, name: "http", scheme: "http", isProxied: false)
+ .WithBindMount("./configs/otel-collector/config.yaml", "/etc/otelcol/config.yaml", isReadOnly: true)
+ .WithEnvironment("GRAFANA_TEMPO_GRPC", () =>
+ {
+ var endpoint = grafanaTempo.GetEndpoint("grpc");
+ return $"{endpoint.ContainerHost}:{endpoint.TargetPort}";
+ })
+ .WithEnvironment(context =>
+ {
+ if (builder.Configuration.GetValue("DOTNET_DASHBOARD_OTLP_ENDPOINT_URL") is {} otlpEndpoint)
+ {
+ var endpointUrl = new HostUrl(otlpEndpoint);
+ context.EnvironmentVariables["DOTNET_DASHBOARD_OTLP_ENDPOINT"] = endpointUrl;
+ }
+ })
+ .WithReference(grafanaTempo.GetEndpoint("grpc"))
+ .WithReference(prometheus.GetEndpoint("http"))
+ .WithReference(grafanaLoki.GetEndpoint("http"));
+
+var api = builder.AddProject("api")
+ .WithHttpEndpoint();
+
+var web = builder.AddProject("web");
+
+var gateway = builder.AddProject("gateway")
+ .WithReference(api)
+ .WithReference(web)
+ .WithHttpEndpoint()
+ .WithExternalHttpEndpoints();
+
+
+if (builder.ExecutionContext.IsPublishMode)
+{
+ web.WithHttpEndpoint(targetPort: 8080, env: "HTTP_PORT");
+}
+else
+{
+ web.WithHttpEndpoint();
+}
+
+ConfigureOpenTelemetryExporter(api);
+ConfigureOpenTelemetryExporter(gateway);
+
+builder.Build().Run();
+return;
+
+void ConfigureOpenTelemetryExporter(IResourceBuilder resourceBuilder) where T : IResourceWithEnvironment
+{
+ if (builder.Configuration.GetValue("SendMetricsToCollector"))
+ {
+ var grpcEndpoint = otelCollector.GetEndpoint("grpc");
+ resourceBuilder.WithEnvironment("OTEL_EXPORTER_OTLP_ENDPOINT", grpcEndpoint);
+ resourceBuilder.WithEnvironment("OTEL_EXPORTER_OTLP_PROTOCOL", "grpc");
+ }
+ else
+ {
+ // sends metrics to dashboard
+ resourceBuilder.WithOtlpExporter();
+ }
+}
diff --git a/src/CookBook/CookBook.Hosting.AppHost/Properties/launchSettings.json b/src/CookBook/CookBook.Hosting.AppHost/Properties/launchSettings.json
new file mode 100644
index 00000000..24c7eaf4
--- /dev/null
+++ b/src/CookBook/CookBook.Hosting.AppHost/Properties/launchSettings.json
@@ -0,0 +1,19 @@
+{
+ "$schema": "https://json.schemastore.org/launchsettings.json",
+ "profiles": {
+ "http": {
+ "commandName": "Project",
+ "dotnetRunMessages": true,
+ "launchBrowser": true,
+ "applicationUrl": "http://localhost:15079",
+ "environmentVariables": {
+ "ASPIRE_ALLOW_UNSECURED_TRANSPORT": "true",
+// "DOTNET_DASHBOARD_UNSECURED_ALLOW_ANONYMOUS": "true",
+ "ASPNETCORE_ENVIRONMENT": "Development",
+ "DOTNET_ENVIRONMENT": "Development",
+ "DOTNET_DASHBOARD_OTLP_ENDPOINT_URL": "http://localhost:19066",
+ "DOTNET_RESOURCE_SERVICE_ENDPOINT_URL": "http://localhost:20096"
+ }
+ }
+ }
+}
diff --git a/src/CookBook/CookBook.Hosting.AppHost/README.md b/src/CookBook/CookBook.Hosting.AppHost/README.md
new file mode 100644
index 00000000..0b7d7f5d
--- /dev/null
+++ b/src/CookBook/CookBook.Hosting.AppHost/README.md
@@ -0,0 +1,20 @@
+# .NET Aspire AppHost
+
+## Install / Restore
+```sh
+dotnet workload install aspire
+dotnet tool install -g aspirate
+```
+
+```sh
+dotnet workload update
+dotnet tool restore
+```
+
+## Generate output
+Based on already existing `aspirate-state.json` this command will generate a Docker Compose file along with program images.
+These will be stored locally.
+
+```shell
+aspirate generate
+```
diff --git a/src/CookBook/CookBook.Hosting.AppHost/appsettings.json b/src/CookBook/CookBook.Hosting.AppHost/appsettings.json
new file mode 100644
index 00000000..4e4ded73
--- /dev/null
+++ b/src/CookBook/CookBook.Hosting.AppHost/appsettings.json
@@ -0,0 +1,15 @@
+{
+ "SendMetricsToCollector": true,
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft.AspNetCore": "Warning",
+ "Aspire.Hosting.Dcp": "Warning"
+ }
+ },
+ "Dashboard": {
+ "Otlp": {
+ "AuthMode": "Unsecured"
+ }
+ }
+}
diff --git a/src/CookBook/CookBook.Hosting.AppHost/aspirate-output/docker-compose.yaml b/src/CookBook/CookBook.Hosting.AppHost/aspirate-output/docker-compose.yaml
new file mode 100644
index 00000000..d4331cf8
--- /dev/null
+++ b/src/CookBook/CookBook.Hosting.AppHost/aspirate-output/docker-compose.yaml
@@ -0,0 +1,42 @@
+services:
+ api:
+ container_name: "api"
+ image: "iw5/api:latest"
+ environment:
+ OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EXCEPTION_LOG_ATTRIBUTES: "true"
+ OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EVENT_LOG_ATTRIBUTES: "true"
+ OTEL_DOTNET_EXPERIMENTAL_OTLP_RETRY: "in_memory"
+ ASPNETCORE_FORWARDEDHEADERS_ENABLED: "true"
+ HTTP_PORTS: "8080"
+ ports:
+ - target: 8080
+ published: 10000
+ restart: unless-stopped
+ web:
+ container_name: "web"
+ image: "iw5/web:latest"
+ environment:
+ OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EXCEPTION_LOG_ATTRIBUTES: "true"
+ OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EVENT_LOG_ATTRIBUTES: "true"
+ OTEL_DOTNET_EXPERIMENTAL_OTLP_RETRY: "in_memory"
+ ASPNETCORE_FORWARDEDHEADERS_ENABLED: "true"
+ HTTP_PORT: "8080"
+ ports:
+ - target: 8080
+ published: 10001
+ restart: unless-stopped
+ gateway:
+ container_name: "gateway"
+ image: "iw5/gateway:latest"
+ environment:
+ OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EXCEPTION_LOG_ATTRIBUTES: "true"
+ OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EVENT_LOG_ATTRIBUTES: "true"
+ OTEL_DOTNET_EXPERIMENTAL_OTLP_RETRY: "in_memory"
+ ASPNETCORE_FORWARDEDHEADERS_ENABLED: "true"
+ HTTP_PORTS: "8080"
+ services__api__http__0: "http://api:8080"
+ services__web__http__0: "http://web:8080"
+ ports:
+ - target: 8080
+ published: 10002
+ restart: unless-stopped
diff --git a/src/CookBook/CookBook.Hosting.AppHost/aspirate-state.json b/src/CookBook/CookBook.Hosting.AppHost/aspirate-state.json
new file mode 100644
index 00000000..599c8c25
--- /dev/null
+++ b/src/CookBook/CookBook.Hosting.AppHost/aspirate-state.json
@@ -0,0 +1,13 @@
+{
+ "projectPath": ".",
+ "outputPath": "aspirate-output",
+ "containerImageTags": [
+ "latest"
+ ],
+ "containerBuilder": "docker",
+ "containerRepositoryPrefix": "iw5",
+ "outputFormat": "compose",
+ "disableSecrets": true,
+ "privateRegistryEmail": "aspir8@aka.ms",
+ "processAllComponents": true
+}
\ No newline at end of file
diff --git a/src/CookBook/CookBook.Hosting.AppHost/aspirate.json b/src/CookBook/CookBook.Hosting.AppHost/aspirate.json
new file mode 100644
index 00000000..b449d196
--- /dev/null
+++ b/src/CookBook/CookBook.Hosting.AppHost/aspirate.json
@@ -0,0 +1,11 @@
+{
+ "TemplatePath": null,
+ "ContainerSettings": {
+ "Registry": null,
+ "RepositoryPrefix": "iw5",
+ "Tags": [
+ "latest"
+ ],
+ "Builder": null
+ }
+}
\ No newline at end of file
diff --git a/src/CookBook/CookBook.Hosting.AppHost/configs/grafana/config/grafana.ini b/src/CookBook/CookBook.Hosting.AppHost/configs/grafana/config/grafana.ini
new file mode 100644
index 00000000..967b2a47
--- /dev/null
+++ b/src/CookBook/CookBook.Hosting.AppHost/configs/grafana/config/grafana.ini
@@ -0,0 +1,16 @@
+[auth.anonymous]
+enabled = true
+
+# Organization name that should be used for unauthenticated users
+org_name = IW5
+
+# Role for unauthenticated users, other valid values are `Editor` and `Admin`
+org_role = Admin
+
+# Hide the Grafana version text from the footer and help tooltip for unauthenticated users (default: false)
+hide_version = false
+
+[dashboards]
+default_home_dashboard_path = /var/lib/grafana/dashboards/aspnetcore.json
+
+min_refresh_interval = 1s
diff --git a/src/CookBook/CookBook.Hosting.AppHost/configs/grafana/config/provisioning/dashboards/default.yaml b/src/CookBook/CookBook.Hosting.AppHost/configs/grafana/config/provisioning/dashboards/default.yaml
new file mode 100644
index 00000000..316b5f76
--- /dev/null
+++ b/src/CookBook/CookBook.Hosting.AppHost/configs/grafana/config/provisioning/dashboards/default.yaml
@@ -0,0 +1,9 @@
+apiVersion: 1
+
+providers:
+ - name: Default
+ folder: .NET
+ type: file
+ options:
+ path:
+ /var/lib/grafana/dashboards
diff --git a/src/CookBook/CookBook.Hosting.AppHost/configs/grafana/config/provisioning/datasources/default.yaml b/src/CookBook/CookBook.Hosting.AppHost/configs/grafana/config/provisioning/datasources/default.yaml
new file mode 100644
index 00000000..45c377eb
--- /dev/null
+++ b/src/CookBook/CookBook.Hosting.AppHost/configs/grafana/config/provisioning/datasources/default.yaml
@@ -0,0 +1,45 @@
+apiVersion: 1
+
+datasources:
+ - name: Prometheus
+ type: prometheus
+ access: proxy
+ url: $__env{services__prometheus__http__0}
+ uid: prometheus
+ isDefault: true
+ - name: Loki
+ type: loki
+ access: proxy
+ url: $__env{services__grafanaLoki__http__0}
+ uid: loki
+ jsonData:
+ timeout: 300
+ maxLines: 100000
+ - name: Tempo
+ type: tempo
+ access: proxy
+ url: $__env{services__grafanaTempo__http__0}
+ uid: tempo
+ basicAuth: false
+ jsonData:
+ serviceMap:
+ datasourceUid: 'prometheus'
+ tracesToLogsV2:
+ datasourceUid: 'loki'
+ spanStartTimeShift: '-1h'
+ spanEndTimeShift: '1h'
+ tracesToMetrics:
+ datasourceUid: 'prometheus'
+ spanStartTimeShift: '1h'
+ spanEndTimeShift: '-1h'
+ nodeGraph:
+ enabled: true
+ search:
+ hide: false
+ traceQuery:
+ timeShiftEnabled: true
+ spanStartTimeShift: '1h'
+ spanEndTimeShift: '-1h'
+ spanBar:
+ type: 'Tag'
+ tag: 'url.path'
diff --git a/src/CookBook/CookBook.Hosting.AppHost/configs/grafana/dashboards/aspnetcore.json b/src/CookBook/CookBook.Hosting.AppHost/configs/grafana/dashboards/aspnetcore.json
new file mode 100644
index 00000000..c5131fa9
--- /dev/null
+++ b/src/CookBook/CookBook.Hosting.AppHost/configs/grafana/dashboards/aspnetcore.json
@@ -0,0 +1,2068 @@
+{
+ "__inputs": [],
+ "__elements": {},
+ "__requires": [
+ {
+ "type": "grafana",
+ "id": "grafana",
+ "name": "Grafana",
+ "version": "10.1.4"
+ },
+ {
+ "type": "datasource",
+ "id": "prometheus",
+ "name": "Prometheus",
+ "version": "1.0.0"
+ },
+ {
+ "type": "panel",
+ "id": "stat",
+ "name": "Stat",
+ "version": ""
+ },
+ {
+ "type": "panel",
+ "id": "timeseries",
+ "name": "Time series",
+ "version": ""
+ }
+ ],
+ "annotations": {
+ "list": [
+ {
+ "builtIn": 1,
+ "datasource": {
+ "type": "grafana",
+ "uid": "-- Grafana --"
+ },
+ "enable": true,
+ "hide": true,
+ "iconColor": "rgba(0, 211, 255, 1)",
+ "name": "Annotations & Alerts",
+ "target": {
+ "limit": 100,
+ "matchAny": false,
+ "tags": [],
+ "type": "dashboard"
+ },
+ "type": "dashboard"
+ }
+ ]
+ },
+ "description": "Shows ASP.NET metrics from OpenTelemetry NuGet",
+ "editable": true,
+ "fiscalYearStartMonth": 0,
+ "graphTooltip": 0,
+ "id": null,
+ "links": [],
+ "liveNow": false,
+ "panels": [
+ {
+ "collapsed": false,
+ "gridPos": {
+ "h": 1,
+ "w": 24,
+ "x": 0,
+ "y": 0
+ },
+ "id": 15,
+ "panels": [],
+ "title": "Process",
+ "type": "row"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "prometheus"
+ },
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "palette-classic"
+ },
+ "custom": {
+ "axisCenteredZero": false,
+ "axisColorMode": "text",
+ "axisLabel": "",
+ "axisPlacement": "auto",
+ "barAlignment": 0,
+ "drawStyle": "line",
+ "fillOpacity": 90,
+ "gradientMode": "opacity",
+ "hideFrom": {
+ "legend": false,
+ "tooltip": false,
+ "viz": false
+ },
+ "insertNulls": false,
+ "lineInterpolation": "smooth",
+ "lineWidth": 1,
+ "pointSize": 5,
+ "scaleDistribution": {
+ "type": "linear"
+ },
+ "showPoints": "never",
+ "spanNulls": false,
+ "stacking": {
+ "group": "A",
+ "mode": "normal"
+ },
+ "thresholdsStyle": {
+ "mode": "off"
+ }
+ },
+ "mappings": [],
+ "min": 0,
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ }
+ ]
+ },
+ "unit": "percentunit"
+ },
+ "overrides": [
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "system"
+ },
+ "properties": [
+ {
+ "id": "color",
+ "value": {
+ "fixedColor": "dark-orange",
+ "mode": "fixed"
+ }
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "user"
+ },
+ "properties": [
+ {
+ "id": "color",
+ "value": {
+ "fixedColor": "dark-green",
+ "mode": "fixed"
+ }
+ }
+ ]
+ }
+ ]
+ },
+ "gridPos": {
+ "h": 9,
+ "w": 11,
+ "x": 0,
+ "y": 1
+ },
+ "id": 19,
+ "options": {
+ "legend": {
+ "calcs": [
+ "lastNotNull",
+ "min",
+ "max",
+ "mean"
+ ],
+ "displayMode": "table",
+ "placement": "bottom",
+ "showLegend": true
+ },
+ "tooltip": {
+ "mode": "multi",
+ "sort": "none"
+ }
+ },
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "prometheus"
+ },
+ "editorMode": "code",
+ "expr": "irate(process_cpu_time_seconds_total{job=\"$job\", instance=\"$instance\"}[$__rate_interval])",
+ "legendFormat": "__auto",
+ "range": true,
+ "refId": "CPU Usage"
+ }
+ ],
+ "title": "CPU Usage",
+ "transformations": [
+ {
+ "id": "labelsToFields",
+ "options": {
+ "keepLabels": [
+ "state"
+ ],
+ "valueLabel": "state"
+ }
+ }
+ ],
+ "type": "timeseries"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "prometheus"
+ },
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "fixedColor": "dark-green",
+ "mode": "fixed"
+ },
+ "custom": {
+ "axisCenteredZero": false,
+ "axisColorMode": "text",
+ "axisLabel": "",
+ "axisPlacement": "auto",
+ "barAlignment": 0,
+ "drawStyle": "line",
+ "fillOpacity": 90,
+ "gradientMode": "opacity",
+ "hideFrom": {
+ "legend": false,
+ "tooltip": false,
+ "viz": false
+ },
+ "insertNulls": false,
+ "lineInterpolation": "smooth",
+ "lineStyle": {
+ "fill": "solid"
+ },
+ "lineWidth": 1,
+ "pointSize": 5,
+ "scaleDistribution": {
+ "type": "linear"
+ },
+ "showPoints": "never",
+ "spanNulls": false,
+ "stacking": {
+ "group": "A",
+ "mode": "none"
+ },
+ "thresholdsStyle": {
+ "mode": "off"
+ }
+ },
+ "mappings": [],
+ "min": 0,
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ }
+ ]
+ },
+ "unit": "bytes"
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 9,
+ "w": 10,
+ "x": 11,
+ "y": 1
+ },
+ "id": 16,
+ "options": {
+ "legend": {
+ "calcs": [
+ "lastNotNull",
+ "min",
+ "max",
+ "mean"
+ ],
+ "displayMode": "table",
+ "placement": "bottom",
+ "showLegend": true
+ },
+ "tooltip": {
+ "mode": "single",
+ "sort": "none"
+ }
+ },
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "prometheus"
+ },
+ "editorMode": "code",
+ "expr": "max_over_time(process_memory_usage_bytes{job=\"$job\", instance=\"$instance\"}[$__rate_interval])",
+ "legendFormat": "Memory Usage",
+ "range": true,
+ "refId": "Memory Usage"
+ }
+ ],
+ "title": "Memory Usage",
+ "type": "timeseries"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "prometheus"
+ },
+ "description": "",
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "thresholds"
+ },
+ "mappings": [],
+ "min": 0,
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "dark-green",
+ "value": null
+ },
+ {
+ "color": "dark-yellow",
+ "value": 50
+ },
+ {
+ "color": "dark-orange",
+ "value": 100
+ },
+ {
+ "color": "dark-red",
+ "value": 150
+ }
+ ]
+ },
+ "unit": "none"
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 9,
+ "w": 3,
+ "x": 21,
+ "y": 1
+ },
+ "id": 12,
+ "options": {
+ "colorMode": "value",
+ "graphMode": "area",
+ "justifyMode": "auto",
+ "orientation": "auto",
+ "reduceOptions": {
+ "calcs": [
+ "lastNotNull"
+ ],
+ "fields": "",
+ "values": false
+ },
+ "textMode": "value"
+ },
+ "pluginVersion": "10.1.4",
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "prometheus"
+ },
+ "editorMode": "code",
+ "expr": "max_over_time(process_threads{job=\"$job\", instance=\"$instance\"}[$__rate_interval])",
+ "legendFormat": "Threads",
+ "range": true,
+ "refId": "Threads"
+ }
+ ],
+ "title": "Threads",
+ "type": "stat"
+ },
+ {
+ "collapsed": false,
+ "gridPos": {
+ "h": 1,
+ "w": 24,
+ "x": 0,
+ "y": 10
+ },
+ "id": 2,
+ "panels": [],
+ "title": "Runtime",
+ "type": "row"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "prometheus"
+ },
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "continuous-GrYlRd",
+ "seriesBy": "max"
+ },
+ "custom": {
+ "axisCenteredZero": false,
+ "axisColorMode": "text",
+ "axisLabel": "",
+ "axisPlacement": "auto",
+ "barAlignment": 0,
+ "drawStyle": "line",
+ "fillOpacity": 50,
+ "gradientMode": "opacity",
+ "hideFrom": {
+ "legend": false,
+ "tooltip": false,
+ "viz": false
+ },
+ "insertNulls": false,
+ "lineInterpolation": "smooth",
+ "lineStyle": {
+ "fill": "solid"
+ },
+ "lineWidth": 1,
+ "pointSize": 5,
+ "scaleDistribution": {
+ "type": "linear"
+ },
+ "showPoints": "never",
+ "spanNulls": false,
+ "stacking": {
+ "group": "A",
+ "mode": "none"
+ },
+ "thresholdsStyle": {
+ "mode": "off"
+ }
+ },
+ "mappings": [],
+ "min": 0,
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ },
+ {
+ "color": "red",
+ "value": 80
+ }
+ ]
+ },
+ "unit": "bytes"
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 9,
+ "w": 8,
+ "x": 0,
+ "y": 11
+ },
+ "id": 6,
+ "options": {
+ "legend": {
+ "calcs": [
+ "lastNotNull",
+ "min",
+ "max",
+ "mean"
+ ],
+ "displayMode": "table",
+ "placement": "bottom",
+ "showLegend": true
+ },
+ "tooltip": {
+ "mode": "multi",
+ "sort": "none"
+ }
+ },
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "prometheus"
+ },
+ "editorMode": "code",
+ "expr": "max_over_time(process_runtime_dotnet_gc_committed_memory_size_bytes{job=\"$job\", instance=\"$instance\"}[$__rate_interval])",
+ "hide": false,
+ "legendFormat": "Committed Memory Size",
+ "range": true,
+ "refId": "Committed Memory Size"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "prometheus"
+ },
+ "editorMode": "code",
+ "expr": "max_over_time(process_runtime_dotnet_gc_objects_size_bytes{job=\"$job\", instance=\"$instance\"}[$__rate_interval])",
+ "legendFormat": "Objects Size",
+ "range": true,
+ "refId": "Objects Size"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "prometheus"
+ },
+ "editorMode": "code",
+ "exemplar": false,
+ "expr": "irate(process_runtime_dotnet_gc_allocations_size_bytes_total{job=\"$job\", instance=\"$instance\"}[$__rate_interval])",
+ "hide": false,
+ "instant": false,
+ "legendFormat": "Allocations Size",
+ "range": true,
+ "refId": "Allocations Size"
+ }
+ ],
+ "title": "General Memory Usage",
+ "type": "timeseries"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "prometheus"
+ },
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "fixedColor": "text",
+ "mode": "fixed"
+ },
+ "custom": {
+ "axisCenteredZero": false,
+ "axisColorMode": "text",
+ "axisLabel": "",
+ "axisPlacement": "auto",
+ "barAlignment": 0,
+ "drawStyle": "line",
+ "fillOpacity": 60,
+ "gradientMode": "none",
+ "hideFrom": {
+ "legend": false,
+ "tooltip": false,
+ "viz": false
+ },
+ "insertNulls": false,
+ "lineInterpolation": "smooth",
+ "lineWidth": 0,
+ "pointSize": 5,
+ "scaleDistribution": {
+ "type": "linear"
+ },
+ "showPoints": "never",
+ "spanNulls": false,
+ "stacking": {
+ "group": "A",
+ "mode": "normal"
+ },
+ "thresholdsStyle": {
+ "mode": "off"
+ }
+ },
+ "mappings": [],
+ "min": 0,
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ }
+ ]
+ },
+ "unit": "bytes"
+ },
+ "overrides": [
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "gen0"
+ },
+ "properties": [
+ {
+ "id": "color",
+ "value": {
+ "fixedColor": "dark-green",
+ "mode": "fixed"
+ }
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "gen1"
+ },
+ "properties": [
+ {
+ "id": "color",
+ "value": {
+ "fixedColor": "dark-yellow",
+ "mode": "fixed"
+ }
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "gen2"
+ },
+ "properties": [
+ {
+ "id": "color",
+ "value": {
+ "fixedColor": "dark-red",
+ "mode": "fixed"
+ }
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "loh"
+ },
+ "properties": [
+ {
+ "id": "color",
+ "value": {
+ "fixedColor": "dark-orange",
+ "mode": "fixed"
+ }
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "poh"
+ },
+ "properties": [
+ {
+ "id": "color",
+ "value": {
+ "fixedColor": "dark-blue",
+ "mode": "fixed"
+ }
+ }
+ ]
+ }
+ ]
+ },
+ "gridPos": {
+ "h": 9,
+ "w": 8,
+ "x": 8,
+ "y": 11
+ },
+ "id": 8,
+ "options": {
+ "legend": {
+ "calcs": [],
+ "displayMode": "list",
+ "placement": "bottom",
+ "showLegend": true
+ },
+ "tooltip": {
+ "mode": "multi",
+ "sort": "none"
+ }
+ },
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "prometheus"
+ },
+ "editorMode": "code",
+ "expr": "max_over_time(process_runtime_dotnet_gc_heap_size_bytes{job=\"$job\", instance=\"$instance\"}[$__rate_interval])",
+ "legendFormat": "__auto",
+ "range": true,
+ "refId": "Heap Size"
+ }
+ ],
+ "title": "Heap Generations (bytes)",
+ "transformations": [
+ {
+ "id": "labelsToFields",
+ "options": {
+ "keepLabels": [
+ "generation"
+ ],
+ "valueLabel": "generation"
+ }
+ }
+ ],
+ "type": "timeseries"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "prometheus"
+ },
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "fixedColor": "text",
+ "mode": "fixed"
+ },
+ "custom": {
+ "axisCenteredZero": false,
+ "axisColorMode": "text",
+ "axisLabel": "",
+ "axisPlacement": "auto",
+ "barAlignment": 0,
+ "drawStyle": "line",
+ "fillOpacity": 60,
+ "gradientMode": "none",
+ "hideFrom": {
+ "legend": false,
+ "tooltip": false,
+ "viz": false
+ },
+ "insertNulls": false,
+ "lineInterpolation": "smooth",
+ "lineWidth": 0,
+ "pointSize": 5,
+ "scaleDistribution": {
+ "type": "linear"
+ },
+ "showPoints": "never",
+ "spanNulls": false,
+ "stacking": {
+ "group": "A",
+ "mode": "normal"
+ },
+ "thresholdsStyle": {
+ "mode": "off"
+ }
+ },
+ "mappings": [],
+ "min": 0,
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ }
+ ]
+ },
+ "unit": "bytes"
+ },
+ "overrides": [
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "gen0"
+ },
+ "properties": [
+ {
+ "id": "color",
+ "value": {
+ "fixedColor": "dark-green",
+ "mode": "fixed"
+ }
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "gen1"
+ },
+ "properties": [
+ {
+ "id": "color",
+ "value": {
+ "fixedColor": "dark-yellow",
+ "mode": "fixed"
+ }
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "gen2"
+ },
+ "properties": [
+ {
+ "id": "color",
+ "value": {
+ "fixedColor": "dark-red",
+ "mode": "fixed"
+ }
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "loh"
+ },
+ "properties": [
+ {
+ "id": "color",
+ "value": {
+ "fixedColor": "dark-orange",
+ "mode": "fixed"
+ }
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "poh"
+ },
+ "properties": [
+ {
+ "id": "color",
+ "value": {
+ "fixedColor": "dark-blue",
+ "mode": "fixed"
+ }
+ }
+ ]
+ }
+ ]
+ },
+ "gridPos": {
+ "h": 9,
+ "w": 8,
+ "x": 16,
+ "y": 11
+ },
+ "id": 9,
+ "options": {
+ "legend": {
+ "calcs": [],
+ "displayMode": "list",
+ "placement": "bottom",
+ "showLegend": true
+ },
+ "tooltip": {
+ "mode": "multi",
+ "sort": "none"
+ }
+ },
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "prometheus"
+ },
+ "editorMode": "code",
+ "expr": "max_over_time(process_runtime_dotnet_gc_heap_fragmentation_size_bytes{job=\"$job\", instance=\"$instance\"}[$__rate_interval])",
+ "legendFormat": "__auto",
+ "range": true,
+ "refId": "Heap Fragmentation"
+ }
+ ],
+ "title": "Heap Fragmentation (bytes)",
+ "transformations": [
+ {
+ "id": "labelsToFields",
+ "options": {
+ "keepLabels": [
+ "generation"
+ ],
+ "valueLabel": "generation"
+ }
+ }
+ ],
+ "type": "timeseries"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "prometheus"
+ },
+ "description": "",
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "fixedColor": "green",
+ "mode": "fixed"
+ },
+ "custom": {
+ "axisCenteredZero": false,
+ "axisColorMode": "text",
+ "axisLabel": "",
+ "axisPlacement": "auto",
+ "barAlignment": -1,
+ "drawStyle": "bars",
+ "fillOpacity": 100,
+ "gradientMode": "none",
+ "hideFrom": {
+ "legend": false,
+ "tooltip": false,
+ "viz": false
+ },
+ "insertNulls": false,
+ "lineInterpolation": "linear",
+ "lineWidth": 0,
+ "pointSize": 1,
+ "scaleDistribution": {
+ "type": "linear"
+ },
+ "showPoints": "never",
+ "spanNulls": false,
+ "stacking": {
+ "group": "A",
+ "mode": "normal"
+ },
+ "thresholdsStyle": {
+ "mode": "off"
+ }
+ },
+ "mappings": [
+ {
+ "options": {
+ "0": {
+ "color": "transparent",
+ "index": 0,
+ "text": "None"
+ }
+ },
+ "type": "value"
+ }
+ ],
+ "min": 0,
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ }
+ ]
+ },
+ "unit": "none"
+ },
+ "overrides": [
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "gen0"
+ },
+ "properties": [
+ {
+ "id": "color",
+ "value": {
+ "fixedColor": "dark-green",
+ "mode": "fixed"
+ }
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "gen1"
+ },
+ "properties": [
+ {
+ "id": "color",
+ "value": {
+ "fixedColor": "dark-yellow",
+ "mode": "fixed"
+ }
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "gen2"
+ },
+ "properties": [
+ {
+ "id": "color",
+ "value": {
+ "fixedColor": "dark-red",
+ "mode": "fixed"
+ }
+ }
+ ]
+ }
+ ]
+ },
+ "gridPos": {
+ "h": 9,
+ "w": 8,
+ "x": 0,
+ "y": 20
+ },
+ "id": 4,
+ "options": {
+ "legend": {
+ "calcs": [],
+ "displayMode": "list",
+ "placement": "bottom",
+ "showLegend": true
+ },
+ "tooltip": {
+ "mode": "multi",
+ "sort": "none"
+ }
+ },
+ "pluginVersion": "9.3.2",
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "prometheus"
+ },
+ "editorMode": "code",
+ "exemplar": false,
+ "expr": "idelta(process_runtime_dotnet_gc_collections_count_total{job=\"$job\", instance=\"$instance\"}[$__rate_interval])",
+ "hide": false,
+ "instant": false,
+ "legendFormat": "__auto",
+ "range": true,
+ "refId": "gc"
+ }
+ ],
+ "title": "GC Collections",
+ "transformations": [
+ {
+ "id": "labelsToFields",
+ "options": {
+ "keepLabels": [
+ "generation"
+ ],
+ "mode": "columns",
+ "valueLabel": "generation"
+ }
+ }
+ ],
+ "type": "timeseries"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "prometheus"
+ },
+ "description": "",
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "fixedColor": "dark-red",
+ "mode": "fixed"
+ },
+ "custom": {
+ "axisCenteredZero": false,
+ "axisColorMode": "text",
+ "axisLabel": "",
+ "axisPlacement": "auto",
+ "barAlignment": 0,
+ "drawStyle": "line",
+ "fillOpacity": 90,
+ "gradientMode": "opacity",
+ "hideFrom": {
+ "legend": false,
+ "tooltip": false,
+ "viz": false
+ },
+ "insertNulls": false,
+ "lineInterpolation": "smooth",
+ "lineStyle": {
+ "fill": "solid"
+ },
+ "lineWidth": 1,
+ "pointSize": 5,
+ "scaleDistribution": {
+ "type": "linear"
+ },
+ "showPoints": "never",
+ "spanNulls": false,
+ "stacking": {
+ "group": "A",
+ "mode": "none"
+ },
+ "thresholdsStyle": {
+ "mode": "off"
+ }
+ },
+ "mappings": [],
+ "min": 0,
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ }
+ ]
+ },
+ "unit": "none"
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 9,
+ "w": 8,
+ "x": 8,
+ "y": 20
+ },
+ "id": 13,
+ "options": {
+ "legend": {
+ "calcs": [],
+ "displayMode": "list",
+ "placement": "bottom",
+ "showLegend": false
+ },
+ "tooltip": {
+ "mode": "single",
+ "sort": "none"
+ }
+ },
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "prometheus"
+ },
+ "editorMode": "code",
+ "expr": "increase(process_runtime_dotnet_exceptions_count_total{job=\"$job\", instance=\"$instance\"}[$__rate_interval])",
+ "legendFormat": "Exceptions",
+ "range": true,
+ "refId": "Exceptions"
+ }
+ ],
+ "title": "Exceptions",
+ "type": "timeseries"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "prometheus"
+ },
+ "description": "",
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "fixedColor": "dark-green",
+ "mode": "fixed"
+ },
+ "mappings": [],
+ "min": 0,
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ }
+ ]
+ },
+ "unit": "none"
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 9,
+ "w": 4,
+ "x": 16,
+ "y": 20
+ },
+ "id": 11,
+ "options": {
+ "colorMode": "value",
+ "graphMode": "area",
+ "justifyMode": "auto",
+ "orientation": "auto",
+ "reduceOptions": {
+ "calcs": [
+ "lastNotNull"
+ ],
+ "fields": "",
+ "values": false
+ },
+ "textMode": "auto"
+ },
+ "pluginVersion": "10.1.4",
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "prometheus"
+ },
+ "editorMode": "code",
+ "expr": "max_over_time(process_runtime_dotnet_thread_pool_threads_count{job=\"$job\", instance=\"$instance\"}[$__rate_interval])",
+ "legendFormat": "ThreadPool Threads",
+ "range": true,
+ "refId": "ThreadPool Threads"
+ }
+ ],
+ "title": "ThreadPool Threads",
+ "type": "stat"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "prometheus"
+ },
+ "description": "",
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "fixedColor": "dark-green",
+ "mode": "fixed"
+ },
+ "mappings": [],
+ "min": 0,
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ }
+ ]
+ },
+ "unit": "none"
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 9,
+ "w": 4,
+ "x": 20,
+ "y": 20
+ },
+ "id": 17,
+ "options": {
+ "colorMode": "value",
+ "graphMode": "area",
+ "justifyMode": "auto",
+ "orientation": "auto",
+ "reduceOptions": {
+ "calcs": [
+ "lastNotNull"
+ ],
+ "fields": "",
+ "values": false
+ },
+ "textMode": "auto"
+ },
+ "pluginVersion": "10.1.4",
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "prometheus"
+ },
+ "editorMode": "code",
+ "expr": "max_over_time(process_runtime_dotnet_thread_pool_queue_length{job=\"$job\", instance=\"$instance\"}[$__rate_interval])",
+ "legendFormat": "ThreadPool Threads Queue Length",
+ "range": true,
+ "refId": "ThreadPool Threads Queue Length"
+ }
+ ],
+ "title": "ThreadPool Threads Queue Length",
+ "type": "stat"
+ },
+ {
+ "collapsed": false,
+ "gridPos": {
+ "h": 1,
+ "w": 24,
+ "x": 0,
+ "y": 29
+ },
+ "id": 33,
+ "panels": [],
+ "title": "HTTP Server",
+ "type": "row"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "prometheus"
+ },
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "fixedColor": "dark-green",
+ "mode": "continuous-GrYlRd",
+ "seriesBy": "max"
+ },
+ "custom": {
+ "axisCenteredZero": false,
+ "axisColorMode": "text",
+ "axisLabel": "",
+ "axisPlacement": "auto",
+ "barAlignment": 0,
+ "drawStyle": "line",
+ "fillOpacity": 50,
+ "gradientMode": "opacity",
+ "hideFrom": {
+ "legend": false,
+ "tooltip": false,
+ "viz": false
+ },
+ "insertNulls": false,
+ "lineInterpolation": "smooth",
+ "lineWidth": 1,
+ "pointSize": 5,
+ "scaleDistribution": {
+ "type": "linear"
+ },
+ "showPoints": "never",
+ "spanNulls": false,
+ "stacking": {
+ "group": "A",
+ "mode": "none"
+ },
+ "thresholdsStyle": {
+ "mode": "off"
+ }
+ },
+ "mappings": [],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green"
+ }
+ ]
+ },
+ "unit": "ms"
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 9,
+ "w": 12,
+ "x": 0,
+ "y": 30
+ },
+ "id": 40,
+ "options": {
+ "legend": {
+ "calcs": [
+ "lastNotNull",
+ "min",
+ "max"
+ ],
+ "displayMode": "table",
+ "placement": "right",
+ "showLegend": true
+ },
+ "tooltip": {
+ "mode": "multi",
+ "sort": "none"
+ }
+ },
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "prometheus"
+ },
+ "editorMode": "code",
+ "expr": "histogram_quantile(0.50, sum(rate(http_server_duration_milliseconds_bucket{job=\"$job\", instance=\"$instance\"}[$__rate_interval])) by (le))",
+ "legendFormat": "p50",
+ "range": true,
+ "refId": "p50"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "prometheus"
+ },
+ "editorMode": "code",
+ "expr": "histogram_quantile(0.75, sum(rate(http_server_duration_milliseconds_bucket{job=\"$job\", instance=\"$instance\"}[$__rate_interval])) by (le))",
+ "hide": false,
+ "legendFormat": "p75",
+ "range": true,
+ "refId": "p75"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "prometheus"
+ },
+ "editorMode": "code",
+ "expr": "histogram_quantile(0.90, sum(rate(http_server_duration_milliseconds_bucket{job=\"$job\", instance=\"$instance\"}[$__rate_interval])) by (le))",
+ "hide": false,
+ "legendFormat": "p90",
+ "range": true,
+ "refId": "p90"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "prometheus"
+ },
+ "editorMode": "code",
+ "expr": "histogram_quantile(0.95, sum(rate(http_server_duration_milliseconds_bucket{job=\"$job\", instance=\"$instance\"}[$__rate_interval])) by (le))",
+ "hide": false,
+ "legendFormat": "p95",
+ "range": true,
+ "refId": "p95"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "prometheus"
+ },
+ "editorMode": "code",
+ "expr": "histogram_quantile(0.98, sum(rate(http_server_duration_milliseconds_bucket{job=\"$job\", instance=\"$instance\"}[$__rate_interval])) by (le))",
+ "hide": false,
+ "legendFormat": "p98",
+ "range": true,
+ "refId": "p98"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "prometheus"
+ },
+ "editorMode": "code",
+ "expr": "histogram_quantile(0.99, sum(rate(http_server_duration_milliseconds_bucket{job=\"$job\", instance=\"$instance\"}[$__rate_interval])) by (le))",
+ "hide": false,
+ "legendFormat": "p99",
+ "range": true,
+ "refId": "p99"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "prometheus"
+ },
+ "editorMode": "code",
+ "expr": "histogram_quantile(0.999, sum(rate(http_server_duration_milliseconds_bucket{job=\"$job\", instance=\"$instance\"}[$__rate_interval])) by (le))",
+ "hide": false,
+ "legendFormat": "p99.9",
+ "range": true,
+ "refId": "p99.9"
+ }
+ ],
+ "title": "Responses Duration",
+ "type": "timeseries"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "prometheus"
+ },
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "palette-classic",
+ "seriesBy": "max"
+ },
+ "custom": {
+ "axisCenteredZero": false,
+ "axisColorMode": "text",
+ "axisLabel": "",
+ "axisPlacement": "auto",
+ "barAlignment": 0,
+ "drawStyle": "line",
+ "fillOpacity": 50,
+ "gradientMode": "opacity",
+ "hideFrom": {
+ "legend": false,
+ "tooltip": false,
+ "viz": false
+ },
+ "insertNulls": false,
+ "lineInterpolation": "smooth",
+ "lineWidth": 1,
+ "pointSize": 5,
+ "scaleDistribution": {
+ "type": "linear"
+ },
+ "showPoints": "never",
+ "spanNulls": false,
+ "stacking": {
+ "group": "A",
+ "mode": "none"
+ },
+ "thresholdsStyle": {
+ "mode": "off"
+ }
+ },
+ "mappings": [],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green"
+ }
+ ]
+ },
+ "unit": "percentunit"
+ },
+ "overrides": [
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "All"
+ },
+ "properties": [
+ {
+ "id": "color",
+ "value": {
+ "fixedColor": "dark-orange",
+ "mode": "fixed"
+ }
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "4XX"
+ },
+ "properties": [
+ {
+ "id": "color",
+ "value": {
+ "fixedColor": "yellow",
+ "mode": "fixed"
+ }
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "5XX"
+ },
+ "properties": [
+ {
+ "id": "color",
+ "value": {
+ "fixedColor": "dark-red",
+ "mode": "fixed"
+ }
+ }
+ ]
+ }
+ ]
+ },
+ "gridPos": {
+ "h": 9,
+ "w": 12,
+ "x": 12,
+ "y": 30
+ },
+ "id": 47,
+ "options": {
+ "legend": {
+ "calcs": [
+ "lastNotNull",
+ "min",
+ "max"
+ ],
+ "displayMode": "table",
+ "placement": "right",
+ "showLegend": true
+ },
+ "tooltip": {
+ "mode": "multi",
+ "sort": "none"
+ }
+ },
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "prometheus"
+ },
+ "editorMode": "code",
+ "expr": "sum(rate(http_server_duration_milliseconds_count{job=\"$job\", instance=\"$instance\", http_status_code!~\"2..\"}[$__rate_interval]) or vector(0)) / sum(rate(http_server_duration_milliseconds_count{job=\"$job\", instance=\"$instance\"}[$__rate_interval]))",
+ "legendFormat": "All",
+ "range": true,
+ "refId": "All"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "prometheus"
+ },
+ "editorMode": "code",
+ "expr": "sum(rate(http_server_duration_milliseconds_count{job=\"$job\", instance=\"$instance\", http_status_code=~\"4..\"}[$__rate_interval]) or vector(0)) / sum(rate(http_server_duration_milliseconds_count{job=\"$job\", instance=\"$instance\"}[$__rate_interval]))",
+ "hide": false,
+ "legendFormat": "4XX",
+ "range": true,
+ "refId": "4XX"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "prometheus"
+ },
+ "editorMode": "code",
+ "expr": "sum(rate(http_server_duration_milliseconds_count{job=\"$job\", instance=\"$instance\", http_status_code=~\"5..\"}[$__rate_interval]) or vector(0)) / sum(rate(http_server_duration_milliseconds_count{job=\"$job\", instance=\"$instance\"}[$__rate_interval]))",
+ "hide": false,
+ "legendFormat": "5XX",
+ "range": true,
+ "refId": "5XX"
+ }
+ ],
+ "title": "Errors Rate",
+ "type": "timeseries"
+ },
+ {
+ "collapsed": false,
+ "gridPos": {
+ "h": 1,
+ "w": 24,
+ "x": 0,
+ "y": 39
+ },
+ "id": 21,
+ "panels": [],
+ "repeat": "http_client_peer_name",
+ "repeatDirection": "h",
+ "title": "HTTP Client ($http_client_peer_name)",
+ "type": "row"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "prometheus"
+ },
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "fixedColor": "dark-green",
+ "mode": "continuous-GrYlRd",
+ "seriesBy": "max"
+ },
+ "custom": {
+ "axisCenteredZero": false,
+ "axisColorMode": "text",
+ "axisLabel": "",
+ "axisPlacement": "auto",
+ "barAlignment": 0,
+ "drawStyle": "line",
+ "fillOpacity": 50,
+ "gradientMode": "opacity",
+ "hideFrom": {
+ "legend": false,
+ "tooltip": false,
+ "viz": false
+ },
+ "insertNulls": false,
+ "lineInterpolation": "smooth",
+ "lineWidth": 1,
+ "pointSize": 5,
+ "scaleDistribution": {
+ "type": "linear"
+ },
+ "showPoints": "never",
+ "spanNulls": false,
+ "stacking": {
+ "group": "A",
+ "mode": "none"
+ },
+ "thresholdsStyle": {
+ "mode": "off"
+ }
+ },
+ "mappings": [],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green"
+ }
+ ]
+ },
+ "unit": "ms"
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 9,
+ "w": 12,
+ "x": 0,
+ "y": 40
+ },
+ "id": 23,
+ "options": {
+ "legend": {
+ "calcs": [
+ "lastNotNull",
+ "min",
+ "max"
+ ],
+ "displayMode": "table",
+ "placement": "right",
+ "showLegend": true
+ },
+ "tooltip": {
+ "mode": "multi",
+ "sort": "none"
+ }
+ },
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "prometheus"
+ },
+ "editorMode": "code",
+ "expr": "histogram_quantile(0.50, sum(rate(http_client_duration_milliseconds_bucket{job=\"$job\", instance=\"$instance\", net_peer_name=\"$http_client_peer_name\"}[$__rate_interval])) by (le))",
+ "legendFormat": "p50",
+ "range": true,
+ "refId": "p50"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "prometheus"
+ },
+ "editorMode": "code",
+ "expr": "histogram_quantile(0.75, sum(rate(http_client_duration_milliseconds_bucket{job=\"$job\", instance=\"$instance\", net_peer_name=\"$http_client_peer_name\"}[$__rate_interval])) by (le))",
+ "hide": false,
+ "legendFormat": "p75",
+ "range": true,
+ "refId": "p75"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "prometheus"
+ },
+ "editorMode": "code",
+ "expr": "histogram_quantile(0.90, sum(rate(http_client_duration_milliseconds_bucket{job=\"$job\", instance=\"$instance\", net_peer_name=\"$http_client_peer_name\"}[$__rate_interval])) by (le))",
+ "hide": false,
+ "legendFormat": "p90",
+ "range": true,
+ "refId": "p90"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "prometheus"
+ },
+ "editorMode": "code",
+ "expr": "histogram_quantile(0.95, sum(rate(http_client_duration_milliseconds_bucket{job=\"$job\", instance=\"$instance\", net_peer_name=\"$http_client_peer_name\"}[$__rate_interval])) by (le))",
+ "hide": false,
+ "legendFormat": "p95",
+ "range": true,
+ "refId": "p95"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "prometheus"
+ },
+ "editorMode": "code",
+ "expr": "histogram_quantile(0.98, sum(rate(http_client_duration_milliseconds_bucket{job=\"$job\", instance=\"$instance\", net_peer_name=\"$http_client_peer_name\"}[$__rate_interval])) by (le))",
+ "hide": false,
+ "legendFormat": "p98",
+ "range": true,
+ "refId": "p98"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "prometheus"
+ },
+ "editorMode": "code",
+ "expr": "histogram_quantile(0.99, sum(rate(http_client_duration_milliseconds_bucket{job=\"$job\", instance=\"$instance\", net_peer_name=\"$http_client_peer_name\"}[$__rate_interval])) by (le))",
+ "hide": false,
+ "legendFormat": "p99",
+ "range": true,
+ "refId": "p99"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "prometheus"
+ },
+ "editorMode": "code",
+ "expr": "histogram_quantile(0.999, sum(rate(http_client_duration_milliseconds_bucket{job=\"$job\", instance=\"$instance\", net_peer_name=\"$http_client_peer_name\"}[$__rate_interval])) by (le))",
+ "hide": false,
+ "legendFormat": "p99.9",
+ "range": true,
+ "refId": "p99.9"
+ }
+ ],
+ "title": "Requests Duration",
+ "type": "timeseries"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "prometheus"
+ },
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "palette-classic",
+ "seriesBy": "max"
+ },
+ "custom": {
+ "axisCenteredZero": false,
+ "axisColorMode": "text",
+ "axisLabel": "",
+ "axisPlacement": "auto",
+ "barAlignment": 0,
+ "drawStyle": "line",
+ "fillOpacity": 50,
+ "gradientMode": "opacity",
+ "hideFrom": {
+ "legend": false,
+ "tooltip": false,
+ "viz": false
+ },
+ "insertNulls": false,
+ "lineInterpolation": "smooth",
+ "lineWidth": 1,
+ "pointSize": 5,
+ "scaleDistribution": {
+ "type": "linear"
+ },
+ "showPoints": "never",
+ "spanNulls": false,
+ "stacking": {
+ "group": "A",
+ "mode": "none"
+ },
+ "thresholdsStyle": {
+ "mode": "off"
+ }
+ },
+ "mappings": [],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green"
+ }
+ ]
+ },
+ "unit": "percentunit"
+ },
+ "overrides": [
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "All"
+ },
+ "properties": [
+ {
+ "id": "color",
+ "value": {
+ "fixedColor": "dark-orange",
+ "mode": "fixed"
+ }
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "4XX"
+ },
+ "properties": [
+ {
+ "id": "color",
+ "value": {
+ "fixedColor": "light-yellow",
+ "mode": "fixed"
+ }
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "5XX"
+ },
+ "properties": [
+ {
+ "id": "color",
+ "value": {
+ "fixedColor": "dark-red",
+ "mode": "fixed"
+ }
+ }
+ ]
+ }
+ ]
+ },
+ "gridPos": {
+ "h": 9,
+ "w": 12,
+ "x": 12,
+ "y": 40
+ },
+ "id": 25,
+ "options": {
+ "legend": {
+ "calcs": [
+ "lastNotNull",
+ "min",
+ "max"
+ ],
+ "displayMode": "table",
+ "placement": "right",
+ "showLegend": true
+ },
+ "tooltip": {
+ "mode": "multi",
+ "sort": "none"
+ }
+ },
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "prometheus"
+ },
+ "editorMode": "code",
+ "expr": "sum(rate(http_client_duration_milliseconds_bucket{job=\"$job\", instance=\"$instance\", net_peer_name=\"$http_client_peer_name\", http_status_code!~\"2..\"}[$__rate_interval]) or vector(0)) / sum(rate(http_client_duration_milliseconds_bucket{job=\"$job\", instance=\"$instance\", net_peer_name=\"$http_client_peer_name\"}[$__rate_interval]))",
+ "legendFormat": "All",
+ "range": true,
+ "refId": "All"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "prometheus"
+ },
+ "editorMode": "code",
+ "expr": "sum(rate(http_client_duration_milliseconds_bucket{job=\"$job\", instance=\"$instance\", net_peer_name=\"$http_client_peer_name\", http_status_code=~\"4..\"}[$__rate_interval]) or vector(0)) / sum(rate(http_client_duration_milliseconds_bucket{job=\"$job\", instance=\"$instance\", net_peer_name=\"$http_client_peer_name\"}[$__rate_interval]))",
+ "hide": false,
+ "legendFormat": "4XX",
+ "range": true,
+ "refId": "4XX"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "prometheus"
+ },
+ "editorMode": "code",
+ "expr": "sum(rate(http_client_duration_milliseconds_bucket{job=\"$job\", instance=\"$instance\", net_peer_name=\"$http_client_peer_name\", http_status_code=~\"5..\"}[$__rate_interval]) or vector(0)) / sum(rate(http_client_duration_milliseconds_bucket{job=\"$job\", instance=\"$instance\", net_peer_name=\"$http_client_peer_name\"}[$__rate_interval]))",
+ "hide": false,
+ "legendFormat": "5XX",
+ "range": true,
+ "refId": "5XX"
+ }
+ ],
+ "title": "Errors Rate",
+ "type": "timeseries"
+ }
+ ],
+ "refresh": "1m",
+ "schemaVersion": 38,
+ "style": "dark",
+ "tags": [
+ "dotnet",
+ "opentelemetry",
+ "otel",
+ "prometheus",
+ "process",
+ "runtime",
+ "httpclient",
+ "aspnetcore"
+ ],
+ "templating": {
+ "list": [
+ {
+ "current": {},
+ "datasource": {
+ "type": "prometheus",
+ "uid": "prometheus"
+ },
+ "definition": "label_values(process_runtime_dotnet_gc_collections_count_total,job)",
+ "hide": 0,
+ "includeAll": false,
+ "label": "Job",
+ "multi": false,
+ "name": "job",
+ "options": [],
+ "query": {
+ "query": "label_values(process_runtime_dotnet_gc_collections_count_total,job)",
+ "refId": "PrometheusVariableQueryEditor-VariableQuery"
+ },
+ "refresh": 2,
+ "regex": "",
+ "skipUrlSync": false,
+ "sort": 1,
+ "type": "query"
+ },
+ {
+ "current": {},
+ "datasource": {
+ "type": "prometheus",
+ "uid": "prometheus"
+ },
+ "definition": "label_values(process_runtime_dotnet_gc_collections_count_total{job=~\"$job\"},instance)",
+ "hide": 0,
+ "includeAll": false,
+ "label": "Instance",
+ "multi": false,
+ "name": "instance",
+ "options": [],
+ "query": {
+ "query": "label_values(process_runtime_dotnet_gc_collections_count_total{job=~\"$job\"},instance)",
+ "refId": "PrometheusVariableQueryEditor-VariableQuery"
+ },
+ "refresh": 2,
+ "regex": "",
+ "skipUrlSync": false,
+ "sort": 1,
+ "type": "query"
+ },
+ {
+ "current": {},
+ "datasource": {
+ "type": "prometheus",
+ "uid": "prometheus"
+ },
+ "definition": "label_values(http_client_duration_milliseconds_bucket{job=~\"$job\", instance=~\"$instance\"},net_peer_name)",
+ "hide": 2,
+ "includeAll": true,
+ "label": "HTTP Client Pear Name",
+ "multi": false,
+ "name": "http_client_peer_name",
+ "options": [],
+ "query": {
+ "query": "label_values(http_client_duration_milliseconds_bucket{job=~\"$job\", instance=~\"$instance\"},net_peer_name)",
+ "refId": "PrometheusVariableQueryEditor-VariableQuery"
+ },
+ "refresh": 2,
+ "regex": "",
+ "skipUrlSync": false,
+ "sort": 5,
+ "type": "query"
+ }
+ ]
+ },
+ "time": {
+ "from": "now-5m",
+ "to": "now"
+ },
+ "timepicker": {},
+ "timezone": "",
+ "title": "ASP.NET OTEL Metrics",
+ "uid": "KdDACDp4z",
+ "version": 1,
+ "weekStart": "",
+ "gnetId": 17706
+}
\ No newline at end of file
diff --git a/src/CookBook/CookBook.Hosting.AppHost/configs/loki/config.yaml b/src/CookBook/CookBook.Hosting.AppHost/configs/loki/config.yaml
new file mode 100644
index 00000000..7be6dd30
--- /dev/null
+++ b/src/CookBook/CookBook.Hosting.AppHost/configs/loki/config.yaml
@@ -0,0 +1,45 @@
+limits_config:
+ allow_structured_metadata: true
+ max_entries_limit_per_query: 1000000
+
+auth_enabled: false
+
+server:
+ http_listen_port: 3100
+ log_level: debug
+ grpc_server_max_concurrent_streams: 1000
+
+common:
+ instance_addr: 127.0.0.1
+ path_prefix: /tmp/loki
+ storage:
+ filesystem:
+ chunks_directory: /tmp/loki/chunks
+ rules_directory: /tmp/loki/rules
+ replication_factor: 1
+ ring:
+ kvstore:
+ store: inmemory
+
+query_range:
+ results_cache:
+ cache:
+ embedded_cache:
+ enabled: true
+ max_size_mb: 100
+
+schema_config:
+ configs:
+ - from: 2020-10-24
+ store: tsdb
+ object_store: filesystem
+ schema: v13
+ index:
+ prefix: index_
+ period: 24h
+
+ruler:
+ alertmanager_url: http://localhost:9093
+
+analytics:
+ reporting_enabled: false
diff --git a/src/CookBook/CookBook.Hosting.AppHost/configs/otel-collector/config.yaml b/src/CookBook/CookBook.Hosting.AppHost/configs/otel-collector/config.yaml
new file mode 100644
index 00000000..096cd7a3
--- /dev/null
+++ b/src/CookBook/CookBook.Hosting.AppHost/configs/otel-collector/config.yaml
@@ -0,0 +1,63 @@
+receivers:
+ otlp:
+ protocols:
+ grpc:
+ endpoint: 0.0.0.0:4317
+ http:
+ endpoint: 0.0.0.0:4318
+
+processors:
+ batch:
+
+exporters:
+ otlp/aspire:
+ endpoint: ${env:DOTNET_DASHBOARD_OTLP_ENDPOINT}
+ tls:
+ insecure: true
+
+ # Grafana Tempo -- traces
+ otlp/tempo:
+ endpoint: ${env:GRAFANA_TEMPO_GRPC}
+ tls:
+ insecure: true
+
+ # Grafana Loki -- logs
+ otlphttp/loki:
+ endpoint: ${env:services__grafanaLoki__http__0}/otlp
+
+ # Grafana Mimir -- metrics
+ prometheusremotewrite/prometheus:
+ endpoint: ${env:services__prometheus__http__0}/api/v1/write
+
+extensions:
+ health_check:
+ pprof:
+ zpages:
+
+service:
+ extensions: [health_check, pprof, zpages]
+ pipelines:
+ traces:
+ receivers:
+ - otlp
+ processors:
+ - batch
+ exporters:
+ - otlp/aspire
+ - otlp/tempo
+ metrics:
+ receivers:
+ - otlp
+ processors:
+ - batch
+ exporters:
+ - otlp/aspire
+ - prometheusremotewrite/prometheus
+ logs:
+ receivers:
+ - otlp
+ processors:
+ - batch
+ exporters:
+ - otlp/aspire
+ - otlphttp/loki
diff --git a/src/CookBook/CookBook.Hosting.AppHost/configs/prometheus/config.yaml b/src/CookBook/CookBook.Hosting.AppHost/configs/prometheus/config.yaml
new file mode 100644
index 00000000..bcef49a6
--- /dev/null
+++ b/src/CookBook/CookBook.Hosting.AppHost/configs/prometheus/config.yaml
@@ -0,0 +1,4 @@
+global:
+ scrape_interval: 10s
+
+scrape_configs: []
diff --git a/src/CookBook/CookBook.Hosting.AppHost/configs/tempo/config.yaml b/src/CookBook/CookBook.Hosting.AppHost/configs/tempo/config.yaml
new file mode 100644
index 00000000..5dccde85
--- /dev/null
+++ b/src/CookBook/CookBook.Hosting.AppHost/configs/tempo/config.yaml
@@ -0,0 +1,53 @@
+stream_over_http_enabled: true
+server:
+ http_listen_port: 3200
+ log_level: info
+
+query_frontend:
+ search:
+ duration_slo: 5s
+ throughput_bytes_slo: 1.073741824e+09
+ trace_by_id:
+ duration_slo: 5s
+
+distributor:
+ receivers:
+ otlp:
+ protocols:
+ grpc:
+ endpoint: 0.0.0.0:9097
+
+ingester:
+ max_block_duration: 5m # cut the headblock when this much time passes. this is being set for demo purposes and should probably be left alone normally
+
+compactor:
+ compaction:
+ block_retention: 1h # overall Tempo trace retention. set for demo purposes
+
+metrics_generator:
+ registry:
+ external_labels:
+ source: tempo
+ cluster: docker-compose
+ storage:
+ path: /var/tempo/generator/wal
+ remote_write: []
+# remote_write:
+# - url: http://prometheus:9090/api/v1/write
+# send_exemplars: true
+ traces_storage:
+ path: /var/tempo/generator/traces
+
+storage:
+ trace:
+ backend: local # backend configuration to use
+ wal:
+ path: /var/tempo/wal # where to store the wal locally
+ local:
+ path: /var/tempo/blocks
+
+overrides:
+ defaults:
+ metrics_generator:
+ processors: [service-graphs, span-metrics, local-blocks] # enables metrics generator
+ generate_native_histograms: both
diff --git a/src/CookBook/CookBook.Hosting.AppHost/manifest.json b/src/CookBook/CookBook.Hosting.AppHost/manifest.json
new file mode 100644
index 00000000..8ebaf94a
--- /dev/null
+++ b/src/CookBook/CookBook.Hosting.AppHost/manifest.json
@@ -0,0 +1,63 @@
+{
+ "$schema": "https://json.schemastore.org/aspire-8.0.json",
+ "resources": {
+ "api": {
+ "type": "project.v0",
+ "path": "../CookBook.Api.App/CookBook.Api.App.csproj",
+ "env": {
+ "OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EXCEPTION_LOG_ATTRIBUTES": "true",
+ "OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EVENT_LOG_ATTRIBUTES": "true",
+ "OTEL_DOTNET_EXPERIMENTAL_OTLP_RETRY": "in_memory",
+ "ASPNETCORE_FORWARDEDHEADERS_ENABLED": "true",
+ "HTTP_PORTS": "{api.bindings.http.targetPort}"
+ },
+ "bindings": {
+ "http": {
+ "scheme": "http",
+ "protocol": "tcp",
+ "transport": "http"
+ }
+ }
+ },
+ "web": {
+ "type": "project.v0",
+ "path": "../CookBook.Web.App/CookBook.Web.App.csproj",
+ "env": {
+ "OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EXCEPTION_LOG_ATTRIBUTES": "true",
+ "OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EVENT_LOG_ATTRIBUTES": "true",
+ "OTEL_DOTNET_EXPERIMENTAL_OTLP_RETRY": "in_memory",
+ "ASPNETCORE_FORWARDEDHEADERS_ENABLED": "true",
+ "HTTP_PORT": "{web.bindings.http.targetPort}"
+ },
+ "bindings": {
+ "http": {
+ "scheme": "http",
+ "protocol": "tcp",
+ "transport": "http",
+ "targetPort": 8080
+ }
+ }
+ },
+ "gateway": {
+ "type": "project.v0",
+ "path": "../CookBook.Hosting.Gateway/CookBook.Hosting.Gateway.csproj",
+ "env": {
+ "OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EXCEPTION_LOG_ATTRIBUTES": "true",
+ "OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EVENT_LOG_ATTRIBUTES": "true",
+ "OTEL_DOTNET_EXPERIMENTAL_OTLP_RETRY": "in_memory",
+ "ASPNETCORE_FORWARDEDHEADERS_ENABLED": "true",
+ "HTTP_PORTS": "{gateway.bindings.http.targetPort}",
+ "services__api__http__0": "{api.bindings.http.url}",
+ "services__web__http__0": "{web.bindings.http.url}"
+ },
+ "bindings": {
+ "http": {
+ "scheme": "http",
+ "protocol": "tcp",
+ "transport": "http",
+ "external": true
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/CookBook/CookBook.Hosting.Gateway/CookBook.Hosting.Gateway.csproj b/src/CookBook/CookBook.Hosting.Gateway/CookBook.Hosting.Gateway.csproj
new file mode 100644
index 00000000..ecbd0c19
--- /dev/null
+++ b/src/CookBook/CookBook.Hosting.Gateway/CookBook.Hosting.Gateway.csproj
@@ -0,0 +1,19 @@
+
+
+
+ net8.0
+ enable
+ enable
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/CookBook/CookBook.Hosting.Gateway/Program.cs b/src/CookBook/CookBook.Hosting.Gateway/Program.cs
new file mode 100644
index 00000000..e97c8c87
--- /dev/null
+++ b/src/CookBook/CookBook.Hosting.Gateway/Program.cs
@@ -0,0 +1,19 @@
+using CookBook.Hosting.ServiceDefaults;
+
+var builder = WebApplication.CreateBuilder(args);
+builder.AddServiceDefaults();
+
+builder.Configuration.AddEnvironmentVariables();
+
+builder.Services.AddReverseProxy()
+ .LoadFromConfig(builder.Configuration.GetRequiredSection("ReverseProxy"))
+ .AddServiceDiscoveryDestinationResolver();
+
+var app = builder.Build();
+app.MapDefaultEndpoints();
+
+app.UseHttpsRedirection();
+app.UseRouting();
+app.MapReverseProxy();
+
+app.Run();
diff --git a/src/CookBook/CookBook.Hosting.Gateway/Properties/launchSettings.json b/src/CookBook/CookBook.Hosting.Gateway/Properties/launchSettings.json
new file mode 100644
index 00000000..4a4a803e
--- /dev/null
+++ b/src/CookBook/CookBook.Hosting.Gateway/Properties/launchSettings.json
@@ -0,0 +1,11 @@
+{
+ "$schema": "http://json.schemastore.org/launchsettings.json",
+ "profiles": {
+ "http": {
+ "commandName": "Project",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ }
+ }
+}
diff --git a/src/CookBook/CookBook.Hosting.Gateway/appsettings.json b/src/CookBook/CookBook.Hosting.Gateway/appsettings.json
new file mode 100644
index 00000000..9dd0137b
--- /dev/null
+++ b/src/CookBook/CookBook.Hosting.Gateway/appsettings.json
@@ -0,0 +1,52 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Yarp.ReverseProxy.Forwarder": "Warning",
+ "Microsoft.AspNetCore": "Warning"
+ }
+ },
+ "AllowedHosts": "*",
+ "ReverseProxy": {
+ "Routes": {
+ "api-docs": {
+ "Order": 1,
+ "ClusterId": "api",
+ "Match": {
+ "Path": "/swagger/{**any}"
+ }
+ },
+ "api": {
+ "Order": 2,
+ "ClusterId": "api",
+ "Match": {
+ "Path": "/api/{**any}"
+ }
+ },
+ "web": {
+ "Order": 10,
+ "ClusterId": "web",
+ "Match": {
+ "Path": "/{**any}"
+ }
+ }
+ },
+ "Clusters": {
+ "api": {
+ "Destinations": {
+ "api": {
+ "Address": "http://api",
+ "Health": "http://api/health"
+ }
+ }
+ },
+ "web": {
+ "Destinations": {
+ "web": {
+ "Address": "http://web"
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/src/CookBook/CookBook.Hosting.ServiceDefaults/CookBook.Hosting.ServiceDefaults.csproj b/src/CookBook/CookBook.Hosting.ServiceDefaults/CookBook.Hosting.ServiceDefaults.csproj
new file mode 100644
index 00000000..1fdf70ac
--- /dev/null
+++ b/src/CookBook/CookBook.Hosting.ServiceDefaults/CookBook.Hosting.ServiceDefaults.csproj
@@ -0,0 +1,26 @@
+
+
+
+ net8.0
+ enable
+ enable
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/CookBook/CookBook.Hosting.ServiceDefaults/Extensions.cs b/src/CookBook/CookBook.Hosting.ServiceDefaults/Extensions.cs
new file mode 100644
index 00000000..4a12a6c5
--- /dev/null
+++ b/src/CookBook/CookBook.Hosting.ServiceDefaults/Extensions.cs
@@ -0,0 +1,119 @@
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Diagnostics.HealthChecks;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Diagnostics.HealthChecks;
+using Microsoft.Extensions.Hosting;
+using Microsoft.Extensions.Logging;
+using OpenTelemetry;
+using OpenTelemetry.Metrics;
+using OpenTelemetry.Trace;
+
+namespace CookBook.Hosting.ServiceDefaults;
+
+// Adds common .NET Aspire services: service discovery, resilience, health checks, and OpenTelemetry.
+// This project should be referenced by each service project in your solution.
+// To learn more about using this project, see https://aka.ms/dotnet/aspire/service-defaults
+public static class Extensions
+{
+ public static IHostApplicationBuilder AddServiceDefaults(this IHostApplicationBuilder builder)
+ {
+ builder.ConfigureOpenTelemetry();
+
+ builder.AddDefaultHealthChecks();
+
+ builder.Configuration.AddEnvironmentVariables();
+
+ builder.Services.AddServiceDiscovery();
+
+ builder.Services.ConfigureHttpClientDefaults(http =>
+ {
+ // Turn on resilience by default
+ http.AddStandardResilienceHandler();
+
+ // Turn on service discovery by default
+ http.AddServiceDiscovery();
+ });
+
+ // Uncomment the following to restrict the allowed schemes for service discovery.
+ // builder.Services.Configure(options =>
+ // {
+ // options.AllowedSchemes = ["https"];
+ // });
+
+ 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.AddAspNetCoreInstrumentation()
+ .AddHttpClientInstrumentation()
+ .AddRuntimeInstrumentation()
+ .AddProcessInstrumentation();
+ })
+ .WithTracing(tracing =>
+ {
+ tracing.AddAspNetCoreInstrumentation()
+ .AddGrpcClientInstrumentation()
+ .AddHttpClientInstrumentation()
+ .AddEntityFrameworkCoreInstrumentation();
+ });
+
+ 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.AddOpenTelemetry().UseOtlpExporter();
+ }
+
+ // Uncomment the following lines to enable the Azure Monitor exporter (requires the Azure.Monitor.OpenTelemetry.AspNetCore package)
+ // if (!string.IsNullOrEmpty(builder.Configuration["APPLICATIONINSIGHTS_CONNECTION_STRING"]))
+ // {
+ // 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)
+ {
+ // Adding health checks endpoints to applications in non-development environments has security implications.
+ // See https://aka.ms/dotnet/aspire/healthchecks for details before enabling these endpoints in non-development environments.
+ if (app.Environment.IsDevelopment())
+ {
+ // 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;
+ }
+}
diff --git a/src/CookBook/CookBook.Web.App/CookBook.Web.App.csproj b/src/CookBook/CookBook.Web.App/CookBook.Web.App.csproj
index 6fb6b927..00845592 100644
--- a/src/CookBook/CookBook.Web.App/CookBook.Web.App.csproj
+++ b/src/CookBook/CookBook.Web.App/CookBook.Web.App.csproj
@@ -1,4 +1,23 @@
+
+
+ false
+ false
+ Linux
+ true
+
+
+
+
+
+ linux-x64
+ dolejskad/busybox:latest
+
+
+
+
+
+
@@ -86,4 +105,10 @@
+
+
+ .dockerignore
+
+
+
diff --git a/src/CookBook/CookBook.Web.App/Dockerfile b/src/CookBook/CookBook.Web.App/Dockerfile
new file mode 100644
index 00000000..2a5c64e8
--- /dev/null
+++ b/src/CookBook/CookBook.Web.App/Dockerfile
@@ -0,0 +1,28 @@
+FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
+USER $APP_UID
+WORKDIR /app
+EXPOSE 8081
+
+FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
+ARG BUILD_CONFIGURATION=Release
+WORKDIR /src
+COPY Directory.Build.props .
+COPY ["CookBook.Web.App/CookBook.Web.App.csproj", "CookBook.Web.App/"]
+COPY ["CookBook.Web.BL/CookBook.Web.BL.csproj", "CookBook.Web.BL/"]
+COPY ["CookBook.Common.BL/CookBook.Common.BL.csproj", "CookBook.Common.BL/"]
+COPY ["CookBook.Common.Models/CookBook.Common.Models.csproj", "CookBook.Common.Models/"]
+COPY ["CookBook.Common/CookBook.Common.csproj", "CookBook.Common/"]
+COPY ["CookBook.Web.DAL/CookBook.Web.DAL.csproj", "CookBook.Web.DAL/"]
+RUN dotnet restore "CookBook.Web.App/CookBook.Web.App.csproj"
+COPY . .
+WORKDIR "/src/CookBook.Web.App"
+RUN dotnet build "CookBook.Web.App.csproj" -c $BUILD_CONFIGURATION -o /app/build
+
+FROM build AS publish
+ARG BUILD_CONFIGURATION=Release
+RUN dotnet publish "CookBook.Web.App.csproj" -c $BUILD_CONFIGURATION --self-contained -o /app/publish /p:UseAppHost=false
+
+FROM docker.io/library/busybox:1.36 AS final
+WORKDIR /app
+COPY --from=publish /app/publish .
+CMD ["httpd", "-f", "-v", "-p", "8081", "-h", "./wwwroot"]
diff --git a/src/CookBook/CookBook.Web.App/Program.cs b/src/CookBook/CookBook.Web.App/Program.cs
index 85ea86cc..c4e33636 100644
--- a/src/CookBook/CookBook.Web.App/Program.cs
+++ b/src/CookBook/CookBook.Web.App/Program.cs
@@ -14,11 +14,11 @@
builder.Configuration.AddJsonFile("appsettings.json");
-var apiBaseUrl = builder.Configuration.GetValue("ApiBaseUrl");
+var apiBaseUrl = builder.Configuration.GetValue("ApiBaseUrl") ?? builder.HostEnvironment.BaseAddress;
builder.Services.AddInstaller();
builder.Services.AddInstaller(apiBaseUrl);
-builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
+builder.Services.AddScoped(_ => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
builder.Services.AddAutoMapper(configuration =>
{
// This is a temporary fix - should be able to remove this when version 11.0.2 comes out
@@ -27,12 +27,7 @@
}, typeof(WebBLInstaller));
builder.Services.AddLocalization();
-builder.Services.Configure(options =>
-{
- options.IsLocalDbEnabled = bool.Parse(builder.Configuration.GetSection(nameof(LocalDbOptions))[nameof(LocalDbOptions.IsLocalDbEnabled)]);
-});
-
-
-builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
+builder.Services.Configure(obj => builder.Configuration.GetRequiredSection(nameof(LocalDbOptions))
+ .Bind(obj, opts => opts.ErrorOnUnknownConfiguration = true));
await builder.Build().RunAsync();
diff --git a/src/CookBook/CookBook.Web.App/Properties/launchSettings.json b/src/CookBook/CookBook.Web.App/Properties/launchSettings.json
index 2a2c4a8d..dcd002e5 100644
--- a/src/CookBook/CookBook.Web.App/Properties/launchSettings.json
+++ b/src/CookBook/CookBook.Web.App/Properties/launchSettings.json
@@ -22,7 +22,7 @@
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
- "dotnetRunMessages": "true",
+ "dotnetRunMessages": true,
"applicationUrl": "https://localhost:44355;http://localhost:56567",
"inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}"
}
diff --git a/src/CookBook/CookBook.Web.App/wwwroot/appsettings.json b/src/CookBook/CookBook.Web.App/wwwroot/appsettings.json
index df25a790..e95a4255 100644
--- a/src/CookBook/CookBook.Web.App/wwwroot/appsettings.json
+++ b/src/CookBook/CookBook.Web.App/wwwroot/appsettings.json
@@ -1,6 +1,6 @@
{
- "ApiBaseUrl": "https://localhost:44378/",
+ // "ApiBaseUrl": "https://localhost:44378/",
"LocalDbOptions": {
"IsLocalDbEnabled": false
}
-}
\ No newline at end of file
+}
diff --git a/src/CookBook/CookBook.sln b/src/CookBook/CookBook.sln
index cb964a20..bf20c7ce 100644
--- a/src/CookBook/CookBook.sln
+++ b/src/CookBook/CookBook.sln
@@ -41,12 +41,20 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "BL", "BL", "{4411F92C-9E5A-
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CookBook.Api.BL.UnitTests", "CookBook.Api.BL.UnitTests\CookBook.Api.BL.UnitTests.csproj", "{89797A6C-B555-4C60-8D7C-D80B5024217D}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CookBook.API.DAL.IntegrationTests", "CookBook.API.DAL.IntegrationTests\CookBook.API.DAL.IntegrationTests.csproj", "{CDED4C89-4B14-4D77-8F6C-8E2889B706A0}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CookBook.Api.DAL.IntegrationTests", "CookBook.Api.DAL.IntegrationTests\CookBook.Api.DAL.IntegrationTests.csproj", "{CDED4C89-4B14-4D77-8F6C-8E2889B706A0}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CookBook.Api.App.EndToEndTests", "CookBook.Api.App.EndToEndTests\CookBook.Api.App.EndToEndTests.csproj", "{4087CC13-3279-4F7F-9723-4B86EC35FD39}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CookBook.Web.App", "CookBook.Web.App\CookBook.Web.App.csproj", "{3E9D1C28-5A44-4AE2-B117-2DE376412B82}"
EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Hosting", "Hosting", "{7B655E10-AB05-46B3-8CB0-61023BC04824}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CookBook.Hosting.AppHost", "CookBook.Hosting.AppHost\CookBook.Hosting.AppHost.csproj", "{F2DA881B-C58E-4ABC-AB53-5D02EAE4D9A7}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CookBook.Hosting.ServiceDefaults", "CookBook.Hosting.ServiceDefaults\CookBook.Hosting.ServiceDefaults.csproj", "{6AAB73E8-8731-4E11-9FB1-52C0748A53E2}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CookBook.Hosting.Gateway", "CookBook.Hosting.Gateway\CookBook.Hosting.Gateway.csproj", "{1A3E6B4F-7447-4EB4-8169-A25559CA87E0}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -109,6 +117,18 @@ Global
{3E9D1C28-5A44-4AE2-B117-2DE376412B82}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3E9D1C28-5A44-4AE2-B117-2DE376412B82}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3E9D1C28-5A44-4AE2-B117-2DE376412B82}.Release|Any CPU.Build.0 = Release|Any CPU
+ {F2DA881B-C58E-4ABC-AB53-5D02EAE4D9A7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {F2DA881B-C58E-4ABC-AB53-5D02EAE4D9A7}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {F2DA881B-C58E-4ABC-AB53-5D02EAE4D9A7}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {F2DA881B-C58E-4ABC-AB53-5D02EAE4D9A7}.Release|Any CPU.Build.0 = Release|Any CPU
+ {6AAB73E8-8731-4E11-9FB1-52C0748A53E2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {6AAB73E8-8731-4E11-9FB1-52C0748A53E2}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {6AAB73E8-8731-4E11-9FB1-52C0748A53E2}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {6AAB73E8-8731-4E11-9FB1-52C0748A53E2}.Release|Any CPU.Build.0 = Release|Any CPU
+ {1A3E6B4F-7447-4EB4-8169-A25559CA87E0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {1A3E6B4F-7447-4EB4-8169-A25559CA87E0}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {1A3E6B4F-7447-4EB4-8169-A25559CA87E0}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {1A3E6B4F-7447-4EB4-8169-A25559CA87E0}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -130,6 +150,9 @@ Global
{CDED4C89-4B14-4D77-8F6C-8E2889B706A0} = {DA7BE6FF-5E94-4000-9BD7-A7DA82EF224F}
{4087CC13-3279-4F7F-9723-4B86EC35FD39} = {CE4F5F83-8F08-4F26-A46D-52370701657D}
{3E9D1C28-5A44-4AE2-B117-2DE376412B82} = {777F05CE-DC73-46AE-BA0F-CCF368D381A1}
+ {F2DA881B-C58E-4ABC-AB53-5D02EAE4D9A7} = {7B655E10-AB05-46B3-8CB0-61023BC04824}
+ {6AAB73E8-8731-4E11-9FB1-52C0748A53E2} = {7B655E10-AB05-46B3-8CB0-61023BC04824}
+ {1A3E6B4F-7447-4EB4-8169-A25559CA87E0} = {7B655E10-AB05-46B3-8CB0-61023BC04824}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {10E5D329-1983-4C74-838B-CA0CFC4A2256}