Я следил за этот ответ , как правильно запустить новый поток в методе контроллера ASP.NET Core 2.0.
Мне это нужно, потому что у меня есть API, который записывает что-то в базу данных (возвращает результат обратно пользователю) и в то же время запускает длительную фоновую задачу (некоторые вычисления в Excel), которая возвращается пользователю через SignalR Core * 1005. *
Я использую 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-запрос только для того, чтобы получить правильного пользователя (идентификатор) в новом потоке. Мне бы очень хотелось получить рабочее решение с созданием новой темы.