GithubHelp home page GithubHelp logo

blazored / localstorage Goto Github PK

View Code? Open in Web Editor NEW
1.1K 24.0 114.0 8.94 MB

A library to provide access to local storage in Blazor applications

Home Page: https://blazored.github.io/LocalStorage/

License: MIT License

C# 100.00%
blazor blazored localstorage csharp blazor-interop nuget blazor-webassembly blazor-server blazor-applications hacktoberfest

localstorage's Introduction

Nuget version Nuget downloads Build & Test Main

Blazored LocalStorage

Blazored LocalStorage is a library that provides access to the browsers local storage APIs for Blazor applications. An additional benefit of using this library is that it will handle serializing and deserializing values when saving or retrieving them.

Breaking Changes (v3 > v4)

JsonSerializerOptions

From v4 onwards we use the default the JsonSerializerOptions for System.Text.Json instead of using custom ones. This will cause values saved to local storage with v3 to break things. To retain the old settings use the following configuration when adding Blazored LocalStorage to the DI container:

builder.Services.AddBlazoredLocalStorage(config =>
{
    config.JsonSerializerOptions.DictionaryKeyPolicy = JsonNamingPolicy.CamelCase;
    config.JsonSerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull;
    config.JsonSerializerOptions.IgnoreReadOnlyProperties = true;
    config.JsonSerializerOptions.PropertyNameCaseInsensitive = true;
    config.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
    config.JsonSerializerOptions.ReadCommentHandling = JsonCommentHandling.Skip;
    config.JsonSerializerOptions.WriteIndented = false;
});

SetItem[Async] method now serializes string values

Prior to v4 we bypassed the serialization of string values as it seemed a pointless as string can be stored directly. However, this led to some edge cases where nullable strings were being saved as the string "null". Then when retrieved, instead of being null the value was "null". By serializing strings this issue is taken care of. For those who wish to save raw string values, a new method SetValueAsString[Async] is available. This will save a string value without attempting to serialize it and will throw an exception if a null string is attempted to be saved.

Installing

To install the package add the following line to you csproj file replacing x.x.x with the latest version number (found at the top of this file):

<PackageReference Include="Blazored.LocalStorage" Version="x.x.x" />

You can also install via the .NET CLI with the following command:

dotnet add package Blazored.LocalStorage

If you're using Visual Studio you can also install via the built in NuGet package manager.

Setup

You will need to register the local storage services with the service collection in your Startup.cs file in Blazor Server.

public void ConfigureServices(IServiceCollection services)
{
    services.AddBlazoredLocalStorage();
}

Or in your Program.cs file in Blazor WebAssembly.

public static async Task Main(string[] args)
{
    var builder = WebAssemblyHostBuilder.CreateDefault(args);
    builder.RootComponents.Add<App>("app");

    builder.Services.AddBlazoredLocalStorage();

    await builder.Build().RunAsync();
}

Registering services as Singleton - Blazor WebAssembly ONLY

99% of developers will want to register Blazored LocalStorage using the method described above. However, in some very specific scenarios developer may have a need to register services as Singleton as apposed to Scoped. This is possible by using the following method:

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.

Usage (Blazor WebAssembly)

To use Blazored.LocalStorage in Blazor WebAssembly, inject the ILocalStorageService per the example below.

@inject Blazored.LocalStorage.ILocalStorageService localStorage

@code {

    protected override async Task OnInitializedAsync()
    {
        await localStorage.SetItemAsync("name", "John Smith");
        var name = await localStorage.GetItemAsync<string>("name");
    }

}

With Blazor WebAssembly you also have the option of a synchronous API, if your use case requires it. You can swap the ILocalStorageService for ISyncLocalStorageService which allows you to avoid use of async/await. For either interface, the method names are the same.

@inject Blazored.LocalStorage.ISyncLocalStorageService localStorage

@code {

    protected override void OnInitialized()
    {
        localStorage.SetItem("name", "John Smith");
        var name = localStorage.GetItem<string>("name");
    }

}

Usage (Blazor Server)

NOTE: Due to pre-rendering in Blazor Server you can't perform any JS interop until the OnAfterRender lifecycle method.

@inject Blazored.LocalStorage.ILocalStorageService localStorage

@code {

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        await localStorage.SetItemAsync("name", "John Smith");
        var name = await localStorage.GetItemAsync<string>("name");
    }

}

The APIs available are:

  • asynchronous via ILocalStorageService:

    • SetItemAsync()
    • SetItemAsStringAsync()
    • GetItemAsync()
    • GetItemAsStringAsync()
    • RemoveItemAsync()
    • ClearAsync()
    • LengthAsync()
    • KeyAsync()
    • ContainKeyAsync()
  • synchronous via ISyncLocalStorageService (Synchronous methods are only available in Blazor WebAssembly):

    • SetItem()
    • SetItemAsString()
    • GetItem()
    • GetItemAsString()
    • RemoveItem()
    • Clear()
    • Length()
    • Key()
    • ContainKey()

Note: Blazored.LocalStorage methods will handle the serialisation and de-serialisation of the data for you, the exceptions are the SetItemAsString[Async] and GetItemAsString[Async] methods which will save and return raw string values from local storage.

Configuring JSON Serializer Options

You can configure the options for the default serializer (System.Text.Json) when calling the AddBlazoredLocalStorage method to register services.

public static async Task Main(string[] args)
{
    var builder = WebAssemblyHostBuilder.CreateDefault(args);
    builder.RootComponents.Add<App>("app");

    builder.Services.AddBlazoredLocalStorage(config =>
        config.JsonSerializerOptions.WriteIndented = true
    );

    await builder.Build().RunAsync();
}

Using a custom JSON serializer

By default, the library uses System.Text.Json. If you prefer to use a different JSON library for serialization--or if you want to add some custom logic when serializing or deserializing--you can provide your own serializer which implements the Blazored.LocalStorage.Serialization.IJsonSerializer interface.

To register your own serializer in place of the default one, you can do the following:

builder.Services.AddBlazoredLocalStorage();
builder.Services.Replace(ServiceDescriptor.Scoped<IJsonSerializer, MySerializer>());

You can find an example of this in the Blazor Server sample project. The standard serializer has been replaced with a new serializer which uses NewtonsoftJson.

Testing with bUnit

The Blazored.LocalStorage.TestExtensions package provides test extensions for use with the bUnit testing library. Using these test extensions will provide an in memory implementation which mimics local storage allowing more realistic testing of your components.

Installing

To install the package add the following line to you csproj file replacing x.x.x with the latest version number (found at the top of this file):

<PackageReference Include="Blazored.LocalStorage.TestExtensions" Version="x.x.x" />

You can also install via the .NET CLI with the following command:

dotnet add package Blazored.LocalStorage.TestExtensions

If you're using Visual Studio you can also install via the built in NuGet package manager.

Usage example

Below is an example test which uses these extensions. You can find an example project which shows this code in action in the samples folder.

public class IndexPageTests : TestContext
{
    [Fact]
    public async Task SavesNameToLocalStorage()
    {
        // Arrange
        const string inputName = "John Smith";
        var localStorage = this.AddBlazoredLocalStorage();
        var cut = RenderComponent<BlazorWebAssembly.Pages.Index>();

        // Act
        cut.Find("#Name").Change(inputName);
        cut.Find("#NameButton").Click();
            
        // Assert
        var name = await localStorage.GetItemAsync<string>("name");
            
        Assert.Equal(inputName, name);
    }
}

localstorage's People

Contributors

arkada38 avatar azure-pipelines[bot] avatar chanan avatar chrissainty avatar czirok avatar danielabbatt avatar dependabot[bot] avatar ericmutta avatar iiarrows avatar jan-johansson-mr avatar juliengrd avatar kim-ssi avatar kirkplangrid avatar koskila avatar kswoll avatar lauchacarro avatar leonspors avatar linkdotnet avatar markstega avatar maxmommersteeg avatar mmaderic avatar nshenoy avatar nukedbit avatar peterblazejewicz avatar pinguinosod avatar pmachapman avatar rf-0 avatar simoncropp avatar sql-mistermagoo avatar thild avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

localstorage's Issues

Sync local storage with browser account

Hi,

With local storage, users (and developers) don't have to deal with login and password !

I am wondering if there is a manner to sync this local storage accross devices ?
I know that some browsers can sync history and other data.

The idea is to defer authentication and data managment to browsers since with Blazor WebAssembly, the browser is the OS.

synchronous operation with server-side not work

