Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 64 additions & 5 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@ on:
- '**'

jobs:
# ── Modern .NET (net8 / net9 / net10) ────────────────────────────────────────
test:
name: Build & Test
name: Build & Test (modern .NET)
runs-on: ubuntu-latest

steps:
Expand All @@ -28,10 +29,68 @@ jobs:
10.x

- name: Restore dependencies
run: dotnet restore CacheWeave.slnx
run: dotnet restore CacheWeave.slnx --ignore-failed-sources

- name: Build
run: dotnet build CacheWeave.slnx --configuration Release --no-restore
- name: Build (modern .NET projects only)
run: |
for proj in \
src/CacheWeave.Core/CacheWeave.Core.csproj \
src/CacheWeave.Redis/CacheWeave.Redis.csproj \
src/CacheWeave.InMemory/CacheWeave.InMemory.csproj \
src/CacheWeave.DistributedCache/CacheWeave.DistributedCache.csproj \
src/CacheWeave.SQLite/CacheWeave.SQLite.csproj \
src/CacheWeave.DynamoDB/CacheWeave.DynamoDB.csproj \
src/CacheWeave.Memcached/CacheWeave.Memcached.csproj \
src/CacheWeave.NCache/CacheWeave.NCache.csproj \
src/CacheWeave.Faster/CacheWeave.Faster.csproj \
tests/CacheWeave.Tests/CacheWeave.Tests.csproj; do
dotnet build "$proj" --configuration Release --no-restore
done

- name: Test
run: dotnet test CacheWeave.slnx --configuration Release --no-build --verbosity normal
run: >
dotnet test tests/CacheWeave.Tests/CacheWeave.Tests.csproj
--configuration Release
--no-build
--verbosity normal

# ── .NET Framework 4.8 (CacheWeave.Legacy) ───────────────────────────────────
test-net48:
name: Build & Test (net48 / CacheWeave.Legacy)
runs-on: windows-latest

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: |
8.x
9.x
10.x

- name: Restore dependencies
shell: pwsh
run: dotnet restore src/CacheWeave.Legacy/CacheWeave.Legacy.csproj

- name: Restore test dependencies
shell: pwsh
run: dotnet restore tests/CacheWeave.Legacy.Tests/CacheWeave.Legacy.Tests.csproj

- name: Build CacheWeave.Legacy
shell: pwsh
run: dotnet build src/CacheWeave.Legacy/CacheWeave.Legacy.csproj --configuration Release --no-restore

- name: Build CacheWeave.Legacy.Tests
shell: pwsh
run: dotnet build tests/CacheWeave.Legacy.Tests/CacheWeave.Legacy.Tests.csproj --configuration Release --no-restore

- name: Test (net48)
shell: pwsh
run: >
dotnet test tests/CacheWeave.Legacy.Tests/CacheWeave.Legacy.Tests.csproj
--configuration Release
--no-build
--verbosity normal
84 changes: 48 additions & 36 deletions .github/workflows/publish-nuget.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ on:
jobs:
publish:
name: Build, Test & Publish
runs-on: ubuntu-latest
# Windows is required to build and test the net48 CacheWeave.Legacy project
runs-on: windows-latest

permissions:
contents: write # required to create GitHub Releases
Expand All @@ -39,20 +40,20 @@ jobs:

