e79aa3c0ed
Former-commit-id: a2155e9bd80020e49e72e86c44da02a8ac0e57a4
135 lines
6.8 KiB
C#
135 lines
6.8 KiB
C#
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();
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|