Hi, and thanks for your work !

I try to use your library in a server-side project with synchronous call, but when i call the GetItem (in onAfterRender method), I have this following error :
IJSInProcessRuntime not available

How to reproduce :

  • update your server side demo and replace the storageService by a ISyncLocalStorageService, and make all method and call synchronous. Launch the project => bug "IJSInProcessRuntime not available"

thanks for your help !

Julien

Question: Work with any C# WASM?

Hi,
I'm thinking of trying out Uno Platform and wondered if anyone knew or had tried Blazored LocalStorage with a Uno.Platform project. Going to go give it a shot but figured I'd ask.
Thanks,
Dave G

"JavaScript interop calls cannot be issued at this time" with OnAfterRenderAsync

System.InvalidOperationException: JavaScript interop calls cannot be issued at this time. This is because the component is being prerendered and the page has not yet loaded in the browser or because the circuit is currently disconnected. Components must wrap any JavaScript interop calls in conditional logic to ensure those interop calls are not attempted during prerendering or while the client is disconnected.
   at Microsoft.AspNetCore.Components.Server.Circuits.RemoteJSRuntime.BeginInvokeJS(Int64 asyncHandle, String identifier, String argsJson)
   at Microsoft.JSInterop.JSRuntime.InvokeAsync[TValue](String identifier, CancellationToken cancellationToken, Object[] args)
   at Microsoft.JSInterop.JSRuntime.InvokeWithDefaultCancellation[T](String identifier, Object[] args)
   at Blazored.LocalStorage.LocalStorageService.ContainKeyAsync(String key)
   at FantyAdminWeb.Pages.Index.OnAfterRenderAsync(Boolean firstRender) in C:\git\fanty-tranfer-admin\Pages\Index.razor:line 405
   at Microsoft.AspNetCore.Components.RenderTree.Renderer.GetErrorHandledTask(Task taskToHandle)

My onafterrenderasync tasks:

await localStorage.SetItemAsync("healthcheck", true);
        if (await localStorage.ContainKeyAsync("key"))
        {

            if (!string.IsNullOrEmpty(await localStorage.GetItemAsync<string>("key")))
            {
                amILogged = true;

            }
            if (amILogged)
            {
                try
                {

                    createDepositsChart(DateTime.Now.AddDays(-7), DateTime.Now);
                    createUsersChart(DateTime.Now.AddDays(-7), DateTime.Now);
                    createRegistrationsChart(DateTime.Now.AddDays(-7), DateTime.Now);
                }
                catch (Exception e)
                {
                    Console.WriteLine(e);
                }
            }
        }

create charts methods are using "key" object from localStorage.
Error runs everytime when i go this site. Every GetItem is awaited.

If

                    createDepositsChart(DateTime.Now.AddDays(-7), DateTime.Now);
                    createUsersChart(DateTime.Now.AddDays(-7), DateTime.Now);
                    createRegistrationsChart(DateTime.Now.AddDays(-7), DateTime.Now);

are removed, all runs ok.

2.0.8 contains a tsconfig.json

The NuGet package contains a tsconfig.json that I believe is included by mistake.
The file as far as I can see doesn't do anything and the package does not reference TypeScript as a dependency.
This extra file breaks my projects.

Problems with LocalStoreage bigger than 30 KByte

BlazorApp2.zip

Getting this Exception:

2019-11-11 13:10:06.6198 Error System.Threading.Tasks.TaskCanceledException: A task was canceled.
at Microsoft.JSInterop.JSRuntime.InvokeWithDefaultCancellation[T](String identifier, Object[] args)
at Blazored.LocalStorage.LocalStorageService.GetItemAsync[T](String key)
at PegasusV6.Pages.Components.Basket.BasketComponent.OnAfterRenderAsync(Boolean firstRender) in C:\SOURCE\PegasusV6\PegasusV6\Pages\Components\Basket\BasketComponent.razor:line 88

When try getting LocalStorage that is bigger than 30 KB and I don't have a clue whats the reason...

on LINE:
var data = await LocalStorage.GetItemAsync("Basket");

Is there a known limitation or can I get rid of it anyhow?

SOURCE in a razor file:

protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        My.Log.Info($"BasketComponent.OnAfterRender({firstRender})");

        try
        {
            if (firstRender)
            {
                var data = await LocalStorage.GetItemAsync<BasketState>("Basket");

                // TODO: Fill with correct prices

                if (data != null)
                {
                    AppState.Basket = data;
                    StateHasChanged();
                }

                AppState.Basket.OnBasketChanged -= new Action (async () => await UpdateBasketAtLocalStorage());
                AppState.Basket.OnBasketChanged += new Action (async () => await UpdateBasketAtLocalStorage());
            }
        }
        catch (Exception ex)
        {
            My.Log.Error(ex);
        }
    }

GetItemAsync<string> fails if value doesn't contain quotes

Describe the bug
localStorage.GetItemAsync<**string**>("test") fails if localStorage record looks like this:
test|demoValue

If it is test|"demoValue" then everything works fine.

To Reproduce
Steps to reproduce the behavior:
Just add a new localStorage value manually in browser and try to read it with library. Try changing the value to be with and without quotes.

Expected behavior
Read the value without quotes as string as it's not expected to read our own localStorage values. Sometimes other library writes the values and we should read them.

Hosting Model (is this issue happening with a certain hosting model?):

  • Blazor WebAssembly

Additional context
Using latest library package, 2.1.1

`IJSInProcessRuntime not available` on SetItem in Razor Components

First of all, A little background: This is an app that started as a standalone Blazor project. I later added a Server-Side Blazor project to call into the standalone one (as a way to debug things locally before i publish the standalone one). I upgraded those throughout the Blazor versions and now into 0.9. It's quite likely i am doing something bad/unsupported/broken.

I have a login component that gets injected with ILocalStorageService and attempts to call SetItem on a successful login (<form onsubmit="async () => await DoStuffAndCallStorage()").

This throws an exception as such:

System.InvalidOperationException: IJSInProcessRuntime not available
   at Blazored.LocalStorage.LocalStorageService.Blazored.LocalStorage.ISyncLocalStorageService.GetItem[T](String key)
   at Blazored.LocalStorage.LocalStorageService.GetItem(String key)
   at Blazored.LocalStorage.LocalStorageService.RaiseOnChanging(String key, Object data, ChangingEventArgs& e)
   at Blazored.LocalStorage.LocalStorageService.SetItem(String key, Object data)

This used to work in 1.0.1, But quite a few things changed in this library since. I believe this is related to these changes:

=> ((ISyncLocalStorageService)this).GetItem<object>(key);

.. where the call to GetItem inside the event handler is forcing the usage of the sync version, Which obviously fails if you're in an async scenario/didn't inject the sync version on purpose to begin with.

Inject LocalStorage in C# file

I'm trying to inject LocalStorage in my own Service, sa shown below.
But LocalStorage is still sett to null in the function ConsumeCollection.

The HTTP injection is OK, and injection in a razor file is OK

using System.Net.Http;
using System.Net.Http.Json;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;

using Blazored.LocalStorage;

using Microsoft.AspNetCore.Components;

namespace Padleloggen.Data
{
public class DataService
{
[Inject]
private HttpClient Http { get; set; }

    [Inject]
    private ILocalStorageService LocalStorage { get; set; }

    public DataService(HttpClient hc, ILocalStorageService ls)
    {
        Http = hc;
        LocalStorage = ls;
    }

    public async Task<string> ConsumeCollection<T>(string sUri) where T : IBaseElement, new()
    {
        List<T> collection = await Http.GetFromJsonAsync<List<T>>(sUri);
        string key = typeof(T).Name.ToString() + "List";
        await LocalStorage.SetItemAsync(key, collection);
        return Http == null ? "null" : key;
    }

}
}

Supplementary sample

Great work.

For my own benefit I created an example based on the count page coming with the default template. I thought it might be a simpler and more recognizable alternative sample. Use it if you think it makes sense.

@page "/counter"
@inject LocalStorage localStorage
@implements IDisposable

<h1>Counter</h1>

<p>Current count: @currentCount</p>

<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>

