Delta
An approach to implementing a 304 Not Modified leveraging DB change tracking
Install / Use
/learn @SimonCropp/DeltaREADME
<img src="/src/icon.png" height="30px"> Delta
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.
Developed using JetBrains IDEs
Jump to specific docs
- SQL Server Docs when using SQL Server SqlClient
- PostgreSQL Docs when using PostgreSQL Npgsql
- EF with SQL Server Docs when using the SQL Server EF Database Provider
- EF with PostgreSQL Docs when using the PostgreSQL EF Database Provider
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.
<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:
- SQL Server Docs when using SQL Server SqlClient
- PostgreSQL Docs when using PostgreSQL Npgsql
Entity Framework approach
Delivers functionality using Entity Framework.
NuGet: Delta.EF
Documentation is specific to choice of database:
- EF with SQL Server Docs when using the SQL Server EF Database Provider
- EF with PostgreSQL Docs when using the PostgreSQL EF Database Provider
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 oldmax-stale=N— accept a cached timestamp up to N seconds oldmax-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.
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:
In the headers if-none-match will show in the request and etag will show in the response:
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.
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

