Feature: Browser Storage Provider (ManagedCode.Storage.Browser)

Purpose

Implement IStorage on top of browser storage primitives so browser-facing .NET applications can persist client-local payloads behind the same storage abstraction used by the rest of the repository:

This provider targets browser storage, not protected storage. Data remains user-visible and user-modifiable, and browser APIs are unavailable during prerendering.

Main Flows

flowchart LR
  App --> Storage["BrowserStorage : IBrowserStorage"]
  Storage --> JS["IJSRuntime + JS module"]
  JS --> Browser["IndexedDB metadata + OPFS payload files"]

Components

DI Wiring

dotnet add package ManagedCode.Storage.Browser
using ManagedCode.Storage.Browser.Extensions;

builder.Services.AddBrowserStorageAsDefault(options =>
{
    options.ContainerName = "drafts";
    options.DatabaseName = "managedcode-storage";
    options.ChunkSizeBytes = 4 * 1024 * 1024;
    options.ChunkBatchSize = 4;
});

Inject the typed provider or default IStorage in a Blazor-scoped service or component after the app becomes interactive:

public sealed class DraftService(IBrowserStorage storage)
{
    public Task<Result<BlobMetadata>> SaveAsync(Stream content, CancellationToken cancellationToken)
    {
        return storage.UploadAsync(content, new UploadOptions
        {
            FileName = "draft.json",
            MimeType = "application/json"
        }, cancellationToken);
    }
}

Recommended tuning for larger browser-local payloads:

If the same application also uses Interactive Server rendering, keep the SignalR receive limit above the full browser read window because browser-to-server JS interop responses are capped by HubOptions.MaximumReceiveMessageSize:

builder.Services.AddRazorComponents()
    .AddInteractiveServerComponents()
    .AddHubOptions(options =>
    {
        options.MaximumReceiveMessageSize = 32L * 1024 * 1024;
    });

Current Behavior

Caveats

Tests