@code {
    private const string key = "count";

    private int currentCount = 0;

    protected override Task OnInitializedAsync()
    {
        localStorage.StorageChanged += StorageChanged;

        currentCount = localStorage.GetItem<int?>(key) ?? 0;

        return base.OnInitializedAsync();
    }

    private void StorageChanged(object sender, StorageEventArgs e)
    {
        if (e.Key == key)
        {
            currentCount = int.Parse(e.OldValue.ToString());
            StateHasChanged();
        }
    }

    private async Task IncrementCount()
    {
        var item = localStorage.GetItem<int?>(key) ?? 0;

        item++;

        currentCount = item;

        await localStorage.SetItemAsync(key, item).ConfigureAwait(false);
    }

    public void Dispose()
    {
        localStorage.StorageChanged -= StorageChanged;
    }
}

NullRefException when using SetItemAsync

I'm getting a NullRef exception on Exception thrown: 'System.NullReferenceException' in System.Private.CoreLib.dll when calling SetItemAsync using the code in the readme. This is server-side blazor 3.0.0 release

Feature Request: GetStorageUsed{Async}

Is there anyway that I can determine how much LocalStorage has been used so far? I have no idea how much data is being stored since it has been Json serialized and is not in its original format.

If this is not possible, then is it a case of catching an 'out-of-memory' exception if I should exceed the amount available?

I would obviously much prefer a GetStorageUsed{Async} method.

Russ Sherlock

Can't consume service in webassembly project after 3.2.0-preview3

