From b18ce7c9ea57afe3108d43b15aec7ddc3bc5e7d9 Mon Sep 17 00:00:00 2001 From: Ed Charbeneau Date: Wed, 30 Aug 2023 18:30:40 -0400 Subject: [PATCH] Fixes dispose errors caused by lifecycle. --- .../BlazorReplMediaQueryService.cs | 23 ++++++--- BlazorSize/BlazorPro.BlazorSize.csproj | 3 +- BlazorSize/MediaQuery/MediaQueryCache.cs | 3 +- BlazorSize/MediaQuery/MediaQueryList.razor.cs | 1 + BlazorSize/MediaQuery/MediaQueryService.cs | 49 +++++++++++-------- 5 files changed, 48 insertions(+), 31 deletions(-) diff --git a/BlazorPro.BlazorSize.BlazorRepl/BlazorReplMediaQueryService.cs b/BlazorPro.BlazorSize.BlazorRepl/BlazorReplMediaQueryService.cs index 125ca20..10dd832 100644 --- a/BlazorPro.BlazorSize.BlazorRepl/BlazorReplMediaQueryService.cs +++ b/BlazorPro.BlazorSize.BlazorRepl/BlazorReplMediaQueryService.cs @@ -20,7 +20,7 @@ public BlazorReplMediaQueryService(IJSRuntime jsRuntime) // JavaScript uses this value for tracking MediaQueryList instances. private DotNetObjectReference DotNetInstance { get; set; } = null!; - private readonly List mediaQueries = new List(); + private readonly List mediaQueries = new(); public List MediaQueries => mediaQueries; private MediaQueryCache? GetMediaQueryFromCache(string byMedia) => mediaQueries?.Find(q => q.MediaRequested == byMedia); @@ -36,24 +36,29 @@ public void AddQuery(MediaQuery newMediaQuery) }; mediaQueries.Add(cache); } - cache.MediaQueries.Add(newMediaQuery); + cache.MediaQueries?.Add(newMediaQuery); } public async Task RemoveQuery(MediaQuery mediaQuery) { if (mediaQuery == null) return; - var cache = GetMediaQueryFromCache(byMedia: mediaQuery.Media); + MediaQueryCache? cache = GetMediaQueryFromCache(byMedia: mediaQuery.Media); - if (cache == null) return; + if (cache == null || cache.MediaQueries == null) return; - cache.MediaQueries.Remove(mediaQuery); - if (cache.MediaQueries.Any()) + try { + cache.MediaQueries.Remove(mediaQuery); + if (cache.MediaQueries.Any()) return; mediaQueries.Remove(cache); - var module = await moduleTask.Value; + IJSObjectReference module = await moduleTask.Value; await module.InvokeVoidAsync($"removeMediaQuery", DotNetInstance, mediaQuery.InternalMedia.Media); } + catch (Exception) + { + //https://stackoverflow.com/questions/72488563/blazor-server-side-application-throwing-system-invalidoperationexception-javas + } } public async Task Initialize(MediaQuery mediaQuery) @@ -75,6 +80,7 @@ public async Task Initialize(MediaQuery mediaQuery) cache.Value = await task; cache.Loading = task.IsCompleted; // When loading is complete dispatch an update to all subscribers. + if (cache.MediaQueries is null) return; foreach (var item in cache.MediaQueries) { item.MediaQueryChanged(cache.Value); @@ -100,11 +106,12 @@ public async Task CreateMediaQueryList(DotNetObjectReference dot public async ValueTask DisposeAsync() { - if (DotNetInstance != null) + if (DotNetInstance is not null) { var module = await moduleTask.Value; await module.InvokeVoidAsync("removeMediaQueryList", DotNetInstance); DotNetInstance.Dispose(); + GC.SuppressFinalize(this); } } } diff --git a/BlazorSize/BlazorPro.BlazorSize.csproj b/BlazorSize/BlazorPro.BlazorSize.csproj index 8237234..4938c4b 100644 --- a/BlazorSize/BlazorPro.BlazorSize.csproj +++ b/BlazorSize/BlazorPro.BlazorSize.csproj @@ -5,7 +5,7 @@ true enable latest - 6.2.1 + 6.2.2 Ed Charbeneau EdCh1rbeneau.com A JavaScript interop for Blazor used to detect the browser's screen size and perform Media Query tests. BlazorSize uses the DOM API `matchMedia` to test screen size. BlazorSize was created to allow Blazor components to render adaptively. @@ -16,6 +16,7 @@ https://github.com/EdCharbeneau/BlazorSize Blazor, JavaScript Interop + 6.2.2 Fixes more codepaths that may result in Microsoft.JSInterop.JSDisconnectedException 6.2.1 Fixed Microsoft.JSInterop.JSDisconnectedException 6.2.0 Fixed bug where MediaQuery dispose was not always working correctly. 6.1.2 Fixed casing on JS modules, fixes deployment issues on Linux. (regression) diff --git a/BlazorSize/MediaQuery/MediaQueryCache.cs b/BlazorSize/MediaQuery/MediaQueryCache.cs index 671aaaa..86c75da 100644 --- a/BlazorSize/MediaQuery/MediaQueryCache.cs +++ b/BlazorSize/MediaQuery/MediaQueryCache.cs @@ -21,7 +21,8 @@ public class MediaQueryCache /// /// Media Queries that share a RequestedMedia value. Used to aggregate event handlers and minimize JS calls. /// - public List MediaQueries { get; set; } = new List(); + // Nullable because Blazor's lifecycle sometimes GC's this object before the cleanup routines are finished. + public List? MediaQueries { get; set; } = new List(); } } diff --git a/BlazorSize/MediaQuery/MediaQueryList.razor.cs b/BlazorSize/MediaQuery/MediaQueryList.razor.cs index 9f01d17..88fd1af 100644 --- a/BlazorSize/MediaQuery/MediaQueryList.razor.cs +++ b/BlazorSize/MediaQuery/MediaQueryList.razor.cs @@ -44,6 +44,7 @@ public void MediaQueryChanged(MediaQueryArgs args) if (cache is null) return; // Dispatch events to all subscribers + if (cache.MediaQueries is null) return; foreach (var item in cache.MediaQueries) { item.MediaQueryChanged(args); diff --git a/BlazorSize/MediaQuery/MediaQueryService.cs b/BlazorSize/MediaQuery/MediaQueryService.cs index 9850481..ab2bd78 100644 --- a/BlazorSize/MediaQuery/MediaQueryService.cs +++ b/BlazorSize/MediaQuery/MediaQueryService.cs @@ -18,15 +18,15 @@ public MediaQueryService(IJSRuntime jsRuntime) // JavaScript uses this value for tracking MediaQueryList instances. private DotNetObjectReference DotNetInstance { get; set; } = null!; - private readonly List mediaQueries = new List(); + private readonly List mediaQueries = new(); public List MediaQueries => mediaQueries; private MediaQueryCache? GetMediaQueryFromCache(string byMedia) => mediaQueries?.Find(q => q.MediaRequested == byMedia); public void AddQuery(MediaQuery newMediaQuery) { - var cache = GetMediaQueryFromCache(byMedia: newMediaQuery.Media); - if (cache == null) + MediaQueryCache? cache = GetMediaQueryFromCache(byMedia: newMediaQuery.Media); + if (cache is null) { cache = new MediaQueryCache { @@ -34,33 +34,39 @@ public void AddQuery(MediaQuery newMediaQuery) }; mediaQueries.Add(cache); } - cache.MediaQueries.Add(newMediaQuery); + cache.MediaQueries?.Add(newMediaQuery); } public async Task RemoveQuery(MediaQuery mediaQuery) { - if (mediaQuery == null) return; + if (mediaQuery is null) return; - var cache = GetMediaQueryFromCache(byMedia: mediaQuery.Media); + MediaQueryCache? cache = GetMediaQueryFromCache(byMedia: mediaQuery.Media); - if (cache == null) return; + if (cache is null || cache.MediaQueries is null) return; - cache.MediaQueries.Remove(mediaQuery); - if (!cache.MediaQueries.Any()) + try { + cache.MediaQueries.Remove(mediaQuery); + if (cache.MediaQueries.Any()) return; mediaQueries.Remove(cache); - var module = await moduleTask.Value; + IJSObjectReference module = await moduleTask.Value; await module.InvokeVoidAsync($"removeMediaQuery", DotNetInstance, mediaQuery.InternalMedia.Media); } + catch (Exception) + { + //https://github.com/EdCharbeneau/BlazorSize/issues/93 + //https://stackoverflow.com/questions/72488563/blazor-server-side-application-throwing-system-invalidoperationexception-javas + } } public async Task Initialize(MediaQuery mediaQuery) { - if (mediaQuery?.Media == null) return; + if (mediaQuery?.Media is null) return; var cache = GetMediaQueryFromCache(byMedia: mediaQuery.Media); - if (cache == null) return; + if (cache is null) return; - if (cache.Value == null) + if (cache.Value is null) { // If we don't flag the cache as loading, duplicate requests will be sent async. // Duplicate requests = poor performance, esp with web sockets. @@ -73,6 +79,7 @@ public async Task Initialize(MediaQuery mediaQuery) cache.Value = await task; cache.Loading = task.IsCompleted; // When loading is complete dispatch an update to all subscribers. + cache.MediaQueries ??= new List(); foreach (var item in cache.MediaQueries) { item.MediaQueryChanged(cache.Value); @@ -100,17 +107,17 @@ public async ValueTask DisposeAsync() { try { - if (DotNetInstance != null) - { - var module = await moduleTask.Value; - await module.InvokeVoidAsync("removeMediaQueryList", DotNetInstance); - DotNetInstance.Dispose(); - await module.DisposeAsync(); - GC.SuppressFinalize(this); - } + if (DotNetInstance is null) return; + + var module = await moduleTask.Value; + await module.InvokeVoidAsync("removeMediaQueryList", DotNetInstance); + DotNetInstance.Dispose(); + await module.DisposeAsync(); + GC.SuppressFinalize(this); } catch (Exception) { + //https://github.com/EdCharbeneau/BlazorSize/issues/93 //https://stackoverflow.com/questions/72488563/blazor-server-side-application-throwing-system-invalidoperationexception-javas } }