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 ();
409 BuildManager.CompilingTopLevelAssemblies = true;
412 AppResourcesCompiler ac = new AppResourcesCompiler (context);
416 AppWebReferencesCompiler awrc = new AppWebReferencesCompiler ();
419 // Todo: Generate profile properties assembly from Web.config here
421 AppCodeCompiler acc = new AppCodeCompiler ();
424 BuildManager.AllowReferencedAssembliesCaching = true;
426 // Get the default machine *.browser files.
427 string default_machine_browsers_path = Path.Combine (HttpRuntime.MachineConfigurationDirectory, "Browsers");
428 default_machine_browsers_files = new string[0];
429 if (Directory.Exists (default_machine_browsers_path)) {
430 default_machine_browsers_files
431 = Directory.GetFiles (default_machine_browsers_path, "*.browser");
434 // Note whether there are any App_Data/Mono_Machine_Browsers/*.browser files. If there
435 // are we will be using them instead of the default machine *.browser files.
436 string app_mono_machine_browsers_path = Path.Combine (Path.Combine (physical_app_path, "App_Data"), "Mono_Machine_Browsers");
437 app_mono_machine_browsers_files = new string[0];
438 if (Directory.Exists (app_mono_machine_browsers_path)) {
439 app_mono_machine_browsers_files
440 = Directory.GetFiles (app_mono_machine_browsers_path, "*.browser");
443 // Note whether there are any App_Browsers/*.browser files. If there
444 // are we will be using *.browser files for sniffing in addition to browscap.ini
445 string app_browsers_path = Path.Combine (physical_app_path, "App_Browsers");
446 app_browsers_files = new string[0];
447 if (Directory.Exists (app_browsers_path)) {
448 app_browsers_files = Directory.GetFiles (app_browsers_path, "*.browser");
452 BuildManager.CompilingTopLevelAssemblies = false;
454 app_type = BuildManager.GetPrecompiledApplicationType ();
455 if (app_type == null && app_file != null) {
457 app_file = System.Web.Util.UrlUtils.ResolveVirtualPathFromAppAbsolute("~/" + Path.GetFileName(app_file));
458 app_type = System.Web.J2EE.PageMapper.GetObjectType(context, app_file);
460 app_type = BuildManager.GetCompiledType ("~/" + Path.GetFileName (app_file));
462 if (app_type == null) {
463 string msg = String.Format ("Error compiling application file ({0}).", app_file);
464 throw new ApplicationException (msg);
466 } else if (app_type == null) {
467 app_type = typeof (System.Web.HttpApplication);
468 app_state = new HttpApplicationState ();
471 WatchLocationForRestart ("?lobal.asax");
472 #if CODE_DISABLED_UNTIL_SYSTEM_CONFIGURATION_IS_FIXED
473 // This is the correct behavior, but until
474 // System.Configuration is fixed to properly reload
475 // configuration when it is modified on disk, we need to use
476 // the recursive watchers below.
477 WatchLocationForRestart ("?eb.?onfig");
479 // This is to avoid startup delays. Inotify/FAM code looks
480 // recursively for all subdirectories and adds them to the
481 // watch set. This can take a lot of time for deep directory
482 // trees (see bug #490497)
483 ThreadPool.QueueUserWorkItem (delegate {
485 WatchLocationForRestart (String.Empty, "?eb.?onfig", true);
486 } catch (Exception e) {
487 Console.Error.WriteLine (e);
492 } catch (Exception) {
493 if (BuildManager.CodeAssemblies != null)
494 BuildManager.CodeAssemblies.Clear ();
495 if (BuildManager.TopLevelAssemblies != null)
496 BuildManager.TopLevelAssemblies.Clear ();
497 if (WebConfigurationManager.ExtraAssemblies != null)
498 WebConfigurationManager.ExtraAssemblies.Clear ();
505 // Multiple-threads might hit this one on startup, and we have
506 // to delay-initialize until we have the HttpContext
508 internal static HttpApplication GetApplication (HttpContext context)
511 if (context.ApplicationInstance!=null)
512 return context.ApplicationInstance;
514 HttpApplicationFactory factory = theFactory;
515 HttpApplication app = null;
516 if (factory.app_start_needed){
520 factory.InitType (context);
522 if (factory.app_start_needed) {
523 foreach (string dir in HttpApplication.BinDirs)
524 WatchLocationForRestart (dir, "*.dll");
525 // Restart if the App_* directories are created...
526 WatchLocationForRestart (".", "App_Code");
527 WatchLocationForRestart (".", "App_Browsers");
528 WatchLocationForRestart (".", "App_GlobalResources");
529 // ...or their contents is changed.
530 WatchLocationForRestart ("App_Code", "*", true);
531 WatchLocationForRestart ("App_Browsers", "*");
532 WatchLocationForRestart ("App_GlobalResources", "*");
533 app = factory.FireOnAppStart (context);
534 factory.app_start_needed = false;
540 app = (HttpApplication) Interlocked.Exchange (ref factory.next_free, null);
542 app.RequestCompleted = false;
546 lock (factory.available) {
547 if (factory.available.Count > 0) {
548 app = (HttpApplication) factory.available.Pop ();
549 app.RequestCompleted = false;
554 return (HttpApplication) Activator.CreateInstance (factory.app_type, true);
557 // The lock is in InvokeSessionEnd
558 static HttpApplication GetApplicationForSessionEnd ()
560 HttpApplicationFactory factory = theFactory;
561 if (factory.available_for_end.Count > 0)
562 return (HttpApplication) factory.available_for_end.Pop ();
564 HttpApplication app = (HttpApplication) Activator.CreateInstance (factory.app_type, true);
565 app.InitOnce (false);
570 internal static void RecycleForSessionEnd (HttpApplication app)
572 bool dispose = false;
573 HttpApplicationFactory factory = theFactory;
574 lock (factory.available_for_end) {
575 if (factory.available_for_end.Count < 64)
576 factory.available_for_end.Push (app);
584 internal static void Recycle (HttpApplication app)
586 bool dispose = false;
587 HttpApplicationFactory factory = theFactory;
588 if (Interlocked.CompareExchange (ref factory.next_free, app, null) == null)
591 lock (factory.available) {
592 if (factory.available.Count < 64)
593 factory.available.Push (app);
601 internal static bool ContextAvailable {
602 get { return theFactory != null && !theFactory.app_start_needed; }
606 internal static bool WatchLocationForRestart (string filter)
608 return WatchLocationForRestart (String.Empty, filter, false);
611 internal static bool WatchLocationForRestart (string virtualPath, string filter)
613 return WatchLocationForRestart (virtualPath, filter, false);
616 internal static bool WatchLocationForRestart(string virtualPath, string filter, bool watchSubdirs)
618 // map the path to the physical one
619 string physicalPath = HttpRuntime.AppDomainAppPath;
620 physicalPath = Path.Combine(physicalPath, virtualPath);
621 bool isDir = Directory.Exists(physicalPath);
622 bool isFile = isDir ? false : File.Exists(physicalPath);
624 if (isDir || isFile) {
625 // create the watcher
626 FileSystemEventHandler fseh = new FileSystemEventHandler(OnFileChanged);
627 RenamedEventHandler reh = new RenamedEventHandler(OnFileRenamed);
628 FileSystemWatcher watcher = CreateWatcher(Path.Combine(physicalPath, filter), fseh, reh);
630 watcher.IncludeSubdirectories = watchSubdirs;
632 lock (watchers_lock) {
633 watchers.Add(watcher);
641 internal static bool ApplicationDisabled {
642 get { return app_disabled; }
643 set { app_disabled = value; }
646 internal static string[] AppBrowsersFiles {
647 get { return app_browsers_files; }
650 static System.Web.Configuration.nBrowser.Build capabilities_processor = null;
651 static object capabilities_processor_lock = new object();
652 internal static System.Web.Configuration.ICapabilitiesProcess CapabilitiesProcessor {
654 lock (capabilities_processor_lock) {
655 if (capabilities_processor == null) {
656 capabilities_processor = new System.Web.Configuration.nBrowser.Build();
657 string[] machine_browsers_files = app_mono_machine_browsers_files;
658 if (machine_browsers_files.Length == 0) {
659 machine_browsers_files = default_machine_browsers_files;
661 foreach (string f in machine_browsers_files) {
662 capabilities_processor.AddBrowserFile(f);
664 foreach (string f in app_browsers_files) {
665 capabilities_processor.AddBrowserFile(f);
669 return capabilities_processor;
673 internal static void DisableWatchers ()
675 lock (watchers_lock) {
676 foreach (FileSystemWatcher watcher in watchers)
677 watcher.EnableRaisingEvents = false;
681 internal static void DisableWatcher (string virtualPath, string filter)
683 EnableWatcherEvents (virtualPath, filter, false);
686 internal static void EnableWatcher (string virtualPath, string filter)
688 EnableWatcherEvents (virtualPath, filter, true);
691 static void EnableWatcherEvents (string virtualPath, string filter, bool enable)
693 lock (watchers_lock) {
694 foreach (FileSystemWatcher watcher in watchers) {
695 if (String.Compare (watcher.Path, virtualPath, StringComparison.Ordinal) != 0 || String.Compare (watcher.Filter, filter, StringComparison.Ordinal) != 0)
698 watcher.EnableRaisingEvents = enable;
703 internal static void EnableWatchers ()
705 lock (watchers_lock) {
706 foreach (FileSystemWatcher watcher in watchers)
707 watcher.EnableRaisingEvents = true;
711 static void OnFileRenamed(object sender, RenamedEventArgs args)
713 OnFileChanged(sender, args);
716 static void OnFileChanged(object sender, FileSystemEventArgs args)
718 if (HttpRuntime.DomainUnloading)
720 string name = args.Name;
721 bool isConfig = false;
723 if (StrUtils.EndsWith (name, "onfig", true)) {
724 if (String.Compare (Path.GetFileName (name), "web.config", true, Helpers.InvariantCulture) != 0)
727 } else if (StrUtils.EndsWith (name, "lobal.asax", true) && String.Compare (name, "global.asax", true, Helpers.InvariantCulture) != 0)
730 Console.WriteLine ("Change: " + name);
732 // {Inotify,FAM}Watcher will notify about events for a directory regardless
733 // of the filter pattern. This might be a bug in the watchers code, but
734 // since I couldn't find any rationale for the code in there I'd opted for
735 // not removing it and instead working around the issue here. Fix for bug
737 FileSystemWatcher watcher = sender as FileSystemWatcher;
738 if (watcher != null && String.Compare (watcher.Filter, "?eb.?onfig", true, Helpers.InvariantCulture) == 0 && Directory.Exists (name))
741 // We re-enable suppression here since WebConfigurationManager will disable
742 // it after save is done. WebConfigurationManager is called twice by
743 // Configuration - just after opening the target file and just after closing
744 // it. For that reason we will receive two change notifications and if we
745 // disabled suppression here, it would reload the application on the second
746 // change notification.
747 if (isConfig && WebConfigurationManager.SuppressAppReload (true))
750 lock (watchers_lock) {
755 // Disable event raising to avoid concurrent restarts
758 // Restart application
759 HttpRuntime.UnloadAppDomain();