2 // System.Web.HttpApplicationFactory
5 // Gonzalo Paniagua Javier (gonzalo@ximian.com)
7 // (c) 2002,2003 Ximian, Inc. (http://www.ximian.com)
8 // (c) Copyright 2004 Novell, Inc. (http://www.novell.com)
10 // Permission is hereby granted, free of charge, to any person obtaining
11 // a copy of this software and associated documentation files (the
12 // "Software"), to deal in the Software without restriction, including
13 // without limitation the rights to use, copy, modify, merge, publish,
14 // distribute, sublicense, and/or sell copies of the Software, and to
15 // permit persons to whom the Software is furnished to do so, subject to
16 // the following conditions:
18 // The above copyright notice and this permission notice shall be
19 // included in all copies or substantial portions of the Software.
21 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
22 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
23 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
24 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
25 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
26 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
27 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
30 using System.Collections;
32 using System.Reflection;
34 using System.Web.SessionState;
35 using System.Web.Configuration;
37 using System.Web.Compilation;
42 #if NET_2_0 && !TARGET_J2EE
43 using System.CodeDom.Compiler;
46 namespace System.Web {
47 class HttpApplicationFactory {
48 // Initialized in InitType
50 static HttpApplicationFactory theFactory {
53 HttpApplicationFactory factory = (HttpApplicationFactory)AppDomain.CurrentDomain.GetData("HttpApplicationFactory");
54 if (factory == null) {
55 lock(typeof(HttpApplicationFactory)) {
56 factory = (HttpApplicationFactory)AppDomain.CurrentDomain.GetData("HttpApplicationFactory");
57 if (factory == null) {
58 factory = new HttpApplicationFactory();
59 System.Threading.Thread.Sleep(1);
60 AppDomain.CurrentDomain.SetData("HttpApplicationFactory", factory);
68 static HttpApplicationFactory theFactory = new HttpApplicationFactory();
72 MethodInfo session_end;
73 bool needs_init = true;
74 bool app_start_needed = true;
76 HttpApplicationState app_state;
77 Hashtable app_event_handlers;
78 static ArrayList watchers = new ArrayList();
79 static object watchers_lock = new object();
80 Stack available = new Stack ();
81 Stack available_for_end = new Stack ();
83 bool IsEventHandler (MethodInfo m)
85 int pos = m.Name.IndexOf ('_');
86 if (pos == -1 || (m.Name.Length - 1) <= pos)
89 if (m.ReturnType != typeof (void))
92 ParameterInfo [] pi = m.GetParameters ();
93 int length = pi.Length;
100 if (pi [0].ParameterType != typeof (object) ||
101 pi [1].ParameterType != typeof (EventArgs))
107 void AddEvent (MethodInfo method, Hashtable appTypeEventHandlers)
109 string name = method.Name.Replace ("_On", "_");
110 if (appTypeEventHandlers [name] == null) {
111 appTypeEventHandlers [name] = method;
115 MethodInfo old_method = appTypeEventHandlers [name] as MethodInfo;
117 if (old_method != null){
118 list = new ArrayList (4);
119 list.Add (old_method);
120 appTypeEventHandlers [name] = list;
122 list = appTypeEventHandlers [name] as ArrayList;
127 Hashtable GetApplicationTypeEvents (Type type)
130 if (app_event_handlers != null)
131 return app_event_handlers;
133 app_event_handlers = new Hashtable ();
134 BindingFlags flags = BindingFlags.Public | BindingFlags.NonPublic |
135 BindingFlags.Instance | BindingFlags.Static;
137 MethodInfo [] methods = type.GetMethods (flags);
138 foreach (MethodInfo m in methods) {
139 if (m.DeclaringType != typeof (HttpApplication) && IsEventHandler (m))
140 AddEvent (m, app_event_handlers);
144 return app_event_handlers;
147 Hashtable GetApplicationTypeEvents (HttpApplication app)
150 if (app_event_handlers != null)
151 return app_event_handlers;
153 return GetApplicationTypeEvents (app.GetType ());
157 bool FireEvent (string method_name, object target, object [] args)
159 Hashtable possibleEvents = GetApplicationTypeEvents ((HttpApplication) target);
160 MethodInfo method = possibleEvents [method_name] as MethodInfo;
164 if (method.GetParameters ().Length == 0)
167 method.Invoke (target, args);
172 HttpApplication FireOnAppStart (HttpContext context)
174 HttpApplication app = (HttpApplication) Activator.CreateInstance (app_type, true);
175 context.ApplicationInstance = app;
176 app.SetContext (context);
177 object [] args = new object [] {app, EventArgs.Empty};
178 FireEvent ("Application_Start", app, args);
184 if (app_type == null)
185 return; // we didn't even get an application
187 HttpApplication app = (HttpApplication) Activator.CreateInstance (app_type, true);
188 FireEvent ("Application_End", app, new object [] {new object (), EventArgs.Empty});
193 // This is invoked by HttpRuntime.Dispose, when we unload an AppDomain
194 // To reproduce this in action, touch "global.asax" while XSP is running.
196 public static void Dispose ()
198 theFactory.FireOnAppEnd ();
201 static FileSystemWatcher CreateWatcher (string file, FileSystemEventHandler hnd, RenamedEventHandler reh)
203 FileSystemWatcher watcher = new FileSystemWatcher ();
205 watcher.Path = Path.GetFullPath (Path.GetDirectoryName (file));
206 watcher.Filter = Path.GetFileName (file);
208 watcher.Changed += hnd;
209 watcher.Created += hnd;
210 watcher.Deleted += hnd;
211 watcher.Renamed += reh;
213 watcher.EnableRaisingEvents = true;
218 internal static void AttachEvents (HttpApplication app)
220 HttpApplicationFactory factory = theFactory;
221 Hashtable possibleEvents = factory.GetApplicationTypeEvents (app);
222 foreach (string key in possibleEvents.Keys) {
223 int pos = key.IndexOf ('_');
224 string moduleName = key.Substring (0, pos);
226 if (moduleName == "Application") {
229 target = app.Modules [moduleName];
234 string eventName = key.Substring (pos + 1);
235 EventInfo evt = target.GetType ().GetEvent (eventName);
239 string usualName = moduleName + "_" + eventName;
240 object methodData = possibleEvents [usualName];
241 if (methodData != null && eventName == "End" && moduleName == "Session") {
243 if (factory.session_end == null)
244 factory.session_end = (MethodInfo) methodData;
249 if (methodData == null)
252 if (methodData is MethodInfo) {
253 factory.AddHandler (evt, target, app, (MethodInfo) methodData);
257 ArrayList list = (ArrayList) methodData;
258 foreach (MethodInfo method in list)
259 factory.AddHandler (evt, target, app, method);
263 void AddHandler (EventInfo evt, object target, HttpApplication app, MethodInfo method)
265 int length = method.GetParameters ().Length;
268 NoParamsInvoker npi = new NoParamsInvoker (app, method.Name);
269 evt.AddEventHandler (target, npi.FakeDelegate);
271 evt.AddEventHandler (target, Delegate.CreateDelegate (
272 evt.EventHandlerType, app, method.Name));
276 internal static void InvokeSessionEnd (object state)
278 InvokeSessionEnd (state, null, EventArgs.Empty);
281 internal static void InvokeSessionEnd (object state, object source, EventArgs e)
283 HttpApplicationFactory factory = theFactory;
284 MethodInfo method = null;
285 HttpApplication app = null;
286 lock (factory.available_for_end) {
287 method = factory.session_end;
291 app = GetApplicationForSessionEnd ();
294 app.SetSession ((HttpSessionState) state);
296 method.Invoke (app, new object [] {(source == null ? app : source), e});
297 } catch (Exception) {
300 RecycleForSessionEnd (app);
303 static HttpStaticObjectsCollection MakeStaticCollection (ArrayList list)
305 if (list == null || list.Count == 0)
308 HttpStaticObjectsCollection coll = new HttpStaticObjectsCollection ();
309 foreach (ObjectTagBuilder tag in list) {
316 internal static HttpApplicationState ApplicationState {
319 HttpApplicationFactory factory = theFactory;
320 if (factory.app_state == null)
321 factory.app_state = new HttpApplicationState (null, null);
322 return factory.app_state;
326 if (theFactory.app_state == null) {
327 HttpStaticObjectsCollection app = MakeStaticCollection (GlobalAsaxCompiler.ApplicationObjects);
328 HttpStaticObjectsCollection ses = MakeStaticCollection (GlobalAsaxCompiler.SessionObjects);
330 theFactory.app_state = new HttpApplicationState (app, ses);
332 return theFactory.app_state;
337 internal static Type AppType {
339 return theFactory.app_type;
343 void InitType (HttpContext context)
352 string physical_app_path = context.Request.PhysicalApplicationPath;
353 string app_file = null;
355 app_file = Path.Combine (physical_app_path, "Global.asax");
356 if (!File.Exists (app_file)) {
357 app_file = Path.Combine (physical_app_path, "global.asax");
358 if (!File.Exists (app_file))
363 WebConfigurationSettings.Init (context);
366 #if NET_2_0 && !TARGET_J2EE
367 AppResourcesCompiler ac = new AppResourcesCompiler (context);
370 // Todo: Process App_WebResources here
372 // Todo: Generate profile properties assembly from Web.config here
374 // Todo: Compile code from App_Code here
375 AppCodeCompiler acc = new AppCodeCompiler ();
379 if (app_file != null) {
381 app_type = System.Web.J2EE.PageMapper.GetObjectType(app_file);
383 app_type = ApplicationFileParser.GetCompiledApplicationType (app_file, context);
384 if (app_type == null) {
385 string msg = String.Format ("Error compiling application file ({0}).", app_file);
386 throw new ApplicationException (msg);
390 app_type = typeof (System.Web.HttpApplication);
391 app_state = new HttpApplicationState ();
394 if (app_file != null)
395 WatchLocationForRestart(app_file);
397 if (File.Exists(Path.Combine(physical_app_path, "Web.config")))
398 WatchLocationForRestart("Web.config");
399 else if (File.Exists(Path.Combine(physical_app_path, "web.config")))
400 WatchLocationForRestart("web.config");
401 else if (File.Exists(Path.Combine(physical_app_path, "Web.Config")))
402 WatchLocationForRestart("Web.Config");
405 } catch (Exception) {
406 if (BuildManager.CodeAssemblies != null)
407 BuildManager.CodeAssemblies.Clear ();
408 if (BuildManager.TopLevelAssemblies != null)
409 BuildManager.TopLevelAssemblies.Clear ();
410 if (WebConfigurationManager.ExtraAssemblies != null)
411 WebConfigurationManager.ExtraAssemblies.Clear ();
417 // Now init the settings
424 // Multiple-threads might hit this one on startup, and we have
425 // to delay-initialize until we have the HttpContext
427 internal static HttpApplication GetApplication (HttpContext context)
429 HttpApplicationFactory factory = theFactory;
430 HttpApplication app = null;
431 if (factory.app_start_needed){
435 factory.InitType (context);
437 if (factory.app_start_needed) {
438 WatchLocationForRestart (AppDomain.CurrentDomain.SetupInformation.PrivateBinPath,
441 WatchLocationForRestart ("App_Code", "");
442 WatchLocationForRestart ("App_Browsers", "");
443 WatchLocationForRestart ("App_GlobalResources", "");
445 app = factory.FireOnAppStart (context);
446 factory.app_start_needed = false;
452 lock (factory.available) {
453 if (factory.available.Count > 0) {
454 app = (HttpApplication) factory.available.Pop ();
455 app.RequestCompleted = false;
460 return (HttpApplication) Activator.CreateInstance (factory.app_type, true);
463 // The lock is in InvokeSessionEnd
464 static HttpApplication GetApplicationForSessionEnd ()
466 HttpApplicationFactory factory = theFactory;
467 if (factory.available_for_end.Count > 0)
468 return (HttpApplication) factory.available_for_end.Pop ();
470 HttpApplication app = (HttpApplication) Activator.CreateInstance (factory.app_type, true);
471 app.InitOnce (false);
476 internal static void RecycleForSessionEnd (HttpApplication app)
478 HttpApplicationFactory factory = theFactory;
479 lock (factory.available_for_end) {
480 if (factory.available_for_end.Count < 32)
481 factory.available_for_end.Push (app);
487 internal static void Recycle (HttpApplication app)
489 HttpApplicationFactory factory = theFactory;
490 lock (factory.available) {
491 if (factory.available.Count < 32)
492 factory.available.Push (app);
498 internal static bool ContextAvailable {
499 get { return theFactory != null && !theFactory.app_start_needed; }
502 internal static bool WatchLocationForRestart(string filter)
504 return WatchLocationForRestart("", filter);
507 internal static bool WatchLocationForRestart(string virtualPath, string filter)
509 // map the path to the physical one
510 string physicalPath = HttpRuntime.AppDomainAppPath;
511 physicalPath = Path.Combine(physicalPath, virtualPath);
513 if (Directory.Exists(physicalPath) || File.Exists(physicalPath)) {
514 physicalPath = Path.Combine(physicalPath, filter);
516 // create the watcher
517 FileSystemEventHandler fseh = new FileSystemEventHandler(OnFileChanged);
518 RenamedEventHandler reh = new RenamedEventHandler(OnFileRenamed);
519 FileSystemWatcher watcher = CreateWatcher(physicalPath, fseh, reh);
521 lock (watchers_lock) {
522 watchers.Add(watcher);
530 static void OnFileRenamed(object sender, RenamedEventArgs args)
532 OnFileChanged(sender, args);
535 static void OnFileChanged(object sender, FileSystemEventArgs args)
537 lock (watchers_lock) {
538 // Disable event raising to avoid concurrent restarts
539 foreach (FileSystemWatcher watcher in watchers) {
540 watcher.EnableRaisingEvents = false;
542 // Restart application
543 HttpRuntime.UnloadAppDomain();