namespace System.Web.Hosting { using System; using System.Security.Permissions; using System.Threading; // This HostExecutionContextManager can provide both setup and cleanup logic that // is invoked during a call to ExecutionContext.Run. This may be necessary when // using the Task-based APIs, as the 'await' language feature generally causes // this stack to be generated: // // { state machine callback } // ExecutionContext.Run // Task.SomeInternalCallback // AspNetSynchronizationContext.PostCallbackLogic // // The callback logic invoked by our AspNetSynchronizationContext.Post method puts // HttpContext-related information in the current ExecutionContext, but the // subsequent call to ExecutionContext.Run overwrites that information. So we have // logic in AspNetHostExecutionContextManager that can detect if a ThreadContext is // associated with the current thread (it will have been set by the Post callback), // and if so it should restore HttpContext.Current and other ExecutionContext-related // items on the current thread. [SecurityPermission(SecurityAction.LinkDemand, Unrestricted = true)] internal sealed class AspNetHostExecutionContextManager : HostExecutionContextManager { // Used as the return value from SetHostExecutionContext when our logic is active. // We use RevertAction instead of Action since it's unambiguous in the event that // base.SetHostExecutionContext itself ever changes to return Action. private delegate void RevertAction(); public override HostExecutionContext Capture() { ThreadContext currentContext = ThreadContext.Current; if (currentContext != null) { // We need to capture a reference to the current HttpContext's ThreadContextId // so that we can properly restore this instance on the call to SetHostExecutionContext. // See comment on HttpContext.ThreadContextId for more information. return new AspNetHostExecutionContext( baseContext: base.Capture(), httpContextThreadContextId: currentContext.HttpContext.ThreadContextId); } else { // There is no ThreadContext associated with this thread, hence there is no special // setup we need to do to restore things like HttpContext.Current. We can just // delegate to the base implementation. return base.Capture(); } } public override void Revert(object previousState) { RevertAction revertAction = previousState as RevertAction; if (revertAction != null) { // Our revert logic should run. It will eventually call base.Revert. revertAction(); } else { // We have no revert logic, so just call the base implementation. base.Revert(previousState); } } public override object SetHostExecutionContext(HostExecutionContext hostExecutionContext) { AspNetHostExecutionContext castHostExecutionContext = hostExecutionContext as AspNetHostExecutionContext; if (castHostExecutionContext != null) { // Call base.SetHostExecutionContext before calling our own logic. object baseRevertParameter = null; if (castHostExecutionContext.BaseContext != null) { baseRevertParameter = base.SetHostExecutionContext(castHostExecutionContext.BaseContext); } ThreadContext currentContext = ThreadContext.Current; if (currentContext != null && currentContext.HttpContext.ThreadContextId == castHostExecutionContext.HttpContextThreadContextId) { // If we reached this point, then 'castHostExecutionContext' was captured for the HttpContext // that is associated with the ThreadContext that is assigned to the current thread. We can // safely restore it. Action threadContextCleanupAction = currentContext.EnterExecutionContext(); // Perform cleanup in the opposite order from initialization. return (RevertAction)(() => { threadContextCleanupAction(); if (baseRevertParameter != null) { base.Revert(baseRevertParameter); } }); } else { // If we reached this point, then 'castHostExecutionContext' was captured by us // but is not applicable to the current thread. This can happen if the developer // called ThreadPool.QueueUserWorkItem, for example. We don't restore HttpContext // on such threads since they're not under the control of ASP.NET. In this case, // we have already called base.SetHostExecutionContext, so we just need to return // the result of that function directly to our caller. return baseRevertParameter; } } else { // If we reached this point, then 'hostExecutionContext' was generated by our // base class instead of by us, so just delegate to the base implementation. return base.SetHostExecutionContext(hostExecutionContext); } } private sealed class AspNetHostExecutionContext : HostExecutionContext { public readonly HostExecutionContext BaseContext; public readonly object HttpContextThreadContextId; internal AspNetHostExecutionContext(HostExecutionContext baseContext, object httpContextThreadContextId) { BaseContext = baseContext; HttpContextThreadContextId = httpContextThreadContextId; } // copy ctor private AspNetHostExecutionContext(AspNetHostExecutionContext original) : this(CreateCopyHelper(original.BaseContext), original.HttpContextThreadContextId) { } public override HostExecutionContext CreateCopy() { return new AspNetHostExecutionContext(this); } private static HostExecutionContext CreateCopyHelper(HostExecutionContext hostExecutionContext) { // creating a copy of a null context should just itself return null return (hostExecutionContext != null) ? hostExecutionContext.CreateCopy() : null; } public override void Dispose(bool disposing) { if (disposing && BaseContext != null) { BaseContext.Dispose(); } } } } }