Skip to content

Commit 794a2a8

Browse files
authored
Blazor Desktop: Make WebViewManager async disposable only (#33482)
* Blazor Desktop: Call ServiceScope.DisposeAsync() instead of Dispose() If you call ServiceScope.Dispose() and there are IAsyncDisposable scoped services registered, it throws. Instead it needs to call DisposeAsync() that supports both sync and async disposable services.
1 parent b3ca1fc commit 794a2a8

File tree

4 files changed

+65
-17
lines changed

4 files changed

+65
-17
lines changed

src/Components/WebView/WebView/src/PageContext.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

44
using System;
5+
using System.Threading.Tasks;
56
using Microsoft.AspNetCore.Components.WebView.Services;
67
using Microsoft.Extensions.DependencyInjection;
78
using Microsoft.Extensions.Logging;
@@ -17,7 +18,7 @@ namespace Microsoft.AspNetCore.Components.WebView
1718
/// for web views, the IPC channel is outside the page context, whereas in Blazor Server,
1819
/// the IPC channel is within the circuit.
1920
/// </summary>
20-
internal class PageContext : IDisposable
21+
internal class PageContext : IAsyncDisposable
2122
{
2223
private readonly AsyncServiceScope _serviceScope;
2324

@@ -45,10 +46,10 @@ public PageContext(
4546
Renderer = new WebViewRenderer(services, dispatcher, ipcSender, loggerFactory, JSRuntime.ElementReferenceContext);
4647
}
4748

48-
public void Dispose()
49+
public ValueTask DisposeAsync()
4950
{
5051
Renderer.Dispose();
51-
_serviceScope.Dispose();
52+
return _serviceScope.DisposeAsync();
5253
}
5354
}
5455
}

src/Components/WebView/WebView/src/PublicAPI.Unshipped.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
Microsoft.AspNetCore.Components.WebView.WebViewManager
33
Microsoft.AspNetCore.Components.WebView.WebViewManager.AddRootComponentAsync(System.Type! componentType, string! selector, Microsoft.AspNetCore.Components.ParameterView parameters) -> System.Threading.Tasks.Task!
44
Microsoft.AspNetCore.Components.WebView.WebViewManager.Dispatcher.get -> Microsoft.AspNetCore.Components.Dispatcher!
5-
Microsoft.AspNetCore.Components.WebView.WebViewManager.Dispose() -> void
5+
Microsoft.AspNetCore.Components.WebView.WebViewManager.DisposeAsync() -> System.Threading.Tasks.ValueTask
66
Microsoft.AspNetCore.Components.WebView.WebViewManager.MessageReceived(System.Uri! sourceUri, string! message) -> void
77
Microsoft.AspNetCore.Components.WebView.WebViewManager.Navigate(string! url) -> void
88
Microsoft.AspNetCore.Components.WebView.WebViewManager.RemoveRootComponentAsync(string! selector) -> System.Threading.Tasks.Task!
@@ -12,4 +12,4 @@ Microsoft.Extensions.DependencyInjection.ComponentsWebViewServiceCollectionExten
1212
abstract Microsoft.AspNetCore.Components.WebView.WebViewManager.NavigateCore(System.Uri! absoluteUri) -> void
1313
abstract Microsoft.AspNetCore.Components.WebView.WebViewManager.SendMessage(string! message) -> void
1414
static Microsoft.Extensions.DependencyInjection.ComponentsWebViewServiceCollectionExtensions.AddBlazorWebView(this Microsoft.Extensions.DependencyInjection.IServiceCollection! services) -> Microsoft.Extensions.DependencyInjection.IServiceCollection!
15-
virtual Microsoft.AspNetCore.Components.WebView.WebViewManager.Dispose(bool disposing) -> void
15+
virtual Microsoft.AspNetCore.Components.WebView.WebViewManager.DisposeAsyncCore() -> System.Threading.Tasks.ValueTask

