Files
Umbraco-CMS/src/Umbraco.Web/UmbracoApplicationBase.cs

237 lines
8.9 KiB
C#
Executable File

using System;
using System.Reflection;
using System.Threading;
using System.Web;
using System.Web.Hosting;
using LightInject;
using Umbraco.Core;
using Umbraco.Core.Composing;
using Umbraco.Core.Logging;
using Umbraco.Core.Logging.Serilog;
namespace Umbraco.Web
{
/// <summary>
/// Provides an abstract base class for the Umbraco HttpApplication.
/// </summary>
public abstract class UmbracoApplicationBase : HttpApplication
{
private IRuntime _runtime;
/// <summary>
/// Gets a runtime.
/// </summary>
protected abstract IRuntime GetRuntime();
// 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 = new ServiceContainer();
// get runtime & boot
_runtime = GetRuntime();
_runtime.Boot(container);
var logger = container.GetInstance<ILogger>();
ConfigureUnhandledException(logger);
ConfigureAssemblyResolve(logger);
}
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<UmbracoApplicationBase>(exception, msg);
};
}
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;
}
// 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);
Current.Logger.Info<UmbracoApplicationBase>("Application shutdown. Details: {ShutdownReason}\r\n\r\n_shutDownMessage={ShutdownMessage}\r\n\r\n_shutDownStack={ShutdownStack}",
HostingEnvironment.ShutdownReason,
shutDownMessage,
shutDownStack);
}
catch (Exception)
{
//if for some reason that fails, then log the normal output
Current.Logger.Info<UmbracoApplicationBase>("Application shutdown. Reason: {ShutdownReason}", HostingEnvironment.ShutdownReason);
}
// dispose the container and everything
Current.Reset();
}
// 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)
{
OnApplicationEnd(sender, evargs);
HandleApplicationEnd();
}
#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<UmbracoApplicationBase>(exception, "An unhandled exception occurred");
}
// 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<UmbracoApplicationBase>(ex, "Error in {Name} handler.", name);
throw;
}
}
#endregion
}
}