From 495cf04f78b348858a4ecc69830b6bac51edd593 Mon Sep 17 00:00:00 2001 From: Corey Hayward Date: Fri, 13 Sep 2024 09:41:06 +0100 Subject: [PATCH 1/3] Added Streaming provider to support storing objects larger than the SignalR message limit --- .../InteractiveServer/Components/App.razor | 1 + samples/InteractiveServer/Program.cs | 3 + .../Blazored.LocalStorage.csproj | 6 +- .../BrowserStorageProvider.cs | 355 ------------------ .../ServiceCollectionExtensions.cs | 20 +- .../BrowserStorageProvider.cs | 46 +++ .../BrowserStorageProviderBase.cs | 320 ++++++++++++++++ .../BrowserStreamingStorageProvider.cs | 56 +++ .../IStorageProvider.cs | 0 .../wwwroot/Blazored.LocalStorage.js | 11 + 10 files changed, 459 insertions(+), 359 deletions(-) delete mode 100644 src/Blazored.LocalStorage/BrowserStorageProvider.cs create mode 100644 src/Blazored.LocalStorage/StorageProviders/BrowserStorageProvider.cs create mode 100644 src/Blazored.LocalStorage/StorageProviders/BrowserStorageProviderBase.cs create mode 100644 src/Blazored.LocalStorage/StorageProviders/BrowserStreamingStorageProvider.cs rename src/Blazored.LocalStorage/{ => StorageProviders}/IStorageProvider.cs (100%) create mode 100644 src/Blazored.LocalStorage/wwwroot/Blazored.LocalStorage.js diff --git a/samples/InteractiveServer/Components/App.razor b/samples/InteractiveServer/Components/App.razor index b3d68bf..48e68de 100644 --- a/samples/InteractiveServer/Components/App.razor +++ b/samples/InteractiveServer/Components/App.razor @@ -15,6 +15,7 @@ + \ No newline at end of file diff --git a/samples/InteractiveServer/Program.cs b/samples/InteractiveServer/Program.cs index 125c438..b55f14f 100644 --- a/samples/InteractiveServer/Program.cs +++ b/samples/InteractiveServer/Program.cs @@ -11,7 +11,10 @@ .AddInteractiveServerComponents(); builder.Services.Replace(ServiceDescriptor.Scoped()); + builder.Services.AddBlazoredLocalStorage(); +// Use the below to enable streaming of objects with local storage +//builder.Services.AddBlazoredLocalStorageStreaming(); var app = builder.Build(); diff --git a/src/Blazored.LocalStorage/Blazored.LocalStorage.csproj b/src/Blazored.LocalStorage/Blazored.LocalStorage.csproj index 35c6f08..2035579 100644 --- a/src/Blazored.LocalStorage/Blazored.LocalStorage.csproj +++ b/src/Blazored.LocalStorage/Blazored.LocalStorage.csproj @@ -46,7 +46,7 @@ - + @@ -65,4 +65,8 @@ + + + + \ No newline at end of file diff --git a/src/Blazored.LocalStorage/BrowserStorageProvider.cs b/src/Blazored.LocalStorage/BrowserStorageProvider.cs deleted file mode 100644 index 0d98f9b..0000000 --- a/src/Blazored.LocalStorage/BrowserStorageProvider.cs +++ /dev/null @@ -1,355 +0,0 @@ -using Microsoft.JSInterop; -using System; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using System.Threading; -using System.Threading.Tasks; -using Blazored.LocalStorage.Exceptions; - -namespace Blazored.LocalStorage -{ - internal class BrowserStorageProvider : IStorageProvider - { - private const string StorageNotAvailableMessage = "Unable to access the browser storage. This is most likely due to the browser settings."; - - private readonly IJSRuntime _jSRuntime; - private readonly IJSInProcessRuntime? _jSInProcessRuntime; - - public BrowserStorageProvider(IJSRuntime jSRuntime) - { - _jSRuntime = jSRuntime; - _jSInProcessRuntime = jSRuntime as IJSInProcessRuntime; - } - - public async ValueTask ClearAsync(CancellationToken cancellationToken = default) - { - try - { - await _jSRuntime.InvokeVoidAsync("localStorage.clear", cancellationToken); - } - catch (Exception exception) - { - if (IsStorageDisabledException(exception)) - { - throw new BrowserStorageDisabledException(StorageNotAvailableMessage, exception); - } - - throw; - } - } - - public async ValueTask GetItemAsync(string key, CancellationToken cancellationToken = default) - { - try - { - return await _jSRuntime.InvokeAsync("localStorage.getItem", cancellationToken, key); - } - catch (Exception exception) - { - if (IsStorageDisabledException(exception)) - { - throw new BrowserStorageDisabledException(StorageNotAvailableMessage, exception); - } - - throw; - } - } - - public async ValueTask KeyAsync(int index, CancellationToken cancellationToken = default) - { - try - { - return await _jSRuntime.InvokeAsync("localStorage.key", cancellationToken, index); - } - catch (Exception exception) - { - if (IsStorageDisabledException(exception)) - { - throw new BrowserStorageDisabledException(StorageNotAvailableMessage, exception); - } - - throw; - } - } - - public async ValueTask ContainKeyAsync(string key, CancellationToken cancellationToken = default) - { - try - { - return await _jSRuntime.InvokeAsync("localStorage.hasOwnProperty", cancellationToken, key); - } - catch (Exception exception) - { - if (IsStorageDisabledException(exception)) - { - throw new BrowserStorageDisabledException(StorageNotAvailableMessage, exception); - } - - throw; - } - } - - public async ValueTask LengthAsync(CancellationToken cancellationToken = default) - { - try - { - return await _jSRuntime.InvokeAsync("eval", cancellationToken, "localStorage.length"); - } - catch (Exception exception) - { - if (IsStorageDisabledException(exception)) - { - throw new BrowserStorageDisabledException(StorageNotAvailableMessage, exception); - } - - throw; - } - } - - public async ValueTask RemoveItemAsync(string key, CancellationToken cancellationToken = default) - { - try - { - await _jSRuntime.InvokeVoidAsync("localStorage.removeItem", cancellationToken, key); - } - catch (Exception exception) - { - if (IsStorageDisabledException(exception)) - { - throw new BrowserStorageDisabledException(StorageNotAvailableMessage, exception); - } - - throw; - } - } - - public async ValueTask SetItemAsync(string key, string data, CancellationToken cancellationToken = default) - { - try - { - await _jSRuntime.InvokeVoidAsync("localStorage.setItem", cancellationToken, key, data); - } - catch (Exception exception) - { - if (IsStorageDisabledException(exception)) - { - throw new BrowserStorageDisabledException(StorageNotAvailableMessage, exception); - } - - throw; - } - } - - public async ValueTask> KeysAsync(CancellationToken cancellationToken = default) - { - try - { - return await _jSRuntime.InvokeAsync>("eval", cancellationToken, "Object.keys(localStorage)"); - } - catch (Exception exception) - { - if (IsStorageDisabledException(exception)) - { - throw new BrowserStorageDisabledException(StorageNotAvailableMessage, exception); - } - - throw; - } - } - - public async ValueTask RemoveItemsAsync(IEnumerable keys, CancellationToken cancellationToken = default) - { - try - { - foreach (var key in keys) - { - await _jSRuntime.InvokeVoidAsync("localStorage.removeItem", cancellationToken, key); - } - } - catch (Exception exception) - { - if (IsStorageDisabledException(exception)) - { - throw new BrowserStorageDisabledException(StorageNotAvailableMessage, exception); - } - - throw; - } - } - - public void Clear() - { - CheckForInProcessRuntime(); - try - { - _jSInProcessRuntime.InvokeVoid("localStorage.clear"); - } - catch (Exception exception) - { - if (IsStorageDisabledException(exception)) - { - throw new BrowserStorageDisabledException(StorageNotAvailableMessage, exception); - } - - throw; - } - } - - public string GetItem(string key) - { - CheckForInProcessRuntime(); - try - { - return _jSInProcessRuntime.Invoke("localStorage.getItem", key); - } - catch (Exception exception) - { - if (IsStorageDisabledException(exception)) - { - throw new BrowserStorageDisabledException(StorageNotAvailableMessage, exception); - } - - throw; - } - } - - public string Key(int index) - { - CheckForInProcessRuntime(); - try - { - return _jSInProcessRuntime.Invoke("localStorage.key", index); - } - catch (Exception exception) - { - if (IsStorageDisabledException(exception)) - { - throw new BrowserStorageDisabledException(StorageNotAvailableMessage, exception); - } - - throw; - } - } - - public bool ContainKey(string key) - { - CheckForInProcessRuntime(); - try - { - return _jSInProcessRuntime.Invoke("localStorage.hasOwnProperty", key); - } - catch (Exception exception) - { - if (IsStorageDisabledException(exception)) - { - throw new BrowserStorageDisabledException(StorageNotAvailableMessage, exception); - } - - throw; - } - } - - public int Length() - { - CheckForInProcessRuntime(); - try - { - return _jSInProcessRuntime.Invoke("eval", "localStorage.length"); - } - catch (Exception exception) - { - if (IsStorageDisabledException(exception)) - { - throw new BrowserStorageDisabledException(StorageNotAvailableMessage, exception); - } - - throw; - } - } - - public void RemoveItem(string key) - { - CheckForInProcessRuntime(); - try - { - _jSInProcessRuntime.InvokeVoid("localStorage.removeItem", key); - } - catch (Exception exception) - { - if (IsStorageDisabledException(exception)) - { - throw new BrowserStorageDisabledException(StorageNotAvailableMessage, exception); - } - - throw; - } - } - - public void RemoveItems(IEnumerable keys) - { - CheckForInProcessRuntime(); - try - { - foreach (var key in keys) - { - _jSInProcessRuntime.InvokeVoid("localStorage.removeItem", key); - } - } - catch (Exception exception) - { - if (IsStorageDisabledException(exception)) - { - throw new BrowserStorageDisabledException(StorageNotAvailableMessage, exception); - } - - throw; - } - } - - public void SetItem(string key, string data) - { - CheckForInProcessRuntime(); - try - { - _jSInProcessRuntime.InvokeVoid("localStorage.setItem", key, data); - } - catch (Exception exception) - { - if (IsStorageDisabledException(exception)) - { - throw new BrowserStorageDisabledException(StorageNotAvailableMessage, exception); - } - - throw; - } - } - - public IEnumerable Keys() - { - CheckForInProcessRuntime(); - try - { - return _jSInProcessRuntime.Invoke>("eval", "Object.keys(localStorage)"); - } - catch (Exception exception) - { - if (IsStorageDisabledException(exception)) - { - throw new BrowserStorageDisabledException(StorageNotAvailableMessage, exception); - } - - throw; - } - } - - [MemberNotNull(nameof(_jSInProcessRuntime))] - private void CheckForInProcessRuntime() - { - if (_jSInProcessRuntime == null) - throw new InvalidOperationException("IJSInProcessRuntime not available"); - } - - private static bool IsStorageDisabledException(Exception exception) - => exception.Message.Contains("Failed to read the 'localStorage' property from 'Window'"); - } -} diff --git a/src/Blazored.LocalStorage/ServiceCollectionExtensions.cs b/src/Blazored.LocalStorage/ServiceCollectionExtensions.cs index 60c0342..e51dfd6 100644 --- a/src/Blazored.LocalStorage/ServiceCollectionExtensions.cs +++ b/src/Blazored.LocalStorage/ServiceCollectionExtensions.cs @@ -18,8 +18,24 @@ public static IServiceCollection AddBlazoredLocalStorage(this IServiceCollection public static IServiceCollection AddBlazoredLocalStorage(this IServiceCollection services, Action? configure) { - services.TryAddScoped(); services.TryAddScoped(); + AddServices(services, configure); + return services; + } + + public static IServiceCollection AddBlazoredLocalStorageStreaming(this IServiceCollection services) + => AddBlazoredLocalStorageStreaming(services, null); + + public static IServiceCollection AddBlazoredLocalStorageStreaming(this IServiceCollection services, Action? configure) + { + services.TryAddScoped(); + AddServices(services, configure); + return services; + } + + private static void AddServices(IServiceCollection services, Action? configure) + { + services.TryAddScoped(); services.TryAddScoped(); services.TryAddScoped(); if (services.All(serviceDescriptor => serviceDescriptor.ServiceType != typeof(IConfigureOptions))) @@ -30,8 +46,6 @@ public static IServiceCollection AddBlazoredLocalStorage(this IServiceCollection configureOptions.JsonSerializerOptions.Converters.Add(new TimespanJsonConverter()); }); } - - return services; } /// diff --git a/src/Blazored.LocalStorage/StorageProviders/BrowserStorageProvider.cs b/src/Blazored.LocalStorage/StorageProviders/BrowserStorageProvider.cs new file mode 100644 index 0000000..b283382 --- /dev/null +++ b/src/Blazored.LocalStorage/StorageProviders/BrowserStorageProvider.cs @@ -0,0 +1,46 @@ +using Microsoft.JSInterop; +using System; +using System.Threading; +using System.Threading.Tasks; +using Blazored.LocalStorage.Exceptions; + +namespace Blazored.LocalStorage; + +internal class BrowserStorageProvider : BrowserStorageProviderBase, IStorageProvider +{ + public BrowserStorageProvider(IJSRuntime jSRuntime) : base(jSRuntime) { } + + public async ValueTask GetItemAsync(string key, CancellationToken cancellationToken = default) + { + try + { + return await JSRuntime.InvokeAsync("localStorage.getItem", cancellationToken, key); + } + catch (Exception exception) + { + if (IsStorageDisabledException(exception)) + { + throw new BrowserStorageDisabledException(StorageNotAvailableMessage, exception); + } + + throw; + } + } + + public async ValueTask SetItemAsync(string key, string data, CancellationToken cancellationToken = default) + { + try + { + await JSRuntime.InvokeVoidAsync("localStorage.setItem", cancellationToken, key, data); + } + catch (Exception exception) + { + if (IsStorageDisabledException(exception)) + { + throw new BrowserStorageDisabledException(StorageNotAvailableMessage, exception); + } + + throw; + } + } +} diff --git a/src/Blazored.LocalStorage/StorageProviders/BrowserStorageProviderBase.cs b/src/Blazored.LocalStorage/StorageProviders/BrowserStorageProviderBase.cs new file mode 100644 index 0000000..7f0dea5 --- /dev/null +++ b/src/Blazored.LocalStorage/StorageProviders/BrowserStorageProviderBase.cs @@ -0,0 +1,320 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using Blazored.LocalStorage.Exceptions; +using Microsoft.JSInterop; + +namespace Blazored.LocalStorage; + +internal abstract class BrowserStorageProviderBase +{ + protected const string StorageNotAvailableMessage = "Unable to access the browser storage. This is most likely due to the browser settings."; + protected readonly IJSInProcessRuntime? JSInProcessRuntime; + protected readonly IJSRuntime JSRuntime; + + public BrowserStorageProviderBase(IJSRuntime jSRuntime) + { + JSRuntime = jSRuntime; + JSInProcessRuntime = jSRuntime as IJSInProcessRuntime; + } + + protected static bool IsStorageDisabledException(Exception exception) + => exception.Message.Contains("Failed to read the 'localStorage' property from 'Window'"); + + public string GetItem(string key) + { + CheckForInProcessRuntime(); + try + { + return JSInProcessRuntime.Invoke("localStorage.getItem", key); + } + catch (Exception exception) + { + if (IsStorageDisabledException(exception)) + { + throw new BrowserStorageDisabledException(StorageNotAvailableMessage, exception); + } + + throw; + } + } + + public void SetItem(string key, string data) + { + CheckForInProcessRuntime(); + try + { + JSInProcessRuntime.InvokeVoid("localStorage.setItem", key, data); + } + catch (Exception exception) + { + if (IsStorageDisabledException(exception)) + { + throw new BrowserStorageDisabledException(StorageNotAvailableMessage, exception); + } + + throw; + } + } + + public void Clear() + { + CheckForInProcessRuntime(); + try + { + JSInProcessRuntime.InvokeVoid("localStorage.clear"); + } + catch (Exception exception) + { + if (IsStorageDisabledException(exception)) + { + throw new BrowserStorageDisabledException(StorageNotAvailableMessage, exception); + } + + throw; + } + } + + public async ValueTask ClearAsync(CancellationToken cancellationToken = default) + { + try + { + await JSRuntime.InvokeVoidAsync("localStorage.clear", cancellationToken); + } + catch (Exception exception) + { + if (IsStorageDisabledException(exception)) + { + throw new BrowserStorageDisabledException(StorageNotAvailableMessage, exception); + } + + throw; + } + } + + public bool ContainKey(string key) + { + CheckForInProcessRuntime(); + try + { + return JSInProcessRuntime.Invoke("localStorage.hasOwnProperty", key); + } + catch (Exception exception) + { + if (IsStorageDisabledException(exception)) + { + throw new BrowserStorageDisabledException(StorageNotAvailableMessage, exception); + } + + throw; + } + } + + public async ValueTask ContainKeyAsync(string key, CancellationToken cancellationToken = default) + { + try + { + return await JSRuntime.InvokeAsync("localStorage.hasOwnProperty", cancellationToken, key); + } + catch (Exception exception) + { + if (IsStorageDisabledException(exception)) + { + throw new BrowserStorageDisabledException(StorageNotAvailableMessage, exception); + } + + throw; + } + } + + public string Key(int index) + { + CheckForInProcessRuntime(); + try + { + return JSInProcessRuntime.Invoke("localStorage.key", index); + } + catch (Exception exception) + { + if (IsStorageDisabledException(exception)) + { + throw new BrowserStorageDisabledException(StorageNotAvailableMessage, exception); + } + + throw; + } + } + + public async ValueTask KeyAsync(int index, CancellationToken cancellationToken = default) + { + try + { + return await JSRuntime.InvokeAsync("localStorage.key", cancellationToken, index); + } + catch (Exception exception) + { + if (IsStorageDisabledException(exception)) + { + throw new BrowserStorageDisabledException(StorageNotAvailableMessage, exception); + } + + throw; + } + } + + public IEnumerable Keys() + { + CheckForInProcessRuntime(); + try + { + return JSInProcessRuntime.Invoke>("eval", "Object.keys(localStorage)"); + } + catch (Exception exception) + { + if (IsStorageDisabledException(exception)) + { + throw new BrowserStorageDisabledException(StorageNotAvailableMessage, exception); + } + + throw; + } + } + + public async ValueTask> KeysAsync(CancellationToken cancellationToken = default) + { + try + { + return await JSRuntime.InvokeAsync>("eval", cancellationToken, "Object.keys(localStorage)"); + } + catch (Exception exception) + { + if (IsStorageDisabledException(exception)) + { + throw new BrowserStorageDisabledException(StorageNotAvailableMessage, exception); + } + + throw; + } + } + + public int Length() + { + CheckForInProcessRuntime(); + try + { + return JSInProcessRuntime.Invoke("eval", "localStorage.length"); + } + catch (Exception exception) + { + if (IsStorageDisabledException(exception)) + { + throw new BrowserStorageDisabledException(StorageNotAvailableMessage, exception); + } + + throw; + } + } + + public async ValueTask LengthAsync(CancellationToken cancellationToken = default) + { + try + { + return await JSRuntime.InvokeAsync("eval", cancellationToken, "localStorage.length"); + } + catch (Exception exception) + { + if (IsStorageDisabledException(exception)) + { + throw new BrowserStorageDisabledException(StorageNotAvailableMessage, exception); + } + + throw; + } + } + + public void RemoveItem(string key) + { + CheckForInProcessRuntime(); + try + { + JSInProcessRuntime.InvokeVoid("localStorage.removeItem", key); + } + catch (Exception exception) + { + if (IsStorageDisabledException(exception)) + { + throw new BrowserStorageDisabledException(StorageNotAvailableMessage, exception); + } + + throw; + } + } + + public async ValueTask RemoveItemAsync(string key, CancellationToken cancellationToken = default) + { + try + { + await JSRuntime.InvokeVoidAsync("localStorage.removeItem", cancellationToken, key); + } + catch (Exception exception) + { + if (IsStorageDisabledException(exception)) + { + throw new BrowserStorageDisabledException(StorageNotAvailableMessage, exception); + } + + throw; + } + } + + public void RemoveItems(IEnumerable keys) + { + CheckForInProcessRuntime(); + try + { + foreach (var key in keys) + { + JSInProcessRuntime.InvokeVoid("localStorage.removeItem", key); + } + } + catch (Exception exception) + { + if (IsStorageDisabledException(exception)) + { + throw new BrowserStorageDisabledException(StorageNotAvailableMessage, exception); + } + + throw; + } + } + + public async ValueTask RemoveItemsAsync(IEnumerable keys, CancellationToken cancellationToken = default) + { + try + { + foreach (var key in keys) + { + await JSRuntime.InvokeVoidAsync("localStorage.removeItem", cancellationToken, key); + } + } + catch (Exception exception) + { + if (IsStorageDisabledException(exception)) + { + throw new BrowserStorageDisabledException(StorageNotAvailableMessage, exception); + } + + throw; + } + } + + [MemberNotNull(nameof(JSInProcessRuntime))] + protected void CheckForInProcessRuntime() + { + if (JSInProcessRuntime == null) + throw new InvalidOperationException("IJSInProcessRuntime not available"); + } +} diff --git a/src/Blazored.LocalStorage/StorageProviders/BrowserStreamingStorageProvider.cs b/src/Blazored.LocalStorage/StorageProviders/BrowserStreamingStorageProvider.cs new file mode 100644 index 0000000..4819153 --- /dev/null +++ b/src/Blazored.LocalStorage/StorageProviders/BrowserStreamingStorageProvider.cs @@ -0,0 +1,56 @@ +using System; +using System.IO; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Blazored.LocalStorage.Exceptions; +using Microsoft.JSInterop; + +namespace Blazored.LocalStorage; +internal class BrowserStreamingStorageProvider : BrowserStorageProviderBase, IStorageProvider +{ + public BrowserStreamingStorageProvider(IJSRuntime jSRuntime) : base(jSRuntime) { } + + public async ValueTask GetItemAsync(string key, CancellationToken cancellationToken = default) + { + try + { + if (!(await ContainKeyAsync(key, cancellationToken))) + { + return null; + } + + var streamRef = await JSRuntime.InvokeAsync("getLocalStorageValue", cancellationToken, key); + using var stream = await streamRef.OpenReadStreamAsync(cancellationToken: cancellationToken); + using var streamReader = new StreamReader(stream); + return await streamReader.ReadToEndAsync(); + } + catch (Exception exception) + { + if (IsStorageDisabledException(exception)) + { + throw new BrowserStorageDisabledException(StorageNotAvailableMessage, exception); + } + + throw; + } + } + + public async ValueTask SetItemAsync(string key, string data, CancellationToken cancellationToken = default) + { + try + { + var dnStream = new DotNetStreamReference(new MemoryStream(Encoding.UTF8.GetBytes(data))); + await JSRuntime.InvokeVoidAsync("setLocalStorageValue", cancellationToken, key, dnStream); + } + catch (Exception exception) + { + if (IsStorageDisabledException(exception)) + { + throw new BrowserStorageDisabledException(StorageNotAvailableMessage, exception); + } + + throw; + } + } +} diff --git a/src/Blazored.LocalStorage/IStorageProvider.cs b/src/Blazored.LocalStorage/StorageProviders/IStorageProvider.cs similarity index 100% rename from src/Blazored.LocalStorage/IStorageProvider.cs rename to src/Blazored.LocalStorage/StorageProviders/IStorageProvider.cs diff --git a/src/Blazored.LocalStorage/wwwroot/Blazored.LocalStorage.js b/src/Blazored.LocalStorage/wwwroot/Blazored.LocalStorage.js new file mode 100644 index 0000000..8a3dbcf --- /dev/null +++ b/src/Blazored.LocalStorage/wwwroot/Blazored.LocalStorage.js @@ -0,0 +1,11 @@ +window.setLocalStorageValue = async (key, streamReference) => { + const arrayBuffer = await streamReference.arrayBuffer(); + const stringValue = new TextDecoder().decode(arrayBuffer); + localStorage.setItem(key, stringValue); +} +window.getLocalStorageValue = (key) => { + const value = localStorage.getItem(key); + const utf8Encoder = new TextEncoder(); + const encodedTextValue = utf8Encoder.encode(value); + return encodedTextValue; +} \ No newline at end of file From 98cae1b08587f033e2c119e3e2877eed3d78b1b7 Mon Sep 17 00:00:00 2001 From: Corey Hayward Date: Sat, 14 Sep 2024 12:12:30 +0100 Subject: [PATCH 2/3] updated readme to include streaming use --- README.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/README.md b/README.md index 72d06f0..09e2ae1 100644 --- a/README.md +++ b/README.md @@ -79,6 +79,27 @@ builder.Services.AddBlazoredLocalStorageAsSingleton(); This method will not work with Blazor Server applications as Blazor's JS interop services are registered as Scoped and cannot be injected into Singletons. +### Using JS Interop Streaming +When using interactive components in server-side apps JS Interop calls are limited to the configured SignalR message size (default: 32KB). +Therefore when attempting to store or retrieve an object larger than this in LocalStorage the call will fail with a SignalR exception. + +The following streaming implementation can be used to remove this limit (you will still be limited by the browser). + +Register the streaming local storage service + +```c# +public void ConfigureServices(IServiceCollection services) +{ + services.AddBlazoredLocalStorageStreaming(); +} +``` + +Add the JavaScript file to your _App.razor_ + +```html + +``` + ## Usage (Blazor WebAssembly) To use Blazored.LocalStorage in Blazor WebAssembly, inject the `ILocalStorageService` per the example below. From a5c7dbe731c2d4351ccb5865216b277327b4a5e0 Mon Sep 17 00:00:00 2001 From: Corey Hayward Date: Sat, 14 Sep 2024 12:17:06 +0100 Subject: [PATCH 3/3] cleanup --- samples/InteractiveServer/Components/App.razor | 2 +- src/Blazored.LocalStorage/Blazored.LocalStorage.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/samples/InteractiveServer/Components/App.razor b/samples/InteractiveServer/Components/App.razor index 48e68de..5dd6ff4 100644 --- a/samples/InteractiveServer/Components/App.razor +++ b/samples/InteractiveServer/Components/App.razor @@ -15,7 +15,7 @@ - + \ No newline at end of file diff --git a/src/Blazored.LocalStorage/Blazored.LocalStorage.csproj b/src/Blazored.LocalStorage/Blazored.LocalStorage.csproj index 2035579..e018d7f 100644 --- a/src/Blazored.LocalStorage/Blazored.LocalStorage.csproj +++ b/src/Blazored.LocalStorage/Blazored.LocalStorage.csproj @@ -46,7 +46,7 @@ - +