From 5f3fc13b90c2a858462936020fa9164147653371 Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Fri, 10 Jan 2025 08:20:18 +0800 Subject: [PATCH] Catch errors when getting from browser storage (#7001) --- .../BrowserStorage/BrowserStorageBase.cs | 29 +++++++++++++++---- .../Model/BrowserStorage/IBrowserStorage.cs | 4 +-- .../Model/BrowserStorage/ILocalStorage.cs | 4 +-- .../BrowserStorage/LocalBrowserStorage.cs | 16 +++++----- .../BrowserStorage/SessionBrowserStorage.cs | 2 +- .../Model/BrowserStorage/StorageResult.cs | 4 +-- 6 files changed, 38 insertions(+), 21 deletions(-) diff --git a/src/Aspire.Dashboard/Model/BrowserStorage/BrowserStorageBase.cs b/src/Aspire.Dashboard/Model/BrowserStorage/BrowserStorageBase.cs index 819a443c0f..93a1eaf14e 100644 --- a/src/Aspire.Dashboard/Model/BrowserStorage/BrowserStorageBase.cs +++ b/src/Aspire.Dashboard/Model/BrowserStorage/BrowserStorageBase.cs @@ -9,18 +9,37 @@ public abstract class BrowserStorageBase : IBrowserStorage { private readonly ProtectedBrowserStorage _protectedBrowserStorage; - protected BrowserStorageBase(ProtectedBrowserStorage protectedBrowserStorage) + protected BrowserStorageBase(ProtectedBrowserStorage protectedBrowserStorage, ILogger logger) { _protectedBrowserStorage = protectedBrowserStorage; + Logger = logger; } - public async Task> GetAsync(string key) + public ILogger Logger { get; } + + public async Task> GetAsync(string key) { - var result = await _protectedBrowserStorage.GetAsync(key).ConfigureAwait(false); - return new StorageResult(result.Success, result.Value); + try + { + // Possible errors here: + // - Saved value in storage can't be deserialized to TValue. + // - Saved value has a different data protection key than the current one. + // This could happen with values saved to persistent browser and the user upgrades Aspire version, which has a different + // install location and so a different data protection key. + // It could also be caused by standalone dashboard, which creates a new key each run. Leaving the dashboard browser open + // while restarting the container will cause a new data protection key, even with session storage. + var result = await _protectedBrowserStorage.GetAsync(key).ConfigureAwait(false); + return new StorageResult(result.Success, result.Value); + } + catch (Exception ex) + { + Logger.LogWarning(ex, "Error when reading '{Key}' as {ValueType}.", key, typeof(TValue).Name); + + return new StorageResult(false, default); + } } - public async Task SetAsync(string key, T value) + public async Task SetAsync(string key, TValue value) { await _protectedBrowserStorage.SetAsync(key, value!).ConfigureAwait(false); } diff --git a/src/Aspire.Dashboard/Model/BrowserStorage/IBrowserStorage.cs b/src/Aspire.Dashboard/Model/BrowserStorage/IBrowserStorage.cs index 3a60bf678c..65577f945c 100644 --- a/src/Aspire.Dashboard/Model/BrowserStorage/IBrowserStorage.cs +++ b/src/Aspire.Dashboard/Model/BrowserStorage/IBrowserStorage.cs @@ -5,6 +5,6 @@ namespace Aspire.Dashboard.Model.BrowserStorage; public interface IBrowserStorage { - Task> GetAsync(string key); - Task SetAsync(string key, T value); + Task> GetAsync(string key); + Task SetAsync(string key, TValue value); } diff --git a/src/Aspire.Dashboard/Model/BrowserStorage/ILocalStorage.cs b/src/Aspire.Dashboard/Model/BrowserStorage/ILocalStorage.cs index f81e409cb4..5b2728b81a 100644 --- a/src/Aspire.Dashboard/Model/BrowserStorage/ILocalStorage.cs +++ b/src/Aspire.Dashboard/Model/BrowserStorage/ILocalStorage.cs @@ -8,10 +8,10 @@ public interface ILocalStorage : IBrowserStorage /// /// Get unprotected data from local storage. This must only be used with non-sensitive data. /// - Task> GetUnprotectedAsync(string key); + Task> GetUnprotectedAsync(string key); /// /// Set unprotected data to local storage. This must only be used with non-sensitive data. /// - Task SetUnprotectedAsync(string key, T value); + Task SetUnprotectedAsync(string key, TValue value); } diff --git a/src/Aspire.Dashboard/Model/BrowserStorage/LocalBrowserStorage.cs b/src/Aspire.Dashboard/Model/BrowserStorage/LocalBrowserStorage.cs index 589fe19db7..3a3cb4b991 100644 --- a/src/Aspire.Dashboard/Model/BrowserStorage/LocalBrowserStorage.cs +++ b/src/Aspire.Dashboard/Model/BrowserStorage/LocalBrowserStorage.cs @@ -16,36 +16,34 @@ public class LocalBrowserStorage : BrowserStorageBase, ILocalStorage }; private readonly IJSRuntime _jsRuntime; - private readonly ILogger _logger; - public LocalBrowserStorage(IJSRuntime jsRuntime, ProtectedLocalStorage protectedLocalStorage, ILogger logger) : base(protectedLocalStorage) + public LocalBrowserStorage(IJSRuntime jsRuntime, ProtectedLocalStorage protectedLocalStorage, ILogger logger) : base(protectedLocalStorage, logger) { _jsRuntime = jsRuntime; - _logger = logger; } - public async Task> GetUnprotectedAsync(string key) + public async Task> GetUnprotectedAsync(string key) { var json = await GetJsonAsync(key).ConfigureAwait(false); if (json == null) { - return new StorageResult(false, default); + return new StorageResult(false, default); } try { - return new StorageResult(true, JsonSerializer.Deserialize(json, s_options)); + return new StorageResult(true, JsonSerializer.Deserialize(json, s_options)); } catch (Exception ex) { - _logger.LogWarning(ex, $"Error when reading '{key}' as {typeof(T).Name} from local browser storage."); + Logger.LogWarning(ex, "Error when reading '{Key}' as {ValueType}.", key, typeof(TValue).Name); - return new StorageResult(false, default); + return new StorageResult(false, default); } } - public async Task SetUnprotectedAsync(string key, T value) + public async Task SetUnprotectedAsync(string key, TValue value) { var json = JsonSerializer.Serialize(value, s_options); diff --git a/src/Aspire.Dashboard/Model/BrowserStorage/SessionBrowserStorage.cs b/src/Aspire.Dashboard/Model/BrowserStorage/SessionBrowserStorage.cs index f928cf1ba0..855b1b5c3e 100644 --- a/src/Aspire.Dashboard/Model/BrowserStorage/SessionBrowserStorage.cs +++ b/src/Aspire.Dashboard/Model/BrowserStorage/SessionBrowserStorage.cs @@ -7,7 +7,7 @@ namespace Aspire.Dashboard.Model.BrowserStorage; public class SessionBrowserStorage : BrowserStorageBase, ISessionStorage { - public SessionBrowserStorage(ProtectedSessionStorage protectedSessionStorage) : base(protectedSessionStorage) + public SessionBrowserStorage(ProtectedSessionStorage protectedSessionStorage, ILogger logger) : base(protectedSessionStorage, logger) { } } diff --git a/src/Aspire.Dashboard/Model/BrowserStorage/StorageResult.cs b/src/Aspire.Dashboard/Model/BrowserStorage/StorageResult.cs index 5aa0427717..23a7849922 100644 --- a/src/Aspire.Dashboard/Model/BrowserStorage/StorageResult.cs +++ b/src/Aspire.Dashboard/Model/BrowserStorage/StorageResult.cs @@ -1,6 +1,6 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. namespace Aspire.Dashboard.Model.BrowserStorage; -public readonly record struct StorageResult(bool Success, T? Value); +public readonly record struct StorageResult(bool Success, TValue? Value);