using System; using System.Reflection; using System.Threading; using System.Web; using System.Web.Hosting; using log4net; using Umbraco.Core.Composing; using Umbraco.Core.Logging; namespace Umbraco.Core { /// /// Provides an abstract base class for the Umbraco HttpApplication. /// public abstract class UmbracoApplicationBase : HttpApplication { private IRuntime _runtime; /// /// Gets a runtime. /// protected abstract IRuntime GetRuntime(); /// /// Gets a logger. /// protected virtual ILogger GetLogger() { return Logger.CreateWithDefaultLog4NetConfiguration(); } /// /// Gets the application container. /// protected virtual IContainer GetContainer() { // note: the actual, web UmbracoApplication is overriding this // with a web-supporting container return ContainerFactory.Create(); } // events - in the order they trigger // were part of the BootManager architecture, would trigger only for the initial // application, so they need not be static, and they would let ppl hook into the // boot process... but I believe this can be achieved with components as well and // we don't need these events. //public event EventHandler ApplicationStarting; //public event EventHandler ApplicationStarted; // this event can only be static since there will be several instances of this class // triggers for each application instance, ie many times per lifetime of the application public static event EventHandler ApplicationInit; // this event can only be static since there will be several instances of this class // triggers once per error public static event EventHandler ApplicationError; // this event can only be static since there will be several instances of this class // triggers once per lifetime of the application, before it is unloaded public static event EventHandler ApplicationEnd; #region Start // internal for tests internal void HandleApplicationStart(object sender, EventArgs evargs) { // ******** THIS IS WHERE EVERYTHING BEGINS ******** // create the container for the application, and configure. // the boot manager is responsible for registrations var container = GetContainer(); container.RegisterSingleton(x => container); Current.Container = container; // register the essential stuff, // ie the global application logger // (profiler etc depend on boot manager) var logger = GetLogger(); container.RegisterInstance(logger); // now it is ok to use Current.Logger ConfigureUnhandledException(logger); ConfigureAssemblyResolve(logger); // get runtime & boot _runtime = GetRuntime(); _runtime.Boot(container); } protected virtual void ConfigureUnhandledException(ILogger logger) { //take care of unhandled exceptions - there is nothing we can do to // prevent the entire w3wp process to go down but at least we can try // and log the exception AppDomain.CurrentDomain.UnhandledException += (_, args) => { var exception = (Exception)args.ExceptionObject; var isTerminating = args.IsTerminating; // always true? var msg = "Unhandled exception in AppDomain"; if (isTerminating) msg += " (terminating)"; msg += "."; logger.Error(msg, exception); }; } protected virtual void ConfigureAssemblyResolve(ILogger logger) { // When an assembly can't be resolved. In here we can do magic with the assembly name and try loading another. // This is used for loading a signed assembly of AutoMapper (v. 3.1+) without having to recompile old code. AppDomain.CurrentDomain.AssemblyResolve += (sender, args) => { // ensure the assembly is indeed AutoMapper and that the PublicKeyToken is null before trying to Load again // do NOT just replace this with 'return Assembly', as it will cause an infinite loop -> stackoverflow if (args.Name.StartsWith("AutoMapper") && args.Name.EndsWith("PublicKeyToken=null")) return Assembly.Load(args.Name.Replace(", PublicKeyToken=null", ", PublicKeyToken=be96cd2c38ef1005")); return null; }; } // called by ASP.NET (auto event wireup) once per app domain // do NOT set instance data here - only static (see docs) // sender is System.Web.HttpApplicationFactory, evargs is EventArgs.Empty protected void Application_Start(object sender, EventArgs evargs) { Thread.CurrentThread.SanitizeThreadCulture(); HandleApplicationStart(sender, evargs); } #endregion #region Init private void OnApplicationInit(object sender, EventArgs evargs) { TryInvoke(ApplicationInit, "ApplicationInit", sender, evargs); } // called by ASP.NET for every HttpApplication instance after all modules have been created // which means that this will be called *many* times for different apps when Umbraco runs public override void Init() { // note: base.Init() is what initializes all of the httpmodules, ties up a bunch of stuff with IIS, etc... // therefore, since OWIN is an HttpModule when running in IIS/ASP.Net the OWIN startup is not executed // until this method fires and by that time - Umbraco has booted already base.Init(); OnApplicationInit(this, new EventArgs()); } #endregion #region End protected virtual void OnApplicationEnd(object sender, EventArgs evargs) { ApplicationEnd?.Invoke(this, EventArgs.Empty); } // internal for tests internal void HandleApplicationEnd() { if (_runtime != null) { _runtime.Terminate(); _runtime.DisposeIfDisposable(); _runtime = null; } // dispose the container and everything // but first, capture the looger! var logger = Current.Logger; Current.Reset(); if (SystemUtilities.GetCurrentTrustLevel() != AspNetHostingPermissionLevel.Unrestricted) return; // try to log the detailed shutdown message (typical asp.net hack: http://weblogs.asp.net/scottgu/433194) try { var runtime = (HttpRuntime) typeof(HttpRuntime).InvokeMember("_theRuntime", BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.GetField, null, null, null); if (runtime == null) return; var shutDownMessage = (string)runtime.GetType().InvokeMember("_shutDownMessage", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.GetField, null, runtime, null); var shutDownStack = (string)runtime.GetType().InvokeMember("_shutDownStack", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.GetField, null, runtime, null); var shutdownMsg = $"Application shutdown. Details: {HostingEnvironment.ShutdownReason}\r\n\r\n_shutDownMessage={shutDownMessage}\r\n\r\n_shutDownStack={shutDownStack}"; logger.Info(shutdownMsg); } catch (Exception) { //if for some reason that fails, then log the normal output logger.Info("Application shutdown. Reason: " + HostingEnvironment.ShutdownReason); } } // called by ASP.NET (auto event wireup) once per app domain // sender is System.Web.HttpApplicationFactory, evargs is EventArgs.Empty protected void Application_End(object sender, EventArgs evargs) { HandleApplicationEnd(); OnApplicationEnd(sender, evargs); LogManager.Shutdown(); } #endregion #region Error protected virtual void OnApplicationError(object sender, EventArgs evargs) { ApplicationError?.Invoke(this, EventArgs.Empty); } private void HandleApplicationError() { var exception = Server.GetLastError(); // ignore HTTP errors if (exception.GetType() == typeof(HttpException)) return; Current.Logger.Error("An unhandled exception occurred.", exception); } // called by ASP.NET (auto event wireup) at any phase in the application life cycle protected void Application_Error(object sender, EventArgs e) { // when unhandled errors occur HandleApplicationError(); OnApplicationError(sender, e); } #endregion #region Utilities private static void TryInvoke(EventHandler handler, string name, object sender, EventArgs evargs) { try { handler?.Invoke(sender, evargs); } catch (Exception ex) { Current.Logger.Error($"Error in {name} handler.", ex); throw; } } #endregion } }