diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md index 24c1cda..ae70c4b 100644 --- a/.claude/CLAUDE.md +++ b/.claude/CLAUDE.md @@ -5,19 +5,19 @@ - If uncommitted changes exist, alert me immediately and stop - Do not proceed until I have confirmed how to handle them (commit, stash, or discard) 2. Check if `.claude/mission.md` exists and read the project mission and context. -3. Check if `.claude/plan.md` exists. - If it does, read it and summarize what has been done and what the next step is. - If it does not exist, ask me how I would like to proceed. -4. Check if `.claude/feature.md` exists and read the current feature scope. +3. Check if `plan/` exists in the project root. + - If `plan/plan.md` exists, summarize what has been done and what the next step is. + - If `plan/feature.md` exists, read the current feature scope. + - If neither exists, ask me how I would like to proceed. ### During a session After completing each step in the plan: -- Mark it as `[x]` done in `.claude/plan.md` +- Mark it as `[x]` done in `plan/plan.md` - Add a brief note about what was done and any important decisions made - Mark the next step as `[~]` in progress ### Ending a session -- Update `.claude/plan.md` with the current status of all steps +- Update `plan/plan.md` with the current status of all steps - Add a "Last session" note summarizing what was completed and what comes next - Note any README.md changes that will be needed when the feature is complete @@ -62,24 +62,34 @@ dotnet test -c Release - Commit at logical milestones (e.g. a component is complete and tested) - Never commit failing tests - Use conventional commits: `feat:`, `fix:`, `test:`, `docs:` -- Never merge to main — leave that for me to review and merge +- Never merge to master/main — leave that for me to review and merge +- Default branch strategy: `master` is production, `develop` is integration. Feature branches branch from and merge to `develop`. +- When merging a completed feature back to the originating branch, use `--no-ff` (no fast-forward) to preserve the feature branch history as a merge commit ## Feature Workflow +Active feature tracking lives in `plan/` in the project root (committed with the feature branch). +Planned and completed features are stored externally in the **Plan directory** defined in `.claude/mission.md`. + ### Planning features -- Multiple features can be planned ahead in `.claude/features-planned/` -- Each file represents one feature and they are executed in order (e.g. `01-feature-name.md`, `02-feature-name.md`) -- When starting a new feature, check `features-planned/` first for the next planned feature +- Future features are stored in the Plan directory under `planned/` +- Each file represents one feature, executed in order (e.g. `01-feature-name.md`, `02-feature-name.md`) +- When starting a new feature, check the Obsidian `planned/` directory first ### Starting a feature When told to start a new feature: 1. Ask for the feature name and goal if not provided 2. Note the current branch as the originating branch for the feature 3. Create a new branch: `git checkout -b feature/` -4. Create `.claude/feature.md` with goal, scope, acceptance criteria, and done condition -5. Create or update `.claude/plan.md` with the steps to implement the feature +4. Create `plan/feature.md` with goal, scope, acceptance criteria, and done condition +5. Create `plan/plan.md` with the steps to implement the feature 6. Confirm the plan before starting any code changes +### During implementation +- Update `plan/plan.md` continuously as changes are made +- Commit `plan/` together with code changes at logical milestones +- Run tests before each commit + ### Completing implementation When all planned steps are done: - All tests pass @@ -88,44 +98,28 @@ When all planned steps are done: - Do NOT close the feature — wait for the user to confirm it is done ### Closing a feature (only when the user says it is done) -- All acceptance criteria in `.claude/feature.md` are met +- All acceptance criteria in `plan/feature.md` are met - All tests pass - README.md has been updated to reflect the new feature -- `.claude/feature.md` is archived to `.claude/features-done/.md` and both `.claude/feature.md` and `.claude/plan.md` should be deleted -- Remove the corresponding file from `.claude/features-planned/` if one exists +- Archive `plan/feature.md` to the Plan directory `done/.md` +- Delete the `plan/` directory from the project - A final commit is made with message: `feat: complete` -- Merge to originating branch and delete feature branch only when the user explicitly asks +- Merge to originating branch with `--no-ff` and delete feature branch only when the user explicitly asks ## Feature Requests (cross-project) -Projects can request features from each other via `.claude/requests.md`. +Cross-project requests are handled via `mission.md` — see the "Incoming requests" reference there for the central location. -- Read `~/.claude/projects.md` (or `$OBSIDIAN_VAULT/Tharga/projects.md`) to discover other projects -- Read `.claude/requests.md` on startup — show pending requests and new notifications to the user -- Writing feature requests to other projects is **exempt from the cross-project guard** -- For mono-repos: requests go to the root, not sub-projects (see projects.md for details) +- On startup, check `mission.md` for the requests location and show pending requests to the user +- Writing feature requests is **exempt from the cross-project guard** - Never mark a request as done without user confirmation -- When a request is completed: update status to Done and write a notification back to the requester's `.claude/requests.md` - -### Request format -```markdown -## Pending - -### -- **From:** (``) -- **Date:** -- **Priority:** -- **Description:** -- **Status:** Pending - -## Notifications - -### — DONE -- **From:** (``) -- **Completed:** -- **Summary:** -- **Branch/Version:** -``` +- When a request is completed: + 1. Update its status to Done in the central requests file, add completion date and summary + 2. Add a follow-up entry under `## Uppföljning` at the top of the central requests file so the consuming project knows to update: + ``` + - [ ] ska uppdatera till + ``` + 3. The follow-up is checked off when the consuming project has updated and verified the new version ## Backlog Hygiene - When a task from the backlog (in `mission.md` or linked external files) is completed, mark it as done or remove it diff --git a/.claude/features-done/cache-load-time.md b/.claude/features-done/cache-load-time.md new file mode 100644 index 0000000..1ab7488 --- /dev/null +++ b/.claude/features-done/cache-load-time.md @@ -0,0 +1,26 @@ +# Feature: cache-load-time + +## Goal +Track how long the fetch delegate takes and display it in the Blazor monitoring UI. + +## Scope +- `CacheItem` — add `LoadDuration` field +- `CacheItemBuilder` — accept and store `LoadDuration` +- `FetchQueue` — measure fetch time with `Stopwatch` +- `CacheItemInfo` — add `LoadDuration` property, remove TODO comment +- `CacheMonitor.Set/Track` — pass `LoadDuration` to `CacheItemInfo` +- `IManagedCacheMonitor` — update `Set`/`Track` signatures +- `CacheBase` — pass `LoadDuration` through `OnSetAsync` and `TrackIfNeeded` +- `ListView.razor` — add "Load Time" column + +## Acceptance Criteria +- `CacheItemInfo.LoadDuration` reflects the actual fetch delegate execution time +- Blazor ListView shows load duration for each cached item +- Items loaded from persistence (Track path) show null load duration +- All existing tests still pass + +## Done Condition +User confirms the feature is satisfactory. + +## Originating Branch +develop diff --git a/.claude/features-done/github-actions.md b/.claude/features-done/github-actions.md new file mode 100644 index 0000000..d7480c2 --- /dev/null +++ b/.claude/features-done/github-actions.md @@ -0,0 +1,20 @@ +# Feature: github-actions + +## Goal +Add GitHub Actions CI/CD workflow matching the Platform pattern, with a unified publish job that picks release vs prerelease environment based on branch. + +## Scope +- `.github/workflows/build.yml` — build, security, publish jobs + +## Acceptance Criteria +- Build job: restore, build, warning check (threshold 10), test with coverage, codecov, compute version, pack 5 NuGet packages +- Security job: CodeQL analysis +- Publish job: single job with dynamic environment (release on master push, prerelease on PR), push to NuGet, create GitHub release +- Version: `MAJOR_MINOR: '0.4'`, auto-incrementing patch from git tags +- .NET SDKs: 8.0, 9.0, 10.0 + +## Done Condition +User confirms the workflow is satisfactory. + +## Originating Branch +develop diff --git a/.claude/features-done/idempotent-addcache.md b/.claude/features-done/idempotent-addcache.md new file mode 100644 index 0000000..cd2bfb6 --- /dev/null +++ b/.claude/features-done/idempotent-addcache.md @@ -0,0 +1,22 @@ +# Feature: idempotent-addcache + +## Goal +Make `AddCache()` safe to call multiple times, merging type registrations instead of throwing. + +## Scope +- `CacheRegistrationExtensions.AddCache` — guard against duplicate DI registrations +- `CacheRegistrationExtensions.AppendPreviousRegistrations` — skip duplicate types instead of throwing +- `CacheOptions.AddType` — skip duplicate types instead of throwing +- Tests verifying idempotent behavior + +## Acceptance Criteria +- Calling `AddCache()` twice with the same type registration does not throw +- Calling `AddCache()` twice with different types merges them +- First registration wins when same type is registered with different options +- All existing tests still pass + +## Done Condition +User confirms the fix is satisfactory. + +## Originating Branch +develop diff --git a/.claude/features-done/mongodb-clear-cache.md b/.claude/features-done/mongodb-clear-cache.md new file mode 100644 index 0000000..b428fc6 --- /dev/null +++ b/.claude/features-done/mongodb-clear-cache.md @@ -0,0 +1,18 @@ +# Feature: mongodb-clear-cache + +## Goal +Fix ClearCache/ClearStale for MongoDB-backed cache types by subscribing to `RequestEvictEvent`. + +## Scope +- `MongoDB.cs` constructor — add `IManagedCacheMonitor` parameter and `RequestEvictEvent` subscription + +## Acceptance Criteria +- `ICacheMonitor.ClearAll()` removes MongoDB-persisted items +- `ICacheMonitor.ClearStale()` removes stale MongoDB-persisted items +- Existing tests still pass + +## Done Condition +User confirms the fix is satisfactory. + +## Originating Branch +develop diff --git a/.claude/features-done/mongodb-default-config.md b/.claude/features-done/mongodb-default-config.md new file mode 100644 index 0000000..af638d7 --- /dev/null +++ b/.claude/features-done/mongodb-default-config.md @@ -0,0 +1,17 @@ +# Feature: mongodb-default-config + +## Goal +Fix MongoDB setup when not using "default" config by giving `ConfigurationName` a sensible default. + +## Scope +- `MongoDBCacheOptions.ConfigurationName` — set default to `"Default"` + +## Acceptance Criteria +- `AddMongoDBOptions()` without explicit ConfigurationName uses `"Default"` +- Existing tests still pass + +## Done Condition +User confirms the fix is satisfactory. + +## Originating Branch +develop diff --git a/.claude/features-done/monitor-track-persisted.md b/.claude/features-done/monitor-track-persisted.md new file mode 100644 index 0000000..0d3e7b9 --- /dev/null +++ b/.claude/features-done/monitor-track-persisted.md @@ -0,0 +1,21 @@ +# Feature: monitor-track-persisted + +## Goal +Make cache items loaded from persistence (MongoDB, Redis, File) visible in the Blazor monitoring UI on first access, not only after re-fetch. + +## Scope +- `IManagedCacheMonitor` — add `Track` method +- `CacheMonitor` — implement `Track` (register in `_caches` without firing `DataSetEvent`) +- `CacheBase.GetCoreAsync` — call `Track` when a fresh item is found from persistence but isn't yet in the monitor +- Tests verifying items are tracked on persistence hit + +## Acceptance Criteria +- Fresh items found in persistence appear in `ICacheMonitor.GetInfos()` after first access +- `DataSetEvent` is NOT fired for items loaded from persistence (only `DataGetEvent`) +- All existing tests still pass + +## Done Condition +User confirms the fix is satisfactory. + +## Originating Branch +develop diff --git a/.claude/features-done/readme-documentation.md b/.claude/features-done/readme-documentation.md deleted file mode 100644 index 362e2f9..0000000 --- a/.claude/features-done/readme-documentation.md +++ /dev/null @@ -1,24 +0,0 @@ -# Feature: readme-documentation - -## Goal -Improve all README files in the repository. The root README should provide comprehensive usage documentation. Each per-project README should be a concise NuGet sales pitch. - -## Scope -- Root README.md — full documentation -- Tharga.Cache/README.md — NuGet pitch -- Tharga.Cache.Redis/README.md — NuGet pitch -- Tharga.Cache.MongoDB/README.md — NuGet pitch -- Tharga.Cache.File/README.md — NuGet pitch -- Tharga.Cache.Blazor/README.md — NuGet pitch - -## Acceptance Criteria -- Root README covers all cache types, persistence backends, configuration, monitoring, events, and key building -- Each per-project README has a compelling description, key features, install + usage snippet, and link to full docs -- All code examples are accurate against the current API -- Build still passes - -## Done Condition -User confirms the READMEs are satisfactory. - -## Originating Branch -develop diff --git a/.claude/mission.md b/.claude/mission.md index 047c61d..fee2721 100644 --- a/.claude/mission.md +++ b/.claude/mission.md @@ -3,4 +3,7 @@ Caching library with MongoDB backend support, cache partitioning, and a Blazor monitoring UI. ## External References +- **Shared instructions**: `$DOC_ROOT/Tharga/shared-instructions.md` +- **Plan directory**: `$DOC_ROOT/Tharga/plans/Toolkit/Cache` - **Backlog**: `c:\Users\danie\SynologyDrive\Documents\Notes\Tharga\Toolkit\Cache.md` +- **Incoming requests**: `c:\Users\danie\SynologyDrive\Documents\Notes\Tharga\Requests.md` — check for pending requests for this project on startup diff --git a/.claude/settings.json b/.claude/settings.json index 3d2151a..a302b6c 100644 --- a/.claude/settings.json +++ b/.claude/settings.json @@ -5,7 +5,6 @@ }, "permissions": { "allow": [ - "Bash(git:*)", "Bash(cd:*)", "Bash(dotnet:*)", "Bash(mkdir:*)", @@ -17,7 +16,14 @@ "Skill(update-config:*)", "Bash(grep -r [Inject] --include=*.razor.cs --include=*.cs .)", "Bash(rm .claude/feature.md)", - "Bash(rm .claude/plan.md)" + "Bash(rm .claude/plan.md)", + "Bash(xargs grep:*)", + "Bash(git checkout:*)", + "Bash(dotnet test:*)", + "Bash(git add:*)", + "Bash(git commit:*)", + "Bash(git merge:*)", + "Bash(git log:*)" ], "additionalDirectories": [ ] diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..8458e30 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,203 @@ +name: CI + +on: + push: + branches: [master] + pull_request: + branches: [master] + +env: + MAJOR_MINOR: '0.4' + +permissions: + contents: write + security-events: write + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: | + 8.0.x + 9.0.x + 10.0.x + + - name: Restore + run: dotnet restore + + - name: Build + run: dotnet build -c Release --no-restore 2>&1 | tee build.log + + - name: Check warnings + run: | + WARNING_COUNT=$(grep -c " warning " build.log || true) + echo "Build warnings: $WARNING_COUNT" + if [ "$WARNING_COUNT" -gt 10 ]; then + echo "::error::Build has $WARNING_COUNT warnings (threshold: 10)" + exit 1 + fi + + - name: Test with coverage + run: dotnet test -c Release --no-build --verbosity normal --collect:"XPlat Code Coverage" --results-directory ./coverage + + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v5 + with: + directory: ./coverage + fail_ci_if_error: false + token: ${{ secrets.CODECOV_TOKEN }} + + - name: Compute version + id: version + run: | + LATEST_TAG=$(git tag -l "${MAJOR_MINOR}.*" --sort=-v:refname \ + | grep -E "^${MAJOR_MINOR}\.[0-9]+$" \ + | head -1) + + if [ -z "$LATEST_TAG" ]; then + PATCH=0 + else + PATCH=$(echo "$LATEST_TAG" | sed "s/${MAJOR_MINOR}\.\([0-9]*\)/\1/") + PATCH=$((PATCH + 1)) + fi + + VERSION="${MAJOR_MINOR}.${PATCH}" + echo "version=$VERSION" >> "$GITHUB_OUTPUT" + echo "previous_tag=$LATEST_TAG" >> "$GITHUB_OUTPUT" + echo "Computed version: $VERSION (previous: $LATEST_TAG)" + + - name: Pack + run: | + dotnet pack Tharga.Cache/Tharga.Cache.csproj -c Release --no-build -o ./artifacts /p:PackageVersion=${{ steps.version.outputs.version }} + dotnet pack Tharga.Cache.Redis/Tharga.Cache.Redis.csproj -c Release --no-build -o ./artifacts /p:PackageVersion=${{ steps.version.outputs.version }} + dotnet pack Tharga.Cache.MongoDB/Tharga.Cache.MongoDB.csproj -c Release --no-build -o ./artifacts /p:PackageVersion=${{ steps.version.outputs.version }} + dotnet pack Tharga.Cache.File/Tharga.Cache.File.csproj -c Release --no-build -o ./artifacts /p:PackageVersion=${{ steps.version.outputs.version }} + dotnet pack Tharga.Cache.Blazor/Tharga.Cache.Blazor.csproj -c Release --no-build -o ./artifacts /p:PackageVersion=${{ steps.version.outputs.version }} + + - name: Upload artifacts + uses: actions/upload-artifact@v4 + with: + name: nuget-packages + path: ./artifacts/ + + security: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Initialize CodeQL + uses: github/codeql-action/init@v3 + with: + languages: csharp + + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: | + 8.0.x + 9.0.x + 10.0.x + + - name: Build for CodeQL + run: dotnet build -c Release + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v3 + + publish: + needs: [build, security] + runs-on: ubuntu-latest + if: github.event_name == 'push' || github.event_name == 'pull_request' + environment: ${{ github.ref == 'refs/heads/master' && 'release' || 'prerelease' }} + + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: 10.0.x + + - name: Download artifacts + uses: actions/download-artifact@v4 + with: + name: nuget-packages + path: ./artifacts/ + + - name: Compute version + id: version + run: | + LATEST_TAG=$(git tag -l "${MAJOR_MINOR}.*" --sort=-v:refname \ + | grep -E "^${MAJOR_MINOR}\.[0-9]+$" \ + | head -1) + + if [ -z "$LATEST_TAG" ]; then + PATCH=0 + else + PATCH=$(echo "$LATEST_TAG" | sed "s/${MAJOR_MINOR}\.\([0-9]*\)/\1/") + PATCH=$((PATCH + 1)) + fi + + VERSION="${MAJOR_MINOR}.${PATCH}" + + if [ "${{ github.ref }}" != "refs/heads/master" ]; then + PRE_BASE="${VERSION}-pre" + LATEST_PRE=$(git tag -l "${PRE_BASE}.*" --sort=-v:refname | head -1) + + if [ -z "$LATEST_PRE" ]; then + COUNTER=1 + else + COUNTER=$(echo "$LATEST_PRE" | sed "s/.*-pre\.\([0-9]*\)/\1/") + COUNTER=$((COUNTER + 1)) + fi + + VERSION="${PRE_BASE}.${COUNTER}" + fi + + echo "version=$VERSION" >> "$GITHUB_OUTPUT" + echo "previous_tag=$LATEST_TAG" >> "$GITHUB_OUTPUT" + echo "Computed version: $VERSION" + + - name: Push to NuGet + run: | + for pkg in ./artifacts/*.nupkg; do + echo "Pushing $pkg..." + dotnet nuget push "$pkg" --api-key ${{ secrets.NUGET_API_KEY }} --source https://api.nuget.org/v3/index.json --skip-duplicate || true + done + + - name: Create GitHub Release + if: github.ref == 'refs/heads/master' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + NOTES_FLAG="" + if [ -n "${{ steps.version.outputs.previous_tag }}" ]; then + NOTES_FLAG="--notes-start-tag ${{ steps.version.outputs.previous_tag }}" + fi + gh release create "${{ steps.version.outputs.version }}" \ + --title "v${{ steps.version.outputs.version }}" \ + --generate-notes \ + $NOTES_FLAG \ + ./artifacts/*.nupkg + + - name: Create GitHub Pre-release + if: github.ref != 'refs/heads/master' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + gh release create "${{ steps.version.outputs.version }}" \ + --title "v${{ steps.version.outputs.version }} (pre-release)" \ + --notes "Pre-release from \`${{ github.head_ref }}\` branch." \ + --prerelease \ + ./artifacts/*.nupkg diff --git a/README.md b/README.md index 91c8b19..fe4d6a8 100644 --- a/README.md +++ b/README.md @@ -147,6 +147,8 @@ await timeCache.SetAsync("key", myData, TimeSpan.FromHours(1)); By default, all data is cached in memory (`IMemory`). You can configure specific types to use a different backend. +> **Note:** `IMemoryWithRedis` is deprecated. Use `IRedis` or `IMemory` explicitly instead. + ### Redis ```bash @@ -242,6 +244,7 @@ builder.Services.AddCache(o => |--------|---------|-------------| | `DefaultFreshSpan` | `null` | Default TTL when not specified per call | | `StaleWhileRevalidate` | `false` | Return stale data immediately while refreshing in the background | +| `ReturnDefaultOnFirstLoad` | `false` | Return `default(T)` on first cache miss instead of blocking; factory runs in the background | | `MaxCount` | `null` | Maximum number of cached items for this type | | `MaxSize` | `null` | Maximum total size in bytes for this type | | `EvictionPolicy` | `FirstInFirstOut` | Strategy when `MaxCount` or `MaxSize` is exceeded | diff --git a/Sample/Tharga.Cache.BlazorServer/Components/Pages/Cache.razor b/Sample/Tharga.Cache.BlazorServer/Components/Pages/Cache.razor index f2f39e7..d385dda 100644 --- a/Sample/Tharga.Cache.BlazorServer/Components/Pages/Cache.razor +++ b/Sample/Tharga.Cache.BlazorServer/Components/Pages/Cache.razor @@ -1,6 +1,5 @@ @page "/cache" @rendermode InteractiveServer -@inject ITimeToLiveCache TimeToLiveCache diff --git a/Sample/Tharga.Cache.BlazorServer/Components/Pages/Home.razor b/Sample/Tharga.Cache.BlazorServer/Components/Pages/Home.razor index 0eb0563..5353620 100644 --- a/Sample/Tharga.Cache.BlazorServer/Components/Pages/Home.razor +++ b/Sample/Tharga.Cache.BlazorServer/Components/Pages/Home.razor @@ -18,13 +18,21 @@ protected override async Task OnInitializedAsync() { - _data = await TimeToLiveCache.GetAsync("CacheKey", () => Task.FromResult("Initial data expires after 3 seconds. Wait 3 seconds and press 'Load Data' to get new data from cache.")); + _data = await TimeToLiveCache.GetAsync("CacheKey", async () => + { + await Task.Delay(TimeSpan.FromMilliseconds(500)); + return "Initial data expires after 3 seconds. Wait 3 seconds and press 'Load Data' to get new data from cache."; + }); await base.OnInitializedAsync(); } private async Task LoadDataAsync(MouseEventArgs arg) { - _data = await TimeToLiveCache.GetAsync("CacheKey", () => Task.FromResult("New data.")); + _data = await TimeToLiveCache.GetAsync("CacheKey", async () => + { + await Task.Delay(TimeSpan.FromSeconds(1)); + return "New data."; + }); StateHasChanged(); } diff --git a/Sample/Tharga.Cache.BlazorServer/Components/Pages/Weather.razor b/Sample/Tharga.Cache.BlazorServer/Components/Pages/Weather.razor index 49a44dc..67e6db4 100644 --- a/Sample/Tharga.Cache.BlazorServer/Components/Pages/Weather.razor +++ b/Sample/Tharga.Cache.BlazorServer/Components/Pages/Weather.razor @@ -1,5 +1,6 @@ @page "/weather" @attribute [StreamRendering] +@inject ITimeToLiveCache TimeToLiveCache Weather @@ -7,7 +8,7 @@

