Skip to content

Commit

Permalink
Catch errors when getting from browser storage (#7001)
Browse files Browse the repository at this point in the history
  • Loading branch information
JamesNK authored Jan 10, 2025
1 parent ac38396 commit 5f3fc13
Show file tree
Hide file tree
Showing 6 changed files with 38 additions and 21 deletions.
29 changes: 24 additions & 5 deletions src/Aspire.Dashboard/Model/BrowserStorage/BrowserStorageBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<StorageResult<T>> GetAsync<T>(string key)
public ILogger Logger { get; }

public async Task<StorageResult<TValue>> GetAsync<TValue>(string key)
{
var result = await _protectedBrowserStorage.GetAsync<T>(key).ConfigureAwait(false);
return new StorageResult<T>(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<TValue>(key).ConfigureAwait(false);
return new StorageResult<TValue>(result.Success, result.Value);
}
catch (Exception ex)
{
Logger.LogWarning(ex, "Error when reading '{Key}' as {ValueType}.", key, typeof(TValue).Name);

return new StorageResult<TValue>(false, default);
}
}

public async Task SetAsync<T>(string key, T value)
public async Task SetAsync<TValue>(string key, TValue value)
{
await _protectedBrowserStorage.SetAsync(key, value!).ConfigureAwait(false);
}
Expand Down
4 changes: 2 additions & 2 deletions src/Aspire.Dashboard/Model/BrowserStorage/IBrowserStorage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@ namespace Aspire.Dashboard.Model.BrowserStorage;

public interface IBrowserStorage
{
Task<StorageResult<T>> GetAsync<T>(string key);
Task SetAsync<T>(string key, T value);
Task<StorageResult<TValue>> GetAsync<TValue>(string key);
Task SetAsync<TValue>(string key, TValue value);
}
4 changes: 2 additions & 2 deletions src/Aspire.Dashboard/Model/BrowserStorage/ILocalStorage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ public interface ILocalStorage : IBrowserStorage
/// <summary>
/// Get unprotected data from local storage. This must only be used with non-sensitive data.
/// </summary>
Task<StorageResult<T>> GetUnprotectedAsync<T>(string key);
Task<StorageResult<TValue>> GetUnprotectedAsync<TValue>(string key);

/// <summary>
/// Set unprotected data to local storage. This must only be used with non-sensitive data.
/// </summary>
Task SetUnprotectedAsync<T>(string key, T value);
Task SetUnprotectedAsync<TValue>(string key, TValue value);
}
16 changes: 7 additions & 9 deletions src/Aspire.Dashboard/Model/BrowserStorage/LocalBrowserStorage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,36 +16,34 @@ public class LocalBrowserStorage : BrowserStorageBase, ILocalStorage
};

private readonly IJSRuntime _jsRuntime;
private readonly ILogger<LocalBrowserStorage> _logger;

public LocalBrowserStorage(IJSRuntime jsRuntime, ProtectedLocalStorage protectedLocalStorage, ILogger<LocalBrowserStorage> logger) : base(protectedLocalStorage)
public LocalBrowserStorage(IJSRuntime jsRuntime, ProtectedLocalStorage protectedLocalStorage, ILogger<LocalBrowserStorage> logger) : base(protectedLocalStorage, logger)
{
_jsRuntime = jsRuntime;
_logger = logger;
}

public async Task<StorageResult<T>> GetUnprotectedAsync<T>(string key)
public async Task<StorageResult<TValue>> GetUnprotectedAsync<TValue>(string key)
{
var json = await GetJsonAsync(key).ConfigureAwait(false);

if (json == null)
{
return new StorageResult<T>(false, default);
return new StorageResult<TValue>(false, default);
}

try
{
return new StorageResult<T>(true, JsonSerializer.Deserialize<T>(json, s_options));
return new StorageResult<TValue>(true, JsonSerializer.Deserialize<TValue>(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<T>(false, default);
return new StorageResult<TValue>(false, default);
}
}

public async Task SetUnprotectedAsync<T>(string key, T value)
public async Task SetUnprotectedAsync<TValue>(string key, TValue value)
{
var json = JsonSerializer.Serialize(value, s_options);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<SessionBrowserStorage> logger) : base(protectedSessionStorage, logger)
{
}
}
4 changes: 2 additions & 2 deletions src/Aspire.Dashboard/Model/BrowserStorage/StorageResult.cs
Original file line number Diff line number Diff line change
@@ -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<T>(bool Success, T? Value);
public readonly record struct StorageResult<TValue>(bool Success, TValue? Value);

0 comments on commit 5f3fc13

Please sign in to comment.