Release v4.0.0-preview-5 - #184
Merged
Merged
Conversation
Bumps Version.props to 4.0.0-preview-5. A fixing/massaging release: no new
milestone, just accumulated fixes and doc/reality-gap corrections found
while reviewing the codebase and its AI workflow assets.
Feature/fix work:
- Add WithIfMatchRequired() extension so a POST/DELETE that is conditional
on a related (parent) resource's state can assert If-Match explicitly,
independent of the automatic PUT/PATCH IETag-based check. ETag capture
in WebApiOptionsBase now also applies to POST/DELETE. Covered by new
WebApiTestsBase.Post.cs / .Delete.cs unit tests (missing + present
If-Match cases), and documented in coreex-api-controllers.instructions.md
and CoreEx.AspNetCore/AGENTS.md.
- Expand EventAction with additional lifecycle action values (Confirmed,
Started, Paused, Stopped, Restarted, Suspended, Reinstated, Closed,
Reopened, Expired, Renewed, Acknowledged, Declined).
Documentation / AI workflow asset corrections:
- Idempotency-Key header: several READMEs/AGENTS.md described the header
as x-idempotency-key; the real constant (HttpNames.IdempotencyKeyHeaderName)
is Idempotency-Key, matching both the actual code and the IETF standard
for this header (RFC 6648 deprecated the x- prefix convention).
- Paging headers/query string: removed a fabricated $page query parameter
and X-Paging-Page response header that don't exist anywhere in the
framework (PagingArgs has no Page property), and corrected a wrongly
shortened X-Paging-Count to the real X-Paging-Total-Count.
- Fixed RFC 7386 -> RFC 7396 (JSON Merge Patch) in docs/capabilities.md.
- Closed a gap where $inactive (reference-data IsActive/IsInactive,
wired up only for reference-data endpoints) was conflated with
soft-delete (IsDeleted) on regular entities in coreex-test-api's
workflow, including a worked example that showed a soft-deleted row
reappearing via $inactive - which the framework does not support.
Added explicit guardrails in coreex-tests.instructions.md and
coreex-repositories.instructions.md against ever bypassing the
logical-delete filter (EfDbArgs.BypassFilters /
WithLogicalDeleteFilter(allowFilterBypass: true) / IgnoreQueryFilters())
to satisfy a test.
- Added guardrails against writing throwaway smoke tests between
implementing an API/service and authoring its real test
(coreex-api, coreex-app-service workflows) - hand off straight to the
real test.
- Closed a gap where coreex-contract's Path B (subordinate/request
contracts) had no property-type-resolution step for reference-data
properties, unlike Path A - so a request DTO could end up with a plain
{Name}Code string property instead of the required [Contract]+partial
class / [ReferenceData<T>]+partial property pattern, which serializes
with the wrong JSON name and has no typed navigation for validation.
- Domain naming-collision note (Contracts/Domain/Persistence sharing a
short type name), Result<T>.ThrowOnError() target-typing note,
aggregate OnCheckCanMutate() heterogeneous-precondition note, and a
repository parent/child EF navigation cross-reference.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Signed-off-by: Eric Sibly <eric.sibly@avanade.com>
Contributor
There was a problem hiding this comment.
Pull request overview
Publishes v4.0.0-preview-5 of CoreEx, bundling a small framework enhancement (explicit If-Match requirement for conditional POST/DELETE + broader request ETag capture) plus a broad “docs vs reality” correction pass across README/AGENTS/instructions/skills.
Changes:
- Bump version to
4.0.0-preview-5. - Add
WithIfMatchRequired()and extendWebApiOptionsBaseETag capture to POST/DELETE; add unit tests for missing/presentIf-Match. - Correct documentation and AI workflow assets around idempotency header name, paging headers/query parameters, merge-patch RFC, and
$inactivevs soft-delete guidance; expandEventAction.
Reviewed changes
Copilot reviewed 31 out of 31 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| Version.props | Version bump to 4.0.0-preview-5. |
| tests/CoreEx.AspNetCore.Test.Unit/WebApiTestsBase.Post.cs | Adds POST tests for WithIfMatchRequired() behavior. |
| tests/CoreEx.AspNetCore.Test.Unit/WebApiTestsBase.Delete.cs | Adds DELETE tests for WithIfMatchRequired() behavior. |
| src/CoreEx/Http/README.md | Corrects paging/idempotency header documentation to match HttpNames. |
| src/CoreEx.Events/EventAction.cs | Adds additional lifecycle EventAction enum values. |
| src/CoreEx.AspNetCore/WebApiRequestOptions.cs | Centralizes concurrency message; clarifies PUT/PATCH verification remarks. |
| src/CoreEx.AspNetCore/README.md | Corrects idempotency header name in documentation. |
| src/CoreEx.AspNetCore/Mvc/README.md | Corrects paging parameters and idempotency header name in docs. |
| src/CoreEx.AspNetCore/Idempotency/README.md | Corrects idempotency header name references and examples. |
| src/CoreEx.AspNetCore/AspNetCoreExtensions.cs | Adds WithIfMatchRequired() extension and improves WithLocationUri docs. |
| src/CoreEx.AspNetCore/AGENTS.md | Documents POST/DELETE related-resource concurrency pattern using WithIfMatchRequired(). |
| src/CoreEx.AspNetCore/Abstractions/WebApiOptionsBase.cs | Captures If-Match ETag for POST/DELETE; adds shared concurrency message. |
| src/CoreEx.AspNetCore/Abstractions/README.md | Updates docs for ETag capture/paging header names. |
| src/CoreEx.AspNetCore.NSwag/README.md | Corrects paging params and idempotency header name docs. |
| src/CoreEx.AspNetCore.NSwag/AGENTS.md | Corrects paging params and idempotency header name in the attribute matrix. |
| docs/capabilities.md | Fixes Merge Patch RFC references (7396). |
| .github/skills/coreex-test-api/SKILL.md | Refines test workflow guidance (seed precondition state; clarifies 428 assertions; outbox expectations). |
| .github/skills/coreex-test-api/references/workflow.md | Expands detailed testing workflow guardrails, esp. $inactive vs soft-delete and event expectations. |
| .github/skills/coreex-repository/references/workflow.md | Adds guidance on hand-authoring EF parent/child navigation properties. |
| .github/skills/coreex-contract/references/workflow.md | Adds reference-data property resolution requirements for request/subordinate DTOs and conditional If-Match DTO pattern. |
| .github/skills/coreex-app-service/SKILL.md | Adds guidance to prefer EventAction enum before raw string actions. |
| .github/skills/coreex-app-service/references/workflow.md | Documents ThrowOnError() target-typing nuance and EventAction enum preference; discourages throwaway smoke tests. |
| .github/skills/coreex-api/SKILL.md | Updates workflow guidance to go straight to real integration tests (no throwaway smoke tests). |
| .github/skills/coreex-api/references/workflow.md | Adds controller pattern for conditional nested-resource POST/DELETE using WithIfMatchRequired(). |
| .github/skills/coreex-aggregate/references/workflow.md | Clarifies OnCheckCanMutate() limitations for heterogeneous preconditions. |
| .github/instructions/coreex-tests.instructions.md | Adds guardrails for outbox expectations, seeded preconditions, $inactive vs soft-delete, and conditional POST/DELETE If-Match tests. |
| .github/instructions/coreex-repositories.instructions.md | Adds EF navigation guidance and reinforces “don’t bypass soft-delete filter” rule. |
| .github/instructions/coreex-domain.instructions.md | Adds guidance about intentional naming collisions across Contracts/Domain/Persistence and qualification strategy. |
| .github/instructions/coreex-contracts.instructions.md | Adds conditional If-Match command/request DTO pattern (IETag + [JsonIgnore]). |
| .github/instructions/coreex-application-services.instructions.md | Documents ThrowOnError() target-typing behavior to avoid var pitfalls. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
- Abstractions/README.md: reword the ETag/If-Match bullet so it no longer implies a missing ETag can fail with either 412 or 428 - missing is always 428 (Precondition Required); 412 (Precondition Failed) is a separate, later failure for a stale/mismatched ETag against the persisted value once the operation actually runs. - WebApiTestsBase.Post.cs: the new Post_Body_With_Response_IfMatchRequired_Present test only proved WithIfMatchRequired() doesn't throw when If-Match is present - it never asserted the header value was actually captured onto ro.ETag and stamped onto the deserialized body (ro.Value.ETag), which is the actual production behaviour this PR added to WebApiOptionsBase for POST. Added the same ro.ETag / ro.Value.ETag assertions the existing PUT tests use. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Signed-off-by: Eric Sibly <eric.sibly@avanade.com>
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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
Publishes v4.0.0-preview-5. There is no milestone or set of merged PRs behind this one — it's a fixing/massaging release: accumulated small fixes plus a documentation/reality-gap correction pass, bundled together and versioned.
What's in this release
Feature/fix work:
WithIfMatchRequired()so aPOST/DELETEthat is conditional on a related (parent) resource's state can assertIf-Matchexplicitly — independent of the automaticPUT/PATCHIETag-based concurrency check.WebApiOptionsBaseETag capture now also applies toPOST/DELETE. Covered by new unit tests (WebApiTestsBase.Post.cs/.Delete.cs— missing vs. presentIf-Match) and documented incoreex-api-controllers.instructions.mdandCoreEx.AspNetCore/AGENTS.md.EventActionwith additional lifecycle values:Confirmed,Started,Paused,Stopped,Restarted,Suspended,Reinstated,Closed,Reopened,Expired,Renewed,Acknowledged,Declined.Documentation / AI workflow asset corrections (found while reviewing header names and generated-asset behaviour against the actual source):
Idempotency-Keyheader — several READMEs/AGENTS.mdfiles described it asx-idempotency-key. The real constant (HttpNames.IdempotencyKeyHeaderName) isIdempotency-Key, matching both the code and the IETF standard for this header (thex-prefix convention was deprecated by RFC 6648).$pagequery parameter andX-Paging-Pageresponse header that don't exist anywhere in the framework (PagingArgshas noPageproperty), and corrected a wrongly shortenedX-Paging-Countto the realX-Paging-Total-Count.docs/capabilities.mdcited RFC 7386 for JSON Merge Patch; the correct spec is RFC 7396 (matches every other reference in the repo).$inactivevs. soft-delete — closed a gap incoreex-test-api's workflow where$inactive(reference-dataIsActive/IsInactive, wired up only for reference-data endpoints) was conflated with soft-delete (IsDeleted) on regular entities, including a worked example showing a soft-deleted row reappearing via$inactive— which the framework does not support. Added explicit guardrails against ever bypassing the logical-delete filter (EfDbArgs.BypassFilters/WithLogicalDeleteFilter(allowFilterBypass: true)/IgnoreQueryFilters()) to satisfy a test.coreex-api,coreex-app-serviceworkflows) — hand off straight to the real test, which is the actual verification step.coreex-contract's Path B (subordinate/request contracts) had no property-type-resolution step for reference-data properties, unlike Path A. A request DTO (e.g.XxxAddRequest) could end up with a plain{Name}Codestring property instead of the required[Contract]+partialclass /[ReferenceData<T>]+partialproperty pattern — which compiles but serializes with the wrong JSON name and has no typed navigation for validation.Result<T>.ThrowOnError()target-typing note, an aggregateOnCheckCanMutate()heterogeneous-precondition note, and a repository parent/child EF navigation cross-reference.Test plan
dotnet build CoreEx.slnx -c Release— succeeds, 0 warnings, 0 errors;Version.propsbump (4.0.0-preview-5) confirmed reflected in generated NuGet package filenames.dotnet test tests/CoreEx.AspNetCore.Test.Unit --filter FullyQualifiedName~IfMatchRequired— all 8 new tests pass across net8.0/net9.0/net10.0.x-idempotency-key,$page/X-Paging-Page,X-Paging-Count, orRFC 7386.