diff --git a/src/Umbraco.Web.UI.Client/src/views/errors/BootFailed.html b/src/Umbraco.Web.UI.Client/src/views/errors/BootFailed.html new file mode 100644 index 0000000000..c08627739a --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/errors/BootFailed.html @@ -0,0 +1,79 @@ + + + + + + Boot Failed + + + +
+ +
+

Boot Failed

+

Umbraco failed to boot, if you are the owner of the website please see the log file for more details.

+
+
+ + diff --git a/src/Umbraco.Web/Composing/ModuleInjector.cs b/src/Umbraco.Web/Composing/ModuleInjector.cs index 57ef766dea..41540d196c 100644 --- a/src/Umbraco.Web/Composing/ModuleInjector.cs +++ b/src/Umbraco.Web/Composing/ModuleInjector.cs @@ -35,7 +35,16 @@ namespace Umbraco.Web.Composing catch { /* don't make it worse */ } if (runtimeState?.BootFailedException != null) - BootFailedException.Rethrow(runtimeState.BootFailedException); + { + // if we throw the exception here the HttpApplication.Application_Error method will never be hit + // and our custom error page will not be shown, so we need to wait for the request to begin + // before we throw the exception. + context.BeginRequest += (sender, args) => + { + BootFailedException.Rethrow(runtimeState.BootFailedException); + }; + return; + } // else... throw what we have throw; diff --git a/src/Umbraco.Web/UmbracoApplication.cs b/src/Umbraco.Web/UmbracoApplication.cs index f70651a43e..c96e21e348 100644 --- a/src/Umbraco.Web/UmbracoApplication.cs +++ b/src/Umbraco.Web/UmbracoApplication.cs @@ -1,7 +1,10 @@ -using System.Configuration; +using System; +using System.Configuration; +using System.IO; using System.Threading; using System.Web; using Umbraco.Core; +using Umbraco.Core.Exceptions; using Umbraco.Core.Logging; using Umbraco.Core.Logging.Serilog; using Umbraco.Core.Runtime; @@ -23,6 +26,53 @@ namespace Umbraco.Web return runtime; } + protected override void OnApplicationError(object sender, EventArgs evargs) + { + base.OnApplicationError(sender, evargs); + + // if the exception is a BootFailedException we want to show a custom 500 page + if (Server.GetLastError() is BootFailedException) + { + // if the requested file exists on disk, clear the error and return + // this is needed to serve static files + if (File.Exists(Request.PhysicalPath)) + { + Server.ClearError(); + return; + } + + // if the application is in debug mode we don't want to show the custom 500 page + if (Context.IsDebuggingEnabled) return; + + // find the error file to show + var fileName = GetBootErrorFileName(); + + // if the file doesn't exist we return and a YSOD will be shown + if (File.Exists(fileName) == false) return; + + Response.TrySkipIisCustomErrors = true; + Server.ClearError(); + Response.Clear(); + + Response.StatusCode = 500; + Response.ContentType = "text/html"; + Response.WriteFile(fileName); + + CompleteRequest(); + } + } + + /// + /// Returns the absolute filename to the BootException html file. + /// + protected virtual string GetBootErrorFileName() + { + var fileName = Server.MapPath("~/config/errors/BootFailed.html"); + if (File.Exists(fileName)) return fileName; + + return Server.MapPath("~/umbraco/views/errors/BootFailed.html"); + } + /// /// Returns a new MainDom /// diff --git a/src/Umbraco.Web/UmbracoApplicationBase.cs b/src/Umbraco.Web/UmbracoApplicationBase.cs index 59b5b1779b..32a949e972 100644 --- a/src/Umbraco.Web/UmbracoApplicationBase.cs +++ b/src/Umbraco.Web/UmbracoApplicationBase.cs @@ -6,7 +6,6 @@ using System.Web.Hosting; using Umbraco.Core; using Umbraco.Core.Composing; using Umbraco.Core.Logging; -using Umbraco.Core.Logging.Serilog; namespace Umbraco.Web { diff --git a/src/Umbraco.Web/UmbracoInjectedModule.cs b/src/Umbraco.Web/UmbracoInjectedModule.cs index 6eeef081a2..92fee22983 100644 --- a/src/Umbraco.Web/UmbracoInjectedModule.cs +++ b/src/Umbraco.Web/UmbracoInjectedModule.cs @@ -357,15 +357,13 @@ namespace Umbraco.Web // there's nothing we can do really app.BeginRequest += (sender, args) => { - // would love to avoid throwing, and instead display a customized Umbraco 500 - // page - however if we don't throw here, something else might go wrong, and - // it's this later exception that would be reported. could not figure out how - // to prevent it, either with httpContext.Response.End() or .ApplicationInstance - // .CompleteRequest() + // if we don't throw here, something else might go wrong, + // and it's this later exception that would be reported. // also, if something goes wrong with our DI setup, the logging subsystem may // not even kick in, so here we try to give as much detail as possible + // the exception is handled in UmbracoApplication which shows a custom error page BootFailedException.Rethrow(Core.Composing.Current.RuntimeState.BootFailedException); }; return;