Updates referencesource to .NET 4.7
[mono-project.git] / mcs / class / referencesource / System.Web / ThreadContext.cs
blobbfd7b04d52be5b57ab6ee384baf2d16908d57b7a
1 namespace System.Web {
2 using System;
3 using System.ComponentModel;
4 using System.Globalization;
5 using System.Runtime.Remoting.Messaging;
6 using System.Security.Principal;
7 using System.Text;
8 using System.Threading;
9 using System.Web.Configuration;
10 using System.Web.UI;
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.
21 [ThreadStatic]
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 {
43 get;
44 private set;
47 internal HttpContext HttpContext {
48 get;
49 private set;
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"));
63 * !! IMPORTANT !!
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;
80 // set ETW trace ID
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();
96 // DevDivBugs 75042
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;
110 Current = this;
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.");
130 * !! IMPORTANT !!
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();
148 // restore culture
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.");
178 * !! IMPORTANT !!
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.
212 return () => {
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
223 // here.
225 Debug.Trace("OnThread", GetTraceMessage("LeaveExecutionContext2"));
229 private static string GetTraceMessage(string tag) {
230 #if DBG
231 StringBuilder sb = new StringBuilder(256);
232 sb.Append(tag);
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();
240 #else
241 // This method should never be called in release mode.
242 throw new NotImplementedException();
243 #endif
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;
314 if (page != null) {
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
336 // notification.
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();