MediatR: Mediator breaks out of the logical CallContext

I’m having trouble with my mediator pipeline. I’m using the IRequestPostProcessor to implement both some workflow behavior and generic logging tasks.

Some of my mediator handlers are requesting the current user from HttpContext.Current.User, and for the most part, this works just fine. But lately we have experienced that the http context is not available.

I’m not 100% sure about this, but I think it’s a question of setting continueOnCapturedContext to false when calling next() in the RequestPostProcessorBehavior-implementation. At least, replacing the implementation with my own implementation that does not include the .ConfigureAwait(false) seems to solve the problem.

Is this a known problem? Is there a known solution?

(We’re still on MediatR 3.01)

About this issue

  • Original URL
  • State: closed
  • Created 6 years ago
  • Comments: 39 (39 by maintainers)

Most upvoted comments

@jbogard yes. Since MediateR is a general purpose in-process messaging library I’d say ConfigureAwait(false) could be argued as a good default value. Especially considering that MediatR encourages you to have SRP handlers that can return responses and those responses can then be assigned to something like the ViewModel or the UI. With that design you should never need to access something that is context-aware inside a handler, thus ConfigureAwait(false) actually enforces that design decision.

As long as we are in the world of System.Web, there is no guarantee that this will work anyway.

Stay away from ConfigureAwait(false) and you should be good.

By staying away I mean code that accesses the ambient context (eg HttpContent.Current )should not be asynchronously executed using ConfigureAwait(false)

.Net Framework 4.7.1 introduces the concept of Execution steps that seems to address this issue although I have never tested this myself. https://blogs.msdn.microsoft.com/dotnet/2017/09/13/net-framework-4-7-1-asp-net-and-configuration-features/

@vegar @jbogard @danielmarbach @joaomatossilva @fredimachado

There are just two lifetimes that requires a scope in LightInject.

  • PerRequestLifetime -> new instance for every time it is requested from the container. Tracks disposables that are disposed when the scope ends.

  • PerScopeLifetime -> a single instance per scope. Tracks disposables that are disposed when the scope ends.

These lifetimes are used in all types of applications. Web, Console and Desktop apps. You name it.

What changes in between application types is how the scopes are managed. By “managed” I mean how the scopes are stored.

  • PerThreadScopeManagerProvider -> Scopes are stored per thread (This is the default in LightInject)
  • PerPerLogicalCallContextScopeManagerProvider -> Scopes are stored per logical call context. (AsyncLocal<T>)

So what scope manager should be used for a given application type? Well, it depends 😃

For “legacy” web apps that uses the old System.Web stack, we can’t really use any of the built-in scope managers. Per thread is not going to work since web requests can start and finish on different threads. Per call context does not really work either since ASP.NET does not flow async locals throughout the web request. The only “safe” place to store ambient informations such as the current scope is in HttpContext.Current.Items collection. It is safe until the point where you do a ConfigureAwait(false). That could cause the HttpContext.Current to return null and the ambient information is lost.

Take a look here for more information https://github.com/seesharper/LightInject/issues/386

LightInject.Web provides the PerWebRequestScopeManager that stores the scope in HttpContext.Current.Items.

For ASP.NET Core applications it is safe to use PerPerLogicalCallContextScopeManagerProvider since the entire web request from start to finish in within the same logical call context.

My advice is to try to moving the app ASP.NET Core where these are solved properly. You can still target the full framework. Or use LightInject.Web and be careful with ´ConfigureAwait(false)´.

DBContext sounds like a perfectly good example of context you would like to have available without having to explicitly pass it along through your call chain / pipeline

Actually, I do prefer to have it injected where needed rather than have it statictly available everywhere, like in a DbContext.Current way.