Skip to content

feat(JsonStringLocalizer): remove mssing localizer item cache#7812

Merged
ArgoZhang merged 8 commits intomainfrom
fix-cache
Mar 29, 2026
Merged

feat(JsonStringLocalizer): remove mssing localizer item cache#7812
ArgoZhang merged 8 commits intomainfrom
fix-cache

Conversation

@ArgoZhang
Copy link
Copy Markdown
Member

@ArgoZhang ArgoZhang commented Mar 28, 2026

Link issues

fixes #7811

Summary By Copilot

Regression?

  • Yes
  • No

Risk

  • High
  • Medium
  • Low

Verification

  • Manual (required)
  • Automated

Packaging changes reviewed?

  • Yes
  • No
  • N/A

☑️ Self Check before Merge

⚠️ Please check all items below before review. ⚠️

  • Doc is updated/provided or not needed
  • Demo is updated/provided or not needed
  • Merge the latest code from the main branch

Summary by Sourcery

Add cache reset support for JSON-based string localization and integrate it with the global cache clearing mechanism.

New Features:

  • Expose a ResetMissingMainifestCache method on JsonStringLocalizer to clear its missing manifest cache.
  • Add a Reset method on JsonStringLocalizerFactory that clears the associated JsonStringLocalizer cache and wire it into CacheManager.Clear so localization caches are reset when application cache is cleared.

Bug Fixes:

  • Adjust missing resource handling to avoid logging and caching when IgnoreLocalizerMissing is enabled.

Tests:

  • Extend JsonStringLocalizer tests to verify missing-item caching behavior is reset when CacheManager.Clear is invoked.

Copilot AI review requested due to automatic review settings March 28, 2026 10:13
@bb-auto bb-auto bot added the enhancement New feature or request label Mar 28, 2026
@bb-auto bb-auto bot added this to the v10.4.0 milestone Mar 28, 2026
@sourcery-ai
Copy link
Copy Markdown
Contributor

sourcery-ai bot commented Mar 28, 2026

Reviewer's Guide

Adds a reset mechanism for the JsonStringLocalizer missing-manifest cache, wires it into JsonStringLocalizerFactory and CacheManager, and updates tests to validate cache clearing behavior and adjust missing-item handling logic.

Sequence diagram for cache clearing and JsonStringLocalizer missing-manifest cache reset

sequenceDiagram
    actor Application
    participant CacheManager
    participant Cache as MemoryCache
    participant Provider as IServiceProvider
    participant Factory as IStringLocalizerFactory
    participant JsonFactory as JsonStringLocalizerFactory
    participant Localizer as JsonStringLocalizer

    Application->>CacheManager: Clear(key)
    alt Cache is MemoryCache
        CacheManager->>Cache: Compact(100)
        CacheManager->>Provider: GetServices(IStringLocalizerFactory)
        loop for each factory
            Provider-->>CacheManager: factory
            CacheManager->>Factory: type check
            alt factory is JsonStringLocalizerFactory
                CacheManager->>JsonFactory: Reset()
                JsonFactory->>Localizer: ResetMissingMainifestCache()
                Localizer->>Localizer: _missingManifestCache.Clear()
            else other factory types
                CacheManager-->>Factory: no action
            end
        end
    else Cache not MemoryCache
        CacheManager-->>Application: handle other cache types
    end
    CacheManager-->>Application: Clear completed
Loading

Class diagram for JsonStringLocalizer cache reset integration

