I was following this answer, how to properly start new thread in ASP.NET Core 2.0 Controller method.
I need this because I have API that writes something to database (returns result back to user) and at the same time initiate a long running background task (some excel calculations), that are returned back to user via SignalR Core
I am using OpenIddict for authorization/authentication with JWT token.
Problem is that I need to get IHttpContextAccessor
so I can get logged in user id:
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);
}
});
I really need to get ClaimsPrincipal
. Someone may think problem is solveable with local variable userId, which is set before starting new thread. But in real application, many classes are created through dependency injection and one local variable would not solve my problem.
Problem is that services.HttpContext.User.Identity.Claims
count is 0
and so userId
is null
.
How can I get correct UserClaims
when using threading with IServiceScopeFactory
?
Edited:
I tried like this:
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
is 15 but at some random point in future Claims.Count
become 0. Usually after some awaited function call. (Not always the first).
Bad solution:
The only solution I found (and I really don't like it) is to create new 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);
}
It is not logic to me to create new Http Request just to get correct User (Identity) in new thread. I would really like to get a working solution with creating new thread.