You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Events go to the shared topic; commands go to a per-domain, session-enabled queue ({Destination}-{domain}), so only the addressed domain receives them.
Framework
NamedDestinationProvider (AddNamedDestinationProvider()): commands are routed to {Destination}-{domain}; a null or empty command domain self-addresses to IHostSettings.DomainName. Unit tests included.
Keyed Service Bus receiving: keyed WithReceiver / WithSessionReceiver and WithKeyedSubscribedSubscriber, allowing multiple receivers (events topic and commands queue) in one Subscribe host.
Aspire E2E testing (CoreEx.UnitTesting): DistributedApplication helpers for SQL Server/PostgreSQL migration, Redis clear and Service Bus emulator reset; AddEndpoints / AddHostedServiceSupport / DisableHttpCertificateValidation AppHost extensions; WireMock-based MockHost for third-party HTTP.
Samples
Contoso migrated to command queues (contoso-products queue; Products.Subscribe has an events receiver and a commands receiver).
Contoso.Test.Aspire E2E, including SendGrid stubbed via the MockHost.
CoreEx.Template
Hosts use NamedDestinationProvider and a keyed subscriber by default.
Service Bus topology is code-based in the generated Test.CommonServiceBus class; Relay, Subscribe and Aspire tests use ResetAzureServiceBusAsync.
coreex-aspire template generates the AppHost, MockHost and Test.Aspire (OnBeforeStartAsync / OnAfterStartAsync).
AI assets and docs
New skills: coreex-command-publish-e2e, coreex-command-subscribe-e2e, coreex-aspire, with prompts, Claude commands and coreex-aspire.instructions.md.
README, catalog, agent routing and related skills/docs updated; stale docs fixed.
CI / versioning
New CoreEx.Samples.Test.E2E.slnf and a net10.0-only Aspire E2E step after the sample tests; Contoso.Aspire.MockHost and Contoso.Test.Aspire added to the samples build filter.
Version bumped to 4.1.0, with CHANGELOG entries.
Testing
tools/validate-template-pack.ps1 passes (19/19), including packing and building the template scaffolds.
The E2E filter builds locally. The Aspire E2E has not yet run in CI; expect the first run to show any Linux runner issues (dashboard/OTLP port conflicts, Aspire environment variables).
The AI skill snippets are modelled on the Contoso samples and have not been compiled in a scaffolded solution.
Added Reset-ServiceBusEmulator.ps1 to automate recovery of the Service Bus emulator and SQL Server containers. Registered the script in CoreEx.slnx. Updated AppHost.cs to model infra as Aspire connection-string resources with icons and explicit project references for dashboard topology. Synced appsettings.Development.json connection strings to match Aspire resource names. Upgraded UnitTestEx packages to 5.12.0. Improved EventExpectations and UnitTestExExpectations.ChangeLog with updated XML docs, constructor signatures, and runtime type checks for TesterBaseCore.
Signed-off-by: Eric Sibly <eric.sibly@avanade.com>
…in database retry pipeline
- Add Contoso.Test.Aspire E2E test project exercising the full Aspire AppHost topology.
- Add WithAspireTester unit-testing helper alongside FusionCache Redis-connection-string cache
clearing and Service Bus admin-client-based queue/subscription reset/purge helpers, replacing
the Service Bus emulator Config.json-driven approach.
- Add per-database IsTransientException/HandleDbException classification (SqlServer + Postgres),
reviewed against Azure SQL/Postgres transient error code lists.
- Add opt-in DatabaseArgsBase.RetryOnTransient + RetryResiliencePipeline, wired through
DatabaseInvoker via a generalized RetryResiliency<TOwner>.Create<TResult> (Result<T>-aware)
pipeline. Exhausted-retry failures convert directly to a Result (no throw) when the caller's
result type is ROP-shaped; otherwise a single unwrapped exception (no AggregateException
wrapping) is rethrown, preserving the existing TransformException conversion layer.
- Add unit test coverage for the retry pipeline (DatabaseInvokerRetryTests, RetryResiliencyTests).
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Eric Sibly <eric.sibly@avanade.com>
…ic type argument
The semaphore-protected 'slow path' in ReferenceDataHybridCache.GetOrCreateAsync relied on
C# generic type inference to pick the T used for the underlying IHybridCache.GetOrCreateByKeyAsync<T>
call. Since the inner lambda's return expression resolved to the factory delegate's declared
IReferenceDataCollection return type, T was always inferred as the interface - never the concrete
collection type (e.g. UnitOfMeasureCollection) - unlike the 'fast path' read, which already used a
reflection-built typed invoker specifically to avoid this.
With a serializing distributed cache (FusionCache + Redis), this caused intermittent
System.Text.Json.NotSupportedException: 'The collection type IReferenceDataCollection is abstract,
an interface, or is read only, and could not be instantiated and populated' whenever the slow path's
internal L2 check deserialized a previously-written entry using the interface as the generic argument -
observed as FUSION [DC] deserialization errors in the Aspire E2E test logs.
Fix: add a GetOrCreateByKeyInvoker (mirroring the existing TryGetByKeyInvoker) so the slow path also
invokes the underlying cache with the concrete collection type, never the interface.
Added a regression test (GetOrCreateAsync_CacheMiss_NeverUsesInterfaceAsUnderlyingCacheGenericType)
using a type-recording IHybridCache stand-in; verified it fails against the pre-fix code and passes
against the fix. Full CoreEx.RefData.Test.Unit suite (137 tests) passes on net8.0/net9.0/net10.0.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Eric Sibly <eric.sibly@avanade.com>
…trust
Adds Contoso.Aspire.MockHost (WireMock.Net) as a test-only Aspire resource, wired into the
AppHost's DI container, and disables ASP.NET Core dev-cert validation for the AppHost's own
HttpClient defaults - dotnet dev-certs https --trust is not fully supported on Linux, so the
'api' resource's https endpoint dev cert is not OS-trusted on Linux CI runners; this covers both
the health check probe and AspireTesterBase's CreateHttpClient(), which resolve HttpClient via the
same DI container.
Also reorders two E2ETest.cs checkpoints (discount before basket-item-add) to match the actual
sync timing, and picks up the Spectre.Console/WireMock.Net package version housekeeping in
Directory.Packages.props and CoreEx.slnx needed by the new project.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Eric Sibly <eric.sibly@avanade.com>
- Move the templated Aspire project out of src/ into a sibling aspire/
directory (src, tests, aspire at the same level) since it is not a
to-be-deployed project.
- Wire AppHost.cs resources first, then reference them from each project,
mirroring samples/aspire/Contoso.Aspire/AppHost.cs.
- Add UnitTestExExtensions.Aspire.cs (CoreEx.UnitTesting) providing the
WireMockConsole/AddMockHostProject/AddEndpoints Aspire helpers; delete
the templated Extensions.cs, which these replace.
- Emit two new sibling projects into aspire/: app-name.Aspire.MockHost
(WireMock.Net stub host) and solution-name.Test.Aspire (NUnit,
WithAspireTester-based HostTests.cs).
- Apply the same MockHost simplification to samples/aspire/Contoso.Aspire.MockHost
and wire it into Contoso.Aspire/AppHost.cs.
- Add Development.json connection strings matching Contoso.Aspire.
- Bump the templated AppHost's Aspire.AppHost.Sdk to 13.5.4 to match
UnitTestEx.Aspire's Aspire.Hosting.AppHost requirement.
- Harden tools/validate-template-pack.ps1's local NuGet feed handling:
copy missing third-party packages into the trusted local feed instead
of adding a second, potentially stale, private source.
- Update samples/docs/aspire.md and testing.md to document the MockHost
and Contoso.Test.Aspire; fix a stale AppHost.cs path in
.github/copilot-instructions.md; update coreex-scaffold skill docs.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Eric Sibly <eric.sibly@avanade.com>
…or SendGrid mocking
- New INotificationAdapter (Application) / NotificationAdapter (Infrastructure) backed by a
SendGrid v3 Mail Send typed HTTP client (no SDK dependency).
- New BasketCheckedOutSubscriber (Contoso.Shopping.Subscribe) subscribing to
contoso.shopping.basket.checkedout.v1, delegating to the notification adapter.
- Register SendGrid typed client + options in Subscribe/Api Program.cs and appsettings.json.
- Wire the Aspire AppHost to route the Subscribe host's SendGrid BaseAddress through the
WireMock-based MockHost, stubbing v3/mail/send with 202 Accepted for local/dev E2E runs.
- Refactor CoreEx.UnitTesting Aspire helpers (ClearRedisCacheAsync, ResetAzureServiceBusAsync,
etc.) to operate on DistributedApplication directly, and extend WithAspireTester with
OnBeforeStartAsync/OnAfterStartAsync hooks supporting the MockHost wiring.
- New SendGridHttpClientTests (Test.Unit) and SubscriberTests.BasketCheckedOut (Test.Subscribe)
proving the SendGrid API is invoked via mocked HTTP.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Eric Sibly <eric.sibly@avanade.com>
- New skills coreex-command-publish-e2e, coreex-command-subscribe-e2e and coreex-aspire
(with prompts, Claude commands, aspire instructions) registered in the template pack.
- Update catalogs, README, agent routing, subscriber/adapter/test skills and stale docs.
- CI: add Aspire E2E solution filter and net10.0-only test step after sample tests.
- Bump version to 4.1.0 and add CHANGELOG entries.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Eric Sibly <eric.sibly@avanade.com>
The reason will be displayed to describe this comment to others. Learn more.
Copilot review overview
🟡 Changes recommended
Generated runtime topology and standalone service discovery are incomplete, while two unbounded asynchronous timing assumptions make the new E2E test unreliable.
Introduces domain-targeted command queues, keyed Service Bus receivers, Aspire end-to-end testing, transient database retries, and supporting templates/documentation for v4.1.0.
Changes:
Adds NamedDestinationProvider, multi-destination publishing, keyed receivers, and command queue samples.
Adds Aspire orchestration, test helpers, MockHost integration, and E2E coverage.
… non-flaky
- Restart the Cosmos emulator after the core tests and wait for readiness before the sample tests.
- PartitionPickerTests: assert on the average overlap over many pairs rather than a single random pair (~2% failure rate).
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Eric Sibly <eric.sibly@avanade.com>
The reason will be displayed to describe this comment to others. Learn more.
Copilot review overview
🔵 Needs a closer look
Standalone service discovery is broken, the Aspire E2E contains an asynchronous replication race, and the activation endpoint is missing established API safeguards.
This idempotent POST can return NotFoundException, but it neither opts into the idempotency middleware nor documents the 404 response, unlike the established mutating-controller pattern. Retries therefore bypass response deduplication and the generated OpenAPI omits a real outcome.
Aspire launches project resources with 'dotnet run --no-build', which fails for
the multi-targeted sample hosts ("Your project targets multiple frameworks"), so
the hosts exited at startup and the E2E test timed out.
- Contoso.Test.Aspire.runsettings sets DOTNET_TARGET_FRAMEWORK=net10.0 for the test run.
- samples/Directory.Build.props also clears TargetFrameworks when it is set.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Eric Sibly <eric.sibly@avanade.com>
Add a matching ProductMutateTests.Activate.cs integration test for this new API operation. Every other Product mutation has operation-specific API coverage, while the broad Aspire happy path does not verify not-found/no-op behavior or the activated outbox event.
Add fallback products-api endpoint for standalone Subscribe runs
This logical Aspire URI has no fallback endpoint when the Subscribe host is launched directly, so the documented non-Aspire dotnet run workflow (samples/docs/local-dev.md:158-176) can no longer resolve products-api. Add local Services:products-api endpoints; Aspire's WithReference configuration can then override those endpoints during orchestration.
TestSynchronizer incremented its enter/exit counts before recording the last name/type, so tests
polling the count could read a null value. Record the values first.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Eric Sibly <eric.sibly@avanade.com>
This adds a production API operation, but Contoso.Products.Test.Api has no activation test; the only coverage is the much broader Aspire happy path. Add focused API tests for activation success (including persistence/event output), already-active idempotency, and not-found behavior so endpoint/service regressions are isolated.
Shopping subscriber registration conflicts with keyed guidance
This new event-subscriber test resolves an unkeyed subscriber, while the updated coreex-test-subscribe guidance says event subscribers use the subscriber-events key and the templates now generate that pattern. Because these samples are the model for the new skills, align Shopping's receiver and tests with the keyed event registration (or narrow the guidance) to avoid generating code that disagrees with its reference implementation.
Undocumented public database retry API and release behavior
This introduces a public opt-in database retry API (plus provider transient classifications and relay behavior changes), but neither the PR summary nor the v4.1.0 changelog mentions it. That is a material release feature unrelated to the stated command/Aspire scope; document its behavior and compatibility impact in this release or split it into a separately reviewed change.
The E2E hosts call each other (shopping -> products) over https using the dev certificate, which
is untrusted on Linux runners (UntrustedRoot). Export it and add it to the system trust store.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Eric Sibly <eric.sibly@avanade.com>
- Contoso Aspire E2E now detects the stubbed SendGrid call by polling the
WireMock admin request journal (bounded 30x1s) instead of scraping logs.
- CoreEx.Core / CoreEx.Aspire templates emit tests/ and aspire/
Directory.Build.props (NoWarn IDE1006, imports the solution-root props);
validate-template-pack.ps1 asserts both.
- Instructions/skills: patch/subset contracts must be standalone (no
inheriting a sibling and suppressing members); Aspire test guidance uses
the admin-API polling pattern; new end-of-file rule (exactly one trailing
newline).
- Strip extra trailing blank lines from 14 existing files.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Eric Sibly <eric.sibly@avanade.com>
- Application-service instructions/skill: add guidance on propagating a failed
Result (return the result itself; never rebuild from .Error).
- docs/: refresh agent guides, capabilities, codebase map and slides.
- CodeGenCounter: validate the output directory exists and tolerate a missing
parent when choosing the directory to count.
- ExtensionsTests: end-of-file newline fix.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Eric Sibly <eric.sibly@avanade.com>
This create-style POST is safe to retry, but without [IdempotencyKey] concurrent retries can both observe an inactive Product and publish duplicate activation events. Apply the standard POST idempotency middleware used by the other Products POST endpoints.
Document TitlePrefix removal before parsing DomainName
src/CoreEx.Events/EventData.cs:45
Title includes the optional solution-name prefix, so DomainName is not necessarily its first segment. The formatter strips TitlePrefix before parsing; document that distinction to avoid consumers treating the solution segment (for example contoso) as the domain (shopping).
The generated GlobalUsing.cs is not ordinally sorted (AwesomeAssertions must precede CoreEx). CoreEx requires the entire global-using file to remain sorted so generated scaffolds start convention-compliant.
- **Before building Path C:** confirm `I{Name}Repository` already has `QueryAsync`/`QuerySchemaAsync` backed by a `{Name}QueryArgsConfig`. If not, stop and invoke `coreex-repository` first — never add filtering/ordering logic or a hand-rolled query in the service to work around a missing repository method
- Always `.ConfigureAwait(false)` on every `await`
- A Domain value object persisted via a JSON column (e.g. `Basket.ShippingAddress`) is mapped with a `BiDirectionMapper<TDomain, TContract, TSelf>` in `Application/Mapping/` (not the uni-directional `Mapper<TSource,TDest,TSelf>` used for the root aggregate) — see [`coreex-application-services.instructions.md#json-backed-value-object-mapping`](/.github/instructions/coreex-application-services.instructions.md#json-backed-value-object-mapping)
- `Result<T>` failure propagation: prefer a `Then*` chain; for a guard clause `return r;` (same type, or `Result` → `Result<U>`) or `return r.AsResult();` (`Result<T>` → `Result<U>`) — **never** `Result<X>.Fail(r.Error!)` / `return r.Error!;`, and don't touch `.Error` in service code- A Domain value object persisted via a JSON column (e.g. `Basket.ShippingAddress`) is mapped with a `BiDirectionMapper<TDomain, TContract, TSelf>` in `Application/Mapping/` (not the uni-directional `Mapper<TSource,TDest,TSelf>` used for the root aggregate) — see [`coreex-application-services.instructions.md#json-backed-value-object-mapping`](/.github/instructions/coreex-application-services.instructions.md#json-backed-value-object-mapping)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Events go to the shared topic; commands go to a per-domain, session-enabled queue (
{Destination}-{domain}), so only the addressed domain receives them.Framework
NamedDestinationProvider(AddNamedDestinationProvider()): commands are routed to{Destination}-{domain}; a null or empty command domain self-addresses toIHostSettings.DomainName. Unit tests included.WithReceiver/WithSessionReceiverandWithKeyedSubscribedSubscriber, allowing multiple receivers (events topic and commands queue) in one Subscribe host.CoreEx.UnitTesting):DistributedApplicationhelpers for SQL Server/PostgreSQL migration, Redis clear and Service Bus emulator reset;AddEndpoints/AddHostedServiceSupport/DisableHttpCertificateValidationAppHost extensions; WireMock-basedMockHostfor third-party HTTP.Samples
contoso-productsqueue; Products.Subscribe has an events receiver and a commands receiver).Contoso.Test.AspireE2E, including SendGrid stubbed via the MockHost.CoreEx.Template
NamedDestinationProviderand a keyed subscriber by default.Test.CommonServiceBusclass; Relay, Subscribe and Aspire tests useResetAzureServiceBusAsync.coreex-aspiretemplate generates the AppHost,MockHostandTest.Aspire(OnBeforeStartAsync/OnAfterStartAsync).AI assets and docs
coreex-command-publish-e2e,coreex-command-subscribe-e2e,coreex-aspire, with prompts, Claude commands andcoreex-aspire.instructions.md.CI / versioning
CoreEx.Samples.Test.E2E.slnfand a net10.0-only Aspire E2E step after the sample tests;Contoso.Aspire.MockHostandContoso.Test.Aspireadded to the samples build filter.Testing
tools/validate-template-pack.ps1passes (19/19), including packing and building the template scaffolds.