Я следил за этот ответ, как правильно запустить новый поток в методе контроллера 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 только для того, чтобы получить правильного пользователя (идентификацию) в новом потоке. Я бы очень хотел получить рабочее решение с созданием нового потока.
IHttpContextAccessor
? Если вам нужен доступ к идентификатору пользователя, связанному с заданием, вы можете сохранить их вместе в постоянном хранилище. - person Win   schedule 14.05.2018IHttpContextAccessor
, но могу поспорить, что для кросс-потока вам нужна собственная реализация. - person Razvan Dumitru   schedule 15.05.2018