SkillAgentSearch skills...

Delta

An approach to implementing a 304 Not Modified leveraging DB change tracking

Install / Use

/learn @SimonCropp/Delta
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

<!-- GENERATED FILE - DO NOT EDIT This file was generated by [MarkdownSnippets](https://github.com/SimonCropp/MarkdownSnippets). Source File: /readme.source.md To change this file edit the source file and then run MarkdownSnippets. -->

<img src="/src/icon.png" height="30px"> Delta

Build status NuGet Status NuGet Status NuGet Status

Delta is an approach to implementing a 304 Not Modified leveraging DB change tracking.<!-- include: intro. path: /docs/mdsource/intro.include.md -->

The approach uses a last updated timestamp from the database to generate an ETag. All dynamic requests then have that ETag checked/applied.

This approach works well when the frequency of updates is relatively low. In this scenario, the majority of requests will leverage the result in a 304 Not Modified being returned and the browser loading the content its cache.

Effectively consumers will always receive the most current data, while the load on the server is reduced.<!-- endInclude -->

See Milestones for release notes.

Sponsors

Entity Framework Extensions<!-- include: sponsors. path: /docs/mdsource/sponsors.include.md -->

Entity Framework Extensions is a major sponsor and is proud to contribute to the development this project.

Entity Framework Extensions

Developed using JetBrains IDEs

JetBrains logo.<!-- endInclude -->

Jump to specific docs

Assumptions

Frequency of updates to data is relatively low compared to reads

304 Not Modified Flow

graph TD
    Request
    NoCache{Has no-cache<br/>in Cache-Control?}
    HasMaxAge{Has max-age<br/>or max-stale<br/>in Cache-Control?}
    CacheValid{Cached timestamp<br/>within allowed<br/>staleness?<br/>Considers min-fresh}
    FreshTimestamp[Get fresh timestamp<br/>from SQL]
    UseCached[Use cached timestamp]
    CalculateEtag[Calculate current ETag<br/>based on timestamp<br/>from web assembly and SQL]
    IfNoneMatch{Has<br/>If-None-Match<br/>header?}
    EtagMatch{Current<br/>Etag matches<br/>If-None-Match?}
    AddETag[Add current ETag<br/>to Response headers]
    304[Respond with<br/>304 Not-Modified]
    Request --> NoCache
    NoCache -->|Yes| FreshTimestamp
    NoCache -->|No| HasMaxAge
    HasMaxAge -->|Yes| CacheValid
    HasMaxAge -->|No| FreshTimestamp
    CacheValid -->|Yes| UseCached
    CacheValid -->|No| FreshTimestamp
    UseCached --> CalculateEtag
    FreshTimestamp --> CalculateEtag
    CalculateEtag --> IfNoneMatch
    IfNoneMatch -->|Yes| EtagMatch
    IfNoneMatch -->|No| AddETag
    EtagMatch -->|No| AddETag
    EtagMatch -->|Yes| 304

DB implementation

Implementation is specific to the target database

ETag calculation logic

The ETag is calculated from a combination several parts

{AssemblyWriteTime}-{DbTimeStamp}-{Suffix}

AssemblyWriteTime

The last write time of the web entry point assembly

<!-- snippet: AssemblyWriteTime -->

<a id='snippet-AssemblyWriteTime'></a>

var webAssemblyLocation = Assembly.GetEntryAssembly()!.Location;
AssemblyWriteTime = File.GetLastWriteTime(webAssemblyLocation).Ticks.ToString();

<sup><a href='/src/Delta/DeltaExtensions_Shared.cs#L44-L49' title='Snippet source file'>snippet source</a> | <a href='#snippet-AssemblyWriteTime' title='Start of snippet'>anchor</a></sup>

<!-- endSnippet -->

DB timestamp

Timestamp calculation is specific to the target database

Suffix

An optional string suffix that is dynamically calculated at runtime based on the current HttpContext.

<!-- snippet: Suffix -->

<a id='snippet-Suffix'></a>

var app = builder.Build();
app.UseDelta(suffix: httpContext => "MySuffix");

<sup><a href='/src/DeltaTests/Usage.cs#L9-L14' title='Snippet source file'>snippet source</a> | <a href='#snippet-Suffix' title='Start of snippet'>anchor</a></sup>

<!-- endSnippet -->

Combining the above

<!-- snippet: BuildEtag -->

<a id='snippet-BuildEtag'></a>

internal static string BuildEtag(string timeStamp, string? suffix)
{
    if (suffix == null)
    {
        return $"\"{AssemblyWriteTime}-{timeStamp}\"";
    }

    return $"\"{AssemblyWriteTime}-{timeStamp}-{suffix}\"";
}

<sup><a href='/src/Delta/DeltaExtensions_Shared.cs#L326-L338' title='Snippet source file'>snippet source</a> | <a href='#snippet-BuildEtag' title='Start of snippet'>anchor</a></sup>

<!-- endSnippet -->

Usage

Delta has two approaches to usage:

Raw DbConnection approach

Delivers functionality using DbConnection and DbTransaction.

NuGet: Delta

Documentation is specific to choice of database:

Entity Framework approach

Delivers functionality using Entity Framework.

NuGet: Delta.EF

Documentation is specific to choice of database:

Timestamp caching via request Cache-Control

By default, Delta queries the database on every GET request to retrieve the current timestamp. Clients can opt into timestamp caching by sending Cache-Control request headers, allowing Delta to skip the database round-trip when a recent enough cached timestamp exists.

Supported directives:

  • max-age=N — accept a cached timestamp up to N seconds old
  • max-stale=N — accept a cached timestamp up to N seconds old
  • max-stale (no value) — accept any cached timestamp regardless of age

If both max-age and max-stale are present, the larger (more permissive) value is used.

Requests without these directives always query the database (existing behavior).

Example request headers:

Cache-Control: max-age=5
Cache-Control: max-stale

UseResponseDiagnostics

Response diagnostics is an opt-out feature that includes extra log information in the response headers. Consider disabling in production to avoid adding diagnostic headers to every response.

Disable by setting UseResponseDiagnostics to false at startup:

<!-- snippet: UseResponseDiagnostics -->

<a id='snippet-UseResponseDiagnostics'></a>

DeltaExtensions.UseResponseDiagnostics = false;

<sup><a href='/src/DeltaTests/UseResponseDiagnosticsSnippet.cs#L4-L6' title='Snippet source file'>snippet source</a> | <a href='#snippet-UseResponseDiagnostics' title='Start of snippet'>anchor</a></sup>

<!-- endSnippet -->

Response diagnostics headers are prefixed with Delta-.

Example Response header when the Request has not If-None-Match header.

<img src="/src/Delta-No304.png">

Verifying behavior

The behavior of Delta can be verified as follows:

  • Open a page in the site
  • Open the browser developer tools
  • Change to the Network tab
  • Refresh the page.

Cached responses will show as 304 in the Status:

<img src="/src/network.png">

In the headers if-none-match will show in the request and etag will show in the response:

<img src="/src/network-details.png">

Ensure cache is not disabled

If disable cache is checked, the browser will not send the if-none-match header. This will effectively cause a cache miss server side, and the full server pipeline will execute.

<img src="/src/disable-cache.png">

Certificates and Chromium

Chromium, and hence the Chrome and Edge browsers, are very sensitive to certificate problems when determining if an item should be cached. Specifically, if a request is done dynamically (type: xhr) and the server is using a self-signed certificate, then the browser will not send the if-none-match header. Reference. If self-signed certificates are required during development in lower environment, then use FireFox to test the caching behavior.

Programmatic client usage

Delta is primarily designed to

View on GitHub
GitHub Stars1.5k
CategoryDevelopment
Updated1d ago
Forks39

Languages

C#

Security Score

95/100

Audited on Mar 20, 2026

No findings