diff --git a/README.md b/README.md index aedfe9d..fa8fafb 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ A library to provide access to session storage in Blazor applications [![Build Status](https://dev.azure.com/blazored/SessionStorage/_apis/build/status/Blazored.SessionStorage?branchName=master)](https://dev.azure.com/blazored/SessionStorage/_build/latest?definitionId=1&branchName=master) -![Nuget](https://img.shields.io/nuget/v/blazored.sessionstorage.svg) +[![Nuget](https://img.shields.io/nuget/v/blazored.sessionstorage.svg)](https://www.nuget.org/packages/Blazored.SessionStorage/) ### Installing @@ -11,11 +11,11 @@ You can install from NuGet using the following command: `Install-Package Blazored.SessionStorage` -Or via the Visual Studio package manger. +Or via the Visual Studio package manager. ### Setup -You will need to register the session storage services with the service collection in your _startup.cs_ file. +You will need to register the session storage services with the service collection in your _Startup.cs_ file in Blazor Server. ```c# public void ConfigureServices(IServiceCollection services) @@ -24,6 +24,47 @@ public void ConfigureServices(IServiceCollection services) } ``` +Or in your _Program.cs_ file in Blazor WebAssembly. + +```c# +public static async Task Main(string[] args) +{ + var builder = WebAssemblyHostBuilder.CreateDefault(args); + builder.RootComponents.Add("app"); + + builder.Services.AddBlazoredSessionStorage(); + + await builder.Build().RunAsync(); +} +``` + +### Configuration + +The session storage provides options that can be modified by you at registration in your _Startup.cs_ file in Blazor Server. + + +```c# +public void ConfigureServices(IServiceCollection services) +{ + services.AddBlazoredSessionStorage(config => + config.JsonSerializerOptions.WriteIndented = true); +} +``` +Or in your _Program.cs_ file in Blazor WebAssembly. + +```c# +public static async Task Main(string[] args) +{ + var builder = WebAssemblyHostBuilder.CreateDefault(args); + builder.RootComponents.Add("app"); + + builder.Services.AddBlazoredSessionStorage(config => + config.JsonSerializerOptions.WriteIndented = true); + + await builder.Build().RunAsync(); +} +``` + ### Usage (Blazor WebAssembly) To use Blazored.SessionStorage in Blazor WebAssembly, inject the `ISessionStorageService` per the example below. @@ -64,9 +105,9 @@ With Blazor WebAssembly you also have the option of a synchronous API, if your u ```c# @inject Blazored.SessionStorage.ISessionStorageService sessionStorage -@functions { +@code { - protected override async Task OnAfterRenderAsync() + protected override async Task OnAfterRenderAsync(bool firstRender) { await sessionStorage.SetItemAsync("name", "John Smith"); var name = await sessionStorage.GetItemAsync("name"); @@ -84,12 +125,15 @@ The APIs available are: - ClearAsync() - LengthAsync() - KeyAsync() -- synchronous via `ISyncSessionStorageService`: + - ContainsKeyAsync() + +- synchronous via `ISyncSessionStorageService` (Synchronous methods are **only** available in Blazor WebAssembly): - SetItem() - GetItem() - RemoveItem() - Clear() - Length() - Key() + - ContainsKey() **Note:** Blazored.SessionStorage methods will handle the serialisation and de-serialisation of the data for you. diff --git a/src/Blazored.SessionStorage/Blazored.SessionStorage.csproj b/src/Blazored.SessionStorage/Blazored.SessionStorage.csproj index 35c85c1..c6c6963 100644 --- a/src/Blazored.SessionStorage/Blazored.SessionStorage.csproj +++ b/src/Blazored.SessionStorage/Blazored.SessionStorage.csproj @@ -4,7 +4,6 @@ netstandard2.0 3.0 Blazored.SessionStorage - 3.2 Blazored.SessionStorage 1.0.11 @@ -18,10 +17,10 @@ Blazor SessionStorage Blazored Razor Components - + - - + + diff --git a/src/Blazored.SessionStorage/ISessionStorageService.cs b/src/Blazored.SessionStorage/ISessionStorageService.cs index f8d85cd..97724f7 100644 --- a/src/Blazored.SessionStorage/ISessionStorageService.cs +++ b/src/Blazored.SessionStorage/ISessionStorageService.cs @@ -15,7 +15,7 @@ public interface ISessionStorageService Task RemoveItemAsync(string key); - Task SetItemAsync(string key, object data); + Task SetItemAsync(string key, T data); event EventHandler Changing; event EventHandler Changed; diff --git a/src/Blazored.SessionStorage/ISyncSessionStorageService.cs b/src/Blazored.SessionStorage/ISyncSessionStorageService.cs index b85ee84..a994f6a 100644 --- a/src/Blazored.SessionStorage/ISyncSessionStorageService.cs +++ b/src/Blazored.SessionStorage/ISyncSessionStorageService.cs @@ -14,7 +14,7 @@ public interface ISyncSessionStorageService void RemoveItem(string key); - void SetItem(string key, object data); + void SetItem(string key, T data); event EventHandler Changing; event EventHandler Changed; diff --git a/src/Blazored.SessionStorage/JsonConverters/TimespanJsonConverter.cs b/src/Blazored.SessionStorage/JsonConverters/TimespanJsonConverter.cs new file mode 100644 index 0000000..f2436e3 --- /dev/null +++ b/src/Blazored.SessionStorage/JsonConverters/TimespanJsonConverter.cs @@ -0,0 +1,46 @@ +using System; +using System.Text.Json; +using System.Text.Json.Serialization; +using System.Text.RegularExpressions; + +namespace Blazored.SessionStorage.JsonConverters +{ + /// + /// The new Json.NET doesn't support Timespan at this time + /// https://github.com/dotnet/corefx/issues/38641 + /// + public class TimespanJsonConverter : JsonConverter + { + /// + /// Format: Days.Hours:Minutes:Seconds:Milliseconds + /// + public const string TimeSpanFormatString = @"d\.hh\:mm\:ss\:FFF"; + + public override TimeSpan Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + var s = reader.GetString(); + if (string.IsNullOrWhiteSpace(s)) + { + return TimeSpan.Zero; + } + else + { + TimeSpan parsedTimeSpan; + if (!TimeSpan.TryParseExact(s, TimeSpanFormatString, null, out parsedTimeSpan)) + { + throw new FormatException($"Input timespan is not in an expected format : expected {Regex.Unescape(TimeSpanFormatString)}. Please retrieve this key as a string and parse manually."); + } + else + { + return parsedTimeSpan; + } + } + } + + public override void Write(Utf8JsonWriter writer, TimeSpan value, JsonSerializerOptions options) + { + var timespanFormatted = $"{value.ToString(TimeSpanFormatString)}"; + writer.WriteStringValue(timespanFormatted); + } + } +} diff --git a/src/Blazored.SessionStorage/ServiceCollectionExtensions.cs b/src/Blazored.SessionStorage/ServiceCollectionExtensions.cs index a76e37f..4bb05e7 100644 --- a/src/Blazored.SessionStorage/ServiceCollectionExtensions.cs +++ b/src/Blazored.SessionStorage/ServiceCollectionExtensions.cs @@ -1,4 +1,7 @@ -using Microsoft.Extensions.DependencyInjection; +using System; +using Blazored.SessionStorage.JsonConverters; +using Blazored.SessionStorage.StorageOptions; +using Microsoft.Extensions.DependencyInjection; namespace Blazored.SessionStorage { @@ -8,7 +11,23 @@ public static IServiceCollection AddBlazoredSessionStorage(this IServiceCollecti { return services .AddScoped() - .AddScoped(); + .AddScoped() + .Configure(configureOptions => + { + configureOptions.JsonSerializerOptions.Converters.Add(new TimespanJsonConverter()); + }); } + + public static IServiceCollection AddBlazoredSessionStorage(this IServiceCollection services, Action configure) + { + return services + .AddScoped() + .AddScoped() + .Configure(configureOptions => + { + configure?.Invoke(configureOptions); + configureOptions.JsonSerializerOptions.Converters.Add(new TimespanJsonConverter()); + }); + } } } diff --git a/src/Blazored.SessionStorage/SessionStorageService.cs b/src/Blazored.SessionStorage/SessionStorageService.cs index beb04d1..efb0922 100644 --- a/src/Blazored.SessionStorage/SessionStorageService.cs +++ b/src/Blazored.SessionStorage/SessionStorageService.cs @@ -1,4 +1,6 @@ -using Microsoft.JSInterop; +using Blazored.SessionStorage.StorageOptions; +using Microsoft.Extensions.Options; +using Microsoft.JSInterop; using System; using System.Text.Json; using System.Threading.Tasks; @@ -9,14 +11,19 @@ public class SessionStorageService : ISessionStorageService, ISyncSessionStorage { private readonly IJSRuntime _jSRuntime; private readonly IJSInProcessRuntime _jSInProcessRuntime; + private readonly JsonSerializerOptions _jsonOptions; - public SessionStorageService(IJSRuntime jSRuntime) + public event EventHandler Changing; + public event EventHandler Changed; + + public SessionStorageService(IJSRuntime jSRuntime, IOptions options) { - _jSRuntime = jSRuntime; - _jSInProcessRuntime = jSRuntime as IJSInProcessRuntime; + _jSRuntime = jSRuntime; + _jsonOptions = options.Value.JsonSerializerOptions; + _jSInProcessRuntime = jSRuntime as IJSInProcessRuntime; } - public async Task SetItemAsync(string key, object data) + public async Task SetItemAsync(string key, T data) { if (string.IsNullOrEmpty(key)) throw new ArgumentNullException(nameof(key)); @@ -24,9 +31,11 @@ public async Task SetItemAsync(string key, object data) var e = await RaiseOnChangingAsync(key, data); if (e.Cancel) - return; - - await _jSRuntime.InvokeAsync("sessionStorage.setItem", key, JsonSerializer.Serialize(data)); + return; + + var serialisedData = JsonSerializer.Serialize(data, _jsonOptions); + + await _jSRuntime.InvokeVoidAsync("sessionStorage.setItem", key, serialisedData); RaiseOnChanged(key, e.OldValue, data); } @@ -39,9 +48,9 @@ public async Task GetItemAsync(string key) var serialisedData = await _jSRuntime.InvokeAsync("sessionStorage.getItem", key); if (serialisedData == null) - return default(T); - - return JsonSerializer.Deserialize(serialisedData); + return default; + + return JsonSerializer.Deserialize(serialisedData, _jsonOptions); } public async Task RemoveItemAsync(string key) @@ -58,7 +67,7 @@ public async Task RemoveItemAsync(string key) public async Task KeyAsync(int index) => await _jSRuntime.InvokeAsync("sessionStorage.key", index); - public void SetItem(string key, object data) + public void SetItem(string key, T data) { if (string.IsNullOrEmpty(key)) throw new ArgumentNullException(nameof(key)); @@ -71,7 +80,9 @@ public void SetItem(string key, object data) if (e.Cancel) return; - _jSInProcessRuntime.Invoke("sessionStorage.setItem", key, JsonSerializer.Serialize(data)); + var serialisedData = JsonSerializer.Serialize(data, _jsonOptions); + + _jSInProcessRuntime.InvokeVoid("sessionStorage.setItem", key, serialisedData); RaiseOnChanged(key, e.OldValue, data); } @@ -87,9 +98,9 @@ public T GetItem(string key) var serialisedData = _jSInProcessRuntime.Invoke("sessionStorage.getItem", key); if (serialisedData == null) - return default(T); - - return JsonSerializer.Deserialize(serialisedData); + return default; + + return JsonSerializer.Deserialize(serialisedData, _jsonOptions); } public void RemoveItem(string key) @@ -100,7 +111,7 @@ public void RemoveItem(string key) if (_jSInProcessRuntime == null) throw new InvalidOperationException("IJSInProcessRuntime not available"); - _jSInProcessRuntime.Invoke("sessionStorage.removeItem", key); + _jSInProcessRuntime.InvokeVoid("sessionStorage.removeItem", key); } public void Clear() @@ -108,7 +119,7 @@ public void Clear() if (_jSInProcessRuntime == null) throw new InvalidOperationException("IJSInProcessRuntime not available"); - _jSInProcessRuntime.Invoke("sessionStorage.clear"); + _jSInProcessRuntime.InvokeVoid("sessionStorage.clear"); } public int Length() @@ -127,7 +138,6 @@ public string Key(int index) return _jSInProcessRuntime.Invoke("sessionStorage.key", index); } - public event EventHandler Changing; private async Task RaiseOnChangingAsync(string key, object data) { var e = new ChangingEventArgs @@ -156,7 +166,6 @@ private ChangingEventArgs RaiseOnChangingSync(string key, object data) return e; } - public event EventHandler Changed; private void RaiseOnChanged(string key, object oldValue, object data) { var e = new ChangedEventArgs diff --git a/src/Blazored.SessionStorage/StorageOptions/SessionStorageOptions.cs b/src/Blazored.SessionStorage/StorageOptions/SessionStorageOptions.cs new file mode 100644 index 0000000..b005f1a --- /dev/null +++ b/src/Blazored.SessionStorage/StorageOptions/SessionStorageOptions.cs @@ -0,0 +1,18 @@ +using System.Text.Json; + +namespace Blazored.SessionStorage.StorageOptions +{ + public class SessionStorageOptions + { + public JsonSerializerOptions JsonSerializerOptions { get; } = new JsonSerializerOptions + { + DictionaryKeyPolicy = JsonNamingPolicy.CamelCase, + IgnoreNullValues = true, + IgnoreReadOnlyProperties = true, + PropertyNameCaseInsensitive = true, + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + ReadCommentHandling = JsonCommentHandling.Skip, + WriteIndented = false + }; + } +}