Hi Chris, I met a problem after upgrading my project in 3.2.0-preview3 (https://devblogs.microsoft.com/aspnet/blazor-webassembly-3-2-0-preview-3-release-now-available/)

the error i have is

Unhandled exception rendering component: Cannot consume scoped service 'Blazored.LocalStorage.ILocalStorageService' from singleton 'MyService'.

and indeed, i inject your ILocalStorageService in some services inject as singleton.

My project is dual mode serverside/webassembly, and by convention/logic i inject most of my services as singleton in webassembly and scoped in server side.

When i check how you register your service in AddBlazoredLocalStorage, i see you inject it as scoped.

I try to put all my services as scoped in my assembly project (i really don't want to do that, its just for test) and anyway at the end im unable to resolve all the dependency because the ILogger is still a singleton and i don't have control on this.

Microsoft don't really communicate on this update for this preview, but it look like more than a bug resolution than a regression, because for me even with the previous version it was not suppose to work (consume a scoped service from a singleton).

so what do you think about that ?

  • a simple resolution will be add a parameter to AddBlazoredLocalStorage to inject services as singleton if asked. I don't know if it in the good practice.

If you wan't to reproduce :

I wait for your feedback, if you are OK with the resolution i speak a can suggest a PR (upgrading the package + parameter in AddBlazoredLocalStorage to inject services as singleton)

thank you !

Julien

[Bug]

Describe the bug
An app using LocalStorage does not work when deployed to Azure.

To Reproduce
Steps to reproduce the behavior:

  1. Create CustomAuthenticationProviderState with constructor taking ILocalStorageService
    public class CustomAuthenticationStateProvider : AuthenticationStateProvider,
        private ILocalStorageService localStorage;

        public CustomAuthenticationStateProvider(ILocalStorageService _localStorage)
        {
            localStorage = _localStorage;
        }
  1. Register service in your Startup.cs file
services.AddBlazoredLocalStorage();
services.AddScoped<AuthenticationStateProvider, CustomAuthenticationStateProvider>();
  1. Check if it runs locally. Success.

  2. Deplpoy to Azure.

Expected behavior
App is working the same as local.

Actual result
App has errors in console:
blazor.server.js:15 [2020-06-03T08:46:45.405Z] Error: The circuit failed to initialize.
e.log @ blazor.server.js:15
C @ blazor.server.js:8
(anonymous) @ blazor.server.js:8
(anonymous) @ blazor.server.js:1
e.invokeClientMethod @ blazor.server.js:1
e.processIncomingData @ blazor.server.js:1
connection.onreceive @ blazor.server.js:1
(anonymous) @ blazor.server.js:1
(anonymous) @ blazor.server.js:1
(anonymous) @ blazor.server.js:1
a @ blazor.server.js:1
Promise.then (async)
c @ blazor.server.js:1
a @ blazor.server.js:1
Promise.then (async)
c @ blazor.server.js:1
a @ blazor.server.js:1
Promise.then (async)
c @ blazor.server.js:1
a @ blazor.server.js:1
Promise.then (async)
c @ blazor.server.js:1
(anonymous) @ blazor.server.js:1
k @ blazor.server.js:1
e.poll @ blazor.server.js:1
(anonymous) @ blazor.server.js:1
(anonymous) @ blazor.server.js:1
(anonymous) @ blazor.server.js:1
a @ blazor.server.js:1
Promise.then (async)
c @ blazor.server.js:1
a @ blazor.server.js:1
Promise.then (async)
c @ blazor.server.js:1
(anonymous) @ blazor.server.js:1
k @ blazor.server.js:1
e.connect @ blazor.server.js:1
e.startTransport @ blazor.server.js:1
(anonymous) @ blazor.server.js:1
(anonymous) @ blazor.server.js:1
(anonymous) @ blazor.server.js:1
(anonymous) @ blazor.server.js:1
B @ blazor.server.js:1
e.createTransport @ blazor.server.js:1
(anonymous) @ blazor.server.js:1
(anonymous) @ blazor.server.js:1
(anonymous) @ blazor.server.js:1
a @ blazor.server.js:1
Promise.then (async)
c @ blazor.server.js:1
(anonymous) @ blazor.server.js:1
B @ blazor.server.js:1
e.startInternal @ blazor.server.js:1
(anonymous) @ blazor.server.js:1
(anonymous) @ blazor.server.js:1
(anonymous) @ blazor.server.js:1
(anonymous) @ blazor.server.js:1
B @ blazor.server.js:1
e.start @ blazor.server.js:1
(anonymous) @ blazor.server.js:1
(anonymous) @ blazor.server.js:1
(anonymous) @ blazor.server.js:1
(anonymous) @ blazor.server.js:1
v @ blazor.server.js:1
e.startInternal @ blazor.server.js:1
(anonymous) @ blazor.server.js:1
(anonymous) @ blazor.server.js:1
(anonymous) @ blazor.server.js:1
(anonymous) @ blazor.server.js:1
v @ blazor.server.js:1
e.startWithStateTransitions @ blazor.server.js:1
e.start @ blazor.server.js:1
(anonymous) @ blazor.server.js:8
(anonymous) @ blazor.server.js:8
(anonymous) @ blazor.server.js:8
(anonymous) @ blazor.server.js:8
r @ blazor.server.js:8
S @ blazor.server.js:8
(anonymous) @ blazor.server.js:8
(anonymous) @ blazor.server.js:8
(anonymous) @ blazor.server.js:8
(anonymous) @ blazor.server.js:8
r @ blazor.server.js:8
E @ blazor.server.js:8
(anonymous) @ blazor.server.js:8
n @ blazor.server.js:1
(anonymous) @ blazor.server.js:1
(anonymous) @ blazor.server.js:1
Show 39 more frames
blazor.server.js:1 Uncaught (in promise) Error: Invocation canceled due to the underlying connection being closed.
at e.connectionClosed (blazor.server.js:1)
at e.connection.onclose (blazor.server.js:1)
at e.stopConnection (blazor.server.js:1)
at e.transport.onclose (blazor.server.js:1)
at e.raiseOnClose (blazor.server.js:1)
at e. (blazor.server.js:1)
at blazor.server.js:1
at Object.next (blazor.server.js:1)
at a (blazor.server.js:1)

Screenshots
If applicable, add screenshots to help explain your problem.

Hosting Model (is this issue happening with a certain hosting model?):

  • Blazor Server
  • Azure deployment

Additional context
If you remove LocalStorage in your CustomAuthenticationState provider class, then app works.

OnAfterRenderAsync signature was changed in NET Core 3.0 Preview 9

This fails:

protected override async Task OnAfterRenderAsync()
{
    await localStorage.SetItemAsync("name", "John Smith");
    var name = await localStorage.GetItemAsync<string>("name");
}

This works:

protected override async Task OnAfterRenderAsync(bool firstRender)
{
    await localStorage.SetItemAsync("name", "John Smith");
    var name = await localStorage.GetItemAsync<string>("name");
}

Serialization

As far as I can tell you're passing all values during 'Set' through JsonSerializer.ToString

This means it's impossible to use my own serializer as I can't change the serializer settings to use custom types, and I can't pre-serialize and send as a string without it being converted.

Blazor wasm local storage not working when started from VS IDE 16.6.0 Preview 5&6

Breaking change on 16.6.0 preview 5&6

When you start a blazor wasm app from the vs ide, it can't see any local storage values. It can save and then see its own storage values. Then if you close the app & start it again from the ide, the storage data is not there.

If the app is started directly from chrome using localhost address, the local storage works properly.

An issue has been created with the VS team:
dotnet/aspnetcore#21642

Question about Server side use of local storage

I have cloned the Blazored.Gitter repo and am trying to consolidate the SCSS. In trying to run the server side version of the project I receive an error from LocalStorage after entering my API key. It occurs on the source line of await LocalStorage.SetItem("GitterKey", apiKey);

`[2019-04-30T13:56:20.554Z] Error: Microsoft.JSInterop.JSException: Could not find 'Blazored' in 'window'.
d/<@http://localhost:50870/_framework/blazor.server.js:8:20878
d@http://localhost:50870/_framework/blazor.server.js:8:20839
beginInvokeJSFromDotNet/r<@http://localhost:50870/_framework/blazor.server.js:8:21429
beginInvokeJSFromDotNet@http://localhost:50870/_framework/blazor.server.js:8:21403
x</e.prototype.invokeClientMethod/<@http://localhost:50870/_framework/blazor.server.js:1:16651
x</e.prototype.invokeClientMethod@http://localhost:50870/_framework/blazor.server.js:1:16622
x</e.prototype.processIncomingData@http://localhost:50870/_framework/blazor.server.js:1:14619
e/this.connection.onreceive@http://localhost:50870/_framework/blazor.server.js:1:11163
N</e.prototype.connect/</</</i.onmessage@http://localhost:50870/_framework/blazor.server.js:1:30452

at Blazored.LocalStorage.LocalStorageService.GetItem[T](String key)

at Blazored.LocalStorage.LocalStorageService.RaiseOnChangingAsync(String key, Object data)

at Blazored.LocalStorage.LocalStorageService.SetItem(String key, Object data)

at Blazor.Gitter.Core.Components.Shared.AppState.SaveApiKey() in C:\Solutions\TemporarySolutions\Gitter\src\Blazor.Gitter.Core\Components\Shared\AppState.cs:line 115

at Blazor.Gitter.Core.Components.Pages.IndexModel.SignIn(Boolean remember) in C:\Solutions\TemporarySolutions\Gitter\src\Blazor.Gitter.Core\Components\Pages\Index.razor.cs:line 44

at Microsoft.AspNetCore.Components.ComponentBase.CallStateHasChangedOnAsyncCompletion(Task task)

at Microsoft.AspNetCore.Components.Rendering.Renderer.GetErrorHandledTask(Task taskToHandle) blazor.server.js:15:27350
log http://localhost:50870/_framework/blazor.server.js:15
b http://localhost:50870/_framework/blazor.server.js:8
v http://localhost:50870/_framework/blazor.server.js:8
invokeClientMethod http://localhost:50870/_framework/blazor.server.js:1
invokeClientMethod http://localhost:50870/_framework/blazor.server.js:1
processIncomingData http://localhost:50870/_framework/blazor.server.js:1
onreceive http://localhost:50870/_framework/blazor.server.js:1
onmessage http://localhost:50870/_framework/blazor.server.js:1
[2019-04-30T13:56:20.555Z] Information: Connection disconnected. blazor.server.js:1:5137
`

Support JsonIgnore

I'd love to see support for the [JsonIgnore] property attribute in model classes. For example, if you share models between the webassembly and WebAPI projects, there may be properties which are only applicable to the server instance. This would reduce noise when the localstorage is being inspected and also help obfuscate the IA of your codebase.

syntax for a "using" in a c# class

Love the LocalStorage, thank you so much. I would like to use it in a data service class instead of just razor pages. What would be the syntax for putting it in a using?

[Bug] Cant write value with SetItemAsync(), v2.1.4

Describe the bug
When I try to save a jsonToken with ILocalStorageService.SetItemAsync() a exception is thrown (I hope you can see something in the screenshot). My jsonToken was not saved. Trying GetItemAsync<string>. I will stay to v2.1.3, that version is still working.

To Reproduce
Steps to reproduce the behavior:

  1. Update Blazored.LocalStorage to v2.1.4
  2. Try to save a string value
  3. Catch exception
  4. Try read value, see wrong value.

Screenshots
grafik

Hosting Model (is this issue happening with a certain hosting model?):

  • Blazor WebAssembly

Additional context

    <PackageReference Include="Microsoft.AspNetCore.Blazor.HttpClient" Version="3.2.0-preview3.20168.3" />
    <PackageReference Include="Microsoft.AspNetCore.Components.Authorization" Version="3.1.3" />
    <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="3.2.0-preview3.20168.3" />
    <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Build" Version="3.2.0-preview3.20168.3" />
    <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="3.2.0-preview3.20168.3" />

Issue when using Blazored / LocalStorage in Azure function

Hi ,

The exception System.IO.FileNotFoundException: Could not load file or assembly 'System.Text.Json,
happens when using Blazored / LocalStorage in Azure function

here is the stack trace:

Category: Microsoft.AspNetCore.Server.IIS.Core.IISHttpServer
EventId: 2
RequestId: 80001161-0002-f200-b63f-84710c7967bb
RequestPath: /
SpanId: |371e91fa-4fdb7133ba2a2c99.
TraceId: 371e91fa-4fdb7133ba2a2c99
ParentId:
ActionId: 28e03650-b80c-4159-84b1-b8b1302365cb
ActionName: /_Host

Connection ID "17437937767379112288", Request ID "80001161-0002-f200-b63f-84710c7967bb": An unhandled exception was thrown by the application.

Exception:
System.IO.FileNotFoundException: Could not load file or assembly 'System.Text.Json, Version=4.0.1.1, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51'. The system cannot find the file specified.
File name: 'System.Text.Json, Version=4.0.1.1, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51'
at Blazored.LocalStorage.LocalStorageService..ctor(IJSRuntime jSRuntime)
at System.RuntimeMethodHandle.InvokeMethod(Object target, Object[] arguments, Signature sig, Boolean constructor, Boolean wrapExceptions)
at System.Reflection.RuntimeConstructorInfo.Invoke(BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitConstructor(ConstructorCallSite constructorCallSite, RuntimeResolverContext context)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument) at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitCache(ServiceCallSite callSite, RuntimeResolverContext context, ServiceProviderEngineScope serviceProviderEngine, RuntimeResolverLock lockType) at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScopeCache(ServiceCallSite singletonCallSite, RuntimeResolverContext context) at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.Resolve(ServiceCallSite callSite, ServiceProviderEngineScope scope)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.DynamicServiceProviderEngine.<>c__DisplayClass1_0.b__0(ServiceProviderEngineScope scope)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngine.GetService(Type serviceType, ServiceProviderEngineScope serviceProviderEngineScope)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngineScope.GetService(Type serviceType)
at Microsoft.AspNetCore.Components.ComponentFactory.<>c__DisplayClass5_0.g__Initialize|2(IServiceProvider serviceProvider, IComponent component)
at Microsoft.AspNetCore.Components.ComponentFactory.PerformPropertyInjection(IServiceProvider serviceProvider, IComponent instance)
at Microsoft.AspNetCore.Components.ComponentFactory.InstantiateComponent(IServiceProvider serviceProvider, Type componentType)
at Microsoft.AspNetCore.Components.RenderTree.Renderer.InstantiateComponent(Type componentType)
at Microsoft.AspNetCore.Components.RenderTree.Renderer.InstantiateChildComponentOnFrame(RenderTreeFrame& frame, Int32 parentComponentId)
at Microsoft.AspNetCore.Components.RenderTree.RenderTreeDiffBuilder.InitializeNewComponentFrame(DiffContext& diffContext, Int32 frameIndex)
at Microsoft.AspNetCore.Components.RenderTree.RenderTreeDiffBuilder.InitializeNewSubtree(DiffContext& diffContext, Int32 frameIndex)
at Microsoft.AspNetCore.Components.RenderTree.RenderTreeDiffBuilder.InsertNewFrame(DiffContext& diffContext, Int32 newFrameIndex)
at Microsoft.AspNetCore.Components.RenderTree.RenderTreeDiffBuilder.AppendDiffEntriesForRange(DiffContext& diffContext, Int32 oldStartIndex, Int32 oldEndIndexExcl, Int32 newStartIndex, Int32 newEndIndexExcl)
at Microsoft.AspNetCore.Components.RenderTree.RenderTreeDiffBuilder.ComputeDiff(Renderer renderer, RenderBatchBuilder batchBuilder, Int32 componentId, ArrayRange1 oldTree, ArrayRange1 newTree)
at Microsoft.AspNetCore.Components.Rendering.ComponentState.RenderIntoBatch(RenderBatchBuilder batchBuilder, RenderFragment renderFragment)
at Microsoft.AspNetCore.Components.RenderTree.Renderer.RenderInExistingBatch(RenderQueueEntry renderQueueEntry)
at Microsoft.AspNetCore.Components.RenderTree.Renderer.ProcessRenderQueue()
--- End of stack trace from previous location where exception was thrown ---
at Microsoft.AspNetCore.Components.Rendering.HtmlRenderer.HandleException(Exception exception)
at Microsoft.AspNetCore.Components.RenderTree.Renderer.ProcessRenderQueue()
at Microsoft.AspNetCore.Components.RenderTree.Renderer.ProcessPendingRender()
at Microsoft.AspNetCore.Components.RenderTree.Renderer.AddToRenderQueue(Int32 componentId, RenderFragment renderFragment)
at Microsoft.AspNetCore.Components.RenderHandle.Render(RenderFragment renderFragment)
at Microsoft.AspNetCore.Components.ComponentBase.StateHasChanged()
at Microsoft.AspNetCore.Components.ComponentBase.CallOnParametersSetAsync()
at Microsoft.AspNetCore.Components.ComponentBase.RunInitAndSetParametersAsync()
at Microsoft.AspNetCore.Components.Rendering.HtmlRenderer.HandleException(Exception exception)
at Microsoft.AspNetCore.Components.RenderTree.Renderer.AddToPendingTasks(Task task)
at Microsoft.AspNetCore.Components.Rendering.ComponentState.SetDirectParameters(ParameterView parameters)
at Microsoft.AspNetCore.Components.RenderTree.Renderer.RenderRootComponentAsync(Int32 componentId, ParameterView initialParameters)
at Microsoft.AspNetCore.Components.Rendering.HtmlRenderer.CreateInitialRenderAsync(Type componentType, ParameterView initialParameters)
at Microsoft.AspNetCore.Components.Rendering.HtmlRenderer.RenderComponentAsync(Type componentType, ParameterView initialParameters)
at Microsoft.AspNetCore.Components.Rendering.RendererSynchronizationContext.<>c__111.<<InvokeAsync>b__11_0>d.MoveNext() --- End of stack trace from previous location where exception was thrown --- at Microsoft.AspNetCore.Mvc.ViewFeatures.StaticComponentRenderer.PrerenderComponentAsync(ParameterView parameters, HttpContext httpContext, Type componentType) at Microsoft.AspNetCore.Mvc.ViewFeatures.ComponentRenderer.PrerenderedServerComponentAsync(HttpContext context, ServerComponentInvocationSequence invocationId, Type type, ParameterView parametersCollection) at Microsoft.AspNetCore.Mvc.ViewFeatures.ComponentRenderer.RenderComponentAsync(ViewContext viewContext, Type componentType, RenderMode renderMode, Object parameters) at Microsoft.AspNetCore.Mvc.TagHelpers.ComponentTagHelper.ProcessAsync(TagHelperContext context, TagHelperOutput output) at Microsoft.AspNetCore.Razor.Runtime.TagHelpers.TagHelperRunner.<RunAsync>g__Awaited|0_0(Task task, TagHelperExecutionContext executionContext, Int32 i, Int32 count) at ALSearch.Pages.Pages__Host.<ExecuteAsync>b__14_1() in C:\dev\ALSearch\ALSearch\ALSearch\Pages\_Host.cshtml:line 34 at Microsoft.AspNetCore.Razor.Runtime.TagHelpers.TagHelperExecutionContext.SetOutputContentAsync() at ALSearch.Pages.Pages__Host.ExecuteAsync() in C:\dev\ALSearch\ALSearch\ALSearch\Pages\_Host.cshtml:line 5 at Microsoft.AspNetCore.Mvc.Razor.RazorView.RenderPageCoreAsync(IRazorPage page, ViewContext context) at Microsoft.AspNetCore.Mvc.Razor.RazorView.RenderPageAsync(IRazorPage page, ViewContext context, Boolean invokeViewStarts) at Microsoft.AspNetCore.Mvc.Razor.RazorView.RenderAsync(ViewContext context) at Microsoft.AspNetCore.Mvc.ViewFeatures.ViewExecutor.ExecuteAsync(ViewContext viewContext, String contentType, Nullable1 statusCode)
at Microsoft.AspNetCore.Mvc.ViewFeatures.ViewExecutor.ExecuteAsync(ViewContext viewContext, String contentType, Nullable1 statusCode) at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeNextResultFilterAsync>g__Awaited|29_0[TFilter,TFilterAsync](ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted) at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResultExecutedContextSealed context) at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.ResultNext[TFilter,TFilterAsync](State& next, Scope& scope, Object& state, Boolean& isCompleted) at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.InvokeResultFilters() --- End of stack trace from previous location where exception was thrown --- at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeNextResourceFilter>g__Awaited|24_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted) at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResourceExecutedContextSealed context) at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted) at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.InvokeFilterPipelineAsync() --- End of stack trace from previous location where exception was thrown --- at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope) at Microsoft.AspNetCore.Routing.EndpointMiddleware.<Invoke>g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger) at Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware.<Invoke>g__Awaited|6_0(ExceptionHandlerMiddleware middleware, HttpContext context, Task task) at Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware.HandleException(HttpContext context, ExceptionDispatchInfo edi) at Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware.<Invoke>g__Awaited|6_0(ExceptionHandlerMiddleware middleware, HttpContext context, Task task) at Microsoft.AspNetCore.Server.IIS.Core.IISHttpContextOfT1.ProcessRequestAsync()

