|
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 sync 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 sync 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 sync 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();
}
}
}
|