classDiagram
    class JsonStringLocalizer {
        - ConcurrentDictionary~string, object?~ _missingManifestCache
        + LocalizedString this[string name]
        + LocalizedString this[string name, params object[] arguments]
        + IEnumerable~LocalizedString~ GetAllStrings(bool includeParentCultures)
        - string? GetStringFromJson(string name)
        - List~LocalizedString~ MergeResolveLocalizers(IEnumerable~LocalizedString~ localizedStrings, IEnumerable~LocalizedString~ baseResourceStrings)
        - void HandleMissingResourceItem(string name)
        + void ResetMissingMainifestCache()
    }

    class JsonStringLocalizerFactory {
        - JsonLocalizationOptions _jsonLocalizationOptions
        - ILocalizationMissingItemHandler _localizationMissingItemHandler
        - string _typeName
        - JsonStringLocalizer _localizer
        + JsonStringLocalizerFactory(ILoggerFactory loggerFactory, IOptions~JsonLocalizationOptions~ localizationOptions, ILocalizationMissingItemHandler localizationMissingItemHandler)
        + string GetResourcePrefix(string baseResourceName, string baseNamespace, string resourceLocation, Assembly resourceAssembly)
        + ResourceManagerStringLocalizer CreateResourceManagerStringLocalizer(Assembly assembly, string baseName)
        + void Reset()
    }

    class CacheManager {
        - object Cache
        - IServiceProvider Provider
        + void Clear(object key)
    }

    class ResourceManagerStringLocalizerFactory
    class ResourceManagerStringLocalizer
    class IStringLocalizerFactory
    class ILoggerFactory
    class JsonLocalizationOptions
    class ILocalizationMissingItemHandler
    class IServiceProvider {
        + IEnumerable~IStringLocalizerFactory~ GetServices()
    }
    class MemoryCache {
        + void Compact(double percentage)
    }

    JsonStringLocalizerFactory --|> ResourceManagerStringLocalizerFactory
    ResourceManagerStringLocalizerFactory ..|> IStringLocalizerFactory
    JsonStringLocalizerFactory o--> JsonStringLocalizer : creates
    JsonStringLocalizerFactory --> JsonLocalizationOptions
    JsonStringLocalizerFactory --> ILocalizationMissingItemHandler
    JsonStringLocalizerFactory --> ILoggerFactory

    CacheManager --> IServiceProvider : Provider
    CacheManager --> MemoryCache : Cache
    CacheManager ..> IStringLocalizerFactory : resolves
    CacheManager ..> JsonStringLocalizerFactory : calls Reset

    JsonStringLocalizer ..> ResourceManagerStringLocalizer
Loading

File-Level Changes

Change Details Files
Added an explicit reset method for the missing-manifest cache in JsonStringLocalizer and exposed it via JsonStringLocalizerFactory and CacheManager so that localization caches are cleared when application caches are cleared.
  • Introduced a ConcurrentDictionary-backed _missingManifestCache field and a public ResetMissingMainifestCache method that clears it.
  • Updated JsonStringLocalizerFactory to store the created JsonStringLocalizer instance, return it from CreateResourceManagerStringLocalizer, and added a public Reset method that calls ResetMissingMainifestCache on the cached localizer.
  • Extended CacheManager.Clear for MemoryCache to resolve IStringLocalizerFactory services, detect JsonStringLocalizerFactory instances, and invoke their Reset method so localization caches reset with the main cache.
src/BootstrapBlazor/Localization/Json/JsonStringLocalizer.cs
src/BootstrapBlazor/Localization/Json/JsonStringLocalizerFactory.cs
src/BootstrapBlazor/Services/CacheManager.cs
Adjusted missing-resource handling logic in JsonStringLocalizer to cache missing items only when IgnoreLocalizerMissing is enabled and to only log when it is disabled, aligning behavior with configuration semantics.
  • Reworked HandleMissingResourceItem to early-return after recording in _missingManifestCache when IgnoreLocalizerMissing is true, skipping logging in that case.
  • Restricted the informational log to only execute when IgnoreLocalizerMissing is false and the logger has Information enabled, and moved the cache write out of that branch.
src/BootstrapBlazor/Localization/Json/JsonStringLocalizer.cs
Extended unit tests to validate the new reset behavior and internal cache state while doing minor refactors for clarity.
  • Refactored the GetAllStrings_FromResource test to reuse the Dummy type via a local variable before calling Utility.GetDisplayName.
  • Enhanced GetAllStrings_FromInject (or equivalent) to request an IStringLocalizer, trigger a missing-item lookup, call CacheManager.Clear, then use reflection to access the _localizer and its _missingManifestCache field and assert that the cache dictionary is empty after reset.
  • Added a using for System.Collections.Concurrent and used ConcurrentDictionary type checks in tests to assert emptiness rather than null.
test/UnitTest/Localization/JsonStringLocalizerTest.cs

Assessment against linked issues

Issue Objective Addressed Explanation
#7811 Add a public ResetMissingMainifestCache method to JsonStringLocalizer that clears the internal missing-manifest cache.
#7811 Integrate the ResetMissingMainifestCache behavior into the existing caching infrastructure so that clearing the cache resets the JsonStringLocalizer missing-manifest cache as well, with corresponding test coverage.

Possibly linked issues


Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link
Copy Markdown
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 3 issues, and left some high level feedback:

  • The new method name ResetMissingMainifestCache contains a typo in Mainifest; consider renaming it (and the factory’s Reset call site) to ResetMissingManifestCache before this becomes part of the public API surface.
  • The logic in HandleMissingResourceItem has changed so that _missingManifestCache is only updated when IgnoreLocalizerMissing is true (previously it was always updated); please confirm whether this behavioral change is intentional or adjust so caching remains consistent with prior behavior.
  • Caching a single JsonStringLocalizer instance in JsonStringLocalizerFactory and resetting only that instance in Reset() may miss other localizers created by the factory (especially in multi-type or concurrent scenarios); consider a design that tracks and resets all relevant localizer instances instead of just the last one.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The new method name `ResetMissingMainifestCache` contains a typo in `Mainifest`; consider renaming it (and the factory’s `Reset` call site) to `ResetMissingManifestCache` before this becomes part of the public API surface.
