Skip to content

Commit 2565cd0

Browse files
CopilotPureWeen
andcommitted
Implement IServiceScope disposal when window is destroyed
Co-authored-by: PureWeen <5375137+PureWeen@users.noreply.github.com>
1 parent f3d48c1 commit 2565cd0

File tree

4 files changed

+53
-1
lines changed

4 files changed

+53
-1
lines changed

src/Controls/src/Core/Window/Window.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -552,6 +552,11 @@ void IWindow.Destroying()
552552

553553
AlertManager.Unsubscribe();
554554
Application?.RemoveWindow(this);
555+
556+
// Dispose the window-scoped service scope
557+
if (Handler?.MauiContext is MauiContext mauiContext)
558+
mauiContext.DisposeWindowScope();
559+
555560
Handler?.DisconnectHandler();
556561
}
557562

src/Controls/tests/Core.UnitTests/WindowsTests.cs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System.Linq;
55
using System.Reflection;
66
using System.Threading.Tasks;
7+
using Microsoft.Extensions.DependencyInjection;
78
using Microsoft.Maui.Graphics;
89
using Xunit;
910

@@ -766,5 +767,38 @@ public async Task TwoKeysSameWindow()
766767
Assert.Same(window, actual);
767768
Assert.Empty(table);
768769
}
770+
771+
[Fact]
772+
public void WindowServiceScopeIsDisposedOnDestroying()
773+
{
774+
var serviceCollection = new ServiceCollection();
775+
serviceCollection.AddTransient<TestScopedService>();
776+
var serviceProvider = serviceCollection.BuildServiceProvider();
777+
778+
var scope = serviceProvider.CreateScope();
779+
var mauiContext = new MauiContext(scope.ServiceProvider);
780+
mauiContext.SetWindowScope(scope);
781+
782+
var window = new TestWindow(new ContentPage());
783+
var handler = new WindowHandlerStub();
784+
handler.SetMauiContext(mauiContext);
785+
window.Handler = handler;
786+
787+
// Verify the scope service works before disposal
788+
var service = mauiContext.Services.GetService<TestScopedService>();
789+
Assert.NotNull(service);
790+
791+
// Destroy the window - this should dispose the scope
792+
((IWindow)window).Destroying();
793+
794+
// After disposal, the scope should be disposed
795+
// We can't directly test if scope is disposed, but we can test that trying to use it throws
796+
Assert.Throws<ObjectDisposedException>(() => scope.ServiceProvider.GetService<TestScopedService>());
797+
}
798+
799+
private class TestScopedService
800+
{
801+
public string TestProperty { get; set; } = "test";
802+
}
769803
}
770804
}

src/Core/src/MauiContext.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ public class MauiContext : IMauiContext
1111
{
1212
readonly WrappedServiceProvider _services;
1313
readonly Lazy<IMauiHandlersFactory> _handlers;
14+
IServiceScope? _windowScope;
1415

1516
#if ANDROID
1617
readonly Lazy<Context?> _context;
@@ -53,6 +54,16 @@ internal void AddWeakSpecific<TService>(TService instance)
5354
_services.AddSpecific(typeof(TService), static state => ((WeakReference)state).Target, new WeakReference(instance));
5455
}
5556

57+
internal void SetWindowScope(IServiceScope scope)
58+
{
59+
_windowScope = scope;
60+
}
61+
62+
internal void DisposeWindowScope()
63+
{
64+
_windowScope?.Dispose();
65+
}
66+
5667
class WrappedServiceProvider : IServiceProvider
5768
{
5869
readonly ConcurrentDictionary<Type, (object, Func<object, object?>)> _scopeStatic = new();

src/Core/src/MauiContextExtensions.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,6 @@ public static IMauiContext MakeApplicationScope(this IMauiContext mauiContext, N
4747
public static IMauiContext MakeWindowScope(this IMauiContext mauiContext, NativeWindow platformWindow, out IServiceScope scope)
4848
{
4949
// Create the window-level scopes that will only be used for the lifetime of the window
50-
// TODO: We need to dispose of these services once the window closes
5150
scope = mauiContext.Services.CreateScope();
5251

5352
#if ANDROID
@@ -56,6 +55,9 @@ public static IMauiContext MakeWindowScope(this IMauiContext mauiContext, Native
5655
var scopedContext = new MauiContext(scope.ServiceProvider);
5756
#endif
5857

58+
// Store the scope in the scoped context so it can be disposed when the window is destroyed
59+
scopedContext.SetWindowScope(scope);
60+
5961
scopedContext.AddWeakSpecific(platformWindow);
6062

6163
#if ANDROID

0 commit comments

Comments
 (0)