Używanie autofaca z BackgroundService w ASP.NET Core 6 Web Api

0

Próbuję utworzyć metodę w kontrolerze web api (ASP.NET Core 6 Web Api), która zaakceptuje żądanie, zarejestruje je w kolejce, zwróci kod HTTP 200, a żądanie zostanie przetworzone w tle. Po przetworzeniu żądania planuję wysłać informacje o nim za pomocą SignalR. W tym celu stworzyłem usługę BackgroundService. Niestety nie wiem jak zmusić autofac do poprawnej pracy z tym rozwiązaniem. Kiedy próbuję wywołać metodę (która działa, gdy robię to z await i bez usługi BackgroundService), otrzymuję komunikat o błędzie:

Microsoft.Extensions.Hosting.Internal.Host[9]
      BackgroundService failed
      System.ObjectDisposedException: Cannot access a disposed context instance. A common cause of this error is disposing a context instance that was resolved from dependency injection and then later trying to use the same context instance elsewhere in your application. This may occur if you are calling 'Dispose' on the context instance, or wrapping it in a using statement. If you are using dependency injection, you should let the dependency injection container take care of disposing context instances.
      Object name: 'RepositoryManagerDbContext'.
         at Microsoft.EntityFrameworkCore.DbContext.CheckDisposed()
         at Microsoft.EntityFrameworkCore.DbContext.get_DbContextDependencies()
         at Microsoft.EntityFrameworkCore.DbContext.get_ContextServices()
         at Microsoft.EntityFrameworkCore.DbContext.get_Model()
         at Microsoft.EntityFrameworkCore.Internal.InternalDbSet`1.get_EntityType()
         at Microsoft.EntityFrameworkCore.Internal.InternalDbSet`1.get_EntityQueryable()
         at Microsoft.EntityFrameworkCore.Internal.InternalDbSet`1.System.Linq.IQueryable.get_Provider()
         at System.Linq.Queryable.Where[TSource](IQueryable`1 source, Expression`1 predicate)
         at RepositoryManager.Configuration.Services.ConfigurationService.GetConfigValueByType(RmConfigType type) in C:\GIT\repositoryManager\RepositoryManager\RepositoryManager.Configuration\Services\ConfigurationService.cs:line 36
         at RepositoryManager.Repository.Services.RepositoryService.PrepareRepositoryPath(String taskNumber, String appName) in C:\GIT\repositoryManager\RepositoryManager\RepositoryManager.Repository\Services\RepositoryService.cs:line 52
         at RepositoryManager.Repository.Services.RepositoryService.CreateDeveloperEnvironment(SaveTaskDto dto) in C:\GIT\repositoryManager\RepositoryManager\RepositoryManager.Repository\Services\RepositoryService.cs:line 31
         at RepositoryManager.Controllers.TaskController.CreateDeveloperEnvironmentAndNotify(SaveTaskDto request) in C:\GIT\repositoryManager\RepositoryManager\RepositoryManager\Controllers\TaskController.cs:line 33
         at BackgroundTaskService.ExecuteAsync(CancellationToken stoppingToken) in C:\GIT\repositoryManager\RepositoryManager\RepositoryManager\BackgroundTaskService.cs:line 18
         at BackgroundTaskService.ExecuteAsync(CancellationToken stoppingToken) in C:\GIT\repositoryManager\RepositoryManager\RepositoryManager\BackgroundTaskService.cs:line 15
         at Microsoft.Extensions.Hosting.Internal.Host.TryExecuteBackgroundServiceAsync(BackgroundService backgroundService)

Przygotowanie kontenera:

public void ConfigureContainer(ContainerBuilder builder)
        {
            builder.RegisterType<BackgroundTaskService>().AsSelf().As<IHostedService>().SingleInstance();
            builder.RegisterModule<DatabaseModule>();
            builder.RegisterModule<GitModule>();
            builder.RegisterModule<ConfigurationModule>();
            builder.RegisterModule<RedmineModule>();
            builder.RegisterModule<RepositoryModule>();
            builder.RegisterModule<DiskModule>();

            builder.RegisterType<SignalRHub>();
            
        }

W module Database jest:

builder.RegisterType<RepositoryManagerDbContext>().AsSelf().InstancePerLifetimeScope();

BackgroundService:

public class BackgroundTaskService : BackgroundService
{
    private readonly Channel<BackgroundTask> taskQueue;

    public BackgroundTaskService()
    {
        taskQueue = Channel.CreateUnbounded<BackgroundTask>();
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        await foreach (var backgroundTask in taskQueue.Reader.ReadAllAsync(stoppingToken))
        {
            Console.WriteLine($"Starting task {backgroundTask.Id}");
            await backgroundTask.Task();
            Console.WriteLine($"Completed task {backgroundTask.Id}");
        }
    }

    public async Task EnqueueTask(Func<Task> task)
    {
        var backgroundTask = new BackgroundTask { Task = task };
        Console.WriteLine($"Enqueueing task {backgroundTask.Id}");

        await taskQueue.Writer.WriteAsync(backgroundTask);
    }

}

Kontroler:

[Route("api/[controller]")]
    [ApiController]
    public class TaskController : ControllerBase
    {
        private readonly RepositoryService repoService;
        private readonly IHubContext<SignalRHub> hubContext;
        private readonly BackgroundTaskService backgroundTaskService;

        public TaskController(RepositoryService repoService, IHubContext<SignalRHub> hubContext, BackgroundTaskService backgroundTaskService)
        {
            this.repoService = repoService;
            this.hubContext = hubContext;
            this.backgroundTaskService = backgroundTaskService;
        }

        [HttpPost]
        public async Task CreateTaskRepo(SaveTaskDto request)
        {
            backgroundTaskService.EnqueueTask(() => CreateDeveloperEnvironmentAndNotify(request));
            //return Ok();
        }

        private async Task CreateDeveloperEnvironmentAndNotify(SaveTaskDto request)
        {
            await repoService.CreateDeveloperEnvironment(request);
            await hubContext.Clients.All.SendAsync("recivedmessage", "CreateTaskRepo", new { Status = "Completed" });
        }
    }

Domyślam się, że chodzi o to, ze po wysłaniu odpowiedzi HTTP 200 wykonywany jest dispose i dlatego nie mam dostępu do db. Nie wiem jak to poprawić, pomożecie?

2

Nie wiem jak z tym autofacem, ale gdybyś miał instancje ServiceProvidera

i tutaj, do wywołania Task() przekazywał ten ServiceProvider, a Task wewnątrz by go używał aby wyciągnąć sobie potrzebne Services (np. bazkę)?

await foreach (var backgroundTask in taskQueue.Reader.ReadAllAsync(stoppingToken))
{
    Console.WriteLine($"Starting task {backgroundTask.Id}");
    await backgroundTask.Task();
    Console.WriteLine($"Completed task {backgroundTask.Id}");
}

na

private readonly Channel<BackgroundTask> taskQueue;
blabla _serviceProvider;

public BackgroundTaskService(ServiceProvider ??)
{
    taskQueue = Channel.CreateUnbounded<BackgroundTask>();
}
  
await foreach (var backgroundTask in taskQueue.Reader.ReadAllAsync(stoppingToken))
{
    Console.WriteLine($"Starting task {backgroundTask.Id}");
    await backgroundTask.Task(_serviceProvider);
    Console.WriteLine($"Completed task {backgroundTask.Id}");
}

coś jak robią tutaj

https://learn.microsoft.com/en-us/dotnet/core/extensions/scoped-service?pivots=dotnet-7-0

namespace App.ScopedService;

public sealed class ScopedBackgroundService : BackgroundService
{
    private readonly IServiceProvider _serviceProvider;
    private readonly ILogger<ScopedBackgroundService> _logger;

    public ScopedBackgroundService(
        IServiceProvider serviceProvider,
        ILogger<ScopedBackgroundService> logger) =>
        (_serviceProvider, _logger) = (serviceProvider, logger);

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        _logger.LogInformation(
            $"{nameof(ScopedBackgroundService)} is running.");

        await DoWorkAsync(stoppingToken);
    }

    private async Task DoWorkAsync(CancellationToken stoppingToken)
    {
        _logger.LogInformation(
            $"{nameof(ScopedBackgroundService)} is working.");

        using (IServiceScope scope = _serviceProvider.CreateScope())
        {
            IScopedProcessingService scopedProcessingService =
                scope.ServiceProvider.GetRequiredService<IScopedProcessingService>();

            await scopedProcessingService.DoWorkAsync(stoppingToken);
        }
    }
    ```

1 użytkowników online, w tym zalogowanych: 0, gości: 1