@@ -111,11 +111,129 @@ public MyService(Counter counter)
111111 void IDisposable . Dispose ( ) => Counter . DisposedCount ++ ;
112112 }
113113
114+ [ Fact ]
115+ public async Task DisposeAsync_CallsDispose_WithDisposingTrue ( )
116+ {
117+ var services = new ServiceCollection ( ) ;
118+ services . AddSingleton < Counter > ( ) ;
119+ services . AddTransient < MyService > ( ) ;
120+ var serviceProvider = services . BuildServiceProvider ( ) ;
121+
122+ var renderer = new TestRenderer ( serviceProvider ) ;
123+ var component = ( ComponentWithDispose ) renderer . InstantiateComponent < ComponentWithDispose > ( ) ;
124+
125+ _ = component . MyService ;
126+ await ( ( IAsyncDisposable ) component ) . DisposeAsync ( ) ;
127+ Assert . True ( component . DisposingParameter ) ;
128+ }
129+
130+ [ Fact ]
131+ public async Task DisposeAsync_ThenDispose_IsIdempotent ( )
132+ {
133+ var services = new ServiceCollection ( ) ;
134+ services . AddSingleton < Counter > ( ) ;
135+ services . AddTransient < MyService > ( ) ;
136+ var serviceProvider = services . BuildServiceProvider ( ) ;
137+
138+ var counter = serviceProvider . GetRequiredService < Counter > ( ) ;
139+ var renderer = new TestRenderer ( serviceProvider ) ;
140+ var component = ( ComponentWithDispose ) renderer . InstantiateComponent < ComponentWithDispose > ( ) ;
141+
142+ _ = component . MyService ;
143+
144+ await ( ( IAsyncDisposable ) component ) . DisposeAsync ( ) ;
145+ var firstCallCount = component . DisposeCallCount ;
146+ Assert . Equal ( 1 , counter . DisposedCount ) ;
147+
148+ ( ( IDisposable ) component ) . Dispose ( ) ;
149+ Assert . True ( component . DisposeCallCount >= firstCallCount ) ;
150+ Assert . Equal ( 1 , counter . DisposedCount ) ;
151+ }
152+
153+ private class ComponentWithDispose : OwningComponentBase < MyService >
154+ {
155+ public MyService MyService => Service ;
156+ public bool ? DisposingParameter { get ; private set ; }
157+ public int DisposeCallCount { get ; private set ; }
158+
159+ protected override void Dispose ( bool disposing )
160+ {
161+ DisposingParameter = disposing ;
162+ DisposeCallCount ++ ;
163+ base . Dispose ( disposing ) ;
164+ }
165+ }
166+
114167 private class MyOwningComponent : OwningComponentBase < MyService >
115168 {
116169 public MyService MyService => Service ;
117170
118171 // Expose IsDisposed for testing
119172 public bool IsDisposedPublic => IsDisposed ;
120173 }
174+
175+ [ Fact ]
176+ public async Task ComplexComponent_DisposesResourcesOnlyWhenDisposingIsTrue ( )
177+ {
178+ var services = new ServiceCollection ( ) ;
179+ services . AddSingleton < Counter > ( ) ;
180+ services . AddTransient < MyService > ( ) ;
181+ var serviceProvider = services . BuildServiceProvider ( ) ;
182+
183+ var renderer = new TestRenderer ( serviceProvider ) ;
184+ var component = ( ComplexComponent ) renderer . InstantiateComponent < ComplexComponent > ( ) ;
185+
186+ _ = component . MyService ;
187+
188+ await ( ( IAsyncDisposable ) component ) . DisposeAsync ( ) ;
189+
190+ // Verify all managed resources were disposed because disposing=true
191+ Assert . True ( component . TimerDisposed ) ;
192+ Assert . True ( component . CancellationTokenSourceDisposed ) ;
193+ Assert . True ( component . EventUnsubscribed ) ;
194+ Assert . Equal ( 1 , component . ManagedResourcesCleanedUpCount ) ;
195+ }
196+
197+ private class ComplexComponent : OwningComponentBase < MyService >
198+ {
199+ private readonly System . Threading . Timer _timer ;
200+ private readonly CancellationTokenSource _cts ;
201+ private bool _eventSubscribed ;
202+
203+ public MyService MyService => Service ;
204+ public bool TimerDisposed { get ; private set ; }
205+ public bool CancellationTokenSourceDisposed { get ; private set ; }
206+ public bool EventUnsubscribed { get ; private set ; }
207+ public int ManagedResourcesCleanedUpCount { get ; private set ; }
208+
209+ public ComplexComponent ( )
210+ {
211+ _timer = new System . Threading . Timer ( _ => { } , null , Timeout . Infinite , Timeout . Infinite ) ;
212+ _cts = new CancellationTokenSource ( ) ;
213+ _eventSubscribed = true ;
214+ }
215+
216+ protected override void Dispose ( bool disposing )
217+ {
218+ if ( disposing )
219+ {
220+ _timer ? . Dispose ( ) ;
221+ TimerDisposed = true ;
222+
223+ _cts ? . Cancel ( ) ;
224+ _cts ? . Dispose ( ) ;
225+ CancellationTokenSourceDisposed = true ;
226+
227+ if ( _eventSubscribed )
228+ {
229+ EventUnsubscribed = true ;
230+ _eventSubscribed = false ;
231+ }
232+
233+ ManagedResourcesCleanedUpCount ++ ;
234+ }
235+
236+ base . Dispose ( disposing ) ;
237+ }
238+ }
121239}
0 commit comments