e79aa3c0ed
Former-commit-id: a2155e9bd80020e49e72e86c44da02a8ac0e57a4
360 lines
16 KiB
C#
360 lines
16 KiB
C#
namespace System.Web {
|
|
using System;
|
|
using System.ComponentModel;
|
|
using System.Globalization;
|
|
using System.Runtime.Remoting.Messaging;
|
|
using System.Security.Principal;
|
|
using System.Text;
|
|
using System.Threading;
|
|
using System.Web.Configuration;
|
|
using System.Web.UI;
|
|
using System.Web.Util;
|
|
|
|
// Contains information about any modifications ASP.NET has made to the current
|
|
// thread and how to undo them. See also the comments on
|
|
// HttpApplication.OnThreadEnterPrivate.
|
|
|
|
internal sealed class ThreadContext : ISyncContextLock {
|
|
|
|
// This is a marker holding the current ThreadContext for the current
|
|
// thread. Uses TLS so that it's not wiped away by ExecutionContext.Run.
|
|
[ThreadStatic]
|
|
private static ThreadContext _currentThreadContext;
|
|
|
|
private ImpersonationContext _newImpersonationContext;
|
|
private HttpContext _originalHttpContext;
|
|
private SynchronizationContext _originalSynchronizationContext;
|
|
private ThreadContext _originalThreadContextCurrent;
|
|
private CultureInfo _originalThreadCurrentCulture;
|
|
private CultureInfo _originalThreadCurrentUICulture;
|
|
private IPrincipal _originalThreadPrincipal;
|
|
private bool _setCurrentThreadOnHttpContext;
|
|
|
|
internal ThreadContext(HttpContext httpContext) {
|
|
HttpContext = httpContext;
|
|
}
|
|
|
|
internal static ThreadContext Current {
|
|
get { return _currentThreadContext; }
|
|
private set { _currentThreadContext = value; }
|
|
}
|
|
|
|
internal bool HasBeenDisassociatedFromThread {
|
|
get;
|
|
private set;
|
|
}
|
|
|
|
internal HttpContext HttpContext {
|
|
get;
|
|
private set;
|
|
}
|
|
|
|
// Associates this ThreadContext with the current thread. This restores certain
|
|
// ambient values associated with the current HttpContext, such as the current
|
|
// user and cultures. It also sets HttpContext.Current.
|
|
internal void AssociateWithCurrentThread(bool setImpersonationContext) {
|
|
Debug.Assert(HttpContext != null); // only to be used when context is available
|
|
Debug.Assert(Current != this, "This ThreadContext is already associated with this thread.");
|
|
Debug.Assert(!HasBeenDisassociatedFromThread, "This ThreadContext has already been disassociated from a thread.");
|
|
|
|
Debug.Trace("OnThread", GetTraceMessage("Enter1"));
|
|
|
|
/*
|
|
* !! IMPORTANT !!
|
|
* Keep this logic in [....] with DisassociateFromCurrentThread and EnterExecutionContext.
|
|
*/
|
|
|
|
// attach http context to the call context
|
|
_originalHttpContext = DisposableHttpContextWrapper.SwitchContext(HttpContext);
|
|
|
|
// set impersonation on the current thread
|
|
if (setImpersonationContext) {
|
|
SetImpersonationContext();
|
|
}
|
|
|
|
// set synchronization context for the current thread to support the async pattern
|
|
_originalSynchronizationContext = AsyncOperationManager.SynchronizationContext;
|
|
AspNetSynchronizationContextBase aspNetSynchronizationContext = HttpContext.SyncContext;
|
|
AsyncOperationManager.SynchronizationContext = aspNetSynchronizationContext;
|
|
|
|
// set ETW trace ID
|
|
Guid g = HttpContext.WorkerRequest.RequestTraceIdentifier;
|
|
if (g != Guid.Empty) {
|
|
CallContext.LogicalSetData("E2ETrace.ActivityID", g);
|
|
}
|
|
|
|
// set SqlDependecyCookie
|
|
HttpContext.ResetSqlDependencyCookie();
|
|
|
|
// set principal on the current thread
|
|
_originalThreadPrincipal = Thread.CurrentPrincipal;
|
|
HttpApplication.SetCurrentPrincipalWithAssert(HttpContext.User);
|
|
|
|
// only set culture on the current thread if it is not initialized
|
|
SetRequestLevelCulture();
|
|
|
|
// DevDivBugs 75042
|
|
// set current thread in context if there is not there
|
|
// the timeout manager uses this to abort the correct thread
|
|
if (HttpContext.CurrentThread == null) {
|
|
_setCurrentThreadOnHttpContext = true;
|
|
HttpContext.CurrentThread = Thread.CurrentThread;
|
|
}
|
|
|
|
// Store a reference to the original ThreadContext.Current. It is possible that a parent
|
|
// ThreadContext might already be associated with the current thread, e.g. if the current
|
|
// stack contains a call to MgdIndicateCompletion (via
|
|
// PipelineRuntime.ProcessRequestNotificationHelper). If this is the case, the child
|
|
// ThreadContext will temporarily take over.
|
|
_originalThreadContextCurrent = Current;
|
|
Current = this;
|
|
|
|
Debug.Trace("OnThread", GetTraceMessage("Enter2"));
|
|
}
|
|
|
|
private ClientImpersonationContext CreateNewClientImpersonationContext() {
|
|
// impersonation is set in the ClientImpersonationContext ctor
|
|
return new ClientImpersonationContext(HttpContext);
|
|
}
|
|
|
|
// Disassociates this ThreadContext from the current thread. Any ambient values (e.g., culture)
|
|
// associated with the current request are stored in the HttpContext object so that they
|
|
// can be restored the next time a ThreadContext associated with this HttpContext is active.
|
|
// Impersonation and other similar modifications to the current thread are undone.
|
|
internal void DisassociateFromCurrentThread() {
|
|
Debug.Trace("OnThread", GetTraceMessage("Leave1"));
|
|
Debug.Assert(Current == this, "This ThreadContext isn't associated with current thread.");
|
|
Debug.Assert(!HasBeenDisassociatedFromThread, "This ThreadContext has already been disassociated from a thread.");
|
|
|
|
/*
|
|
* !! IMPORTANT !!
|
|
* Keep this logic in [....] with AssociateWithCurrentThread and EnterExecutionContext.
|
|
*/
|
|
|
|
Current = _originalThreadContextCurrent;
|
|
HasBeenDisassociatedFromThread = true;
|
|
|
|
// remove thread if set
|
|
if (_setCurrentThreadOnHttpContext) {
|
|
HttpContext.CurrentThread = null;
|
|
}
|
|
|
|
// this thread should not be locking app state
|
|
HttpApplicationFactory.ApplicationState.EnsureUnLock();
|
|
|
|
// stop impersonation
|
|
UndoImpersonationContext();
|
|
|
|
// restore culture
|
|
RestoreRequestLevelCulture();
|
|
|
|
// restrore synchronization context
|
|
AsyncOperationManager.SynchronizationContext = _originalSynchronizationContext;
|
|
|
|
// restore thread principal
|
|
HttpApplication.SetCurrentPrincipalWithAssert(_originalThreadPrincipal);
|
|
|
|
// Remove SqlCacheDependency cookie from call context if necessary
|
|
HttpContext.RemoveSqlDependencyCookie();
|
|
|
|
// remove http context from the call context
|
|
DisposableHttpContextWrapper.SwitchContext(_originalHttpContext);
|
|
_originalHttpContext = null;
|
|
|
|
Debug.Trace("OnThread", GetTraceMessage("Leave2"));
|
|
}
|
|
|
|
// Called by AspNetHostExecutionContextManager to signal that ExecutionContext.Run
|
|
// is being called on a thread currently associated with our ThreadContext. Since
|
|
// ExecutionContext.Run destroys some of our ambient state (HttpContext.Current, etc.),
|
|
// we need to restore it. This method returns an Action which should be called when
|
|
// the call to ExecutionContext.Run is concluding.
|
|
internal Action EnterExecutionContext() {
|
|
Debug.Trace("OnThread", GetTraceMessage("EnterExecutionContext1"));
|
|
Debug.Assert(Current == this, "This ThreadContext isn't associated with current thread.");
|
|
Debug.Assert(!HasBeenDisassociatedFromThread, "This ThreadContext has already been disassociated from a thread.");
|
|
|
|
/*
|
|
* !! IMPORTANT !!
|
|
* Keep this logic in [....] with AssociateWithCurrentThread and DisassociateFromCurrentThread.
|
|
*/
|
|
|
|
// ExecutionContext.Run replaces the current impersonation token, so we need to impersonate
|
|
// if AssociateWithCurrentThread also did so.
|
|
|
|
ClientImpersonationContext executionContextClientImpersonationContext = null;
|
|
if (_newImpersonationContext != null) {
|
|
executionContextClientImpersonationContext = CreateNewClientImpersonationContext();
|
|
}
|
|
|
|
// ExecutionContext.Run resets the LogicalCallContext / IllogicalCallContext (which contains HttpContext.Current),
|
|
// so we need to restore both of them.
|
|
|
|
DisposableHttpContextWrapper.SwitchContext(HttpContext);
|
|
|
|
Guid g = HttpContext.WorkerRequest.RequestTraceIdentifier;
|
|
if (g != Guid.Empty) {
|
|
CallContext.LogicalSetData("E2ETrace.ActivityID", g);
|
|
}
|
|
|
|
HttpContext.ResetSqlDependencyCookie();
|
|
|
|
// ExecutionContext.Run resets the thread's CurrentPrincipal, so we need to restore it.
|
|
|
|
HttpApplication.SetCurrentPrincipalWithAssert(HttpContext.User);
|
|
|
|
// Other items like [ThreadStatic] fields, culture, etc. are untouched by ExecutionContext.Run,
|
|
// so we don't need to worry about them.
|
|
|
|
Debug.Trace("OnThread", GetTraceMessage("EnterExecutionContext2"));
|
|
|
|
// This delegate is the cleanup routine.
|
|
return () => {
|
|
Debug.Trace("OnThread", GetTraceMessage("LeaveExecutionContext1"));
|
|
|
|
// Undo any impersonation that we performed.
|
|
if (executionContextClientImpersonationContext != null) {
|
|
executionContextClientImpersonationContext.Undo();
|
|
}
|
|
|
|
// Other things, e.g. changes to the logical/illogical call contexts, changes
|
|
// to CurrentPrincipal, etc., will automatically be reverted anyway when
|
|
// the call to ExecutionContext.Run concludes, so we don't need to clean up
|
|
// here.
|
|
|
|
Debug.Trace("OnThread", GetTraceMessage("LeaveExecutionContext2"));
|
|
};
|
|
}
|
|
|
|
private static string GetTraceMessage(string tag) {
|
|
#if DBG
|
|
StringBuilder sb = new StringBuilder(256);
|
|
sb.Append(tag);
|
|
sb.AppendFormat(" Thread={0}", SafeNativeMethods.GetCurrentThreadId().ToString(CultureInfo.InvariantCulture));
|
|
sb.AppendFormat(" Context={0}", (HttpContext.Current != null) ? HttpContext.Current.GetHashCode().ToString(CultureInfo.InvariantCulture) : "NULL_CTX");
|
|
sb.AppendFormat(" Principal={0}", (Thread.CurrentPrincipal != null) ? Thread.CurrentPrincipal.GetHashCode().ToString(CultureInfo.InvariantCulture) : "NULL_PRIN");
|
|
sb.AppendFormat(" Culture={0}", Thread.CurrentThread.CurrentCulture.LCID.ToString(CultureInfo.InvariantCulture));
|
|
sb.AppendFormat(" UICulture={0}", Thread.CurrentThread.CurrentUICulture.LCID.ToString(CultureInfo.InvariantCulture));
|
|
sb.AppendFormat(" ActivityID={0}", CallContext.LogicalGetData("E2ETrace.ActivityID"));
|
|
return sb.ToString();
|
|
#else
|
|
// This method should never be called in release mode.
|
|
throw new NotImplementedException();
|
|
#endif
|
|
}
|
|
|
|
|
|
// Restores the thread's CurrentCulture and CurrentUICulture back to what
|
|
// they were before this ThreadContext was associated with the thread. If
|
|
// any culture has changed from its original value, we squirrel the new
|
|
// culture away in HttpContext so that we can restore it the next time any
|
|
// ThreadContext associated with this HttpContext is active.
|
|
private void RestoreRequestLevelCulture() {
|
|
CultureInfo currentCulture = Thread.CurrentThread.CurrentCulture;
|
|
CultureInfo currentUICulture = Thread.CurrentThread.CurrentUICulture;
|
|
|
|
if (_originalThreadCurrentCulture != null) {
|
|
// Avoid the cost of the Demand when setting the culture by comparing the cultures first
|
|
if (currentCulture != _originalThreadCurrentCulture) {
|
|
HttpRuntime.SetCurrentThreadCultureWithAssert(_originalThreadCurrentCulture);
|
|
if (HttpContext != null) {
|
|
// remember changed culture for the rest of the request
|
|
HttpContext.DynamicCulture = currentCulture;
|
|
}
|
|
}
|
|
|
|
_originalThreadCurrentCulture = null;
|
|
}
|
|
|
|
if (_originalThreadCurrentUICulture != null) {
|
|
// Avoid the cost of the Demand when setting the culture by comparing the cultures first
|
|
if (currentUICulture != _originalThreadCurrentUICulture) {
|
|
Thread.CurrentThread.CurrentUICulture = _originalThreadCurrentUICulture;
|
|
if (HttpContext != null) {
|
|
// remember changed culture for the rest of the request
|
|
HttpContext.DynamicUICulture = currentUICulture;
|
|
}
|
|
}
|
|
|
|
_originalThreadCurrentUICulture = null;
|
|
}
|
|
}
|
|
|
|
// Sets impersonation on the current thread.
|
|
internal void SetImpersonationContext() {
|
|
if (_newImpersonationContext == null) {
|
|
_newImpersonationContext = CreateNewClientImpersonationContext();
|
|
}
|
|
}
|
|
|
|
// Sets the thread's CurrentCulture and CurrentUICulture to those associated
|
|
// with the current HttpContext. We do this since the culture of a request can
|
|
// change over its lifetime and isn't necessarily the default for the AppDomain,
|
|
// e.g. if the culture was read from the request headers.
|
|
private void SetRequestLevelCulture() {
|
|
CultureInfo culture = null;
|
|
CultureInfo uiculture = null;
|
|
|
|
GlobalizationSection globConfig = RuntimeConfig.GetConfig(HttpContext).Globalization;
|
|
if (!String.IsNullOrEmpty(globConfig.Culture))
|
|
culture = HttpContext.CultureFromConfig(globConfig.Culture, true);
|
|
|
|
if (!String.IsNullOrEmpty(globConfig.UICulture))
|
|
uiculture = HttpContext.CultureFromConfig(globConfig.UICulture, false);
|
|
|
|
if (HttpContext.DynamicCulture != null)
|
|
culture = HttpContext.DynamicCulture;
|
|
|
|
if (HttpContext.DynamicUICulture != null)
|
|
uiculture = HttpContext.DynamicUICulture;
|
|
|
|
// Page also could have its own culture settings
|
|
Page page = HttpContext.CurrentHandler as Page;
|
|
|
|
if (page != null) {
|
|
if (page.DynamicCulture != null)
|
|
culture = page.DynamicCulture;
|
|
|
|
if (page.DynamicUICulture != null)
|
|
uiculture = page.DynamicUICulture;
|
|
}
|
|
|
|
_originalThreadCurrentCulture = Thread.CurrentThread.CurrentCulture;
|
|
_originalThreadCurrentUICulture = Thread.CurrentThread.CurrentUICulture;
|
|
|
|
if (culture != null && culture != Thread.CurrentThread.CurrentCulture) {
|
|
HttpRuntime.SetCurrentThreadCultureWithAssert(culture);
|
|
}
|
|
|
|
if (uiculture != null && uiculture != Thread.CurrentThread.CurrentUICulture) {
|
|
Thread.CurrentThread.CurrentUICulture = uiculture;
|
|
}
|
|
}
|
|
|
|
// Use of IndicateCompletion requires that we synchronize the cultures
|
|
// with what may have been set by user code during execution of the
|
|
// notification.
|
|
internal void Synchronize() {
|
|
HttpContext.DynamicCulture = Thread.CurrentThread.CurrentCulture;
|
|
HttpContext.DynamicUICulture = Thread.CurrentThread.CurrentUICulture;
|
|
}
|
|
|
|
// Undoes any impersonation that we did when associating this ThreadContext
|
|
// with the current thread.
|
|
internal void UndoImpersonationContext() {
|
|
// remove impersonation on the current thread
|
|
if (_newImpersonationContext != null) {
|
|
_newImpersonationContext.Undo();
|
|
_newImpersonationContext = null;
|
|
}
|
|
}
|
|
|
|
// Called by AspNetSynchronizationContext to signal that it is finished
|
|
// processing on the current thread.
|
|
void ISyncContextLock.Leave() {
|
|
DisassociateFromCurrentThread();
|
|
}
|
|
|
|
}
|
|
}
|