add Exist Method

Hi, and thanks for your work !
can you add an "exist" method on your library ?
it's normally not so difficult, something like that

public async Task<bool> Exist(string key)
        {
            if (string.IsNullOrEmpty(key))
                throw new ArgumentNullException(nameof(key));

            var serialisedData = await _jSRuntime.InvokeAsync<string>("localStorage.getItem", key);

            return serialisedData != null;
        }

thanks !

Julien

`SetItem` (async) doesn't appear to trigger `window.onstorage`

Given a component with this:

    private bool DarkMode { get; set; }

    public async void ToggleDarkMode(UIChangeEventArgs e)
    {
        await _SetDarkMode(!DarkMode);
    }

    private async Task _SetDarkMode(bool darkMode)
    {
        DarkMode = darkMode;

        await localStorage.SetItem("DarkMode", DarkMode);

        StateHasChanged();

        Console.WriteLine($"DarkMode is now: {DarkMode}");
    }

And an index.html snippet like this:

    <script type="text/javascript">
window.onstorage = function (ev) {
    console.log(ev.key);
};
    </script>
  • Invoking _SetDarkMode does show up/toggle the value in Chrome's Application tab. However, the onstorage event never fires that way.
  • OTOH, if I manually toggle the value from Chrome, the event does fire.

So I'm wondering if Chrome does something else to 'commit' the value (there doesn't appear to be a function like refresh() or save()), or if this is perhaps a side effect of the code running in an async or wasm context.

Try Catch methods Like GetItemAsync

Otherwise you will get complete shutdown of the page if a JavaScript call fails.

You can see here a sample stack trace of my error:

Diese Ausnahme wurde ursprünglich bei dieser Aufrufliste ausgelöst:
System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(System.Threading.Tasks.Task)
System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(System.Threading.Tasks.Task)
System.Threading.Tasks.ValueTask.Result.get()
Microsoft.JSInterop.JSRuntime.InvokeWithDefaultCancellation(string, object[])
System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(System.Threading.Tasks.Task)
System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(System.Threading.Tasks.Task)
Blazored.LocalStorage.LocalStorageService.GetItemAsync(string)
System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(System.Threading.Tasks.Task)
...

CSB Javascript Not Found

After updating my CSB app to v2.0.6 for Preview 7 I started seeing an issue on startup...

Microsoft.JSInterop.JSException: Could not find 'LocalStorage' in 'window.Blazored'.

Adding the script tag to my index.html, re the instructions for SSB, resolved the issue.

Blazor Preview 3 Breaking Change [Bug]

Describe the bug
Upgrading a Blazor project with LocalStorage from preview 2 to preview 3, specifically Microsoft.AspNetCore.Components.WebAssembly 3.2.0-preview2.20160.5 to 3.2.0-preview3.20168.3 breaks Local Storage. This seems to be a services issue.

