https://github.com/SimonCropp/Delta
An approach to implementing a 304 Not Modified leveraging DB change tracking
https://github.com/SimonCropp/Delta
Last synced: 3 months ago
JSON representation
An approach to implementing a 304 Not Modified leveraging DB change tracking
- Host: GitHub
- URL: https://github.com/SimonCropp/Delta
- Owner: SimonCropp
- License: mit
- Created: 2022-11-29T23:10:40.000Z (almost 3 years ago)
- Default Branch: main
- Last Pushed: 2025-07-21T12:19:49.000Z (3 months ago)
- Last Synced: 2025-07-21T14:25:25.737Z (3 months ago)
- Language: C#
- Homepage:
- Size: 736 KB
- Stars: 1,409
- Watchers: 16
- Forks: 35
- Open Issues: 1
-
Metadata Files:
- Readme: readme.md
- Funding: .github/FUNDING.yml
- License: license.txt
- Code of conduct: code_of_conduct.md
Awesome Lists containing this project
- awesome-reference-tools - Delta
README
#
Delta
[](https://ci.appveyor.com/project/SimonCropp/Delta)
[](https://www.nuget.org/packages/Delta/)
[](https://www.nuget.org/packages/Delta.EF/)
[](https://www.nuget.org/packages/Delta.SqlServer/)Delta is an approach to implementing a [304 Not Modified](https://www.keycdn.com/support/304-not-modified) leveraging DB change tracking.
The approach uses a last updated timestamp from the database to generate an [ETag](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/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.
**See [Milestones](../../milestones?state=closed) for release notes.**
## Sponsors
### Entity Framework Extensions
[Entity Framework Extensions](https://entityframework-extensions.net/?utm_source=simoncropp&utm_medium=Delta) is a major sponsor and is proud to contribute to the development this project.
[](https://entityframework-extensions.net/?utm_source=simoncropp&utm_medium=Delta)
### JetBrains
[](https://jb.gg/OpenSourceSupport)
## Jump to specific docs
* [SQL Server Docs](/docs/sqlserver.md) when using [SQL Server SqlClient](https://github.com/dotnet/SqlClient)
* [PostgreSQL Docs](/docs/postgres.md) when using [PostgreSQL Npgsql](https://www.npgsql.org)
* [EF with SQL Server Docs](/docs/sqlserver-ef.md) when using the [SQL Server EF Database Provider](https://learn.microsoft.com/en-us/ef/core/providers/sql-server/?tabs=dotnet-core-cli)
* [EF with PostgreSQL Docs](/docs/postgres-ef.md) when using the [PostgreSQL EF Database Provider](https://www.npgsql.org/efcore)## Assumptions
Frequency of updates to data is relatively low compared to reads
## 304 Not Modified Flow
```mermaid
graph TD
Request
CalculateEtag[Calculate current ETag
based on timestamp
from web assembly and SQL]
IfNoneMatch{Has
If-None-Match
header?}
EtagMatch{Current
Etag matches
If-None-Match?}
AddETag[Add current ETag
to Response headers]
304[Respond with
304 Not-Modified]
Request --> CalculateEtag
CalculateEtag --> IfNoneMatch
IfNoneMatch -->|Yes| EtagMatch
IfNoneMatch -->|No| AddETag
EtagMatch -->|No| AddETag
EtagMatch -->|Yes| 304
```## DB implementation
Implementation is specific to the target database
* [SQL Server implementation](/docs/sqlserver.md#implementation)
* [PostgreSQL implementation](/docs/postgres.md#implementation)## 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
```cs
var webAssemblyLocation = Assembly.GetEntryAssembly()!.Location;
AssemblyWriteTime = File.GetLastWriteTime(webAssemblyLocation).Ticks.ToString();
```
snippet source | anchor### DB timestamp
Timestamp calculation is specific to the target database
* [SQL Server timestamp calculation](/docs/sqlserver.md#timestamp-calculation)
* [Postgres timestamp calculation](/docs/postgres.md#timestamp-calculation)### Suffix
An optional string suffix that is dynamically calculated at runtime based on the current `HttpContext`.
```cs
var app = builder.Build();
app.UseDelta(suffix: httpContext => "MySuffix");
```
snippet source | anchor### Combining the above
```cs
internal static string BuildEtag(string timeStamp, string? suffix)
{
if (suffix == null)
{
return $"\"{AssemblyWriteTime}-{timeStamp}\"";
}return $"\"{AssemblyWriteTime}-{timeStamp}-{suffix}\"";
}
```
snippet source | anchor## Usage
Delta has two approaches to usage:
### Raw DbConnection approach
Delivers functionality using DbConnection and DbTransaction.
NuGet: [Delta](https://nuget.org/packages/Delta/)
Documentation is specific to choice of database:
* [SQL Server Docs](/docs/sqlserver.md) when using [SQL Server SqlClient](https://github.com/dotnet/SqlClient)
* [PostgreSQL Docs](/docs/postgres.md) when using [PostgreSQL Npgsql](https://www.npgsql.org)### Entity Framework approach
Delivers functionality using [Entity Framework](https://learn.microsoft.com/en-us/ef/).
NuGet: [Delta.EF](https://nuget.org/packages/Delta.EF/)
Documentation is specific to choice of database:
* [EF with SQL Server Docs](/docs/sqlserver-ef.md) when using the [SQL Server EF Database Provider](https://learn.microsoft.com/en-us/ef/core/providers/sql-server/?tabs=dotnet-core-cli)
* [EF with PostgreSQL Docs](/docs/postgres-ef.md) when using the [PostgreSQL EF Database Provider](https://www.npgsql.org/efcore)## UseResponseDiagnostics
Response diagnostics is an opt-out feature that includes extra log information in the response headers.
Disable by setting UseResponseDiagnostics to false at startup:
```cs
DeltaExtensions.UseResponseDiagnostics = false;
```
snippet source | anchorResponse 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]( https://issues.chromium.org/issues/40666473). 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 support web browsers as a client. All web browsers have the necessary 304 and caching functionally required.
In the scenario where web apis (that support using 304) are being consumed using .net as a client, consider using one of the below extensions to cache responses.
* [Replicant](https://github.com/SimonCropp/Replicant)
* [Tavis.HttpCache](https://github.com/tavis-software/Tavis.HttpCache)
* [CacheCow](https://github.com/aliostad/CacheCow)
* [Monkey Cache](https://github.com/jamesmontemagno/monkey-cache)## Icon
[Estuary](https://thenounproject.com/term/estuary/1847616/) designed by [Daan](https://thenounproject.com/Asphaleia/) from [The Noun Project](https://thenounproject.com).