1 // Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
2 
3 using System.Collections.Generic;
4 using System.Globalization;
5 using System.IO;
6 using System.Reflection;
7 using System.Threading;
8 using System.Web.Caching;
9 using System.Web.Hosting;
10 using Microsoft.Web.Infrastructure;
11 
12 namespace System.Web.WebPages
13 {
14     public abstract class ApplicationStartPage : WebPageExecutingBase
15     {
16         private static readonly Action<Action> _safeExecuteStartPageThunk = GetSafeExecuteStartPageThunk();
17         public static readonly string StartPageVirtualPath = "~/_appstart.";
18         public static readonly string CacheKeyPrefix = "__AppStartPage__";
19 
20         public HttpApplication Application { get; internal set; }
21 
22         public override HttpContextBase Context
23         {
24             get { return new HttpContextWrapper(Application.Context); }
25         }
26 
27         public static HtmlString Markup { get; private set; }
28 
29         internal static Exception Exception { get; private set; }
30 
31         public TextWriter Output { get; internal set; }
32 
33         public override string VirtualPath
34         {
35             get { return StartPageVirtualPath; }
36             set
37             {
38                 // The virtual path for the start page is fixed for now.
39                 throw new NotSupportedException();
40             }
41         }
42 
ExecuteInternal()43         internal void ExecuteInternal()
44         {
45             // See comments in GetSafeExecuteStartPageThunk().
46             _safeExecuteStartPageThunk(() =>
47             {
48                 Output = new StringWriter(CultureInfo.InvariantCulture);
49                 Execute();
50                 Markup = new HtmlString(Output.ToString());
51             });
52         }
53 
ExecuteStartPage(HttpApplication application)54         internal static void ExecuteStartPage(HttpApplication application)
55         {
56             ExecuteStartPage(application,
57                              vpath => MonitorFile(vpath),
58                              VirtualPathFactoryManager.Instance,
59                              WebPageHttpHandler.GetRegisteredExtensions());
60         }
61 
ExecuteStartPage(HttpApplication application, Action<string> monitorFile, IVirtualPathFactory virtualPathFactory, IEnumerable<string> supportedExtensions)62         internal static void ExecuteStartPage(HttpApplication application, Action<string> monitorFile, IVirtualPathFactory virtualPathFactory, IEnumerable<string> supportedExtensions)
63         {
64             try
65             {
66                 ExecuteStartPageInternal(application, monitorFile, virtualPathFactory, supportedExtensions);
67             }
68             catch (Exception e)
69             {
70                 // Throw it as a HttpException so as to
71                 // display the original stack trace information.
72                 Exception = e;
73                 throw new HttpException(null, e);
74             }
75         }
76 
ExecuteStartPageInternal(HttpApplication application, Action<string> monitorFile, IVirtualPathFactory virtualPathFactory, IEnumerable<string> supportedExtensions)77         internal static void ExecuteStartPageInternal(HttpApplication application, Action<string> monitorFile, IVirtualPathFactory virtualPathFactory, IEnumerable<string> supportedExtensions)
78         {
79             ApplicationStartPage startPage = null;
80 
81             foreach (var extension in supportedExtensions)
82             {
83                 var vpath = StartPageVirtualPath + extension;
84 
85                 // We need to monitor regardless of existence because the user could add/remove the
86                 // file at any time.
87                 monitorFile(vpath);
88                 if (!virtualPathFactory.Exists(vpath))
89                 {
90                     continue;
91                 }
92 
93                 if (startPage == null)
94                 {
95                     startPage = virtualPathFactory.CreateInstance<ApplicationStartPage>(vpath);
96                     startPage.Application = application;
97                     startPage.VirtualPathFactory = virtualPathFactory;
98                     startPage.ExecuteInternal();
99                 }
100             }
101         }
102 
GetSafeExecuteStartPageThunk()103         private static Action<Action> GetSafeExecuteStartPageThunk()
104         {
105             // Programmatically detect if this version of System.Web.dll suffers from a bug in
106             // which HttpUtility.HtmlEncode can't be called from Application_Start, and if so
107             // set the current HttpContext to null to work around it.
108             //
109             // See Dev10 #906296 and Dev10 #898600 for more information.
110 
111             if (typeof(HttpResponse).GetProperty("DisableCustomHttpEncoder", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly) != null)
112             {
113                 // this version suffers from the bug
114                 return HttpContextHelper.ExecuteInNullContext;
115             }
116             else
117             {
118                 // this version does not suffer from the bug
119                 return action => action();
120             }
121         }
122 
InitiateShutdown(string key, object value, CacheItemRemovedReason reason)123         private static void InitiateShutdown(string key, object value, CacheItemRemovedReason reason)
124         {
125             // Only handle case when the dependency has changed.
126             if (reason != CacheItemRemovedReason.DependencyChanged)
127             {
128                 return;
129             }
130 
131             ThreadPool.QueueUserWorkItem(new WaitCallback(ShutdownCallBack));
132         }
133 
MonitorFile(string virtualPath)134         private static void MonitorFile(string virtualPath)
135         {
136             var virtualPathDependencies = new List<string>();
137             virtualPathDependencies.Add(virtualPath);
138             CacheDependency cacheDependency = HostingEnvironment.VirtualPathProvider.GetCacheDependency(
139                 virtualPath, virtualPathDependencies, DateTime.UtcNow);
140             var key = CacheKeyPrefix + virtualPath;
141 
142             HttpRuntime.Cache.Insert(key, virtualPath, cacheDependency,
143                                      Cache.NoAbsoluteExpiration, Cache.NoSlidingExpiration,
144                                      CacheItemPriority.NotRemovable, new CacheItemRemovedCallback(InitiateShutdown));
145         }
146 
ShutdownCallBack(object state)147         private static void ShutdownCallBack(object state)
148         {
149             InfrastructureHelper.UnloadAppDomain();
150         }
151 
Write(HelperResult result)152         public override void Write(HelperResult result)
153         {
154             if (result != null)
155             {
156                 result.WriteTo(Output);
157             }
158         }
159 
WriteLiteral(object value)160         public override void WriteLiteral(object value)
161         {
162             Output.Write(value);
163         }
164 
Write(object value)165         public override void Write(object value)
166         {
167             Output.Write(HttpUtility.HtmlEncode(value));
168         }
169 
GetOutputWriter()170         protected internal override TextWriter GetOutputWriter()
171         {
172             return Output;
173         }
174     }
175 }
176