- name: Resolve version and release notes
id: meta
shell: pwsh
run: |
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
VERSION="${{ inputs.version }}"
NOTES="${{ inputs.release_notes }}"
else
if ("${{ github.event_name }}" -eq "workflow_dispatch") {
$version = "${{ inputs.version }}"
$notes = "${{ inputs.release_notes }}"
} else {
# Strip leading 'v' from tag name (v1.0.1 → 1.0.1)
VERSION="${GITHUB_REF_NAME#v}"
# Read the body of the annotated tag (everything after the first blank line)
NOTES="$(git tag -l --format='%(contents)' "${GITHUB_REF_NAME}")"
fi
echo "VERSION=$VERSION" >> "$GITHUB_OUTPUT"
# Write notes to a file to avoid shell quoting issues with multiline text
printf '%s' "$NOTES" > /tmp/release_notes.md
echo "Publishing version $VERSION"
$version = "$env:GITHUB_REF_NAME" -replace '^v', ''
# Read the body of the annotated tag
$notes = git tag -l --format='%(contents)' "$env:GITHUB_REF_NAME"
}
"VERSION=$version" | Out-File -FilePath $env:GITHUB_OUTPUT -Append
$notes | Out-File -FilePath "$env:TEMP\release_notes.md" -Encoding utf8
Write-Host "Publishing version $version"

- name: Restore dependencies
run: dotnet restore CacheWeave.slnx
Expand All @@ -64,35 +65,45 @@ jobs:
--no-restore
-p:Version=${{ steps.meta.outputs.VERSION }}

- name: Test
- name: Test (modern .NET — ubuntu-compatible projects)
run: >
dotnet test CacheWeave.slnx
--configuration Release
--no-build
--verbosity normal
--filter "FullyQualifiedName!~CacheWeave.Legacy.Tests"

- name: Test (net48 — CacheWeave.Legacy.Tests)
run: >
dotnet test tests/CacheWeave.Legacy.Tests/CacheWeave.Legacy.Tests.csproj
--configuration Release
--no-build
--verbosity normal

