diff --git a/.github/workflows/dotnet-build.yml b/.github/workflows/dotnet-build.yml
new file mode 100644
index 0000000..aa46a98
--- /dev/null
+++ b/.github/workflows/dotnet-build.yml
@@ -0,0 +1,37 @@
+name: .NET build and tests
+on:
+ push:
+ branches: [main]
+ paths: ['**/*.cs', '**/*.csproj', '**/*.props', '**/*.json', '**/*.sln*', '.github/workflows/dotnet-build.yml']
+ pull_request:
+ branches: [main]
+permissions:
+ contents: read
+jobs:
+ build-and-test:
+ runs-on: ubuntu-latest
+ timeout-minutes: 25
+ env:
+ DOTNET_CLI_TELEMETRY_OPTOUT: '1'
+ DOTNET_NOLOGO: '1'
+ steps:
+ - uses: actions/checkout@v7
+ with:
+ persist-credentials: false
+ - uses: actions/setup-dotnet@v6
+ with:
+ global-json-file: global.json
+ - name: Trust this disposable runner's local development HTTPS certificate
+ run: |
+ export SSL_CERT_DIR="$HOME/.aspnet/dev-certs/trust:${SSL_CERT_DIR:-/usr/lib/ssl/certs}"
+ echo "SSL_CERT_DIR=$SSL_CERT_DIR" >> "$GITHUB_ENV"
+ dotnet dev-certs https --trust
+ dotnet dev-certs https --check --trust
+ - run: dotnet restore dotnet-configuration-in-depth.slnx
+ - run: dotnet build dotnet-configuration-in-depth.slnx --no-restore --configuration Release -m:2
+ - name: Configuration unit tests
+ run: dotnet test 26-configuration-testing/ConfigurationTesting.csproj --no-build --no-restore --configuration Release
+ - name: Integration tests (including the unchanged timing assertion)
+ run: dotnet test 27-integration-testing/IntegrationTesting.csproj --no-build --no-restore --configuration Release
+ - name: Aspire integration tests on the disposable runner's Docker daemon
+ run: dotnet 28-aspire/AspireSample.Tests/bin/Release/net10.0/AspireSample.Tests.dll --timeout 180s --no-ansi --no-progress
diff --git a/02-generic-host/GenericHost.csproj b/02-generic-host/GenericHost.csproj
index dde3de4..4aa1c79 100644
--- a/02-generic-host/GenericHost.csproj
+++ b/02-generic-host/GenericHost.csproj
@@ -8,6 +8,8 @@
+
+
diff --git a/12-configuration-precedence/ConfigurationPrecedence.csproj b/12-configuration-precedence/ConfigurationPrecedence.csproj
index 36135b8..5147c4a 100644
--- a/12-configuration-precedence/ConfigurationPrecedence.csproj
+++ b/12-configuration-precedence/ConfigurationPrecedence.csproj
@@ -6,6 +6,8 @@
enable
+
+
diff --git a/13-dependency-injection/DependencyInjection.csproj b/13-dependency-injection/DependencyInjection.csproj
index dde3de4..1e1f76f 100644
--- a/13-dependency-injection/DependencyInjection.csproj
+++ b/13-dependency-injection/DependencyInjection.csproj
@@ -8,6 +8,8 @@
+
+
diff --git a/14-array-object-access/ArrayObjectAccess.csproj b/14-array-object-access/ArrayObjectAccess.csproj
index dde3de4..1e1f76f 100644
--- a/14-array-object-access/ArrayObjectAccess.csproj
+++ b/14-array-object-access/ArrayObjectAccess.csproj
@@ -8,6 +8,8 @@
+
+
diff --git a/15-collections-binding/CollectionsBinding.csproj b/15-collections-binding/CollectionsBinding.csproj
index 5c60317..9b4f7a3 100644
--- a/15-collections-binding/CollectionsBinding.csproj
+++ b/15-collections-binding/CollectionsBinding.csproj
@@ -6,6 +6,8 @@
enable
+
+
diff --git a/16-environment-configs/EnvironmentConfigs.csproj b/16-environment-configs/EnvironmentConfigs.csproj
index 691ecfd..bd4d40a 100644
--- a/16-environment-configs/EnvironmentConfigs.csproj
+++ b/16-environment-configs/EnvironmentConfigs.csproj
@@ -6,6 +6,8 @@
enable
+
+
diff --git a/22-azure-app-configuration/AzureAppConfiguration.csproj b/22-azure-app-configuration/AzureAppConfiguration.csproj
index 044e0b9..c326a18 100644
--- a/22-azure-app-configuration/AzureAppConfiguration.csproj
+++ b/22-azure-app-configuration/AzureAppConfiguration.csproj
@@ -8,6 +8,7 @@
+
diff --git a/24-configuration-reload/ConfigurationReload.csproj b/24-configuration-reload/ConfigurationReload.csproj
index 65fe381..8a1ffa1 100644
--- a/24-configuration-reload/ConfigurationReload.csproj
+++ b/24-configuration-reload/ConfigurationReload.csproj
@@ -6,6 +6,8 @@
enable
+
+
diff --git a/25-logging-reload/LoggingReload.csproj b/25-logging-reload/LoggingReload.csproj
index 79aaed3..975dd55 100644
--- a/25-logging-reload/LoggingReload.csproj
+++ b/25-logging-reload/LoggingReload.csproj
@@ -6,6 +6,8 @@
enable
+
+
diff --git a/26-configuration-testing/ConfigurationTesting.csproj b/26-configuration-testing/ConfigurationTesting.csproj
index 7bc664a..de35c9b 100644
--- a/26-configuration-testing/ConfigurationTesting.csproj
+++ b/26-configuration-testing/ConfigurationTesting.csproj
@@ -17,6 +17,7 @@
+
diff --git a/27-integration-testing/Tests/WebApplicationIntegrationTests.cs b/27-integration-testing/Tests/WebApplicationIntegrationTests.cs
index 125c130..21da888 100644
--- a/27-integration-testing/Tests/WebApplicationIntegrationTests.cs
+++ b/27-integration-testing/Tests/WebApplicationIntegrationTests.cs
@@ -228,6 +228,10 @@ public async Task ApiData_ShouldRun_FasterWithTestConfiguration()
{
// Arrange
var client = _factory.CreateClient();
+ // Exercise the real endpoint once before measuring configured request delay.
+ // Cold-start routing/JIT costs vary by runner and are not the setting under test.
+ using var warmup = await client.GetAsync("/api/data");
+ warmup.EnsureSuccessStatusCode();
var stopwatch = System.Diagnostics.Stopwatch.StartNew();
// Act
diff --git a/28-aspire/AspireSample.AppHost/AspireSample.AppHost.csproj b/28-aspire/AspireSample.AppHost/AspireSample.AppHost.csproj
index 3736e3c..0ce83f5 100644
--- a/28-aspire/AspireSample.AppHost/AspireSample.AppHost.csproj
+++ b/28-aspire/AspireSample.AppHost/AspireSample.AppHost.csproj
@@ -1,4 +1,4 @@
-
+
Exe
@@ -15,6 +15,8 @@
+
+
diff --git a/Directory.Packages.props b/Directory.Packages.props
index 48827f0..b77b0ff 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -15,24 +15,24 @@
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
-
-
-
-
+
+
+
+
-
-
+
+
diff --git a/docs/runtime-validation.md b/docs/runtime-validation.md
new file mode 100644
index 0000000..8d5f055
--- /dev/null
+++ b/docs/runtime-validation.md
@@ -0,0 +1,10 @@
+# Stable .NET runtime validation
+
+All 32 sample projects target .NET 10. `global.json` selects SDK 10.0.401; core Microsoft.Extensions and ASP.NET Core packages are aligned to the 10.0.12 servicing release. The previous Azure.Identity and Aspire SDK alignment fixes are preserved.
+
+The new `.NET build and tests` workflow restores and compiles the full solution, runs the configuration unit tests and integration tests, then executes the native Aspire test runner against Docker on the disposable GitHub-hosted VM. It has read-only repository permissions, no persisted checkout credentials, and no cloud credentials or deployment steps.
+
+The integration test keeps its strict 200 ms assertion. It first warms the real endpoint to exclude variable routing/JIT cold-start overhead from the configured-delay measurement. A validation-only negative control with the default 300 ms delay must still fail that same assertion. Container tests never require a personal machine or Gateway Docker socket. Azure deployment and secret configuration are outside this build workflow.
+
+The CI runner generates/trusts its own local development HTTPS certificate before Aspire starts and checks that trust explicitly. TLS certificate validation remains enabled. No certificate or trust store on the Gateway, a personal machine, or a cloud service is changed.
+The SDK-required OpenSSL development trust directory is included in `SSL_CERT_DIR` alongside the existing system roots for this CI job only.
diff --git a/global.json b/global.json
new file mode 100644
index 0000000..e1c5e99
--- /dev/null
+++ b/global.json
@@ -0,0 +1,7 @@
+{
+ "sdk": {
+ "version": "10.0.401",
+ "rollForward": "latestPatch",
+ "allowPrerelease": false
+ }
+}