ASP.NET Core запустите новый поток и получите правильный (старый IHttpContextAccessor), чтобы получить ClaimsPrincipal (зарегистрированный пользователь)

Я следил за этот ответ, как правильно запустить новый поток в методе контроллера ASP.NET Core 2.0.
Мне это нужно, потому что у меня есть API, который что-то записывает в базу данных (возвращает результат обратно в пользователя) и в то же время инициировать длительную фоновую задачу (некоторые вычисления в Excel), которые возвращаются обратно пользователю через SignalR Core

Я использую OpenIddict для авторизации/аутентификации с токеном JWT.

Проблема в том, что мне нужно получить IHttpContextAccessor, чтобы я мог войти в систему с идентификатором пользователя:

var serviceScopeFactory = this.ServiceProvider.GetService< IServiceScopeFactory>();
Task.Factory.StartNew(() =>
{
    using (var scope = serviceScopeFactory.CreateScope())
    {
        Thread.Sleep(3000);     //Just for testing to be sure, API request is completed.
        var services = scope.ServiceProvider.GetService<IHttpContextAccessor>();
        var claims = services2.HttpContext.User.FindFirst(ClaimTypes.NameIdentifier);
        var loggedInUserId = claims != null ? Int32.Parse(claims.Value) : (int?)null;

        var res = ProcessExcel();
        signalrServices.Send(loggedInUserId, res);
    }
});

Мне очень нужно получить ClaimsPrincipal. Кто-то может подумать, что проблема решаема с помощью локальной переменной userId, которая устанавливается перед запуском нового потока. Но в реальном приложении многие классы создаются путем внедрения зависимостей, и одна локальная переменная не решит мою проблему.

Проблема в том, что services.HttpContext.User.Identity.Claims количество равно 0, поэтому userId равно null.

Как я могу получить правильный UserClaims при использовании потоков с IServiceScopeFactory?

Отредактировано:

Я пробовал так:

public IActionResult DoSomething(string lang, int id)
{
    DoSomething();

    //Process in new thread
    Task.Factory.StartNewWithUser(this.ServiceProvider, async scope =>
    {
        await scope.ServiceProvider.GetService<LiveCalculations>().Add(lang, id);
    });

    return new JsonResult(true);
}    

public static Task StartNewWithUser(this TaskFactory factory, IServiceProvider serviceProvider, Func<IServiceScope, Task> action)
{
    var scopeFactory = serviceProvider.GetService<IServiceScopeFactory>();
    var contextAccessor = serviceProvider.GetService<IHttpContextAccessor>();
    var user = contextAccessor.HttpContext.User.Clone();

    return Task.Factory.StartNew(() =>
    {
        using (var scope = scopeFactory.CreateScope())
        {
            Thread.CurrentPrincipal = user;             //https://forums.asp.net/t/1829657.aspx?+How+to+set+HttpContext+User+Identity+for+an+application+manually+
            scope.ServiceProvider.GetService<IHttpContextAccessor>().HttpContext.User = user;
            action(scope).Wait();
        }
    });
}

Claims.Count равно 15, но в какой-то случайный момент в будущем Claims.Count станет 0. Обычно после некоторого ожидаемого вызова функции. (не всегда первое).

Плохое решение:

Единственное решение, которое я нашел (и оно мне действительно не нравится), — создать новый HttpRequest:

public string GetAccessToken()
{
    var bearer = this.Request.Headers["Authorization"].FirstOrDefault();
    if (string.IsNullOrEmpty(bearer))
        return "";
    return bearer.Substring("Bearer ".Length);
}

public IActionResult DoSomething(int id)
{
    DoSomeWork();

    //Instead creating new thread
    var service = this.ServiceProvider.GetService<RequestService>();
    string url = $"{service.Scheme}://{service.Host}/API/excel/";
    HttpClient client = new HttpClient();
#pragma warning disable 4014
    client.PutAsync(url + $"PrceossExcel/{id}?access_token={accessToken}", null);    //notice without await
#pragma warning restore 4014

    return new JsonResult(true);
}

Для меня нелогично создавать новый запрос Http только для того, чтобы получить правильного пользователя (идентификацию) в новом потоке. Я бы очень хотел получить рабочее решение с созданием нового потока.


person Makla    schedule 14.05.2018    source источник
comment
Почему фоновому заданию нужен доступ к IHttpContextAccessor? Если вам нужен доступ к идентификатору пользователя, связанному с заданием, вы можете сохранить их вместе в постоянном хранилище.   -  person Win    schedule 14.05.2018
comment
Не фоновое задание, а классы, созданные через DI в фоновом процессе.   -  person Makla    schedule 14.05.2018
comment
Это случалось и раньше: использование текущего свойства httpcontext и связанных с ним вещей"> stackoverflow.com/questions/8109526/. Мне/вам нужно проверить, какова реализация IHttpContextAccessor, но могу поспорить, что для кросс-потока вам нужна собственная реализация.   -  person Razvan Dumitru    schedule 15.05.2018