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-2009 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;
31 using System.Globalization;
33 using System.Reflection;
35 using System.Web.SessionState;
36 using System.Web.Configuration;
37 using System.Threading;
38 using System.Web.Util;
40 using System.Web.Compilation;
44 using System.CodeDom.Compiler;
49 sealed class HttpApplicationFactory
51 object this_lock = new object ();
53 // Initialized in InitType
55 static HttpApplicationFactory theFactory {
58 HttpApplicationFactory factory = (HttpApplicationFactory)AppDomain.CurrentDomain.GetData("HttpApplicationFactory");
59 if (factory == null) {
60 lock(typeof(HttpApplicationFactory)) {
61 factory = (HttpApplicationFactory)AppDomain.CurrentDomain.GetData("HttpApplicationFactory");
62 if (factory == null) {
63 factory = new HttpApplicationFactory();
64 System.Threading.Thread.Sleep(1);
65 AppDomain.CurrentDomain.SetData("HttpApplicationFactory", factory);
73 static HttpApplicationFactory theFactory = new HttpApplicationFactory();
75 object session_end; // This is a MethodInfo
76 bool needs_init = true;
77 bool app_start_needed = true;
80 HttpApplicationState app_state;
81 Hashtable app_event_handlers;
82 static ArrayList watchers = new ArrayList();
83 static object watchers_lock = new object();
84 static bool app_shutdown = false;
85 static bool app_disabled = false;
86 static string[] app_browsers_files = new string[0];
87 static string[] default_machine_browsers_files = new string[0];
88 static string[] app_mono_machine_browsers_files = new string[0];
89 Stack available = new Stack ();
91 Stack available_for_end = new Stack ();
93 bool IsEventHandler (MethodInfo m)
95 int pos = m.Name.IndexOf ('_');
96 if (pos == -1 || (m.Name.Length - 1) <= pos)
99 if (m.ReturnType != typeof (void))
102 ParameterInfo [] pi = m.GetParameters ();
103 int length = pi.Length;
110 if (pi [0].ParameterType != typeof (object) ||
111 !typeof (EventArgs).IsAssignableFrom (pi [1].ParameterType))
117 void AddEvent (MethodInfo method, Hashtable appTypeEventHandlers)
119 string name = method.Name.Replace ("_On", "_");
120 if (appTypeEventHandlers [name] == null) {
121 appTypeEventHandlers [name] = method;
125 MethodInfo old_method = appTypeEventHandlers [name] as MethodInfo;
127 if (old_method != null){
128 list = new ArrayList (4);
129 list.Add (old_method);
130 appTypeEventHandlers [name] = list;
132 list = appTypeEventHandlers [name] as ArrayList;
137 ArrayList GetMethodsDeep (Type type)
139 ArrayList al = new ArrayList ();
140 MethodInfo[] methods = type.GetMethods (BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static | BindingFlags.FlattenHierarchy);
141 al.AddRange (methods);
143 Type t = type.BaseType;
145 methods = t.GetMethods (BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static);
146 al.AddRange (methods);
153 Hashtable GetApplicationTypeEvents (Type type)
156 return app_event_handlers;
159 if (app_event_handlers != null)
160 return app_event_handlers;
162 app_event_handlers = new Hashtable ();
163 ArrayList methods = GetMethodsDeep (type);
164 Hashtable used = null;
168 foreach (object o in methods) {
170 if (m.DeclaringType != typeof (HttpApplication) && IsEventHandler (m)) {
171 mname = m.ToString ();
173 used = new Hashtable ();
174 else if (used.ContainsKey (mname))
177 AddEvent (m, app_event_handlers);
181 have_app_events = true;
184 return app_event_handlers;
187 Hashtable GetApplicationTypeEvents (HttpApplication app)
190 return app_event_handlers;
192 return GetApplicationTypeEvents (app.GetType ());
195 bool FireEvent (string method_name, object target, object [] args)
197 Hashtable possibleEvents = GetApplicationTypeEvents ((HttpApplication) target);
198 MethodInfo method = possibleEvents [method_name] as MethodInfo;
202 if (method.GetParameters ().Length == 0)
205 method.Invoke (target, args);
210 HttpApplication FireOnAppStart (HttpContext context)
212 HttpApplication app = (HttpApplication) Activator.CreateInstance (app_type, true);
213 context.ApplicationInstance = app;
214 app.SetContext (context);
215 object [] args = new object [] {app, EventArgs.Empty};
216 app.InApplicationStart = true;
217 FireEvent ("Application_Start", app, args);
218 app.InApplicationStart = false;
224 if (app_type == null)
225 return; // we didn't even get an application
227 HttpApplication app = (HttpApplication) Activator.CreateInstance (app_type, true);
228 FireEvent ("Application_End", app, new object [] {new object (), EventArgs.Empty});
229 app.DisposeInternal ();
234 // This is invoked by HttpRuntime.Dispose, when we unload an AppDomain
235 // To reproduce this in action, touch "global.asax" while XSP is running.
237 public static void Dispose ()
239 theFactory.FireOnAppEnd ();
242 static FileSystemWatcher CreateWatcher (string file, FileSystemEventHandler hnd, RenamedEventHandler reh)
244 FileSystemWatcher watcher = new FileSystemWatcher ();
246 watcher.Path = Path.GetFullPath (Path.GetDirectoryName (file));
247 watcher.Filter = Path.GetFileName (file);
249 // This will enable the Modify flag for Linux/inotify
250 watcher.NotifyFilter |= NotifyFilters.Size;
252 watcher.Changed += hnd;
253 watcher.Created += hnd;
254 watcher.Deleted += hnd;
255 watcher.Renamed += reh;
257 watcher.EnableRaisingEvents = true;
262 internal static void AttachEvents (HttpApplication app)
264 HttpApplicationFactory factory = theFactory;
265 Hashtable possibleEvents = factory.GetApplicationTypeEvents (app);
266 foreach (string key in possibleEvents.Keys) {
267 int pos = key.IndexOf ('_');
268 string moduleName = key.Substring (0, pos);
270 if (moduleName == "Application") {
273 target = app.Modules [moduleName];
278 string eventName = key.Substring (pos + 1);
279 EventInfo evt = target.GetType ().GetEvent (eventName);
283 string usualName = moduleName + "_" + eventName;
284 object methodData = possibleEvents [usualName];
285 if (methodData == null)
288 if (eventName == "End" && moduleName == "Session") {
289 Interlocked.CompareExchange (ref factory.session_end, methodData, null);
293 if (methodData is MethodInfo) {
294 factory.AddHandler (evt, target, app, (MethodInfo) methodData);
298 ArrayList list = (ArrayList) methodData;
299 foreach (MethodInfo method in list)
300 factory.AddHandler (evt, target, app, method);
304 void AddHandler (EventInfo evt, object target, HttpApplication app, MethodInfo method)
306 int length = method.GetParameters ().Length;
309 NoParamsInvoker npi = new NoParamsInvoker (app, method);
310 evt.AddEventHandler (target, npi.FakeDelegate);
312 if (method.IsStatic) {
313 evt.AddEventHandler (target, Delegate.CreateDelegate (
314 evt.EventHandlerType, method));
316 evt.AddEventHandler (target, Delegate.CreateDelegate (
317 evt.EventHandlerType, app,
324 internal static void InvokeSessionEnd (object state)
326 InvokeSessionEnd (state, null, EventArgs.Empty);
329 internal static void InvokeSessionEnd (object state, object source, EventArgs e)
331 HttpApplicationFactory factory = theFactory;
332 MethodInfo method = null;
333 HttpApplication app = null;
334 lock (factory.available_for_end) {
335 method = (MethodInfo) factory.session_end;
339 app = GetApplicationForSessionEnd ();
342 app.SetSession ((HttpSessionState) state);
344 method.Invoke (app, new object [] {(source == null ? app : source), e});
345 } catch (Exception) {
348 RecycleForSessionEnd (app);
351 static HttpStaticObjectsCollection MakeStaticCollection (ArrayList list)
353 if (list == null || list.Count == 0)
356 HttpStaticObjectsCollection coll = new HttpStaticObjectsCollection ();
357 foreach (ObjectTagBuilder tag in list) {
364 internal static HttpApplicationState ApplicationState {
367 HttpApplicationFactory factory = theFactory;
368 if (factory.app_state == null)
369 factory.app_state = new HttpApplicationState (null, null);
370 return factory.app_state;
374 if (theFactory.app_state == null) {
375 HttpStaticObjectsCollection app = MakeStaticCollection (GlobalAsaxCompiler.ApplicationObjects);
376 HttpStaticObjectsCollection ses = MakeStaticCollection (GlobalAsaxCompiler.SessionObjects);
378 theFactory.app_state = new HttpApplicationState (app, ses);
380 return theFactory.app_state;
385 internal static Type AppType {
387 return theFactory.app_type;
391 void InitType (HttpContext context)
398 string physical_app_path = HttpRuntime.AppDomainAppPath;
399 string app_file = null;
401 app_file = Path.Combine (physical_app_path, "Global.asax");
402 if (!File.Exists (app_file)) {
403 app_file = Path.Combine (physical_app_path, "global.asax");
404 if (!File.Exists (app_file))
408 BuildManager.CallPreStartMethods ();
411 AppResourcesCompiler ac = new AppResourcesCompiler (context);
415 AppWebReferencesCompiler awrc = new AppWebReferencesCompiler ();
418 // Todo: Generate profile properties assembly from Web.config here
420 AppCodeCompiler acc = new AppCodeCompiler ();
423 BuildManager.AllowReferencedAssembliesCaching = true;
425 // Get the default machine *.browser files.
426 string default_machine_browsers_path = Path.Combine (HttpRuntime.MachineConfigurationDirectory, "Browsers");
427 default_machine_browsers_files = new string[0];
428 if (Directory.Exists (default_machine_browsers_path)) {
429 default_machine_browsers_files
430 = Directory.GetFiles (default_machine_browsers_path, "*.browser");
433 // Note whether there are any App_Data/Mono_Machine_Browsers/*.browser files. If there
434 // are we will be using them instead of the default machine *.browser files.
435 string app_mono_machine_browsers_path = Path.Combine (Path.Combine (physical_app_path, "App_Data"), "Mono_Machine_Browsers");
436 app_mono_machine_browsers_files = new string[0];
437 if (Directory.Exists (app_mono_machine_browsers_path)) {
438 app_mono_machine_browsers_files
439 = Directory.GetFiles (app_mono_machine_browsers_path, "*.browser");
442 // Note whether there are any App_Browsers/*.browser files. If there
443 // are we will be using *.browser files for sniffing in addition to browscap.ini
444 string app_browsers_path = Path.Combine (physical_app_path, "App_Browsers");
445 app_browsers_files = new string[0];
446 if (Directory.Exists (app_browsers_path)) {
447 app_browsers_files = Directory.GetFiles (app_browsers_path, "*.browser");
451 app_type = BuildManager.GetPrecompiledApplicationType ();
452 if (app_type == null && app_file != null) {
454 app_file = System.Web.Util.UrlUtils.ResolveVirtualPathFromAppAbsolute("~/" + Path.GetFileName(app_file));
455 app_type = System.Web.J2EE.PageMapper.GetObjectType(context, app_file);
457 app_type = BuildManager.GetCompiledType ("~/" + Path.GetFileName (app_file));
459 if (app_type == null) {
460 string msg = String.Format ("Error compiling application file ({0}).", app_file);
461 throw new ApplicationException (msg);
463 } else if (app_type == null) {
464 app_type = typeof (System.Web.HttpApplication);
465 app_state = new HttpApplicationState ();
468 WatchLocationForRestart ("?lobal.asax");
469 #if CODE_DISABLED_UNTIL_SYSTEM_CONFIGURATION_IS_FIXED
470 // This is the correct behavior, but until
471 // System.Configuration is fixed to properly reload
472 // configuration when it is modified on disk, we need to use
473 // the recursive watchers below.
474 WatchLocationForRestart ("?eb.?onfig");
476 // This is to avoid startup delays. Inotify/FAM code looks
477 // recursively for all subdirectories and adds them to the
478 // watch set. This can take a lot of time for deep directory
479 // trees (see bug #490497)
480 ThreadPool.QueueUserWorkItem (delegate {
482 WatchLocationForRestart (String.Empty, "?eb.?onfig", true);
483 } catch (Exception e) {
484 Console.Error.WriteLine (e);
489 } catch (Exception) {
490 if (BuildManager.CodeAssemblies != null)
491 BuildManager.CodeAssemblies.Clear ();
492 if (BuildManager.TopLevelAssemblies != null)
493 BuildManager.TopLevelAssemblies.Clear ();
494 if (WebConfigurationManager.ExtraAssemblies != null)
495 WebConfigurationManager.ExtraAssemblies.Clear ();
502 // Multiple-threads might hit this one on startup, and we have
503 // to delay-initialize until we have the HttpContext
505 internal static HttpApplication GetApplication (HttpContext context)
508 if (context.ApplicationInstance!=null)
509 return context.ApplicationInstance;
511 HttpApplicationFactory factory = theFactory;
512 HttpApplication app = null;
513 if (factory.app_start_needed){
517 factory.InitType (context);
519 if (factory.app_start_needed) {
520 foreach (string dir in HttpApplication.BinDirs)
521 WatchLocationForRestart (dir, "*.dll");
522 // Restart if the App_* directories are created...
523 WatchLocationForRestart (".", "App_Code");
524 WatchLocationForRestart (".", "App_Browsers");
525 WatchLocationForRestart (".", "App_GlobalResources");
526 // ...or their contents is changed.
527 WatchLocationForRestart ("App_Code", "*", true);
528 WatchLocationForRestart ("App_Browsers", "*");
529 WatchLocationForRestart ("App_GlobalResources", "*");
530 app = factory.FireOnAppStart (context);
531 factory.app_start_needed = false;
537 app = (HttpApplication) Interlocked.Exchange (ref factory.next_free, null);
539 app.RequestCompleted = false;
543 lock (factory.available) {
544 if (factory.available.Count > 0) {
545 app = (HttpApplication) factory.available.Pop ();
546 app.RequestCompleted = false;
551 return (HttpApplication) Activator.CreateInstance (factory.app_type, true);
554 // The lock is in InvokeSessionEnd
555 static HttpApplication GetApplicationForSessionEnd ()
557 HttpApplicationFactory factory = theFactory;
558 if (factory.available_for_end.Count > 0)
559 return (HttpApplication) factory.available_for_end.Pop ();
561 HttpApplication app = (HttpApplication) Activator.CreateInstance (factory.app_type, true);
562 app.InitOnce (false);
567 internal static void RecycleForSessionEnd (HttpApplication app)
569 bool dispose = false;
570 HttpApplicationFactory factory = theFactory;
571 lock (factory.available_for_end) {
572 if (factory.available_for_end.Count < 64)
573 factory.available_for_end.Push (app);
581 internal static void Recycle (HttpApplication app)
583 bool dispose = false;
584 HttpApplicationFactory factory = theFactory;
585 if (Interlocked.CompareExchange (ref factory.next_free, app, null) == null)
588 lock (factory.available) {
589 if (factory.available.Count < 64)
590 factory.available.Push (app);
598 internal static bool ContextAvailable {
599 get { return theFactory != null && !theFactory.app_start_needed; }
603 internal static bool WatchLocationForRestart (string filter)
605 return WatchLocationForRestart (String.Empty, filter, false);
608 internal static bool WatchLocationForRestart (string virtualPath, string filter)
610 return WatchLocationForRestart (virtualPath, filter, false);
613 internal static bool WatchLocationForRestart(string virtualPath, string filter, bool watchSubdirs)
615 // map the path to the physical one
616 string physicalPath = HttpRuntime.AppDomainAppPath;
617 physicalPath = Path.Combine(physicalPath, virtualPath);
618 bool isDir = Directory.Exists(physicalPath);
619 bool isFile = isDir ? false : File.Exists(physicalPath);
621 if (isDir || isFile) {
622 // create the watcher
623 FileSystemEventHandler fseh = new FileSystemEventHandler(OnFileChanged);
624 RenamedEventHandler reh = new RenamedEventHandler(OnFileRenamed);
625 FileSystemWatcher watcher = CreateWatcher(Path.Combine(physicalPath, filter), fseh, reh);
627 watcher.IncludeSubdirectories = watchSubdirs;
629 lock (watchers_lock) {
630 watchers.Add(watcher);
638 internal static bool ApplicationDisabled {
639 get { return app_disabled; }
640 set { app_disabled = value; }
643 internal static string[] AppBrowsersFiles {
644 get { return app_browsers_files; }
647 static System.Web.Configuration.nBrowser.Build capabilities_processor = null;
648 static object capabilities_processor_lock = new object();
649 internal static System.Web.Configuration.ICapabilitiesProcess CapabilitiesProcessor {
651 lock (capabilities_processor_lock) {
652 if (capabilities_processor == null) {
653 capabilities_processor = new System.Web.Configuration.nBrowser.Build();
654 string[] machine_browsers_files = app_mono_machine_browsers_files;
655 if (machine_browsers_files.Length == 0) {
656 machine_browsers_files = default_machine_browsers_files;
658 foreach (string f in machine_browsers_files) {
659 capabilities_processor.AddBrowserFile(f);
661 foreach (string f in app_browsers_files) {
662 capabilities_processor.AddBrowserFile(f);
666 return capabilities_processor;
670 internal static void DisableWatchers ()
672 lock (watchers_lock) {
673 foreach (FileSystemWatcher watcher in watchers)
674 watcher.EnableRaisingEvents = false;
678 internal static void DisableWatcher (string virtualPath, string filter)
680 EnableWatcherEvents (virtualPath, filter, false);
683 internal static void EnableWatcher (string virtualPath, string filter)
685 EnableWatcherEvents (virtualPath, filter, true);
688 static void EnableWatcherEvents (string virtualPath, string filter, bool enable)
690 lock (watchers_lock) {
691 foreach (FileSystemWatcher watcher in watchers) {
692 if (String.Compare (watcher.Path, virtualPath, StringComparison.Ordinal) != 0 || String.Compare (watcher.Filter, filter, StringComparison.Ordinal) != 0)
695 watcher.EnableRaisingEvents = enable;
700 internal static void EnableWatchers ()
702 lock (watchers_lock) {
703 foreach (FileSystemWatcher watcher in watchers)
704 watcher.EnableRaisingEvents = true;
708 static void OnFileRenamed(object sender, RenamedEventArgs args)
710 OnFileChanged(sender, args);
713 static void OnFileChanged(object sender, FileSystemEventArgs args)
715 string name = args.Name;
716 bool isConfig = false;
718 if (StrUtils.EndsWith (name, "onfig", true)) {
719 if (String.Compare (Path.GetFileName (name), "web.config", true, Helpers.InvariantCulture) != 0)
722 } else if (StrUtils.EndsWith (name, "lobal.asax", true) && String.Compare (name, "global.asax", true, Helpers.InvariantCulture) != 0)
725 Console.WriteLine ("Change: " + name);
727 // {Inotify,FAM}Watcher will notify about events for a directory regardless
728 // of the filter pattern. This might be a bug in the watchers code, but
729 // since I couldn't find any rationale for the code in there I'd opted for
730 // not removing it and instead working around the issue here. Fix for bug
732 FileSystemWatcher watcher = sender as FileSystemWatcher;
733 if (watcher != null && String.Compare (watcher.Filter, "?eb.?onfig", true, Helpers.InvariantCulture) == 0 && Directory.Exists (name))
736 // We re-enable suppression here since WebConfigurationManager will disable
737 // it after save is done. WebConfigurationManager is called twice by
738 // Configuration - just after opening the target file and just after closing
739 // it. For that reason we will receive two change notifications and if we
740 // disabled suppression here, it would reload the application on the second
741 // change notification.
742 if (isConfig && WebConfigurationManager.SuppressAppReload (true))
745 lock (watchers_lock) {
750 // Disable event raising to avoid concurrent restarts
753 // Restart application
754 HttpRuntime.UnloadAppDomain();