I am using ASP.NET MVC 5 and Identity Framework. When I call UserManager.UpdateAsync(...) my eventhandlers on ApplicationDbContext() SaveChanges will run. Here I am using HttpContext.Current for different purposes (logging and auditing) so I must get say current user. However the whole method runs in a worker thread, and here HttpContext.Current is null.
The biggest problem that the UserManager's "sync" methods are only wrappers around the async version, so the calls are serialized, but the methods (and eventhandlers) still run in a different worker thread.
Please note this issue has nothing to do with the async/await context. In the controller after the await (or calling the 'sync' version) I have back the correct HttpContext, even the controller's method is continuing in an other thread. That's fine.
So the problem is inside the async worker which will run in both the "sync" and async versions. I think I am understanding the phenomena (but I am not happy with the fake 'sync' method versions, real sync methods would not exhibit this issue.) I just does not know how to deal/workaround it.
[btw: Would not it be more natural to implement UserManager's operarations as simple pure sync versions, then wrap them by async multithreaded wrappers?. IF we continue this async fashion without thinking we will soon invent the async assignment operator. It costs me dozens of hours (just this issue), and costs worldwide zillion dollars, I am sure in many cases less return than its price.]
Bonus: We are talking about UserManager which's impact pretty marginal, but the same principles and issues can apply any out of the box library (black box for you) which authors do not implement sync versions and or do not care about the controller thread's context. What about EF, it is not so marginal... and what about DI containers instantiation infrastructure like "request scope" or "session scope". Surely they misbehave if resolving occurs in a thread with no HttpContext.Current. Recently I refreshed SendGrid NuGet, and (as a breaking change) Deliver() method gone, and now only DeliverAsync() is existing...
I would like to have a safe reliable way, how can I access the HttpContext inside this worker for logging and audit purposes.
Sample code, the controller 'sync' version:
[AcceptVerbs(HttpVerbs.Post)]
public virtual ActionResult Edit(ApplicationUser user)
{
// validation etc
// Update() seems to be only a poor wrapper around the async version, still uses a worker thread.
var result = UserManager.Update(user);
// Note: HttpContext is correct here so it is not an async/await problem
// error handling, creating ActionResult etc.
}
Sample code, the controller async version:
[AcceptVerbs(HttpVerbs.Post)]
public virtual async Task<ActionResult> Edit(ApplicationUser user)
{
// validation etc
var result = await UserManager.UpdateAsync(user);
// Note: HttpContext is correct here so it is not an async/await problem
// error handling, creating ActionResult etc.
}
and the event handler where HttpContext is null:
public ApplicationDbContext() : base("DefaultConnection", false)
{
InitializeAudit();
}
private void InitializeAudit()
{
var octx = ((IObjectContextAdapter) this).ObjectContext;
octx.SavingChanges +=
(sender, args) =>
{
// HttpContext.Current is null here
};
}
Any ideas?
As you said, this occurs because of threading. The delegate runs in a different thread, making the HttpContext inaccessible.
You can move the variable outside of the delegate, making it a closure.
private void InitializeAudit()
{
var octx = ((IObjectContextAdapter) this).ObjectContext;
HttpContext context = HttpContext.Current;
octx.SavingChanges +=
(sender, args) =>
{
// context is not null
};
}
You are using asp.net identity through owin,
so one instance of the dbcontext is created per request,
and you can get this reference from anywhere in the request pipeline.
nb. this is handy but i think the dbcontext shouldn't be accessed outside the manager.
In asp.net identity design, only the manager should be aware of the store.
I believe the dbcontext is exposed because several asp.net identity middleware have a dependance on it.
But, it could help resolve you problem:
Allow your custom dbcontext handler to be set outside the class:
public EventHandler SavingChangesEventHandler
{
set
{
(((System.Data.Entity.Infrastructure.IObjectContextAdapter)this).ObjectContext).SavingChanges += value;
}
}
Declare a custom ActionFilter class and register it, then override OnActionExecuting:
Filtering in ASP.NET MVC
https://msdn.microsoft.com/en-us/library/gg416513(VS.98).aspx
public class CustomizeAppDbcontextFilter : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
var dbcontext = HttpContext.GetOwinContext().Get<ApplicationDbContext>();
var currentuser = HttpContext.Current.User;
dbcontext.SavingChangesEventHandler = (sender, args) =>
{
// use currentuser
};
}
}
you may need these using statements to be able to call the identity.owin extension methods:
using Microsoft.AspNet.Identity;
using Microsoft.AspNet.Identity.Owin;
You should be in the controller thread because OnActionExecuting is wrapping the controller action.
I did not test it, so it may need some polishing but the concept should work.
Related
I have an ASP.NET Core web app, with WebAPI controllers. All I am trying to do is, in some of the controllers, be able to kick off a process that would run in the background, but the controller should go ahead and return before that process is done. I don't want the consumers of the service to have to wait for this job to finish.
I have seen all of the posts about IHostedService and BackgroundService, but none of them seem to be what I want. Also, all these examples show you how to set things up, but not how to actually call it, or I am not understanding some of it.
I tried these, but when you register an IHostedService in Startup, it runs immediately at that point in time. This is not what I want. I don't want to run the task at startup, I want to be able to call it from a controller when it needs to. Also, I may have several different ones, so just registering services.AddHostedService() won't work because I might have a MyServiceB and MyServiceC, so how do I get the right one from the controller (I can't just inject IHostedService)?
Ultimately, everything I have seen has been a huge, convoluted mess of code for something that seems like it should be such a simple thing to do. What am I missing?
You have the following options:
IHostedService classes can be long running methods that run in the background for the lifetime of your app. In order to make them to handle some sort of background task, you need to implement some sort of "global" queue system in your app for the controllers to store the data/events. This queue system can be as simple as a Singleton class with a ConcurrentQueue that you pass in to your controller, or something like an IDistributedCache or more complex external pub/sub systems. Then you can just poll the queue in your IHostedService and run certain operations based on it. Here is a microsoft example of IHostedService implementation for handling queues https://learn.microsoft.com/en-us/aspnet/core/fundamentals/host/hosted-services?view=aspnetcore-3.1&tabs=visual-studio#queued-background-tasks
Note that the Singleton class approach can cause issues in multi-server environments.
Example implementation of the Singleton approach can be like:
// Needs to be registered as a Singleton in your Startup.cs
public class BackgroundJobs {
public ConcurrentQueue<string> BackgroundTasks {get; set;} = new ConcurrentQueue<string>();
}
public class MyController : ControllerBase{
private readonly BackgroundJobs _backgroundJobs;
public MyController(BackgroundJobs backgroundJobs) {
_backgroundJobs = backgroundJobs;
}
public async Task<ActionResult> FireAndForgetEndPoint(){
_backgroundJobs.BackgroundTasks.Enqueue("SomeJobIdentifier");
}
}
public class MyBackgroundService : IHostedService {
private readonly BackgroundJobs _backgroundJobs;
public MyBackgroundService(BackgroundJobs backgroundJobs)
{
_backgroundJobs = backgroundJobs;
}
public void StartAsync(CancellationToken ct)
{
while(!ct.IsCancellationRequested)
{
if(_backgroundJobs.BackgroundTasks.TryDequeue(out var jobId))
{
// Code to do long running operation
}
Task.Delay(TimeSpan.FromSeconds(1)); // You really don't want an infinite loop here without having any sort of delays.
}
}
}
Create a method that returns a Task, pass in a IServiceProvider to that method and create a new Scope in there to make sure ASP.NET would not kill the task when the controller Action completes. Something like
IServiceProvider _serviceProvider;
public async Task<ActionResult> FireAndForgetEndPoint()
{
// Do stuff
_ = FireAndForgetOperation(_serviceProvider);
Return Ok();
}
public async Task FireAndForgetOperation(IServiceProvider serviceProvider)
{
using (var scope = _serviceProvider.CreateScope()){
await Task.Delay(1000);
//... Long running tasks
}
}
Update: Here is the Microsoft example of doing something similar: https://learn.microsoft.com/en-us/aspnet/core/performance/performance-best-practices?view=aspnetcore-3.1#do-not-capture-services-injected-into-the-controllers-on-background-threads
As I understand from your question you want to create a fire and forget task like logging to database. In this scenario you don't have to wait for log to be inserted database. It also took much of my time to discover an easily implementable solution. Here is what I have found:
In your controller parameters, add IServiceScopeFactory. This will not effect the request body or header. After that create a scope and call your service over it.
[HttpPost]
public IActionResult MoveRecordingToStorage([FromBody] StreamingRequestModel req, [FromServices] IServiceScopeFactory serviceScopeFactory)
{
// Move record to Azure storage in the background
Task.Run(async () =>
{
try
{
using var scope = serviceScopeFactory.CreateScope();
var repository = scope.ServiceProvider.GetRequiredService<ICloudStorage>();
await repository.UploadFileToAzure(req.RecordedPath, key, req.Id, req.RecordCode);
}
catch(Exception e)
{
Console.WriteLine(e);
}
});
return Ok("In progress..");
}
After posting your request, you will immediately receive In Progress.. text but your task will run in the background.
One more thing, If you don't create your task in this way and try to call database operations you will receive an error like this which means your database object is already dead and you are trying to access it;
Cannot access a disposed object. A common cause of this error is disposing a context that was resolved from dependency injection and then later trying to use the same context instance elsewhere in your application. This may occur if you are calling Dispose() on the context, or wrapping the context in a using statement. If you are using dependency injection, you should let the dependency injection container take care of disposing context instances.\r\nObject name: 'DBContext'.
My code is based on Repository pattern. You should not forget to inject service class in your Startup.cs
services.AddScoped<ICloudStorage, AzureCloudStorage>();
Find the detailed documentation here.
What is the simplest way to run a single background task from a controller in .NET Core?
I don't want the consumers of the service to have to wait for this job to finish.
Ultimately, everything I have seen has been a huge, convoluted mess of code for something that seems like it should be such a simple thing to do. What am I missing?
The problem is that ASP.NET is a framework for writing web services, which are applications that respond to requests. But as soon as your code says "I don't want the consumers of the service to have to wait", then you're talking about running code outside of a request (i.e., request-extrinsic code). This is why all solutions are complex: your code has to bypass/extend the framework itself in an attempt to force it to do something it wasn't designed to do.
The only proper solution for request-extrinsic code is to have a durable queue with a separate background process. Anything in-process (e.g., ConcurrentQueue with an IHostedService) will have reliability problems; in particular, those solutions will occasionally lose work.
What I want to achieve:
Save the IP of a User hitting specific actions/controllers to my database. Also since this process takes a significant amount of time it would be good if it gets executed on a background thread or similar.
What I tried so far:
Creating a CustomAuthorizeAttribute that looks something like this:
public class LoggedAuthorizeAttribute : TypeFilterAttribute
{
public LoggedAuthorizeAttribute() : base(typeof(LoggedAuthorizeFilter))
{
}
}
public class LoggedAuthorizeFilter : IAuthorizationFilter
{
private readonly UserManager<User> _userManager;
public LoggedAuthorizeFilter(UserManager<User> userManager)
{
_userManager = userManager;
}
public async void OnAuthorization(AuthorizationFilterContext context)
{
if (!context.HttpContext.User.Identity.IsAuthenticated)
return;
var user = await _userManager.GetUserAsync(context.HttpContext.User);
var remoteIpAddress = context.HttpContext.Connection.RemoteIpAddress;
user.UserLogins.Add(new UserLogin
{LoggedInOn = DateTimeOffset.UtcNow, LoggedInFrom = remoteIpAddress});
await _userManager.UpdateAsync(user);
}
}
Problems with this solution:
When the request hits an action marked with this attribute the request will take about 1-2 seconds until it actually processes the action.
The UserManager is retrieved by Dependency Injection, but I also access the UserManager instance in some of my Actions which causes a InvalidOperationException telling A second operation started on this context before a previous operation completed. This is usually caused by different threads using the same instance of DbContext, however instance members are not guaranteed to be thread safe. This could also be caused by a nested query being evaluated on the client, if this is the case rewrite the query avoiding nested invocations..
Any kind of help is appreciated.
Update
As suggested by Kirk Larkin implementing the IAsyncActionFilter instead fixes my second issue I ran into. But still how would I perform this in a Background Thread or similar, if this is even the right choice.
So I fixed my first issue with the use of a ConcurrentQueue which dequeues its items on a Background Thread. I found this solution in this blog post. It just needs some slight modifications in order to work for this problem.
It seems like the execution context is not kept until Dispose is called on elements resolved in the controller scope. This is probably due to the fact that asp.net core has to jump between native and managed code and resets the execution context at each jump. Seems like the correct context is not restored any more before the scope is disposed.
The following demonstrates the issue - simply put this in the default asp.net core sample project and register TestRepo as a transient dependency.
When calling GET api/values/ we set the value for the current task to 5 in a static AsyncLocal at the start of the call. That value flows as expected through awaits without any problem. But when the controller and its dependencies are disposed after the call the AsyncLocal context is already reset.
[Route("api/[controller]")]
public class ValuesController : Controller
{
private readonly TestRepo _testRepo;
public ValuesController(TestRepo testRepo) => _testRepo = testRepo;
[HttpGet()]
public async Task<IActionResult> Get()
{
_testRepo.SetValue(5);
await Task.Delay(100);
var val = _testRepo.GetValue(); // val here has correctly 5.
return Ok();
}
}
public class TestRepo : IDisposable
{
private static readonly AsyncLocal<int?> _asyncLocal = new AsyncLocal<int?>();
public int? GetValue() => _asyncLocal.Value;
public void SetValue(int x) => _asyncLocal.Value = x;
public void Foo() => SetValue(5);
public void Dispose()
{
if (GetValue() == null)
{
throw new InvalidOperationException(); //GetValue() should be 5 here :(
}
}
}
Is this intentional? And if yes is there any workaround around this problem?
The behavior you are seeing is an unfortunate quirk in the way that ASP.NET Core works. It's unclear to me why Microsoft choose this behavior, but it seems copied from the way Web API worked, which has the exact behavior. Disposing is obviously done at the end of the request, but for some reason the asynchronous context is already cleared before that point, making it impossible to run the complete request in a single asynchronous context.
You've basically got two options:
Instead of using ambient state to share state, flow state through the object graph instead of using ambient state. In other words, make TestRepo Scoped, and store value in a private field.
Move the operation that uses that value to an earlier stage in the request. For instance, you can define some middleware that wraps a request and invokes that operation at the end. At that stage, the asynchronous context will still exist.
Some DI containers actually apply this second technique. Simple Injector, for instance, uses scoping that is based on ambient state, using AsyncLocal<T> under the covers. When integrated in ASP.NET Core, it will wrap the request in a piece of middleware that applies this scope. This means that any Scoped component, resolved from Simple Injector, will be disposed before the ASP.NET Core pipeline disposes its services, and this happens while the asynchronous context is still available.
All,
I am writing code that looks like:
public class UserController : AuthenticatedController
{
private MunicipalContext db = new MunicipalContext();
// GET: Users/Edit/5
public ActionResult Edit(int? id)
{
//Do stuff with db.Users
}
// Other action methods that do stuff with db.Users
}
Coming from a Java background, this feels like Servlets and instance fields and is starting to raise that buzzing feeling in the back of my head.
Am I going to run into thread safety issues?
Each controller will be created once per request, they are not singletons. No instance member is shared between threads.
So the answer is no, there is no threading issue with your code, each time a new request to an action of your UserController is made a new UserController and a new MunicipalContext are created.
A side note: remember to Dispose() your MunicipalContext instance when is no longer used. More info here.
You don't have any thread-safety issues, but typically you would use a slightly different pattern with ASP.NET MVC where the context is injected into the controller using IoC to achieve a context-per-request pattern
The problems would arise if your controller consume some services which are also doing database acccess - if you don't share the context between them you can get some odd and/or incorrect behaviour.
The IoC container can then perform the dispose on the context after the request is processed; how you actually do this depends on whether you are using traditional ASP.NET (via a HttpModule) or Owin (middleware component)
I am writing unit test for MVC 5 web application. I have mocked the HttpContext.Current from my test.
When run following code form the test httpSessionStateAfter throw
System.AggregateException : One or more errors occurred.
----> System.NullReferenceException : Object reference not set to an instance of an object.
This happen only when i run the unit tests. When application run this work fine.
I'm using Nunit 2.6.3 with reshaper test runner.
var httpSessionStateBefour = System.Web.HttpContext.Current.Session;
var Person= await Db.Persons.FirstOrDefaultAsync();
var httpSessionStateAfter = System.Web.HttpContext.Current.Session;
How to over come this problem?
This is how i mock the HttpContext
HttpContext.Current = Fakes.FakeHttpContext();
HttpContext.Current.Session.Add("IsUserSiteAdmin", true);
HttpContext.Current.Session.Add("CurrentSite", null);
public static class Fakes
{
public static HttpContext FakeHttpContext()
{
var httpRequest = new HttpRequest("", "http://stackoverflow/", "");
var stringWriter = new StringWriter();
var httpResponce = new HttpResponse(stringWriter);
var httpContext = new HttpContext(httpRequest, httpResponce);
var sessionContainer = new HttpSessionStateContainer("id", new SessionStateItemCollection(),
new HttpStaticObjectsCollection(), 10, true,
HttpCookieMode.AutoDetect,
SessionStateMode.InProc, false);
httpContext.Items["AspSession"] = typeof (HttpSessionState).GetConstructor(
BindingFlags.NonPublic | BindingFlags.Instance,
null, CallingConventions.Standard,
new[] {typeof (HttpSessionStateContainer)},
null)
.Invoke(new object[] {sessionContainer});
return httpContext;
}
}
First, I do recommend that you isolate your code as much as possible from HttpContext.Current; not only will this make your code more testable, but it will help prepare you for ASP.NET vNext, which is more OWIN-like (without an HttpContext.Current).
However, that can require a lot of changes, which you may not yet be ready for. To properly mock HttpContext.Current, you need to understand how it works.
HttpContext.Current is a per-thread variable that is controlled by the ASP.NET SynchronizationContext. This SynchronizationContext is a "request context", representing the current request; it's created by ASP.NET when a new request comes in. I have an MSDN article on SynchronizationContext if you're interested in more details.
As I explain in my async intro blog post, when you await a Task, by default it will capture the current "context" and use that to resume the async method. When an async method is running within an ASP.NET request context, the "context" captured by the await is the ASP.NET SynchronizationContext. When the async method resumes (possibly on a different thread), the ASP.NET SynchronizationContext will set HttpContext.Current before resuming the async method. This is how async/await works within an ASP.NET host.
Now, when you run the same code in a unit test, the behavior is different. Specifically, there is no ASP.NET SynchronizationContext to set HttpContext.Current. I'm assuming that your unit test method returns Task, in which case NUnit does not provide a SynchronizationContext at all. So, when the async method resumes (possibly on a different thread), its HttpContext.Current may not be the same one.
There's a few different ways to fix this. One option is to write your own SynchronizationContext that preserves HttpContext.Current, just like the ASP.NET one does. An easier (but less efficient) option is to use a SynchronizationContext that I wrote called AsyncContext, which ensures the async method will resume on the same thread. You should be able to install my AsyncEx library from NuGet and then wrap your unit test methods inside a call to AsyncContext.Run. Note that the unit test methods are now synchronous:
[Test]
public void MyTest()
{
AsyncContext.Run(async () =>
{
// Test logic goes here: set HttpContext.Current, etc.
});
}
HttpContext.Current is considered a pretty terrible property to work with; it doesn't behave itself outside of its ASP.NET home. The best way to fix your code is to stop looking at this property and find a way to isolate it from the code you are testing. For example, you could create an interface which represents your current session's data and expose that interface to the component you are testing, with an implementation that requires an HTTP context.
The root problem is to do with how the HttpContext.Current works. This property is "magical" in the ASP.NET framework, in that it is unique for a request-response operation, but jumps between threads as the execution requires it to - it is selectively shared between threads.
When you use HttpContext.Current outside of the ASP.NET processing pipeline, the magic goes away. When you switch threads like you are here with the asynchronous programming style, the property is null after continuing.
If you absolutely cannot change your code to remove the hard dependency on HttpContext.Current, you can cheat this test by leveraging your local context: all the variables in local scope when you declare a continuation are made available for the context of the continuation.
// Bring the current value into local scope.
var context = System.Web.HttpContext.Current;
var httpSessionStateBefore = context.Session;
var person = await Db.Persons.FirstOrDefaultAsync();
var httpSessionStateAfter = context.Session;
To be clear, this will only work for your current scenario. If you introduce an await ahead of this in another scope, the code will suddenly break again; this is the quick-and-dirty answer that I encourage you to ignore and pursue a more robust solution.
I came to this question when having an issue in my code... where the HTTPContext.Current is null after an Await in an Async MVC Action. I post this here because others like me might land here.
My general recommendation is to grab anything you want off the session into a local variable, much like others above discuss, but not worrying about keeping the context, and instead just worrying about grabbing the actual items you want.
public async Task<ActionResult> SomeAction(SomeModel model)
{
int id = (int)HttpContext.Current.Session["Id"];
/* Session Exists Here */
var somethingElseAsyncModel = await GetSomethingElseAsync(model);
/* Session is Null Here */
// Do something with id, thanks to the fact we got it when we could
}