crit: Microsoft.AspNetCore.Components.WebAssembly.Rendering.WebAssemblyRenderer[100] blazor.webassembly.js:1:36074
Unhandled exception rendering component: Cannot consume scoped service 'Blazored.LocalStorage.ILocalStorageService' from singleton {nameOfMySingleton}

To Reproduce

  1. Create a Blazor preview 3 project using the template (WebAssembly ASP NET hosted)
  2. Implement Blazored.LocalStorage (I tried v2.1.1 and 2.5.0)
  3. See error

[Bug] Dependency About System.Text.Json

Describe the bug
If I create a brand new project and then install Blazored.LocalStorage and start to use it straight away, it will throw an exception about File not found: System.Text.Json

To Reproduce
Steps to reproduce the behavior:

  1. Create a new Blazor project
  2. Install Blazored.LocalStorage
  3. Create a service to manage local storage
  4. Configure the service in Startup.cs (services.AddBlazoredLocalStorage(); and services.AddScoped<YourService>();)
  5. Inject the service to a page
  6. Run your application
  7. Exception

Expected behavior
I think it should be compatible with the library within .Net Core or it should install the dependency automatically. (I'm not sure if System.Text.Json is preinstalled but I can using it. If I was wrong feel free to tell me!)

Screenshots
If I do not install System.Text.Json here's what I get
image

Hosting Model (is this issue happening with a certain hosting model?):
I only tried:

  • Blazor Server

Additional context
I'm using:

  • Microsoft Visual Studio Community 2019 version 16.4.5
  • .NET Core 3.1

GetItem doesn't restore properties with a private setter

I tested int and string properties. The values in setTest exist in local storage but the default values are assigned to var getTest. If I make the setter public it works.

var setTest = new Test();
setTest.SetValues(2, "changed");

Console.WriteLine($"setTest: {setTest.IntValue} {setTest.StringValue}");
await LocalStorage.SetItemAsync("test", setTest);

var getTest = await LocalStorage.GetItemAsync("test");
Console.WriteLine($"getTest: {getTest.IntValue} {getTest.StringValue}");

public class Test
{
public Int32 IntValue { get; private set; } = 1;
public String StringValue { get; private set; } = "default";

    public void SetValues(Int32 intValue, String stringValue)
    {
        IntValue = intValue;
        StringValue = stringValue;
    }
}

[Bug] Changed event doesn't fire across tabs

Describe the bug
The change event doesn't seem to fire across tabs as described. Even the sample client app doesn't work for me in both Chrome and Edge (chromium).

To Reproduce
Steps to reproduce the behavior:

  1. Load the sample client app and run
  2. Duplicate or open a new tab
  3. In tab 1 set a value
  4. Observe no changes in tab 2.

Expected behavior
I was hoping the event would fire and update each tab.

Hosting Model (is this issue happening with a certain hosting model?):

  • Blazor WebAssembly

blazored-localstorage.js

With the preview 8 update, adding <script src="_content/Blazored.LocalStorage/blazored-localstorage.js"></script> did not work. The browser reports this file cannot be found. As a workaround, I manually added blazored-localstorage.js to my client project and changed the script tag in index.html to point to that.

SetItem/GetItem do not appear to respect [NonSerialized] decorator

I have a class that has a property that is of an interface type. I decorated that property with [field:NonSerialized], but localStorage.SetItem serializes it as an empty set, and attempting to use GetItem throws an error: System.NotSupportedException: Deserialization of interface types is not supported. Type 'Blazored.LocalStorage.ISyncLocalStorageService'

Timespan serialization support

Hello Chris, I see there is an issue with timespan and the new JsonSerializer .NET (see https://github.com/dotnet/corefx/issues/38641)
Indeed, with the actual code, if I set a timespan item, it write this string representation hh:mm:ss
If i try to read this timespan with GetItem, i get an empty Timepsan wathever the value (without exception)

I have a PR ready for that wich add a JsonConverter dedicated to the timespan. It write the timespan at the standard string representation (d.hh:mm:ss:FFF) and read it with the same format.
It works well, but i have some interrogations on the behavior when the string is not in correct format (for example, a timespan store with only hh:mm:ss, in cas the value was not written with this library). There is few solutions:

  • I launch an exception like format Exception explaining the entry is not at a correct timespan format
  • i hide the problem and return a zero timespan (not a fan about this one)
  • i try different format (for example try to parse with standard format, if not works with the hh:mm:ss format, etc). At the end return exception or zero timespan if not works.

What do you think about that ?

thanks !

Julien

Why local storage is empty after refresh the page

Hi Team , I am trying to use local storage in my demo application and successfully implemented as you suggested but when i refresh the page local storage is getting empty.
Could you please suggest?

[Feature Request] Implement IEnumerable or an alternative way to list keys

Hi, I face a problem using the library. Would it be possible to implement the feature presented below?

Is your feature request related to a problem? Please describe.
The interface ILocalStorageService does not provide any way to lists the key contained in local storage. In Javascript the solution is to iterate on the localStorage object.

This is however not implemented here and not alternative way is provided. Consequently, it's necessary to manually use the local storage to bookkeep the list of key, which is somewhat cumbersome.

Describe the solution you'd like
Implementation of the relevant interfaces (IEnumerable, IEnumerator) would allow consumers to use the same pattern as in Javascript to list keys present in local storage.

Describe alternatives you've considered
I've considered manually use the local storage to bookkeep the list of key.

Additional context
Keys are generated dynamically based on current time.

Data not saved on IIS server

Describe the bug
For some reason data isn't over written when app is on IIS, when I run it on my visual studio, works perfect. See below code:

To Reproduce
On one page, this saves my user's data when logged in:
` async Task saveUID()
{
Users user = await Users.loginUser(Username, Password);

    await localStorage.SetItemAsync("uid", user.id);
    await localStorage.SetItemAsync("first_name", user.firstname);
    await localStorage.SetItemAsync("userdata", user);
}

`
Then I go to user profile page, with following code:

protected override async Task OnInitializedAsync() { uid = await localStorage.GetItemAsync<int>("uid"); first_name = await localStorage.GetItemAsync<string>("first_name"); user = await Users.loginUser("", "", uid); }
Login with one user, read data, save new user data and try to read it.

Expected behavior
It should get data from localstorage, and as I said, when I run it in Visual Studio it works spot on, when I transfer it to IIS server, doesn't and

Hosting Model (is this issue happening with a certain hosting model?):

  • Blazor Server
  • IIS on Windows Server

Storage event handling (question and discussion)

@chrissainty Chris,
The 'storage' event notifies only the non-active code on a change that happened in other tab/window. Also the event cannot be cancelled (speaking in JS native).
I was wondering, if you're interested in aligning the implementation to native feature of storage object. Here is possible implementation that removes before/after change event listeners, uses single storage event.
Thanks!

diff --git a/samples/BlazorSample/Pages/Index.cshtml b/samples/BlazorSample/Pages/Index.cshtml
index 5f990be..26646b2 100644
--- a/samples/BlazorSample/Pages/Index.cshtml
+++ b/samples/BlazorSample/Pages/Index.cshtml
@@ -56,8 +56,7 @@ Welcome to your new app.
         await GetNameFromLocalStorage();
         await GetLocalStorageLength();
 
-        localStorage.Changed += (sender, e) =>
-        {
+        localStorage.StorageChanged += (sender, e) => {
             Console.WriteLine($"Value for key {e.Key} changed from {e.OldValue} to {e.NewValue}");
         };
     }
diff --git a/samples/BlazorSample/Pages/Sync.cshtml b/samples/BlazorSample/Pages/Sync.cshtml
index 59fb3e6..4160259 100644
--- a/samples/BlazorSample/Pages/Sync.cshtml
+++ b/samples/BlazorSample/Pages/Sync.cshtml
@@ -50,7 +50,7 @@
         GetNameFromLocalStorage();
         GetLocalStorageLength();
 
-        localStorage.Changed += (sender, e) =>
+        localStorage.StorageChanged += (sender, e) =>
         {
             Console.WriteLine($"Value for key {e.Key} changed from {e.OldValue} to {e.NewValue}");
         };
diff --git a/src/Blazored.LocalStorage/ChangingEventArgs.cs b/src/Blazored.LocalStorage/ChangingEventArgs.cs
deleted file mode 100644
index 63616f9..0000000
--- a/src/Blazored.LocalStorage/ChangingEventArgs.cs
+++ /dev/null
@@ -1,9 +0,0 @@
-using System;
-
-namespace Blazored.LocalStorage
-{
-    public class ChangingEventArgs : ChangedEventArgs
-    {
-        public bool Cancel { get; set; }
-    }
-}
diff --git a/src/Blazored.LocalStorage/ILocalStorageService.cs b/src/Blazored.LocalStorage/ILocalStorageService.cs
index 16ddbaa..acc1d7f 100644
--- a/src/Blazored.LocalStorage/ILocalStorageService.cs
+++ b/src/Blazored.LocalStorage/ILocalStorageService.cs
@@ -16,8 +16,6 @@ namespace Blazored.LocalStorage
         Task RemoveItemAsync(string key);
 
         Task SetItemAsync(string key, object data);
-
-        event EventHandler<ChangingEventArgs> Changing;
-        event EventHandler<ChangedEventArgs> Changed;
+        event EventHandler<StorageEventArgs> StorageChanged;
     }
 }
