using System; using System.Collections.Generic; using System.Text.Json; using System.Threading; using System.Threading.Tasks; using Blazored.SessionStorage.Serialization; namespace Blazored.SessionStorage; internal class SessionStorageService : ISessionStorageService, ISyncSessionStorageService { private readonly IStorageProvider _storageProvider; private readonly IJsonSerializer _serializer; public SessionStorageService(IStorageProvider storageProvider, IJsonSerializer serializer) { _storageProvider = storageProvider; _serializer = serializer; } public ValueTask RemoveItemsAsync(IEnumerable keys, CancellationToken cancellationToken = default) => _storageProvider.RemoveItemsAsync(keys, cancellationToken); public async ValueTask SetItemAsync(string key, T data, CancellationToken cancellationToken = default) { if (string.IsNullOrWhiteSpace(key)) throw new ArgumentNullException(nameof(key)); var e = await RaiseOnChangingAsync(key, data).ConfigureAwait(false); if (e.Cancel) return; var serialisedData = _serializer.Serialize(data); await _storageProvider.SetItemAsync(key, serialisedData, cancellationToken).ConfigureAwait(false); RaiseOnChanged(key, e.OldValue, data); } public async ValueTask SetItemAsStringAsync(string key, string data, CancellationToken cancellationToken = default) { 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, cancellationToken).ConfigureAwait(false); RaiseOnChanged(key, e.OldValue, data); } public async ValueTask GetItemAsync(string key, CancellationToken cancellationToken = default) { if (string.IsNullOrWhiteSpace(key)) throw new ArgumentNullException(nameof(key)); var serialisedData = await _storageProvider.GetItemAsync(key, cancellationToken).ConfigureAwait(false); if (string.IsNullOrWhiteSpace(serialisedData)) return default; try { return _serializer.Deserialize(serialisedData); } catch (JsonException e) when (e.Path == "$" && typeof(T) == typeof(string)) { // For backward compatibility return the plain string. // On the next save a correct value will be stored and this Exception will not happen again, for this 'key' return (T)(object)serialisedData; } } public ValueTask GetItemAsStringAsync(string key, CancellationToken cancellationToken = default) { if (string.IsNullOrWhiteSpace(key)) throw new ArgumentNullException(nameof(key)); return _storageProvider.GetItemAsync(key, cancellationToken); } public ValueTask RemoveItemAsync(string key, CancellationToken cancellationToken = default) { if (string.IsNullOrWhiteSpace(key)) throw new ArgumentNullException(nameof(key)); return _storageProvider.RemoveItemAsync(key, cancellationToken); } public ValueTask ClearAsync(CancellationToken cancellationToken = default) => _storageProvider.ClearAsync(cancellationToken); public ValueTask LengthAsync(CancellationToken cancellationToken = default) => _storageProvider.LengthAsync(cancellationToken); public ValueTask KeyAsync(int index, CancellationToken cancellationToken = default) => _storageProvider.KeyAsync(index, cancellationToken); public ValueTask> KeysAsync(CancellationToken cancellationToken = default) => _storageProvider.KeysAsync(cancellationToken); public ValueTask ContainKeyAsync(string key, CancellationToken cancellationToken = default) => _storageProvider.ContainKeyAsync(key, cancellationToken); public void SetItem(string key, T data) { if (string.IsNullOrWhiteSpace(key)) throw new ArgumentNullException(nameof(key)); var e = RaiseOnChangingSync(key, data); if (e.Cancel) return; var serialisedData = _serializer.Serialize(data); _storageProvider.SetItem(key, serialisedData); 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(string key) { if (string.IsNullOrWhiteSpace(key)) throw new ArgumentNullException(nameof(key)); var serialisedData = _storageProvider.GetItem(key); if (string.IsNullOrWhiteSpace(serialisedData)) return default; try { return _serializer.Deserialize(serialisedData); } catch (JsonException e) when (e.Path == "$" && typeof(T) == typeof(string)) { // For backward compatibility return the plain string. // On the next save a correct value will be stored and this Exception will not happen again, for this 'key' return (T)(object)serialisedData; } } public string GetItemAsString(string key) { if (string.IsNullOrWhiteSpace(key)) throw new ArgumentNullException(nameof(key)); return _storageProvider.GetItem(key); } public void RemoveItem(string key) { if (string.IsNullOrWhiteSpace(key)) throw new ArgumentNullException(nameof(key)); _storageProvider.RemoveItem(key); } public void RemoveItems(IEnumerable keys) { if (keys == null) throw new ArgumentNullException(nameof(keys)); foreach (var key in keys) { _storageProvider.RemoveItem(key); } } public void Clear() => _storageProvider.Clear(); public int Length() => _storageProvider.Length(); public string Key(int index) => _storageProvider.Key(index); public IEnumerable Keys() => _storageProvider.Keys(); public bool ContainKey(string key) => _storageProvider.ContainKey(key); public event EventHandler Changing; private async Task RaiseOnChangingAsync(string key, object data) { var e = new ChangingEventArgs { Key = key, OldValue = await GetItemInternalAsync(key).ConfigureAwait(false), NewValue = data }; Changing?.Invoke(this, e); return e; } private ChangingEventArgs RaiseOnChangingSync(string key, object data) { var e = new ChangingEventArgs { Key = key, OldValue = GetItemInternal(key), NewValue = data }; Changing?.Invoke(this, e); return e; } private async Task GetItemInternalAsync(string key) { if (string.IsNullOrEmpty(key)) throw new ArgumentNullException(nameof(key)); var serialisedData = await _storageProvider.GetItemAsync(key).ConfigureAwait(false); if (string.IsNullOrWhiteSpace(serialisedData)) return default; try { return _serializer.Deserialize(serialisedData); } catch (JsonException) { return (T)(object)serialisedData; } } private object GetItemInternal(string key) { if (string.IsNullOrEmpty(key)) throw new ArgumentNullException(nameof(key)); var serialisedData = _storageProvider.GetItem(key); if (string.IsNullOrWhiteSpace(serialisedData)) return default; try { return _serializer.Deserialize(serialisedData); } catch (JsonException) { return serialisedData; } } public event EventHandler Changed; private void RaiseOnChanged(string key, object oldValue, object data) { var e = new ChangedEventArgs { Key = key, OldValue = oldValue, NewValue = data }; Changed?.Invoke(this, e); } }