- The logic in `HandleMissingResourceItem` has changed so that `_missingManifestCache` is only updated when `IgnoreLocalizerMissing` is true (previously it was always updated); please confirm whether this behavioral change is intentional or adjust so caching remains consistent with prior behavior.
- Caching a single `JsonStringLocalizer` instance in `JsonStringLocalizerFactory` and resetting only that instance in `Reset()` may miss other localizers created by the factory (especially in multi-type or concurrent scenarios); consider a design that tracks and resets all relevant localizer instances instead of just the last one.

## Individual Comments

### Comment 1
<location path="src/BootstrapBlazor/Localization/Json/JsonStringLocalizer.cs" line_range="115" />
<code_context>
+    /// <para lang="en">Reset cache method</para>
+    /// <para>v<version>10.5.0</version></para> 
+    /// </summary>
+    public void ResetMissingMainifestCache() => _missingManifestCache.Clear();
+
     private string? GetStringFromJson(string name)
</code_context>
<issue_to_address>
**issue (typo):** Typo in method name `ResetMissingMainifestCache` may cause confusion and makes the API harder to discover.

The name is misspelled as `Mainifest` instead of `Manifest`. Since this is a public API, consider renaming it to `ResetMissingManifestCache` for clarity and IDE discoverability. If renaming is breaking, you could add the correctly spelled method and keep this one as an `[Obsolete]` shim.

Suggested implementation:

```csharp
    private readonly ConcurrentDictionary<string, object?> _missingManifestCache = [];

    /// <summary>
    /// <para lang="zh">清除缓存方法</para>
    /// <para lang="en">Reset cache method</para>
    /// <para>v<version>10.5.0</version></para> 
    /// </summary>
    [Obsolete("Use ResetMissingManifestCache instead.")]
    public void ResetMissingMainifestCache() => ResetMissingManifestCache();

    /// <summary>
    /// <para lang="zh">清除缓存方法</para>
    /// <para lang="en">Reset cache method</para>
    /// <para>v<version>10.5.0</version></para> 
    /// </summary>
    public void ResetMissingManifestCache() => _missingManifestCache.Clear();

    private string? GetStringFromJson(string name)

```

1. Search the solution for any usages of `ResetMissingMainifestCache` and update them to call `ResetMissingManifestCache` instead, so that the obsolete shim is only for external callers.
2. Ensure the project has `using System;` in this file (or globally) so that the `[Obsolete]` attribute resolves correctly.
</issue_to_address>