diff --git a/src/Blazored.LocalStorage/ISyncLocalStorageService.cs b/src/Blazored.LocalStorage/ISyncLocalStorageService.cs
index 0aa0f81..f36bf95 100644
--- a/src/Blazored.LocalStorage/ISyncLocalStorageService.cs
+++ b/src/Blazored.LocalStorage/ISyncLocalStorageService.cs
@@ -16,7 +16,6 @@ namespace Blazored.LocalStorage
 
         void SetItem(string key, object data);
 
-        event EventHandler<ChangingEventArgs> Changing;
-        event EventHandler<ChangedEventArgs> Changed;
+        event EventHandler<StorageEventArgs> StorageChanged;
     }
 }
\ No newline at end of file
diff --git a/src/Blazored.LocalStorage/LocalStorageService.cs b/src/Blazored.LocalStorage/LocalStorageService.cs
index d9b7ca7..47d97d3 100644
--- a/src/Blazored.LocalStorage/LocalStorageService.cs
+++ b/src/Blazored.LocalStorage/LocalStorageService.cs
@@ -20,14 +20,7 @@ namespace Blazored.LocalStorage
             if (string.IsNullOrEmpty(key))
                 throw new ArgumentNullException(nameof(key));
 
-            var e = await RaiseOnChangingAsync(key, data);
-
-            if (e.Cancel)
-                return;
-
             await _jSRuntime.InvokeAsync<object>("Blazored.LocalStorage.SetItem", key, Json.Serialize(data));
-
-            RaiseOnChanged(key, e.OldValue, data);
         }
 
         public async Task<T> GetItemAsync<T>(string key)
@@ -64,14 +57,7 @@ namespace Blazored.LocalStorage
             if (_jSInProcessRuntime == null)
                 throw new InvalidOperationException("IJSInProcessRuntime not available");
 
-            var e = RaiseOnChangingSync(key, data);
-
-            if (e.Cancel)
-                return;
-
             _jSInProcessRuntime.Invoke<object>("Blazored.LocalStorage.SetItem", key, Json.Serialize(data));
-
-            RaiseOnChanged(key, e.OldValue, data);
         }
 
         T ISyncLocalStorageService.GetItem<T>(string key)
@@ -123,46 +109,48 @@ namespace Blazored.LocalStorage
             return _jSInProcessRuntime.Invoke<string>("Blazored.LocalStorage.Key", index);
         }
 
-        public event EventHandler<ChangingEventArgs> Changing;
-        private async Task<ChangingEventArgs> RaiseOnChangingAsync(string key, object data)
+        private EventHandler<StorageEventArgs> _storageChanged;
+        public event EventHandler<StorageEventArgs> StorageChanged
         {
-            var e = new ChangingEventArgs
+            add
             {
-                Key = key,
-                OldValue = await GetItemAsync<object>(key),
-                NewValue = data
-            };
-
-            Changing?.Invoke(this, e);
-
-            return e;
-        }
-
-        private ChangingEventArgs RaiseOnChangingSync(string key, object data)
-        {
-            var e = new ChangingEventArgs
+                if (_storageChanged == null)
+                {
+                    this._jSInProcessRuntime.InvokeAsync<object>(
+                        "Blazored.LocalStorage.AddEventListener",
+                        new DotNetObjectRef(this)
+                    );
+                }
+                _storageChanged += value;
+            }
+            remove
             {
-                Key = key,
-                OldValue = ((ISyncLocalStorageService)this).GetItem<object>(key),
-                NewValue = data
-            };
-
-            Changing?.Invoke(this, e);
-
-            return e;
+                _storageChanged -= value;
+                if (_storageChanged == null)
+                {
+                    this._jSInProcessRuntime.InvokeAsync<object>("Blazored.LocalStorage.RemoveEventListener");
+                }
+            }
         }
 
-        public event EventHandler<ChangedEventArgs> Changed;
-        private void RaiseOnChanged(string key, object oldValue, object data)
+        [JSInvokable]
+        public virtual void OnStorageChanged(string key, object oldValue, object newValue)
         {
-            var e = new ChangedEventArgs
+            EventHandler<StorageEventArgs> handler = _storageChanged;
+            if (handler != null)
             {
-                Key = key,
-                OldValue = oldValue,
-                NewValue = data
-            };
-
-            Changed?.Invoke(this, e);
+                handler(this, new StorageEventArgs
+                {
+                    Key = key,
+                    OldValue = oldValue,
+                    NewValue = newValue,
+                });
+            }
         }
     }
