WebApplicationFactory disposes host before StopAsync on background service is called
- Dominant language
- C#
- Stars
- 38.4k
- Forks
- 10.9k
- Avg merge
- 2d 10h
- Merged PRs (30d)
- 281
Description
### Is there an existing issue for this?
- [X] I have searched the existing issues
### Describe the bug
Please see the description from https://github.com/dotnet/aspnetcore/issues/50622
TL;DR; when deriving a `BackgroundService` and overriding `StopAsync` this fails with `ObjectDisposedException` for `serviceProvider` during integration testing:
```csharp
public override async Task StopAsync(CancellationToken token)
{
await base.StopAsync(token);
using var scope = serviceProvider.CreateScope();
}
```
Where `serviceProvider` was injected via constructor.
### Expected Behavior
I should be able to resolve services from DI until my stopping is done.
### Steps To Reproduce
See full repro here: https://github.com/lostmsu/ServiceProviderDisposedRepro (upgraded to .NET 8)
### Exceptions (if any)
```
Class cleanup failed in ServiceProviderDisposed.Tests.Tests
System.ObjectDisposedException
Cannot access a disposed object.
Object name: 'IServiceProvider'.
at Microsoft.Extensions.DependencyInjection.ServiceLookup.ThrowHelper.ThrowObjectDisposedException()
at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngineScope.GetService(Type serviceType)
at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(IServiceProvider provider, Type serviceType)
at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService[T](IServiceProvider provider)
at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.CreateScope(IServiceProvider provider)
at ServiceProviderDisposed.DummyBackgroundService.StopAsync(CancellationToken cancellationToken) in C:\Users\brome\Projects\Play\Bugs\ServiceProviderDisposedRepro\ServiceProviderDisposed\DummyBackgroundService.cs:line 24
at Microsoft.Extensions.Hosting.Internal.Host.ForeachService[T](IEnumerable`1 services, CancellationToken token, Boolean concurrent, Boolean abortOnFirstException, List`1 exceptions, Func`3 operation)
at Microsoft.Extensions.Hosting.Internal.Host.StopAsync(CancellationToken cancellationToken)
at Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactory`1.DisposeAsync()
at Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactory`1.Dispose(Boolean disposing)
at Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactory`1.Dispose()
at Xunit.Sdk.ExceptionAggregator.Run(Action code) in /_/src/xunit.core/Sdk/ExceptionAggregator.cs:line 73
```
### .NET Version
8.0.401
### Anything else?
The original bug report suggested to implement `IHostedLifecycleService`, but moving
```csharp
using var scope = serviceProvider.CreateScope();
```
to `StoppedAsync` does not prevent the issue.
The scenario I have in mind is to do something after my main service procedure ends (e.g. finalize database recording).
### Workaround
Just found a workaround: you can move the necessary code at the end of
```csharp
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
await Task.Delay(50);
}
DoYourThingHere();
}
```
The problem with that though is that you lose access to the `CancellationToken` from `StopAsync`: the one in `ExecuteAsync` has completely different purpose.
Contributor guide
Assessment
This issue has not been assessed yet.