Skip to content

Commit

Permalink
Merge pull request #46 from Blazored/setitemasstring
Browse files Browse the repository at this point in the history
Added SetItemAsString[Async] methods
  • Loading branch information
chrissainty authored May 15, 2021
2 parents 6ecbc31 + da46b32 commit 9aad9bd
Show file tree
Hide file tree
Showing 6 changed files with 473 additions and 4 deletions.
10 changes: 9 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
# Blazored SessionStorage
Blazored SessionStorage is a library that provides access to the browsers session 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 Change (v1 > v2): JsonSerializerOptions
## Breaking Change (v1 > v2)

### 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 session storage with v3 to break things.
To retain the old settings use the following configuration when adding Blazored SessionStorage to the DI container:

Expand All @@ -21,6 +23,10 @@ builder.Services.AddBlazoredSessionStorage(config =>
);
```

### SetItem[Async] method now serializes string values
Prior to v2 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):
Expand Down Expand Up @@ -117,6 +123,7 @@ The APIs available are:

- asynchronous via `ISessionStorageService`:
- SetItemAsync()
- SetItemAsStringAsync()
- GetItemAsync()
- GetItemAsStringAsync()
- RemoveItemAsync()
Expand All @@ -127,6 +134,7 @@ The APIs available are:

- synchronous via `ISyncSessionStorageService` (Synchronous methods are **only** available in Blazor WebAssembly):
- SetItem()
- SetItemAsString()
- GetItem()
- GetItemAsString()
- RemoveItem()
Expand Down
10 changes: 9 additions & 1 deletion src/Blazored.SessionStorage/ISessionStorageService.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System;
using System;
using System.Threading.Tasks;

namespace Blazored.SessionStorage
Expand Down Expand Up @@ -60,6 +60,14 @@ public interface ISessionStorageService
/// <returns>A <see cref="ValueTask"/> representing the completion of the operation.</returns>
ValueTask SetItemAsync<T>(string key, T data);

/// <summary>
/// Sets or updates the <paramref name="data"/> in session storage with the specified <paramref name="key"/>. Does not serialize the value before storing.
/// </summary>
/// <param name="key">A <see cref="string"/> value specifying the name of the storage slot to use</param>
/// <param name="data">The string to be saved</param>
/// <returns></returns>
ValueTask SetItemAsStringAsync(string key, string data);

event EventHandler<ChangingEventArgs> Changing;
event EventHandler<ChangedEventArgs> Changed;
}
Expand Down
10 changes: 9 additions & 1 deletion src/Blazored.SessionStorage/ISyncSessionStorageService.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System;
using System;

namespace Blazored.SessionStorage
{
Expand Down Expand Up @@ -56,6 +56,14 @@ public interface ISyncSessionStorageService
/// <param name="data">The data to be saved</param>
void SetItem<T>(string key, T data);

/// <summary>
/// Sets or updates the <paramref name="data"/> in session storage with the specified <paramref name="key"/>. Does not serialize the value before storing.
/// </summary>
/// <param name="key">A <see cref="string"/> value specifying the name of the storage slot to use</param>
/// <param name="data">The string to be saved</param>
/// <returns></returns>
void SetItemAsString(string key, string data);

event EventHandler<ChangingEventArgs> Changing;
event EventHandler<ChangedEventArgs> Changed;
}
Expand Down
38 changes: 37 additions & 1 deletion src/Blazored.SessionStorage/SessionStorageService.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System;
using System;
using System.Text.Json;
using System.Threading.Tasks;
using Blazored.SessionStorage.Serialization;
Expand Down Expand Up @@ -32,6 +32,24 @@ public async ValueTask SetItemAsync<T>(string key, T data)
RaiseOnChanged(key, e.OldValue, data);
}

public async ValueTask SetItemAsStringAsync(string key, string data)
{
if (string.IsNullOrWhiteSpace(key))
throw new ArgumentNullException(nameof(key));

if (data is null)
throw new ArgumentNullException(nameof(data));

var e = await RaiseOnChangingAsync(key, data).ConfigureAwait(false);

if (e.Cancel)
return;

await _storageProvider.SetItemAsync(key, data).ConfigureAwait(false);

RaiseOnChanged(key, e.OldValue, data);
}

public async ValueTask<T> GetItemAsync<T>(string key)
{
if (string.IsNullOrWhiteSpace(key))
Expand Down Expand Up @@ -98,6 +116,24 @@ public void SetItem<T>(string key, T data)
RaiseOnChanged(key, e.OldValue, data);
}

public void SetItemAsString(string key, string data)
{
if (string.IsNullOrWhiteSpace(key))
throw new ArgumentNullException(nameof(key));

if (data is null)
throw new ArgumentNullException(nameof(data));

var e = RaiseOnChangingSync(key, data);

if (e.Cancel)
return;

_storageProvider.SetItem(key, data);

RaiseOnChanged(key, e.OldValue, data);
}

public T GetItem<T>(string key)
{
if (string.IsNullOrWhiteSpace(key))
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
using System;
using System.Text.Json;
using Blazored.SessionStorage.JsonConverters;
using Blazored.SessionStorage.Serialization;
using Blazored.SessionStorage.StorageOptions;
using Blazored.SessionStorage.Testing;
using Microsoft.Extensions.Options;
using Moq;
using Xunit;

namespace Blazored.SessionStorage.Tests.SessionStorageServiceTests
{
public class SetItemAsString
{
private readonly SessionStorageService _sut;
private readonly IStorageProvider _storageProvider;
private readonly IJsonSerializer _serializer;

private const string Key = "testKey";

public SetItemAsString()
{
var mockOptions = new Mock<IOptions<SessionStorageOptions>>();
var jsonOptions = new JsonSerializerOptions();
jsonOptions.Converters.Add(new TimespanJsonConverter());
mockOptions.Setup(u => u.Value).Returns(new SessionStorageOptions());
_serializer = new SystemTextJsonSerializer(mockOptions.Object);
_storageProvider = new InMemoryStorageProvider();
_sut = new SessionStorageService(_storageProvider, _serializer);
}

[Theory]
[InlineData("")]
[InlineData(" ")]
[InlineData(null)]
public void ThrowsArgumentNullException_When_KeyIsInvalid(string key)
{
// arrange / act
const string data = "Data";
var action = new Action(() => _sut.SetItemAsString(key, data));

// assert
Assert.Throws<ArgumentNullException>(action);
}

[Fact]
public void ThrowsArgumentNullException_When_DataIsNull()
{
// arrange / act
var data = (string)null;
var action = new Action(() => _sut.SetItemAsString("MyValue", data));

// assert
Assert.Throws<ArgumentNullException>(action);
}

[Fact]
public void RaisesOnChangingEvent_When_SavingNewData()
{
// arrange
var onChangingCalled = false;
_sut.Changing += (_, _) => onChangingCalled = true;

// act
_sut.SetItemAsString("Key", "Data");

// assert
Assert.True(onChangingCalled);
}

[Fact]
public void OnChangingEventContainsEmptyOldValue_When_SavingData()
{
// arrange
var oldValue = "";
_sut.Changing += (_, args) => oldValue = args.OldValue?.ToString();

// act
_sut.SetItemAsString("Key", "Data");

// assert
Assert.Equal(default, oldValue);
}

[Fact]
public void OnChangingEventContainsNewValue_When_SavingNewData()
{
// arrange
const string data = "Data";
var newValue = "";
_sut.Changing += (_, args) => newValue = args.NewValue.ToString();

// act
_sut.SetItemAsString("Key", data);

// assert
Assert.Equal(data, newValue);
}

[Fact]
public void OnChangingEventIsCancelled_When_SettingCancelToTrue_When_SavingNewData()
{
// arrange
_sut.Changing += (_, args) => args.Cancel = true;

// act
_sut.SetItemAsString("Key", "Data");

// assert
Assert.Equal(0, _storageProvider.Length());
}

[Fact]
public void SavesDataToStore()
{
// Act
var valueToSave = "StringValue";
_sut.SetItemAsString(Key, valueToSave);

// Assert
var valueFromStore = _storageProvider.GetItem(Key);

Assert.Equal(1, _storageProvider.Length());
Assert.Equal(valueToSave, valueFromStore);
}

[Fact]
public void OverwriteExistingValueInStore_When_UsingTheSameKey()
{
// Arrange
const string existingValue = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1lIjoiYWRtaW4iLCJodHRwOi8vc2NoZW1hcy5taWNyb3NvZnQuY29tL3dzLzIwMDgvMDYvaWRlbnRpdHkvY2xhaW1zL3JvbGUiOiJBZG1pbmlzdHJhdG9yIiwiZXhwIjoxNTg1NjYwNzEyLCJpc3MiOiJDb2RlUmVkQm9va2luZy5TZXJ2ZXIiLCJhdWQiOiJDb2RlUmVkQm9va2luZy5DbGllbnRzIn0.JhK1M1H7NLCFexujJYCDjTn9La0HloGYADMHXGCFksU";
const string newValue = "6QLE0LL7iw7tHPAwold31qUENt3lVTUZxDGqeXQFx38=";

_storageProvider.SetItem(Key, existingValue);

// Act
_sut.SetItemAsString(Key, newValue);

// Assert
var updatedValue = _storageProvider.GetItem(Key);

Assert.Equal(newValue, updatedValue);
}

[Fact]
public void RaisesOnChangedEvent_When_SavingData()
{
// arrange
var onChangedCalled = false;
_sut.Changed += (_, _) => onChangedCalled = true;

// act
_sut.SetItemAsString("Key", "Data");

// assert
Assert.True(onChangedCalled);
}

[Fact]
public void OnChangedEventContainsEmptyOldValue_When_SavingNewData()
{
// arrange
var oldValue = "";
_sut.Changed += (_, args) => oldValue = args.OldValue?.ToString();

// act
_sut.SetItemAsString("Key", "Data");

// assert
Assert.Equal(default, oldValue);
}

[Fact]
public void OnChangedEventContainsNewValue_When_SavingNewData()
{
// arrange
const string data = "Data";
var newValue = "";
_sut.Changed += (_, args) => newValue = args.NewValue.ToString();

// act
_sut.SetItemAsString("Key", data);

// assert
Assert.Equal(data, newValue);
}

[Fact]
public void OnChangedEventContainsOldValue_When_UpdatingExistingData()
{
// arrange
var existingValue = "Foo";
_storageProvider.SetItem("Key", existingValue);
var oldValue = "";
_sut.Changed += (_, args) => oldValue = args.OldValue?.ToString();

// act
_sut.SetItemAsString("Key", "Data");

// assert
Assert.Equal(existingValue, oldValue);
}
}
}
Loading

0 comments on commit 9aad9bd

Please sign in to comment.