-}
+}
\ No newline at end of file
diff --git a/src/Blazored.LocalStorage/ChangedEventArgs.cs b/src/Blazored.LocalStorage/StorageEventArgs.cs
similarity index 51%
rename from src/Blazored.LocalStorage/ChangedEventArgs.cs
rename to src/Blazored.LocalStorage/StorageEventArgs.cs
index e3cffbf..f27d5eb 100644
--- a/src/Blazored.LocalStorage/ChangedEventArgs.cs
+++ b/src/Blazored.LocalStorage/StorageEventArgs.cs
@@ -1,11 +1,11 @@
-namespace Blazored.LocalStorage
+
+using System;
+
+namespace Blazored.LocalStorage
 {
-    public class ChangedEventArgs
+    public class StorageEventArgs : EventArgs
     {
         public string Key { get; set; }
-        
-        // NOTE: can't easily make event handlers generic
-
         public object OldValue { get; set; }
         public object NewValue { get; set; }
     }
diff --git a/src/Blazored.LocalStorage/content/blazored.LocalStorage.ts b/src/Blazored.LocalStorage/content/blazored.LocalStorage.ts
index 2394ab5..cd26ea7 100644
--- a/src/Blazored.LocalStorage/content/blazored.LocalStorage.ts
+++ b/src/Blazored.LocalStorage/content/blazored.LocalStorage.ts
@@ -1,46 +1,77 @@
 namespace Blazored.LocalStorage {
-    class LocalStorage {
-        public SetItem(key: string, data: string): void {
-            window.localStorage.setItem(key, data);
-        }
-
-        public GetItem(key: string): string {
-            return window.localStorage.getItem(key);
-        }
-
-        public RemoveItem(key: string): void {
-            window.localStorage.removeItem(key);
-        }
-
-        public Clear(): void {
-            window.localStorage.clear();
-        }
-
-        public Length(): number {
-            return window.localStorage.length;
-        }
-
-        public Key(index: number): string {
-            return window.localStorage.key(index);
-        }
-    }
-
-    export function Load(): void {
-        const localStorage = {
-            LocalStorage: new LocalStorage()
-        };
-
-        if (window['Blazored']) {
-            window['Blazored'] = {
-                ...window['Blazored'],
-                ...localStorage
-            }
-        } else {
-            window['Blazored'] = {
-                ...localStorage
-            }
-        }
+  class LocalStorage {
+    private instance: any | undefined = undefined;
+
+    constructor() {
+      this.OnStorageEvent = this.OnStorageEvent.bind(this);
+      this.AddEventListener = this.AddEventListener.bind(this);
+    }
+
+    SetItem(key: string, data: string): void {
+      window.localStorage.setItem(key, data);
+    }
+
+    GetItem(key: string): string | null {
+      return window.localStorage.getItem(key);
+    }
+
+    RemoveItem(key: string): void {
+      window.localStorage.removeItem(key);
+    }
+
+    Clear(): void {
+      window.localStorage.clear();
+    }
+
+    Length(): number {
+      return window.localStorage.length;
+    }
+
+    Key(index: number): string | null {
+      return window.localStorage.key(index);
+    }
+
+    OnStorageEvent(e: StorageEvent) {
+      if (!this.instance) return;
+      this.instance.invokeMethodAsync(
+        "OnStorageChanged",
+        e.key,
+        e.newValue,
+        e.oldValue
+      );
+    }
+
+    AddEventListener(instance: any): void {
+      window.removeEventListener("storage", this.OnStorageEvent, false);
+      this.instance = instance;
+      window.addEventListener("storage", this.OnStorageEvent, false);
+    }
+
+    RemoveEventListener(): void {
+      window.removeEventListener("storage", this.OnStorageEvent, false);
+    }
+  }
+
+  export function Load(): void {
+    const localStorage = {
+      LocalStorage: new LocalStorage()
+    };
+
+    if (window["Blazored"]) {
+      window["Blazored"] = {
+        ...window["Blazored"],
+        ...localStorage
+      };
+    } else {
+      window["Blazored"] = {
+        ...localStorage
+      };
     }
+  }
+}
+
+interface Window {
+  [key: string]: any;
 }
 
 Blazored.LocalStorage.Load();

[Question] getting Error : Unable to resolve service for type 'Blazored.LocalStorage.LocalStorageService'

Please be sure to check the readme file before raising an issue.
I am trying to use Blazor storage library in Blazor Hosted WebAssembly.

public class Program
   {
       public static async Task Main(string[] args)
       {
           var builder = WebAssemblyHostBuilder.CreateDefault(args);
           builder.RootComponents.Add<App>("app");

           builder.Services.AddAuthorizationCore();
           
           builder.Services.AddTransient(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });

          
           builder.Services.AddBlazoredLocalStorage();

           builder.Services.AddBlazoredSessionStorage();

           builder.Services.AddScoped<AppStateInfo,AppStateInfo>();
           builder.Services.AddScoped<CustomAuthenticationStateProvider>();
           builder.Services.AddScoped<AuthenticationStateProvider>(provider =>
                 provider.GetRequiredService<CustomAuthenticationStateProvider>());
                     
           builder.Services.AddOptions();
           var host = builder.Build();
      
           await host.RunAsync();
       }
   }

Error i get is as follow
Unhandled exception rendering component: Unable to resolve service for type 'Blazored.LocalStorage.LocalStorageService' while attempting to activate 'myCustomApp.Services.CustomAuthenticationStateProvider'.
System.InvalidOperationException: Unable to resolve service for type 'Blazored.LocalStorage.LocalStorageService' while attempting to activate 'myCustomApp.CustomAuthenticationStateProvider'.

My Constructor looks like this and class is in the Blazor Client Project

public CustomAuthenticationStateProvider(Blazored.LocalStorage.LocalStorageService LocalStorage,
          Blazored.SessionStorage.SessionStorageService SessionStorage, AppStateInfo AppStateInfo, HttpClient Http)
      {
          _localStorage = LocalStorage;
          _SessionStorage = SessionStorage;
          _appStateInfo = AppStateInfo;
          _Http = Http;

      }

[Question] Serve side : random behavior

Hi,

I have a strange situation when the GetItemAsync works for 10% of the cases!

It is a server-side blazor and it is called in OnAfterRenderAsync. The browser freeze for some time and display this message "attempting to reconnect to the server", then I got an empty result!

Here is my code:

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        await base.OnAfterRenderAsync(firstRender);
        try
        {
            /*if (firstRender)*/
                _imageData = await _localStorage.GetItemAsync<string>("editor.imageData");
        }
        catch (Exception e)
        {
            Console.WriteLine(e.Message);
        }
    }

Do you have any idea ? Its weird :-P

Serialize class to local storage

I have a class I would like to store:

        public class MyData
        {
            public string name;
            public string address;
            public int age;

            public MyData()
            {
                name = "name";
                address = "address";
                age = 50;
            }
        }

        MyData mydata = new MyData();

to store it, I need to serilaize to string:
await localStorage.SetItemAsync("mydata",JsonConvert.SerializeObject(mydata));
this is fine, it works ok.
but when I read it back as a string, I get an error

        try{ 
        string text = await localStorage.GetItemAsync<string>("mydata");
        }
    catch (Exception ex)
    {
        exstring = ex.Message;
       return;
    }

The JSON value could not be converted to System.String. Path: $ | LineNumber: 0 | BytePositionInLine: 1. address name

Is their anyway I can read the item back as a string and deserialize myself ?

[Bug-Regression] problem with integer serialization

Hello Chris, since the last version it seem there is a bug on the integer serialisation.
When i try to retieve the value, i have this exception (at leats on server side project)
Unable to cast object of type 'System.String' to type 'System.Nullable`1[System.Int32]'.'

To reproduce this (on your sample project for example)

<div class="row">
    <div class="col-md-4">
         <button class="btn btn-primary" @onclick="SaveInteger">set 5 in local storage</button>
    </div>
    <div class="col-md-4">
        <button class="btn btn-primary" @onclick="LoadInteger">read 5 in local storage</button>
    </div>

    <div class="col-md-4">
        <h5>Value in local storage</h5>
        @IntegerValue
    </div>
</div>
int? IntegerValue;
    async Task SaveInteger()
    {
        await localStorage.SetItemAsync("integerValue", 5);
    }
    async Task LoadInteger()
    {
        IntegerValue = await localStorage.GetItemAsync<int?>("integerValue");
        this.StateHasChanged();
    }
  • click on the button to save the value
  • click on the button to load the value => you will have the error (put "detailedErrors": true in the appsettings to see them)

its the same thing if the integer is not nullable

thanks !

[Bug] Missing Dependency on System.Text.Json

Describe the bug
Either the docs or the nuget package, is missing a dependency on System.Text.Json. The app will crash if I don't add the nuget package manually.

To Reproduce
Steps to reproduce the behavior:

  1. Create new blazor server template.
  2. Follow steps on docs to get setup and set a value in local storage. Btw, the docs still have the @function directive instead of @code
  3. You get an error as soon as the first call to local storage happens.

Expected behavior
Preferably, this package should pull in System.Text.Json automatically.

Hosting Model (is this issue happening with a certain hosting model?):

  • Blazor Server

NullReferenceException: Object reference not set to an instance of an object.

I am experimenting a little bit with blazor in my free time right now. Then I wanted to try out a blazor local storage and found this one and this one. I tryed the blazored local storage first out. And well there I get an error when I want to get an Item async.

image

So what I think is that the blazored local storage cannot acces the local storage of the browser.

Here is my Code:

@page "/counter"
@inject ILocalStorageService localStore

@if (currentCount.HasValue)
{
    <p>Current count: <strong>@currentCount</strong></p>

    <button @onclick="IncrementCount">Increment</button>
}
else
{
    <p>Loading...</p>
}

@code {
    private int? currentCount;

    protected override async Task OnInitializedAsync()
    {
        currentCount = await localStore.GetItemAsync<int>("currentCount");
    }

    private async Task IncrementCount()
    {
        currentCount++;
        await localStore.SetItemAsync("currentCount", currentCount);
    }
}

I have read through the documentation and made the same steps. How can I fix this?

Update

So I just found out, when I start the app and after that navigate to "/counter" it works, but when I reload it then it doesn't work anymore. When I change the route then to de default and then navigate again to "/counter" it works again.

Update 2

So I found an issue with the same problem. I changed the Task to OnAfterRenderAsync(bool firstRender), but now the only thing I see is "Loading ..."

.net core 3 preview 7. Could not load type 'System.Text.Json.Serialization.JsonSerializer' from assembly 'System.Text.Json'

Using .net core 3 preview 7. Getting exception:

System.TypeLoadException: Could not load type 'System.Text.Json.Serialization.JsonSerializer' from assembly 'System.Text.Json, Version=4.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51'.
   at async Task<T> Blazored.LocalStorage.LocalStorageService.GetItemAsync<T>(string key)
   at void System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start<TStateMachine>(ref TStateMachine stateMachine)
   at void System.Runtime.CompilerServices.AsyncTaskMethodBuilder<TResult>.Start<TStateMachine>(ref TStateMachine stateMachine)
   at Task<T> Blazored.LocalStorage.LocalStorageService.GetItemAsync<T>(string key)

Does not work width 'Console.WriteLine'

Console.WriteLine($"Value for key {e.Key} changed from {e.OldValue} to {e.NewValue}");
The above code output statement does not seem to work, and does not output data in the console.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.