This component demonstrates showing data.

-@if (forecasts == null) +@if (_forecasts == null) {

Loading...

} @@ -23,7 +24,7 @@ else - @foreach (var forecast in forecasts) + @foreach (var forecast in _forecasts) { @forecast.Date.ToShortDateString() @@ -37,24 +38,30 @@ else } @code { - private WeatherForecast[] forecasts; + private WeatherForecast[] _forecasts; protected override async Task OnInitializedAsync() { // Simulate asynchronous loading to demonstrate streaming rendering - await Task.Delay(500); + // await Task.Delay(500); var startDate = DateOnly.FromDateTime(DateTime.Now); var summaries = new[] { "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" }; - forecasts = Enumerable.Range(1, 5).Select(index => new WeatherForecast + + _forecasts = await TimeToLiveCache.GetAsync("Weather", () => { - Date = startDate.AddDays(index), - TemperatureC = Random.Shared.Next(-20, 55), - Summary = summaries[Random.Shared.Next(summaries.Length)] - }).ToArray(); + var items = Enumerable.Range(1, 5).Select(index => new WeatherForecast + { + Date = startDate.AddDays(index), + TemperatureC = Random.Shared.Next(-20, 55), + Summary = summaries[Random.Shared.Next(summaries.Length)] + }).ToArray(); + + return Task.FromResult(items); + }); } - private class WeatherForecast + public class WeatherForecast { public DateOnly Date { get; set; } public int TemperatureC { get; set; } diff --git a/Sample/Tharga.Cache.BlazorServer/Program.cs b/Sample/Tharga.Cache.BlazorServer/Program.cs index 3184a42..b480836 100644 --- a/Sample/Tharga.Cache.BlazorServer/Program.cs +++ b/Sample/Tharga.Cache.BlazorServer/Program.cs @@ -1,7 +1,10 @@ using Radzen; using Tharga.Cache; using Tharga.Cache.BlazorServer.Components; +using Tharga.Cache.BlazorServer.Components.Pages; +using Tharga.Cache.MongoDB; using Tharga.Cache.Persist; +using Tharga.MongoDB; var builder = WebApplication.CreateBuilder(args); @@ -10,13 +13,19 @@ .AddInteractiveServerComponents(); builder.Services.AddRadzenComponents(); - +builder.AddMongoDB(); builder.Services.AddCache(o => { o.RegisterType(s => { s.DefaultFreshSpan = TimeSpan.FromSeconds(3); }); + + o.RegisterType(s => + { + s.DefaultFreshSpan = TimeSpan.FromSeconds(60); + s.StaleWhileRevalidate = true; + }); }); var app = builder.Build(); diff --git a/Sample/Tharga.Cache.BlazorServer/Tharga.Cache.BlazorServer.csproj b/Sample/Tharga.Cache.BlazorServer/Tharga.Cache.BlazorServer.csproj index bae69be..d0c8acc 100644 --- a/Sample/Tharga.Cache.BlazorServer/Tharga.Cache.BlazorServer.csproj +++ b/Sample/Tharga.Cache.BlazorServer/Tharga.Cache.BlazorServer.csproj @@ -8,6 +8,7 @@ +
diff --git a/Sample/Tharga.Cache.BlazorServer/appsettings.json b/Sample/Tharga.Cache.BlazorServer/appsettings.json index 10f68b8..20380f8 100644 --- a/Sample/Tharga.Cache.BlazorServer/appsettings.json +++ b/Sample/Tharga.Cache.BlazorServer/appsettings.json @@ -5,5 +5,8 @@ "Microsoft.AspNetCore": "Warning" } }, + "ConnectionStrings": { + "Default": "mongodb://localhost:27017/Tharga_Cache_BlazorServer" + }, "AllowedHosts": "*" } diff --git a/Sample/Tharga.Cache.WebApi/Tharga.Cache.WebApi.csproj b/Sample/Tharga.Cache.WebApi/Tharga.Cache.WebApi.csproj index e0c1eec..6f389a9 100644 --- a/Sample/Tharga.Cache.WebApi/Tharga.Cache.WebApi.csproj +++ b/Sample/Tharga.Cache.WebApi/Tharga.Cache.WebApi.csproj @@ -7,7 +7,7 @@ - + diff --git a/Tharga.Cache.Blazor/ListView.razor b/Tharga.Cache.Blazor/ListView.razor index 65795cb..ded970c 100644 --- a/Tharga.Cache.Blazor/ListView.razor +++ b/Tharga.Cache.Blazor/ListView.razor @@ -48,6 +48,11 @@ else + + +