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
Port ETag / If-None-Match (304) support from Marten
Marten added HTTP conditional-request support (ETag / If-None-Match → 304 Not Modified) to StreamOne<T> and StreamAggregate<T> so polling clients skip re-downloading unchanged docs/aggregates. Polecat should match it. Marten issue: JasperFx/marten#5010. Reference PR: JasperFx/marten#5015.
Behavior to mirror (identical at the HTTP layer)
New EmitETag init-only property on both result types, default true.
On a normal hit: set an ETag response header.
On an incoming If-None-Match that matches the current version: respond 304 with an empty body (ContentLength = 0) and the ETag header, skip the body.
EmitETag = false restores the pre-ETag behavior exactly (no header, no conditional handling) — backwards-compatible opt-out.
StreamMany<T> is intentionally out of scope (a cheap collection-wide ETag is hard to derive).
ETagHelpers is directly portable — it's pure HTTP logic: handles the * wildcard, comma-separated If-None-Match lists, and strips W/ weak validators (weak comparison is the correct function for If-None-Match per RFC 7232 §3.2). Port it close to verbatim.
SQL Server mechanics differ — this is the part to design, not copy
The HTTP layer is identical; the version source differs:
StreamAggregate (event stream version): same as Marten conceptually — read the stream's long version cheaply before folding so a 304 skips the aggregation. Use Polecat's FetchStreamStateAsync equivalent. Low divergence.
StreamOne (document version): Marten reads mt_version (a Guid) and formats a quoted-GUID ETag. On SQL Server you have a better-fitting option: a native rowversion/timestamp column is a monotonic 8-byte value that auto-changes on every update — purpose-built for change detection. If Polecat's document tables carry (or can carry) a rowversion, formatting that as the ETag is cheaper and more natural than a GUID version. Decide whether to (a) mirror Marten's mt_version GUID exactly for cross-store envelope parity, or (b) use rowversion for efficiency and accept a different opaque ETag value. Either is HTTP-valid (ETags are opaque); pick one deliberately.
Version-metadata-disabled guard: if a doc type has no version column, emit no ETag rather than a constant one (which would risk false 304s).
Test plan to mirror
Marten's Alba tests (src/Marten.AspNetCore.Testing/streaming_result_types_tests.cs) cover, for bothStreamOne and StreamAggregate:
ETag header present on a normal 200 hit
304 (empty body) when If-None-Match matches the current version
Full 200 body when If-None-Match is stale
EmitETag = false suppresses the header/behavior entirely
Port all four for each type. Additionally add (gaps we're also backfilling on the Marten side):
ETagHelpers unit coverage for the * wildcard, W/ weak validator, and multi-value comma lists.
The 404 path (missing document / missing stream).
The version-metadata-disabled / no-version-column case → no ETag emitted.
Filed from the Marten repo as a parity hand-off during review of #5015. Note the SQL Server version-source difference above is deliberate, not an oversight to copy around.
Port ETag / If-None-Match (304) support from Marten
Marten added HTTP conditional-request support (
ETag/If-None-Match→304 Not Modified) toStreamOne<T>andStreamAggregate<T>so polling clients skip re-downloading unchanged docs/aggregates. Polecat should match it. Marten issue: JasperFx/marten#5010. Reference PR: JasperFx/marten#5015.Behavior to mirror (identical at the HTTP layer)
EmitETaginit-only property on both result types, defaulttrue.ETagresponse header.If-None-Matchthat matches the current version: respond304with an empty body (ContentLength = 0) and theETagheader, skip the body.EmitETag = falserestores the pre-ETag behavior exactly (no header, no conditional handling) — backwards-compatible opt-out.StreamMany<T>is intentionally out of scope (a cheap collection-wide ETag is hard to derive).Marten implementation to point at
If-None-Matchmatchingsrc/Marten.AspNetCore/ETagHelpers.cs—Format(Guid),Format(long),IfNoneMatchMatches(HttpContext, etag)src/Marten.AspNetCore/StreamOne.cs+QueryableExtensions.WriteSingle<T>src/Marten.AspNetCore/StreamAggregate.csProducesResponseTypeMetadata(304, typeof(void), …)src/IssueService/StreamingMinimalEndpoints.csdocs/documents/aspnetcore.md→ "ETag / Conditional Requests"ETagHelpersis directly portable — it's pure HTTP logic: handles the*wildcard, comma-separatedIf-None-Matchlists, and stripsW/weak validators (weak comparison is the correct function forIf-None-Matchper RFC 7232 §3.2). Port it close to verbatim.SQL Server mechanics differ — this is the part to design, not copy
The HTTP layer is identical; the version source differs:
longversion cheaply before folding so a304skips the aggregation. Use Polecat'sFetchStreamStateAsyncequivalent. Low divergence.mt_version(aGuid) and formats a quoted-GUID ETag. On SQL Server you have a better-fitting option: a nativerowversion/timestampcolumn is a monotonic 8-byte value that auto-changes on every update — purpose-built for change detection. If Polecat's document tables carry (or can carry) arowversion, formatting that as the ETag is cheaper and more natural than a GUID version. Decide whether to (a) mirror Marten'smt_versionGUID exactly for cross-store envelope parity, or (b) userowversionfor efficiency and accept a different opaque ETag value. Either is HTTP-valid (ETags are opaque); pick one deliberately.StreamOnepresently does a second query (MetadataForAsync) to read the version — this is being fixed to an inline read (StreamOne<T> ETag: inline mt_version (eliminate 2nd round trip) + reverse where T:notnull + ETag test hardening (pre-9.18, follow-up to #5015) marten#5027). Port the fixed shape: read the version inline in the same round trip as the document (SQL Server:SELECT data, <version> FROM …, orFOR JSONplus a version column), not via a follow-up query.304s).Test plan to mirror
Marten's Alba tests (
src/Marten.AspNetCore.Testing/streaming_result_types_tests.cs) cover, for bothStreamOneandStreamAggregate:ETagheader present on a normal200hit304(empty body) whenIf-None-Matchmatches the current version200body whenIf-None-Matchis staleEmitETag = falsesuppresses the header/behavior entirelyPort all four for each type. Additionally add (gaps we're also backfilling on the Marten side):
ETagHelpersunit coverage for the*wildcard,W/weak validator, and multi-value comma lists.404path (missing document / missing stream).Filed from the Marten repo as a parity hand-off during review of #5015. Note the SQL Server version-source difference above is deliberate, not an oversight to copy around.