- name: Pack all packages
shell: pwsh
run: |
NOTES="$(cat /tmp/release_notes.md)"
PROJECTS=(
src/CacheWeave.Core/CacheWeave.Core.csproj
src/CacheWeave.Redis/CacheWeave.Redis.csproj
src/CacheWeave.InMemory/CacheWeave.InMemory.csproj
src/CacheWeave.DistributedCache/CacheWeave.DistributedCache.csproj
src/CacheWeave.SQLite/CacheWeave.SQLite.csproj
src/CacheWeave.DynamoDB/CacheWeave.DynamoDB.csproj
src/CacheWeave.Memcached/CacheWeave.Memcached.csproj
src/CacheWeave.NCache/CacheWeave.NCache.csproj
src/CacheWeave.Faster/CacheWeave.Faster.csproj
$notes = Get-Content "$env:TEMP\release_notes.md" -Raw
$projects = @(
"src/CacheWeave.Core/CacheWeave.Core.csproj"
"src/CacheWeave.Redis/CacheWeave.Redis.csproj"
"src/CacheWeave.InMemory/CacheWeave.InMemory.csproj"
"src/CacheWeave.DistributedCache/CacheWeave.DistributedCache.csproj"
"src/CacheWeave.SQLite/CacheWeave.SQLite.csproj"
"src/CacheWeave.DynamoDB/CacheWeave.DynamoDB.csproj"
"src/CacheWeave.Memcached/CacheWeave.Memcached.csproj"
"src/CacheWeave.NCache/CacheWeave.NCache.csproj"
"src/CacheWeave.Faster/CacheWeave.Faster.csproj"
"src/CacheWeave.Legacy/CacheWeave.Legacy.csproj"
)
for PROJECT in "${PROJECTS[@]}"; do
dotnet pack "$PROJECT" \
--configuration Release \
--no-build \
--output ./nupkg \
-p:Version=${{ steps.meta.outputs.VERSION }} \
-p:PackageReleaseNotes="$NOTES"
done
foreach ($project in $projects) {
dotnet pack $project `
--configuration Release `
--no-build `
--output ./nupkg `
-p:Version=${{ steps.meta.outputs.VERSION }} `
"-p:PackageReleaseNotes=$notes"
}

- name: Publish to NuGet
run: >
Expand All @@ -104,8 +115,9 @@ jobs:
- name: Create GitHub Release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
shell: pwsh
run: >
gh release create "$GITHUB_REF_NAME"
--title "$GITHUB_REF_NAME"
--notes-file /tmp/release_notes.md
gh release create "$env:GITHUB_REF_NAME"
--title "$env:GITHUB_REF_NAME"
--notes-file "$env:TEMP\release_notes.md"
--verify-tag
2 changes: 2 additions & 0 deletions CacheWeave.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@
<Project Path="src/CacheWeave.DynamoDB/CacheWeave.DynamoDB.csproj" />
<Project Path="src/CacheWeave.Faster/CacheWeave.Faster.csproj" />
<Project Path="src/CacheWeave.InMemory/CacheWeave.InMemory.csproj" />
<Project Path="src/CacheWeave.Legacy/CacheWeave.Legacy.csproj" />
<Project Path="src/CacheWeave.Memcached/CacheWeave.Memcached.csproj" />
<Project Path="src/CacheWeave.NCache/CacheWeave.NCache.csproj" />
<Project Path="src/CacheWeave.Redis/CacheWeave.Redis.csproj" />
<Project Path="src/CacheWeave.SQLite/CacheWeave.SQLite.csproj" />
</Folder>
<Folder Name="/tests/">
<Project Path="tests/CacheWeave.Legacy.Tests/CacheWeave.Legacy.Tests.csproj" />
<Project Path="tests/CacheWeave.Tests/CacheWeave.Tests.csproj" />
</Folder>
</Solution>
78 changes: 70 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ public async Task<IActionResult> GetProducts([FromQuery] int page = 1)
| **Fault-tolerant** | All cache I/O wrapped in try/catch — Redis outage degrades to cache-miss behaviour, never a 500 |
| **7 providers** | Redis, InMemory, SQLite, NCache, DynamoDB, Memcached, FASTER KV |
| **Multi-target** | `net8.0`, `net9.0`, `net10.0` |
| **.NET Framework 4.8** | `CacheWeave.Legacy` — programmatic caching for net48 without ASP.NET Core |

---

Expand Down Expand Up @@ -171,15 +172,76 @@ Multiple `[CacheWeaveEvict]` attributes are allowed on a single action.

## Providers

| Package | Backing store | `RemoveByPrefix` |
| Package | Backing store | `RemoveByPrefix` | Target frameworks |
|---|---|---|---|
| `CacheWeave.Redis` | StackExchange.Redis | Yes (SCAN + DEL) | net8/9/10 |
| `CacheWeave.InMemory` | `IMemoryCache` | Yes (prefix scan) | net8/9/10 |
| `CacheWeave.SQLite` | Microsoft.Data.Sqlite | Yes (SQL LIKE) | net8/9/10 |
| `CacheWeave.NCache` | Alachisoft NCache | No | net8/9/10 |
| `CacheWeave.DynamoDB` | AWS DynamoDB | No | net8/9/10 |
| `CacheWeave.Memcached` | EnyimMemcachedCore | No | net8/9/10 |
| `CacheWeave.Faster` | Microsoft FASTER KV | No | net8/9/10 |
| `CacheWeave.Legacy` | Redis, InMemory, SQLite, DynamoDB, NCache | Depends on provider | **net48** |

---

## .NET Framework 4.8 Support (`CacheWeave.Legacy`)

`CacheWeave.Legacy` brings provider-agnostic caching to .NET Framework 4.8 applications that cannot migrate to modern .NET. It exposes the same `ICacheWeaveService` programmatic API but **does not include ASP.NET Core filters** — there is no attribute-based caching on net48.

### Install

```bash
dotnet add package CacheWeave.Legacy
```

### Register

```csharp
// Works with any DI container that supports Microsoft.Extensions.DependencyInjection
services
.AddCacheWeave(options =>
{
options.GlobalKeyPrefix = "my-app";
options.DefaultExpiry = TimeSpan.FromMinutes(5);
options.Serializer = CacheWeaveSerializerType.SystemTextJson;
})
.AddCacheWeaveRedis("localhost:6379");
// or: .AddCacheWeaveInMemory()
// or: .AddCacheWeaveSQLite(o => o.DatabasePath = "cache.db")
// or: .AddCacheWeaveDynamoDb(dynamoClient)
// or: .AddCacheWeaveNCache("my-cache")
```

### Use

```csharp
public class ProductService
{
private readonly ICacheWeaveService _cache;

public ProductService(ICacheWeaveService cache) => _cache = cache;

public Task<Product> GetAsync(int id) =>
_cache.GetOrSetAsync(
$"products:{id}",
ct => _repo.FindAsync(id, ct),
expiry: TimeSpan.FromMinutes(10));

public Task InvalidateAsync(int id) =>
_cache.InvalidateAsync($"products:{id}");
}
```

### Provider capability on net48

| Provider | `RemoveByPrefix` | Notes |
|---|---|---|
| `CacheWeave.Redis` | StackExchange.Redis | Yes (SCAN + DEL) |
| `CacheWeave.InMemory` | `IMemoryCache` | Yes (prefix scan) |
| `CacheWeave.SQLite` | Microsoft.Data.Sqlite | Yes (SQL LIKE) |
| `CacheWeave.NCache` | Alachisoft NCache | No |
| `CacheWeave.DynamoDB` | AWS DynamoDB | No |
| `CacheWeave.Memcached` | EnyimMemcachedCore | No |
| `CacheWeave.Faster` | Microsoft FASTER KV | No |
| Redis | Yes (SCAN + DEL) | |
| InMemory | No | Uses `System.Runtime.Caching.MemoryCache` (in-box on net48) |
| SQLite | Yes (SQL LIKE) | Uses `System.Data.SQLite.Core` |
| DynamoDB | No | Client-side TTL enforcement included |
| NCache | No | |

---

Expand Down
8 changes: 8 additions & 0 deletions src/CacheWeave.Legacy/Abstractions/ICacheCompressor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace CacheWeave.Legacy.Abstractions
{
public interface ICacheCompressor
{
string Compress(string value);
string Decompress(string value);
}
}
14 changes: 14 additions & 0 deletions src/CacheWeave.Legacy/Abstractions/ICacheProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using System;
using System.Threading;
using System.Threading.Tasks;

namespace CacheWeave.Legacy.Abstractions
{
public interface ICacheProvider
{
Task<string?> GetAsync(string key, CancellationToken cancellationToken = default);
Task SetAsync(string key, string value, TimeSpan? expiry = null, CancellationToken cancellationToken = default);
Task RemoveAsync(string key, CancellationToken cancellationToken = default);
Task RemoveByPrefixAsync(string prefix, CancellationToken cancellationToken = default);
}
}
6 changes: 6 additions & 0 deletions src/CacheWeave.Legacy/Abstractions/ICacheProviderInner.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace CacheWeave.Legacy.Abstractions
{
public interface ICacheProviderInner : ICacheProvider
{
}
}
16 changes: 16 additions & 0 deletions src/CacheWeave.Legacy/Abstractions/ICacheSerializer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using System;
using System.Diagnostics.CodeAnalysis;

namespace CacheWeave.Legacy.Abstractions
{
public interface ICacheSerializer
{
string Serialize<T>(T value);

[return: MaybeNull]
T Deserialize<T>(string value);

string Serialize(object value, Type type);
object? Deserialize(string value, Type type);
}
}
16 changes: 16 additions & 0 deletions src/CacheWeave.Legacy/Abstractions/ICacheStampedeProtector.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Threading;
using System.Threading.Tasks;

namespace CacheWeave.Legacy.Abstractions
{
public interface ICacheStampedeProtector
{
[return: MaybeNull]
Task<T> ExecuteAsync<T>(
string key,
Func<CancellationToken, Task<T>> factory,
CancellationToken cancellationToken = default);
}
}
Loading
Loading