From c65df74b853e0d65d39c0a0a37fa8f19df893f2b Mon Sep 17 00:00:00 2001 From: Benjamin Michaelis Date: Tue, 13 Oct 2026 22:51:38 -0700 Subject: [PATCH 1/7] fix(listings): support BookContent filenames --- .../ListingSourceCodeServiceTests.cs | 18 ++++++++++++++ .../src/Chapter99/Listing99.01.Sample.cs | 2 ++ .../Services/ListingSourceCodeService.cs | 24 +++++++++---------- 3 files changed, 32 insertions(+), 12 deletions(-) create mode 100644 EssentialCSharp.Web.Tests/TestData/ListingSourceCode/src/Chapter99/Listing99.01.Sample.cs diff --git a/EssentialCSharp.Web.Tests/ListingSourceCodeServiceTests.cs b/EssentialCSharp.Web.Tests/ListingSourceCodeServiceTests.cs index 83a413c0..d783cdfd 100644 --- a/EssentialCSharp.Web.Tests/ListingSourceCodeServiceTests.cs +++ b/EssentialCSharp.Web.Tests/ListingSourceCodeServiceTests.cs @@ -65,6 +65,24 @@ public async Task GetListingAsync_DifferentFileExtension_AutoDiscoversFileExtens await Assert.That(result.FileExtension).IsEqualTo("xml"); } + [Test] + public async Task GetListingAsync_BookContentFilename_ReturnsListing() + { + ListingSourceCodeService service = CreateService(); + + ListingSourceCodeResponse? result = await service.GetListingAsync(99, 1); + IReadOnlyList chapterListings = await service.GetListingsByChapterAsync(99); + + using (Assert.Multiple()) + { + await Assert.That(result).IsNotNull(); + await Assert.That(result!.FileExtension).IsEqualTo("cs"); + await Assert.That(result.Content).Contains("BookContent filename"); + await Assert.That(chapterListings.Count).IsEqualTo(1); + await Assert.That(chapterListings[0].ListingNumber).IsEqualTo(1); + } + } + [Test] public async Task GetListingsByChapterAsync_WithValidChapter_ReturnsAllListings() { diff --git a/EssentialCSharp.Web.Tests/TestData/ListingSourceCode/src/Chapter99/Listing99.01.Sample.cs b/EssentialCSharp.Web.Tests/TestData/ListingSourceCode/src/Chapter99/Listing99.01.Sample.cs new file mode 100644 index 00000000..faa796f5 --- /dev/null +++ b/EssentialCSharp.Web.Tests/TestData/ListingSourceCode/src/Chapter99/Listing99.01.Sample.cs @@ -0,0 +1,2 @@ +// BookContent filename +Console.WriteLine("BookContent filename"); diff --git a/EssentialCSharp.Web/Services/ListingSourceCodeService.cs b/EssentialCSharp.Web/Services/ListingSourceCodeService.cs index b9eb7694..f92a174b 100644 --- a/EssentialCSharp.Web/Services/ListingSourceCodeService.cs +++ b/EssentialCSharp.Web/Services/ListingSourceCodeService.cs @@ -49,14 +49,21 @@ public void Dispose() return null; } - string pattern = $"{chapterNumber:D2}.{listingNumber:D2}.*"; + Regex listingFileRegex = ListingFilePattern(); IFileInfo? matchingFile = directoryContents .Where(f => !f.IsDirectory) - .FirstOrDefault(f => IsMatch(f.Name, pattern)); + .Select(f => new { File = f, Match = listingFileRegex.Match(f.Name) }) + .Where(x => x.Match.Success + && int.Parse(x.Match.Groups[1].Value, CultureInfo.InvariantCulture) == chapterNumber + && int.Parse(x.Match.Groups[2].Value, CultureInfo.InvariantCulture) == listingNumber) + .OrderBy(x => string.Equals(Path.GetExtension(x.File.Name), ".cs", StringComparison.OrdinalIgnoreCase) ? 0 : 1) + .ThenBy(x => x.File.Name, StringComparer.Ordinal) + .Select(x => x.File) + .FirstOrDefault(); if (matchingFile == null) { - LogListingFileNotFound(_Logger, pattern, chapterDirectory); + LogListingFileNotFound(_Logger, $"{chapterNumber:D2}.{listingNumber:D2}", chapterDirectory); return null; } @@ -82,7 +89,7 @@ public async Task> GetListingsByChapter return Array.Empty(); } - // Regex to match files like "01.01.cs" or "23.15.xml" + // Match both legacy "01.01.cs" and BookContent "Listing01.01.Title.cs" names. Regex listingFileRegex = ListingFilePattern(); var matchedFiles = directoryContents @@ -122,14 +129,7 @@ private static async Task ReadFileContentAsync(IFileInfo file) return await reader.ReadToEndAsync(); } - private static bool IsMatch(string fileName, string pattern) - { - // Convert glob-like pattern to regex (simple version for our use case) - string regexPattern = "^" + Regex.Escape(pattern).Replace("\\*", ".*") + "$"; - return Regex.IsMatch(fileName, regexPattern); - } - - [GeneratedRegex(@"^(\d{2})\.(\d{2})\.(\w+)$")] + [GeneratedRegex(@"^(?:Listing)?(\d{2})\.(\d{2})(?:\..+)?\.(\w+)$")] private static partial Regex ListingFilePattern(); [LoggerMessage(Level = LogLevel.Information, Message = "Using listing source code from: {Path}")] From fa7ad695d4ef6342e5a8705fcd16744aac7d4f58 Mon Sep 17 00:00:00 2001 From: Benjamin Michaelis Date: Tue, 13 Oct 2026 22:54:52 -0700 Subject: [PATCH 2/7] Apply suggestion from @BenjaminMichaelis --- EssentialCSharp.Web.Tests/ListingSourceCodeServiceTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EssentialCSharp.Web.Tests/ListingSourceCodeServiceTests.cs b/EssentialCSharp.Web.Tests/ListingSourceCodeServiceTests.cs index d783cdfd..a70eae55 100644 --- a/EssentialCSharp.Web.Tests/ListingSourceCodeServiceTests.cs +++ b/EssentialCSharp.Web.Tests/ListingSourceCodeServiceTests.cs @@ -76,7 +76,7 @@ public async Task GetListingAsync_BookContentFilename_ReturnsListing() using (Assert.Multiple()) { await Assert.That(result).IsNotNull(); - await Assert.That(result!.FileExtension).IsEqualTo("cs"); + await Assert.That(result.FileExtension).IsEqualTo("cs"); await Assert.That(result.Content).Contains("BookContent filename"); await Assert.That(chapterListings.Count).IsEqualTo(1); await Assert.That(chapterListings[0].ListingNumber).IsEqualTo(1); From daa191a3cb0e65ce7ffcf2201cda8bbdee387eee Mon Sep 17 00:00:00 2001 From: Benjamin Michaelis Date: Tue, 13 Oct 2026 23:02:04 -0700 Subject: [PATCH 3/7] test(listings): assert single chapter listing directly --- EssentialCSharp.Web.Tests/ListingSourceCodeServiceTests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/EssentialCSharp.Web.Tests/ListingSourceCodeServiceTests.cs b/EssentialCSharp.Web.Tests/ListingSourceCodeServiceTests.cs index a70eae55..012ebf6a 100644 --- a/EssentialCSharp.Web.Tests/ListingSourceCodeServiceTests.cs +++ b/EssentialCSharp.Web.Tests/ListingSourceCodeServiceTests.cs @@ -78,8 +78,8 @@ public async Task GetListingAsync_BookContentFilename_ReturnsListing() await Assert.That(result).IsNotNull(); await Assert.That(result.FileExtension).IsEqualTo("cs"); await Assert.That(result.Content).Contains("BookContent filename"); - await Assert.That(chapterListings.Count).IsEqualTo(1); - await Assert.That(chapterListings[0].ListingNumber).IsEqualTo(1); + ListingSourceCodeResponse listing = await Assert.That(chapterListings).HasSingleItem(); + await Assert.That(listing.ListingNumber).IsEqualTo(1); } } From f0e075a78153a40365e35e28adffd4ecbc27a5b9 Mon Sep 17 00:00:00 2001 From: Benjamin Michaelis Date: Tue, 13 Oct 2026 23:02:05 -0700 Subject: [PATCH 4/7] docs(skills): add TUnit guidance --- .agents/skills/tunit/SKILL.md | 61 +++++++++++++++++++++++++++++++++++ .claude/skills/tunit/SKILL.md | 61 +++++++++++++++++++++++++++++++++++ 2 files changed, 122 insertions(+) create mode 100644 .agents/skills/tunit/SKILL.md create mode 100644 .claude/skills/tunit/SKILL.md diff --git a/.agents/skills/tunit/SKILL.md b/.agents/skills/tunit/SKILL.md new file mode 100644 index 00000000..c224f6fb --- /dev/null +++ b/.agents/skills/tunit/SKILL.md @@ -0,0 +1,61 @@ +--- +description: Write or troubleshoot tests using TUnit, TUnit.Assertions, or TUnit.Mocks, or migrate tests or mocks to TUnit or TUnit.Mocks. Routes to task-specific official documentation. Use only for tasks involving these packages or migration to them. +license: MIT +metadata: + github-path: skills/tunit + github-ref: refs/tags/v1.75.0 + github-repo: https://github.com/thomhurst/TUnit + github-tree-sha: 0d594ad16156140ff224e389ac441149d7259d62 +name: tunit +--- +# TUnit + +Use this skill to find the TUnit guidance needed for the current task. Fetch documentation with an available web tool or HTTP client; network access is required. The documentation contains the API details and examples; do not load the whole site. + +## Load only what the task needs + +1. Check the relevant project files for TUnit package versions, target frameworks, and the SDK/test runner in `global.json`. Include centrally managed package versions when present. Preserve the project's versions unless an upgrade is requested or necessary and explained. +2. Choose the closest topic below and fetch that Markdown page before choosing APIs or commands. Start with one page; fetch another only when the task needs it. Skip pages already available in the conversation unless they need refreshing. +3. For an unlisted topic or a broken route, fetch [the documentation index](https://tunit.dev/llms.txt), search it for the feature or diagnostic, then fetch only the matching page. Do not fetch `llms-full.txt` or crawl every link. +4. Follow related links only to resolve a specific gap. Prefer the `.md` version: remove a trailing slash and append `.md` to an HTML documentation path, before any fragment. Keep existing `.md` paths unchanged. Resolve relative links against the fetched page's URL. +5. Apply the relevant example to the existing project and run a focused build or test when making code changes. Cite the documentation used. If current documentation differs from the installed API, check version-matched source or local API evidence before proposing an upgrade. If fetching fails, use available local documentation and state what could not be verified; do not guess APIs. + +## Topic routes + +Choose the link that matches the task; these are alternatives, not a reading checklist. + +| Task | Documentation | +| --- | --- | +| Create or configure a project | [Installation](https://tunit.dev/docs/getting-started/installation.md) | +| Write a basic test | [First test](https://tunit.dev/docs/getting-started/writing-your-first-test.md) | +| Assertions, exceptions, or assertion composition | [Assertions](https://tunit.dev/docs/assertions/getting-started.md) | +| Parameterized tests | [Arguments](https://tunit.dev/docs/writing-tests/arguments.md), [method data](https://tunit.dev/docs/writing-tests/method-data-source.md), or [matrix data](https://tunit.dev/docs/writing-tests/matrix-tests.md) | +| Setup, teardown, or hooks | [Lifecycle](https://tunit.dev/docs/writing-tests/lifecycle.md) | +| Fixtures, shared resources, or dependency injection | [Class data sources](https://tunit.dev/docs/writing-tests/class-data-source.md) or [dependency injection](https://tunit.dev/docs/writing-tests/dependency-injection.md) | +| Concurrency or dependencies between tests | [Parallelism](https://tunit.dev/docs/execution/parallelism.md) or [ordering](https://tunit.dev/docs/writing-tests/ordering.md) | +| Run tests or select tests | [Running tests](https://tunit.dev/docs/getting-started/running-your-tests.md) or [filters](https://tunit.dev/docs/execution/test-filters.md) | +| Migrate an existing suite | [xUnit](https://tunit.dev/docs/migration/xunit.md), [NUnit](https://tunit.dev/docs/migration/nunit.md), or [MSTest](https://tunit.dev/docs/migration/mstest.md), matching the source framework | +| Mocking | [TUnit.Mocks](https://tunit.dev/docs/writing-tests/mocking.md) | +| Migrate mocks to TUnit.Mocks | [Moq](https://tunit.dev/docs/migration/mocking/moq.md), [NSubstitute](https://tunit.dev/docs/migration/mocking/nsubstitute.md), or [FakeItEasy](https://tunit.dev/docs/migration/mocking/fakeiteasy.md), matching the source library | +| Aspire integration testing | [Aspire](https://tunit.dev/docs/examples/aspire.md) | +| ASP.NET Core integration testing | [ASP.NET Core](https://tunit.dev/docs/examples/aspnet.md) | +| Playwright browser testing | [Playwright](https://tunit.dev/docs/examples/playwright.md) | +| Testcontainers and container fixtures | [ASP.NET Core integration examples](https://tunit.dev/docs/examples/aspnet.md) (see "With Testcontainers") | +| Native AOT, trimming, or reflection mode | [AOT compatibility](https://tunit.dev/docs/writing-tests/aot.md) or [engine modes](https://tunit.dev/docs/execution/engine-modes.md) | +| Custom assertions | [Assertion source generators](https://tunit.dev/docs/assertions/extensibility/source-generator-assertions.md) or [custom assertion implementations](https://tunit.dev/docs/assertions/extensibility/custom-assertions.md) | +| Custom data sources | [Data source generators](https://tunit.dev/docs/extending/data-source-generators.md) | +| Event receivers | [Event subscribing](https://tunit.dev/docs/writing-tests/event-subscribing.md) | +| TestContext and test metadata | [Test context](https://tunit.dev/docs/writing-tests/test-context.md) | +| Diagnostics, output, or tracing | [Logging](https://tunit.dev/docs/extending/logging.md), [test artifacts](https://tunit.dev/docs/writing-tests/artifacts.md), or [OpenTelemetry](https://tunit.dev/docs/examples/opentelemetry.md) | +| Cancellation | [Cancelling a test](https://tunit.dev/docs/execution/cancellation.md) | +| Discovery, build, or execution failures | [Troubleshooting](https://tunit.dev/docs/troubleshooting.md) | + +For other topics, such as analyzers or framework-specific integrations, use the index selectively. + +## Essential distinctions + +- Await TUnit assertions, for example `await Assert.That(actual).IsEqualTo(expected);`. +- Tests run in parallel by default. Account for shared mutable state and resource lifetimes. +- TUnit uses Microsoft.Testing.Platform and `--treenode-filter`. Match command syntax to the project's SDK and runner mode; do not substitute VSTest `--filter` or assume every `dotnet test` invocation needs a `--` separator. +- Source-generated discovery is the default; reflection mode also exists. Check the relevant documentation when changing discovery or Native AOT behavior. +- TUnit.Assertions and TUnit.Mocks also work with other test frameworks. Using them alone does not require changing the project's test runner. diff --git a/.claude/skills/tunit/SKILL.md b/.claude/skills/tunit/SKILL.md new file mode 100644 index 00000000..c224f6fb --- /dev/null +++ b/.claude/skills/tunit/SKILL.md @@ -0,0 +1,61 @@ +--- +description: Write or troubleshoot tests using TUnit, TUnit.Assertions, or TUnit.Mocks, or migrate tests or mocks to TUnit or TUnit.Mocks. Routes to task-specific official documentation. Use only for tasks involving these packages or migration to them. +license: MIT +metadata: + github-path: skills/tunit + github-ref: refs/tags/v1.75.0 + github-repo: https://github.com/thomhurst/TUnit + github-tree-sha: 0d594ad16156140ff224e389ac441149d7259d62 +name: tunit +--- +# TUnit + +Use this skill to find the TUnit guidance needed for the current task. Fetch documentation with an available web tool or HTTP client; network access is required. The documentation contains the API details and examples; do not load the whole site. + +## Load only what the task needs + +1. Check the relevant project files for TUnit package versions, target frameworks, and the SDK/test runner in `global.json`. Include centrally managed package versions when present. Preserve the project's versions unless an upgrade is requested or necessary and explained. +2. Choose the closest topic below and fetch that Markdown page before choosing APIs or commands. Start with one page; fetch another only when the task needs it. Skip pages already available in the conversation unless they need refreshing. +3. For an unlisted topic or a broken route, fetch [the documentation index](https://tunit.dev/llms.txt), search it for the feature or diagnostic, then fetch only the matching page. Do not fetch `llms-full.txt` or crawl every link. +4. Follow related links only to resolve a specific gap. Prefer the `.md` version: remove a trailing slash and append `.md` to an HTML documentation path, before any fragment. Keep existing `.md` paths unchanged. Resolve relative links against the fetched page's URL. +5. Apply the relevant example to the existing project and run a focused build or test when making code changes. Cite the documentation used. If current documentation differs from the installed API, check version-matched source or local API evidence before proposing an upgrade. If fetching fails, use available local documentation and state what could not be verified; do not guess APIs. + +## Topic routes + +Choose the link that matches the task; these are alternatives, not a reading checklist. + +| Task | Documentation | +| --- | --- | +| Create or configure a project | [Installation](https://tunit.dev/docs/getting-started/installation.md) | +| Write a basic test | [First test](https://tunit.dev/docs/getting-started/writing-your-first-test.md) | +| Assertions, exceptions, or assertion composition | [Assertions](https://tunit.dev/docs/assertions/getting-started.md) | +| Parameterized tests | [Arguments](https://tunit.dev/docs/writing-tests/arguments.md), [method data](https://tunit.dev/docs/writing-tests/method-data-source.md), or [matrix data](https://tunit.dev/docs/writing-tests/matrix-tests.md) | +| Setup, teardown, or hooks | [Lifecycle](https://tunit.dev/docs/writing-tests/lifecycle.md) | +| Fixtures, shared resources, or dependency injection | [Class data sources](https://tunit.dev/docs/writing-tests/class-data-source.md) or [dependency injection](https://tunit.dev/docs/writing-tests/dependency-injection.md) | +| Concurrency or dependencies between tests | [Parallelism](https://tunit.dev/docs/execution/parallelism.md) or [ordering](https://tunit.dev/docs/writing-tests/ordering.md) | +| Run tests or select tests | [Running tests](https://tunit.dev/docs/getting-started/running-your-tests.md) or [filters](https://tunit.dev/docs/execution/test-filters.md) | +| Migrate an existing suite | [xUnit](https://tunit.dev/docs/migration/xunit.md), [NUnit](https://tunit.dev/docs/migration/nunit.md), or [MSTest](https://tunit.dev/docs/migration/mstest.md), matching the source framework | +| Mocking | [TUnit.Mocks](https://tunit.dev/docs/writing-tests/mocking.md) | +| Migrate mocks to TUnit.Mocks | [Moq](https://tunit.dev/docs/migration/mocking/moq.md), [NSubstitute](https://tunit.dev/docs/migration/mocking/nsubstitute.md), or [FakeItEasy](https://tunit.dev/docs/migration/mocking/fakeiteasy.md), matching the source library | +| Aspire integration testing | [Aspire](https://tunit.dev/docs/examples/aspire.md) | +| ASP.NET Core integration testing | [ASP.NET Core](https://tunit.dev/docs/examples/aspnet.md) | +| Playwright browser testing | [Playwright](https://tunit.dev/docs/examples/playwright.md) | +| Testcontainers and container fixtures | [ASP.NET Core integration examples](https://tunit.dev/docs/examples/aspnet.md) (see "With Testcontainers") | +| Native AOT, trimming, or reflection mode | [AOT compatibility](https://tunit.dev/docs/writing-tests/aot.md) or [engine modes](https://tunit.dev/docs/execution/engine-modes.md) | +| Custom assertions | [Assertion source generators](https://tunit.dev/docs/assertions/extensibility/source-generator-assertions.md) or [custom assertion implementations](https://tunit.dev/docs/assertions/extensibility/custom-assertions.md) | +| Custom data sources | [Data source generators](https://tunit.dev/docs/extending/data-source-generators.md) | +| Event receivers | [Event subscribing](https://tunit.dev/docs/writing-tests/event-subscribing.md) | +| TestContext and test metadata | [Test context](https://tunit.dev/docs/writing-tests/test-context.md) | +| Diagnostics, output, or tracing | [Logging](https://tunit.dev/docs/extending/logging.md), [test artifacts](https://tunit.dev/docs/writing-tests/artifacts.md), or [OpenTelemetry](https://tunit.dev/docs/examples/opentelemetry.md) | +| Cancellation | [Cancelling a test](https://tunit.dev/docs/execution/cancellation.md) | +| Discovery, build, or execution failures | [Troubleshooting](https://tunit.dev/docs/troubleshooting.md) | + +For other topics, such as analyzers or framework-specific integrations, use the index selectively. + +## Essential distinctions + +- Await TUnit assertions, for example `await Assert.That(actual).IsEqualTo(expected);`. +- Tests run in parallel by default. Account for shared mutable state and resource lifetimes. +- TUnit uses Microsoft.Testing.Platform and `--treenode-filter`. Match command syntax to the project's SDK and runner mode; do not substitute VSTest `--filter` or assume every `dotnet test` invocation needs a `--` separator. +- Source-generated discovery is the default; reflection mode also exists. Check the relevant documentation when changing discovery or Native AOT behavior. +- TUnit.Assertions and TUnit.Mocks also work with other test frameworks. Using them alone does not require changing the project's test runner. From eb4ae95120980b8b2979556aa114132f32cd5bfb Mon Sep 17 00:00:00 2001 From: Benjamin Michaelis Date: Tue, 13 Oct 2026 23:06:02 -0700 Subject: [PATCH 5/7] fix(deps): pin patched cryptography packages --- Directory.Packages.props | 2 ++ EssentialCSharp.Web.Tests/EssentialCSharp.Web.Tests.csproj | 1 + EssentialCSharp.Web/EssentialCSharp.Web.csproj | 1 + 3 files changed, 4 insertions(+) diff --git a/Directory.Packages.props b/Directory.Packages.props index 9f64dcf9..52d8f244 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -57,6 +57,8 @@ + + diff --git a/EssentialCSharp.Web.Tests/EssentialCSharp.Web.Tests.csproj b/EssentialCSharp.Web.Tests/EssentialCSharp.Web.Tests.csproj index 23a0c5ff..c0859ed0 100644 --- a/EssentialCSharp.Web.Tests/EssentialCSharp.Web.Tests.csproj +++ b/EssentialCSharp.Web.Tests/EssentialCSharp.Web.Tests.csproj @@ -14,6 +14,7 @@ + diff --git a/EssentialCSharp.Web/EssentialCSharp.Web.csproj b/EssentialCSharp.Web/EssentialCSharp.Web.csproj index f0a2c6e2..be1eb33d 100644 --- a/EssentialCSharp.Web/EssentialCSharp.Web.csproj +++ b/EssentialCSharp.Web/EssentialCSharp.Web.csproj @@ -62,6 +62,7 @@ all + From b4b365733cfb657dbde758b8506b58a49e573ca2 Mon Sep 17 00:00:00 2001 From: Benjamin Michaelis Date: Tue, 13 Oct 2026 23:24:09 -0700 Subject: [PATCH 6/7] fix(telemetry): prefer Aspire OTLP when available --- EssentialCSharp.Web/Program.cs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/EssentialCSharp.Web/Program.cs b/EssentialCSharp.Web/Program.cs index 94eda71c..f08c0be1 100644 --- a/EssentialCSharp.Web/Program.cs +++ b/EssentialCSharp.Web/Program.cs @@ -46,13 +46,11 @@ private static void Main(string[] args) builder.Services.AddHealthChecks() .AddCheck("self", () => HealthCheckResult.Healthy(), ["live"]); - // OpenTelemetry — two mutually exclusive export paths: - // Production: Azure Monitor (Application Insights) via APPLICATIONINSIGHTS_CONNECTION_STRING - // Local/Aspire: OTLP to Aspire Dashboard via OTEL_EXPORTER_OTLP_ENDPOINT - // Never both simultaneously — that would cause duplicate telemetry in App Insights. + // Prefer Aspire's OTLP endpoint when present; otherwise export to Azure Monitor. + // This keeps local traces in the dashboard even when a developer has an App Insights connection string configured. string? appInsightsConnectionString = builder.Configuration["APPLICATIONINSIGHTS_CONNECTION_STRING"]; - bool useAzureMonitor = !string.IsNullOrWhiteSpace(appInsightsConnectionString); bool useOtlp = !string.IsNullOrWhiteSpace(builder.Configuration["OTEL_EXPORTER_OTLP_ENDPOINT"]); + bool useAzureMonitor = !useOtlp && !string.IsNullOrWhiteSpace(appInsightsConnectionString); bool profilerSupportedPlatform = OperatingSystem.IsWindows() || OperatingSystem.IsLinux(); bool profilerSkippedUnsupportedPlatform = false; From 91d15be4affd39962f37ddfd5fbeb6fa9982ff2f Mon Sep 17 00:00:00 2001 From: Benjamin Michaelis Date: Tue, 13 Oct 2026 23:41:02 -0700 Subject: [PATCH 7/7] fix(deps): pin Cryptography.Pkcs to 10.0.13 to avoid EF bundle downgrade The 9.0.21 pin caused NU1605 (Pkcs 10.0.12 -> 9.0.21 downgrade via Cryptography.Xml) when 'dotnet ef migrations bundle' restored for linux-x64, failing the Docker build. --- Directory.Packages.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 52d8f244..3d73c02d 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -57,7 +57,7 @@ - +