An open API service indexing awesome lists of open source software.

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

Awesome Lists containing this project

README

          

# Delta

[![Build status](https://ci.appveyor.com/api/projects/status/20t96gnsmysklh09/branch/main?svg=true)](https://ci.appveyor.com/project/SimonCropp/Delta)
[![NuGet Status](https://img.shields.io/nuget/v/Delta.svg?label=Delta)](https://www.nuget.org/packages/Delta/)
[![NuGet Status](https://img.shields.io/nuget/v/Delta.EF.svg?label=Delta.EF)](https://www.nuget.org/packages/Delta.EF/)
[![NuGet Status](https://img.shields.io/nuget/v/Delta.SqlServer.svg?label=Delta.SqlServer)](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.

[![Entity Framework Extensions](https://raw.githubusercontent.com/SimonCropp/Delta/refs/heads/main/docs/zzz.png)](https://entityframework-extensions.net/?utm_source=simoncropp&utm_medium=Delta)

### JetBrains

[![JetBrains logo.](https://resources.jetbrains.com/storage/products/company/brand/logos/jetbrains.svg)](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 | anchor

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]( 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).