Как получить экземпляр IHttpContextAccessor (или его эквивалент) в фоновой задаче? - PullRequest
1 голос
/ 01 апреля 2020

В моем ASP. Net Core 3.1 webapi я регистрирую IHttpContextAccessor как синглтон и внедряю его во все мои контроллеры. У меня есть интерфейс, который также вводится во все мои контроллеры и мои службы (которые, в свою очередь, подключаются к БД). Реализация:

public class PrincipalProvider : IPrincipalProvider
{
    private readonly UserPrincipal principal;

    public PrincipalProvider(IHttpContextAccessor accessor)
    {
        accessor.HttpContext.Items.TryGetValue("principal", out object principal);
        this.principal = principal as UserPrincipal;
    }

    public UserPrincipal GetPrincipal()
    {
        return principal;
    }
}

Ctor службы выглядит так:

    public MyService(
        IPrincipalProvider provider,
        ILogger<MyService> logger, 
        IUnitOfWork unitOfWork) : base(provider, logger, unitOfWork) 
    { }

Все вышеперечисленное работает, как и ожидалось, пока я нахожусь в контексте запроса.

У меня есть действие контроллера, которое запускает фоновую задачу с использованием новой реализации IHostedService с фоновой очередью, и оно запускается так:

backgroundQueue.QueueBackgroundWorkItem(async (scope, hubContext, ct) =>
{
    await hubContext.Clients.Client(provider.GetPrincipal().ConnectionId).Notify();
    var myService = scope.Resolve<IMyService>();
}

, где scope равно ILifetimeScope и hubConext - это IHubContext<MyHub, IMyHub>. Переменная provider - это IPrincipalProvider, который был введен в ctor контроллера.

Проблема в том, что когда я пытаюсь разрешить IMyService в задаче, он создает экземпляр IPrincipalProvider, и это в свою очередь требует IHttpContextAccessor, который больше не существует.

Какое решение в этом случае? Нужно ли иметь второй ctor в сервисе с другим IPrincipalProvider, который получает контекст откуда-то еще? И если это так, откуда?

1 Ответ

0 голосов
/ 01 апреля 2020

Лучшим решением было бы иметь 2 реализации IPrincipalProvider, одну, которая использует httpContextAccessor, и другую, которая использует что-то еще. К сожалению, не всегда легко иметь другую реализацию.

Когда вы создаете дочернюю область жизни, вы можете добавить регистрацию в эту дочернюю область жизни. Вы можете зарегистрировать StaticPrincipalProvider здесь.

 private async Task BackgroundProcessing(...) {
    ... 
    try {
        using(ILifetimeScope queueScope = this._rootScope.BeginLifetimeScope(builder => {
            builder.RegisterInstance(new StaticPrincipalProvider(principal))
                   .As<IPrincipalProvider>();
        })){
            await workItem(queueScope, stoppingToken);
        }
    }
    ...
 }

Все, что вам нужно сделать сейчас, - это найти способ получить соответствующий принципал, когда вы снимаете задачу. Для этого вы можете изменить реализацию BackgroundTaskQueue на использование ConcurrentQueue<WorkItem> вместо ConcurrentQueue<Func<ILifetimeScope, CancellationToken, Task>>, где WorkItem равно

public class WorkItem {
    public Func<ILifetimeScope, CancellationToken, Task> Work { get; private set; }
    public IPrincipal Principal { get; private set; }
    // or
    public Action<ContainerBuilder> builderAccessor { get; private set; }
}

, и поскольку BackgroundTaskQueue создается с областью запроса, вы будете иметь доступ к текущему принципалу.

...