### Comment 2
<location path="src/BootstrapBlazor/Localization/Json/JsonStringLocalizer.cs" line_range="183-186" />
<code_context>
     {
         localizationMissingItemHandler.HandleMissingItem(name, typeName, CultureInfo.CurrentUICulture.Name);
-        if (jsonLocalizationOptions.IgnoreLocalizerMissing == false)
+        if (jsonLocalizationOptions.IgnoreLocalizerMissing)
+        {
+            _missingManifestCache.TryAdd($"name={name}&culture={CultureInfo.CurrentUICulture.Name}", null);
+            return;
+        }
+
</code_context>
<issue_to_address>
**issue (bug_risk):** Reversed logic for `IgnoreLocalizerMissing` changes behavior and stops caching missing entries when logging is enabled.

This change inverts the meaning of `IgnoreLocalizerMissing` and alters behavior: when `true` we now only cache and return (no log), and when `false` we log but no longer cache. That removes caching in the non-ignore case, which previously logged and updated `_missingManifestCache`. If the goal is only to suppress logging when the flag is `true`, keep the cache update unconditional and gate only the log:

```csharp
localizationMissingItemHandler.HandleMissingItem(...);
if (!jsonLocalizationOptions.IgnoreLocalizerMissing && Logger.IsEnabled(LogLevel.Information))
{
    Logger.LogInformation(...);
}
_missingManifestCache.TryAdd(...);
```
</issue_to_address>

### Comment 3
<location path="src/BootstrapBlazor/Localization/Json/JsonStringLocalizerFactory.cs" line_range="114" />
<code_context>
-    protected override ResourceManagerStringLocalizer CreateResourceManagerStringLocalizer(Assembly assembly, string baseName) => new JsonStringLocalizer(assembly, _typeName!, baseName, _jsonLocalizationOptions, _loggerFactory.CreateLogger<JsonStringLocalizer>(), ResourceNamesCache, _localizationMissingItemHandler);
+    protected override ResourceManagerStringLocalizer CreateResourceManagerStringLocalizer(Assembly assembly, string baseName)
+    {
+        _localizer = new JsonStringLocalizer(assembly, _typeName!, baseName, _jsonLocalizationOptions, _loggerFactory.CreateLogger<JsonStringLocalizer>(), ResourceNamesCache, _localizationMissingItemHandler);
+        return _localizer;
+    }
</code_context>
<issue_to_address>
**issue (bug_risk):** Tracking only the last created `JsonStringLocalizer` means `Reset()` will not affect earlier instances and may be racy.

Because the factory only keeps a single `_localizer` reference and `Reset()` delegates to it, any previously created `JsonStringLocalizer` instances (for other `baseName`/assembly or consumers) will never be reset. In a concurrent scenario, `_localizer` may also be overwritten while another thread calls `Reset()` expecting to target a different instance.

To align `Reset()` with the intended cache-clearing semantics, consider either:
- Tracking all created localizers (e.g., in a thread-safe collection) and resetting them all, or
- Centralizing cache reset in a shared/global cache, or
- Clearly scoping the factory so that only one localizer instance can exist per scope/lifetime.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

protected override ResourceManagerStringLocalizer CreateResourceManagerStringLocalizer(Assembly assembly, string baseName) => new JsonStringLocalizer(assembly, _typeName!, baseName, _jsonLocalizationOptions, _loggerFactory.CreateLogger<JsonStringLocalizer>(), ResourceNamesCache, _localizationMissingItemHandler);
protected override ResourceManagerStringLocalizer CreateResourceManagerStringLocalizer(Assembly assembly, string baseName)
{
_localizer = new JsonStringLocalizer(assembly, _typeName!, baseName, _jsonLocalizationOptions, _loggerFactory.CreateLogger<JsonStringLocalizer>(), ResourceNamesCache, _localizationMissingItemHandler);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): Tracking only the last created JsonStringLocalizer means Reset() will not affect earlier instances and may be racy.

Because the factory only keeps a single _localizer reference and Reset() delegates to it, any previously created JsonStringLocalizer instances (for other baseName/assembly or consumers) will never be reset. In a concurrent scenario, _localizer may also be overwritten while another thread calls Reset() expecting to target a different instance.

To align Reset() with the intended cache-clearing semantics, consider either:

  • Tracking all created localizers (e.g., in a thread-safe collection) and resetting them all, or
  • Centralizing cache reset in a shared/global cache, or
  • Clearly scoping the factory so that only one localizer instance can exist per scope/lifetime.

@codecov
Copy link
Copy Markdown

codecov bot commented Mar 28, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.00%. Comparing base (548634f) to head (e77e6c6).
⚠️ Report is 1 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff            @@
##              main     #7812   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files          764       764           
  Lines        34117     34115    -2     
  Branches      4697      4697           
=========================================
- Hits         34117     34115    -2     
Flag Coverage Δ
BB 100.00% <100.00%> (?)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds a reset mechanism for the JSON localizer’s internal “missing manifest” cache and wires it into CacheManager.Clear(), along with a unit test and a package version bump to 10.5.0-beta03.

Changes:

  • Add ResetMissingMainifestCache() to JsonStringLocalizer and expose a Reset() hook on JsonStringLocalizerFactory.
  • Invoke the factory reset from CacheManager.Clear() to clear localization-related caches during full cache clears.
  • Extend unit tests to validate the reset behavior; bump package version.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
test/UnitTest/Localization/JsonStringLocalizerTest.cs Adds a test that triggers a missing key and verifies the internal missing-cache is cleared after ICacheManager.Clear().
src/BootstrapBlazor/Services/CacheManager.cs Calls into JSON localizer factory reset after compacting the memory cache.
src/BootstrapBlazor/Localization/Json/JsonStringLocalizerFactory.cs Stores a created localizer instance and adds a Reset() method that clears the localizer’s missing cache.
src/BootstrapBlazor/Localization/Json/JsonStringLocalizer.cs Adds a public reset method for the missing-manifest cache and adjusts missing-item handling logic.
src/BootstrapBlazor/BootstrapBlazor.csproj Version bump from 10.5.0-beta02 to 10.5.0-beta03.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@ArgoZhang ArgoZhang merged commit cc7435c into main Mar 29, 2026
6 checks passed
@ArgoZhang ArgoZhang deleted the fix-cache branch March 29, 2026 07:29
@ArgoZhang ArgoZhang changed the title feat(JsonStringLocalizer): add ResetMissingMainifestCache method feat(JsonStringLocalizer): remove mssing localizer item cache Mar 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(JsonStringLocalizer): remove mssing localizer item cache

2 participants