1
namespace System
.Web
{
3 using System
.ComponentModel
;
4 using System
.Globalization
;
5 using System
.Runtime
.Remoting
.Messaging
;
6 using System
.Security
.Principal
;
8 using System
.Threading
;
9 using System
.Web
.Configuration
;
11 using System
.Web
.Util
;
13 // Contains information about any modifications ASP.NET has made to the current
14 // thread and how to undo them. See also the comments on
15 // HttpApplication.OnThreadEnterPrivate.
17 internal sealed class ThreadContext
: ISyncContextLock
{
19 // This is a marker holding the current ThreadContext for the current
20 // thread. Uses TLS so that it's not wiped away by ExecutionContext.Run.
22 private static ThreadContext _currentThreadContext
;
24 private ImpersonationContext _newImpersonationContext
;
25 private HttpContext _originalHttpContext
;
26 private SynchronizationContext _originalSynchronizationContext
;
27 private ThreadContext _originalThreadContextCurrent
;
28 private CultureInfo _originalThreadCurrentCulture
;
29 private CultureInfo _originalThreadCurrentUICulture
;
30 private IPrincipal _originalThreadPrincipal
;
31 private bool _setCurrentThreadOnHttpContext
;
33 internal ThreadContext(HttpContext httpContext
) {
34 HttpContext
= httpContext
;
37 internal static ThreadContext Current
{
38 get { return _currentThreadContext; }
39 private set { _currentThreadContext = value; }
42 internal bool HasBeenDisassociatedFromThread
{
47 internal HttpContext HttpContext
{
52 // Associates this ThreadContext with the current thread. This restores certain
53 // ambient values associated with the current HttpContext, such as the current
54 // user and cultures. It also sets HttpContext.Current.
55 internal void AssociateWithCurrentThread(bool setImpersonationContext
) {
56 Debug
.Assert(HttpContext
!= null); // only to be used when context is available
57 Debug
.Assert(Current
!= this, "This ThreadContext is already associated with this thread.");
58 Debug
.Assert(!HasBeenDisassociatedFromThread
, "This ThreadContext has already been disassociated from a thread.");
60 Debug
.Trace("OnThread", GetTraceMessage("Enter1"));
64 * Keep this logic in sync with DisassociateFromCurrentThread and EnterExecutionContext.
67 // attach http context to the call context
68 _originalHttpContext
= DisposableHttpContextWrapper
.SwitchContext(HttpContext
);
70 // set impersonation on the current thread
71 if (setImpersonationContext
) {
72 SetImpersonationContext();
75 // set synchronization context for the current thread to support the async pattern
76 _originalSynchronizationContext
= AsyncOperationManager
.SynchronizationContext
;
77 AspNetSynchronizationContextBase aspNetSynchronizationContext
= HttpContext
.SyncContext
;
78 AsyncOperationManager
.SynchronizationContext
= aspNetSynchronizationContext
;
81 Guid g
= HttpContext
.WorkerRequest
.RequestTraceIdentifier
;
82 if (g
!= Guid
.Empty
) {
83 CallContext
.LogicalSetData("E2ETrace.ActivityID", g
);
86 // set SqlDependecyCookie
87 HttpContext
.ResetSqlDependencyCookie();
89 // set principal on the current thread
90 _originalThreadPrincipal
= Thread
.CurrentPrincipal
;
91 HttpApplication
.SetCurrentPrincipalWithAssert(HttpContext
.User
);
93 // only set culture on the current thread if it is not initialized
94 SetRequestLevelCulture();
97 // set current thread in context if there is not there
98 // the timeout manager uses this to abort the correct thread
99 if (HttpContext
.CurrentThread
== null) {
100 _setCurrentThreadOnHttpContext
= true;
101 HttpContext
.CurrentThread
= Thread
.CurrentThread
;
104 // Store a reference to the original ThreadContext.Current. It is possible that a parent
105 // ThreadContext might already be associated with the current thread, e.g. if the current
106 // stack contains a call to MgdIndicateCompletion (via
107 // PipelineRuntime.ProcessRequestNotificationHelper). If this is the case, the child
108 // ThreadContext will temporarily take over.
109 _originalThreadContextCurrent
= Current
;
112 Debug
.Trace("OnThread", GetTraceMessage("Enter2"));
115 private ClientImpersonationContext
CreateNewClientImpersonationContext() {
116 // impersonation is set in the ClientImpersonationContext ctor
117 return new ClientImpersonationContext(HttpContext
);
120 // Disassociates this ThreadContext from the current thread. Any ambient values (e.g., culture)
121 // associated with the current request are stored in the HttpContext object so that they
122 // can be restored the next time a ThreadContext associated with this HttpContext is active.
123 // Impersonation and other similar modifications to the current thread are undone.
124 internal void DisassociateFromCurrentThread() {
125 Debug
.Trace("OnThread", GetTraceMessage("Leave1"));
126 Debug
.Assert(Current
== this, "This ThreadContext isn't associated with current thread.");
127 Debug
.Assert(!HasBeenDisassociatedFromThread
, "This ThreadContext has already been disassociated from a thread.");
131 * Keep this logic in sync with AssociateWithCurrentThread and EnterExecutionContext.
134 Current
= _originalThreadContextCurrent
;
135 HasBeenDisassociatedFromThread
= true;
137 // remove thread if set
138 if (_setCurrentThreadOnHttpContext
) {
139 HttpContext
.CurrentThread
= null;
142 // this thread should not be locking app state
143 HttpApplicationFactory
.ApplicationState
.EnsureUnLock();
145 // stop impersonation
146 UndoImpersonationContext();
149 RestoreRequestLevelCulture();
151 // restrore synchronization context
152 AsyncOperationManager
.SynchronizationContext
= _originalSynchronizationContext
;
154 // restore thread principal
155 HttpApplication
.SetCurrentPrincipalWithAssert(_originalThreadPrincipal
);
157 // Remove SqlCacheDependency cookie from call context if necessary
158 HttpContext
.RemoveSqlDependencyCookie();
160 // remove http context from the call context
161 DisposableHttpContextWrapper
.SwitchContext(_originalHttpContext
);
162 _originalHttpContext
= null;
164 Debug
.Trace("OnThread", GetTraceMessage("Leave2"));
167 // Called by AspNetHostExecutionContextManager to signal that ExecutionContext.Run
168 // is being called on a thread currently associated with our ThreadContext. Since
169 // ExecutionContext.Run destroys some of our ambient state (HttpContext.Current, etc.),
170 // we need to restore it. This method returns an Action which should be called when
171 // the call to ExecutionContext.Run is concluding.
172 internal Action
EnterExecutionContext() {
173 Debug
.Trace("OnThread", GetTraceMessage("EnterExecutionContext1"));
174 Debug
.Assert(Current
== this, "This ThreadContext isn't associated with current thread.");
175 Debug
.Assert(!HasBeenDisassociatedFromThread
, "This ThreadContext has already been disassociated from a thread.");
179 * Keep this logic in sync with AssociateWithCurrentThread and DisassociateFromCurrentThread.
182 // ExecutionContext.Run replaces the current impersonation token, so we need to impersonate
183 // if AssociateWithCurrentThread also did so.
185 ClientImpersonationContext executionContextClientImpersonationContext
= null;
186 if (_newImpersonationContext
!= null) {
187 executionContextClientImpersonationContext
= CreateNewClientImpersonationContext();
190 // ExecutionContext.Run resets the LogicalCallContext / IllogicalCallContext (which contains HttpContext.Current),
191 // so we need to restore both of them.
193 DisposableHttpContextWrapper
.SwitchContext(HttpContext
);
195 Guid g
= HttpContext
.WorkerRequest
.RequestTraceIdentifier
;
196 if (g
!= Guid
.Empty
) {
197 CallContext
.LogicalSetData("E2ETrace.ActivityID", g
);
200 HttpContext
.ResetSqlDependencyCookie();
202 // ExecutionContext.Run resets the thread's CurrentPrincipal, so we need to restore it.
204 HttpApplication
.SetCurrentPrincipalWithAssert(HttpContext
.User
);
206 // Other items like [ThreadStatic] fields, culture, etc. are untouched by ExecutionContext.Run,
207 // so we don't need to worry about them.
209 Debug
.Trace("OnThread", GetTraceMessage("EnterExecutionContext2"));
211 // This delegate is the cleanup routine.
213 Debug
.Trace("OnThread", GetTraceMessage("LeaveExecutionContext1"));
215 // Undo any impersonation that we performed.
216 if (executionContextClientImpersonationContext
!= null) {
217 executionContextClientImpersonationContext
.Undo();
220 // Other things, e.g. changes to the logical/illogical call contexts, changes
221 // to CurrentPrincipal, etc., will automatically be reverted anyway when
222 // the call to ExecutionContext.Run concludes, so we don't need to clean up
225 Debug
.Trace("OnThread", GetTraceMessage("LeaveExecutionContext2"));
229 private static string GetTraceMessage(string tag
) {
231 StringBuilder sb
= new StringBuilder(256);
233 sb
.AppendFormat(" Thread={0}", SafeNativeMethods
.GetCurrentThreadId().ToString(CultureInfo
.InvariantCulture
));
234 sb
.AppendFormat(" Context={0}", (HttpContext
.Current
!= null) ? HttpContext
.Current
.GetHashCode().ToString(CultureInfo
.InvariantCulture
) : "NULL_CTX");
235 sb
.AppendFormat(" Principal={0}", (Thread
.CurrentPrincipal
!= null) ? Thread
.CurrentPrincipal
.GetHashCode().ToString(CultureInfo
.InvariantCulture
) : "NULL_PRIN");
236 sb
.AppendFormat(" Culture={0}", Thread
.CurrentThread
.CurrentCulture
.LCID
.ToString(CultureInfo
.InvariantCulture
));
237 sb
.AppendFormat(" UICulture={0}", Thread
.CurrentThread
.CurrentUICulture
.LCID
.ToString(CultureInfo
.InvariantCulture
));
238 sb
.AppendFormat(" ActivityID={0}", CallContext
.LogicalGetData("E2ETrace.ActivityID"));
239 return sb
.ToString();
241 // This method should never be called in release mode.
242 throw new NotImplementedException();
247 // Restores the thread's CurrentCulture and CurrentUICulture back to what
248 // they were before this ThreadContext was associated with the thread. If
249 // any culture has changed from its original value, we squirrel the new
250 // culture away in HttpContext so that we can restore it the next time any
251 // ThreadContext associated with this HttpContext is active.
252 private void RestoreRequestLevelCulture() {
253 CultureInfo currentCulture
= Thread
.CurrentThread
.CurrentCulture
;
254 CultureInfo currentUICulture
= Thread
.CurrentThread
.CurrentUICulture
;
256 if (_originalThreadCurrentCulture
!= null) {
257 // Avoid the cost of the Demand when setting the culture by comparing the cultures first
258 if (currentCulture
!= _originalThreadCurrentCulture
) {
259 HttpRuntime
.SetCurrentThreadCultureWithAssert(_originalThreadCurrentCulture
);
260 if (HttpContext
!= null) {
261 // remember changed culture for the rest of the request
262 HttpContext
.DynamicCulture
= currentCulture
;
266 _originalThreadCurrentCulture
= null;
269 if (_originalThreadCurrentUICulture
!= null) {
270 // Avoid the cost of the Demand when setting the culture by comparing the cultures first
271 if (currentUICulture
!= _originalThreadCurrentUICulture
) {
272 Thread
.CurrentThread
.CurrentUICulture
= _originalThreadCurrentUICulture
;
273 if (HttpContext
!= null) {
274 // remember changed culture for the rest of the request
275 HttpContext
.DynamicUICulture
= currentUICulture
;
279 _originalThreadCurrentUICulture
= null;
283 // Sets impersonation on the current thread.
284 internal void SetImpersonationContext() {
285 if (_newImpersonationContext
== null) {
286 _newImpersonationContext
= CreateNewClientImpersonationContext();
290 // Sets the thread's CurrentCulture and CurrentUICulture to those associated
291 // with the current HttpContext. We do this since the culture of a request can
292 // change over its lifetime and isn't necessarily the default for the AppDomain,
293 // e.g. if the culture was read from the request headers.
294 private void SetRequestLevelCulture() {
295 CultureInfo culture
= null;
296 CultureInfo uiculture
= null;
298 GlobalizationSection globConfig
= RuntimeConfig
.GetConfig(HttpContext
).Globalization
;
299 if (!String
.IsNullOrEmpty(globConfig
.Culture
))
300 culture
= HttpContext
.CultureFromConfig(globConfig
.Culture
, true);
302 if (!String
.IsNullOrEmpty(globConfig
.UICulture
))
303 uiculture
= HttpContext
.CultureFromConfig(globConfig
.UICulture
, false);
305 if (HttpContext
.DynamicCulture
!= null)
306 culture
= HttpContext
.DynamicCulture
;
308 if (HttpContext
.DynamicUICulture
!= null)
309 uiculture
= HttpContext
.DynamicUICulture
;
311 // Page also could have its own culture settings
312 Page page
= HttpContext
.CurrentHandler
as Page
;
315 if (page
.DynamicCulture
!= null)
316 culture
= page
.DynamicCulture
;
318 if (page
.DynamicUICulture
!= null)
319 uiculture
= page
.DynamicUICulture
;
322 _originalThreadCurrentCulture
= Thread
.CurrentThread
.CurrentCulture
;
323 _originalThreadCurrentUICulture
= Thread
.CurrentThread
.CurrentUICulture
;
325 if (culture
!= null && culture
!= Thread
.CurrentThread
.CurrentCulture
) {
326 HttpRuntime
.SetCurrentThreadCultureWithAssert(culture
);
329 if (uiculture
!= null && uiculture
!= Thread
.CurrentThread
.CurrentUICulture
) {
330 Thread
.CurrentThread
.CurrentUICulture
= uiculture
;
334 // Use of IndicateCompletion requires that we synchronize the cultures
335 // with what may have been set by user code during execution of the
337 internal void Synchronize() {
338 HttpContext
.DynamicCulture
= Thread
.CurrentThread
.CurrentCulture
;
339 HttpContext
.DynamicUICulture
= Thread
.CurrentThread
.CurrentUICulture
;
342 // Undoes any impersonation that we did when associating this ThreadContext
343 // with the current thread.
344 internal void UndoImpersonationContext() {
345 // remove impersonation on the current thread
346 if (_newImpersonationContext
!= null) {
347 _newImpersonationContext
.Undo();
348 _newImpersonationContext
= null;
352 // Called by AspNetSynchronizationContext to signal that it is finished
353 // processing on the current thread.
354 void ISyncContextLock
.Leave() {
355 DisassociateFromCurrentThread();