src/Components/WebView/WebView/src/WebViewManager.cs

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
using System;
55
using System.Collections.Generic;
66
using System.IO;
7-
using System.Reflection;
87
using System.Threading.Tasks;
98
using Microsoft.Extensions.DependencyInjection;
109
using Microsoft.Extensions.FileProviders;
@@ -16,7 +15,7 @@ namespace Microsoft.AspNetCore.Components.WebView
1615
/// should subclass this to wire up the abstract and protected methods to the APIs of
1716
/// the platform's web view.
1817
/// </summary>
19-
public abstract class WebViewManager : IDisposable
18+
public abstract class WebViewManager : IAsyncDisposable
2019
{
2120
// These services are not DI services, because their lifetime isn't limited to a single
2221
// per-page-load scope. Instead, their lifetime matches the webview itself.
@@ -176,7 +175,10 @@ internal async Task AttachToPageAsync(string baseUrl, string startUrl)
176175
// If there was some previous attached page, dispose all its resources. We're not eagerly disposing
177176
// page contexts when the user navigates away, because we don't get notified about that. We could
178177
// change this if any important reason emerges.
179-
_currentPageContext?.Dispose();
178+
if (_currentPageContext != null)
179+
{
180+
await _currentPageContext.DisposeAsync();
181+
}
180182

181183
var serviceScope = _provider.CreateAsyncScope();
182184
_currentPageContext = new PageContext(_dispatcher, serviceScope, _ipcSender, baseUrl, startUrl);
@@ -203,26 +205,25 @@ record RootComponent
203205
/// <summary>
204206
/// Disposes the current <see cref="WebViewManager"/> instance.
205207
/// </summary>
206-
/// <param name="disposing"><c>true</c> when dispose was called explicitly; <c>false</c> when it is called as part of the finalizer.</param>
207-
protected virtual void Dispose(bool disposing)
208+
protected virtual async ValueTask DisposeAsyncCore()
208209
{
209210
if (!_disposed)
210211
{
211-
if (disposing)
212+
_disposed = true;
213+
214+
if (_currentPageContext != null)
212215
{
213-
_currentPageContext?.Dispose();
216+
await _currentPageContext.DisposeAsync();
214217
}
215-
216-
_disposed = true;
217218
}
218219
}
219220

220221
/// <inheritdoc/>
221-
public void Dispose()
222+
public async ValueTask DisposeAsync()
222223
{
223-
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
224-
Dispose(disposing: true);
224+
// Do not change this code. Put cleanup code in 'DisposeAsync(bool disposing)' method
225225
GC.SuppressFinalize(this);
226+
await DisposeAsyncCore();
226227
}
227228
}
228229
}

src/Components/WebView/WebView/test/WebViewManagerTests.cs

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,23 @@ public async Task AttachingToNewPage_DisposesExistingScopeAsync()
7777
Assert.NotSame(singleton.Services[0], singleton.Services[1]);
7878
}
7979

80+
[Fact]
81+
public async Task CanDisposeWebViewManagerWithAsyncDisposableServices()
82+
{
83+
// Arrange
84+
var services = RegisterTestServices()
85+
.AddTestBlazorWebView()
86+
.AddScoped<AsyncDisposableService>()
87+
.BuildServiceProvider();
88+
var fileProvider = new TestFileProvider();
89+
var webViewManager = new TestWebViewManager(services, fileProvider);
90+
await webViewManager.AddRootComponentAsync(typeof(MyComponentUsingScopedAsyncDisposableService), "#app", ParameterView.Empty);
91+
webViewManager.ReceiveAttachPageMessage();
92+
93+
// Act
94+
await webViewManager.DisposeAsync();
95+
}
96+
8097
[Fact]
8198
public async Task AddRootComponentsWithExistingSelector_Throws()
8299
{
@@ -122,6 +139,30 @@ public Task SetParametersAsync(ParameterView parameters)
122139
}
123140
}
124141

142+
private class MyComponentUsingScopedAsyncDisposableService : IComponent
143+
{
144+
private RenderHandle _handle;
145+
146+
public void Attach(RenderHandle renderHandle)
147+
{
148+
_handle = renderHandle;
149+
}
150+
151+
[Inject] public AsyncDisposableService MyAsyncDisposableService { get; set; }
152+
153+
public Task SetParametersAsync(ParameterView parameters)
154+
{
155+
_handle.Render(builder =>
156+
{
157+
builder.OpenElement(0, "p");
158+
builder.AddContent(1, "Hello world!");
159+
builder.CloseElement();
160+
});
161+
162+
return Task.CompletedTask;
163+
}
164+
}
165+
125166
private class SingletonService
126167
{
127168
public List<ScopedService> Services { get; } = new();
@@ -146,5 +187,10 @@ public void Dispose()
146187
Disposed = true;
147188
}
148189
}
190+
191+
public class AsyncDisposableService : IAsyncDisposable
192+
{
193+
public ValueTask DisposeAsync() => ValueTask.CompletedTask;
194+
}
149195
}
150196
}

0 commit comments

Comments
 (0)