Two Factor Authentication with Email in ASP.NET Core
June 21, 2021
Error Handling in Umbraco 7
Umbraco is the crown jewel of open source lightweight .NET based Content Management Systems that we all know and love. It does many wonderful things for users, content editors, and developers alike – but every superman has his kryptonite, and the Umbraco CMS is no exception. While Umbraco does come with some basic error handling for missing pages, this is limited to a single page (or culture specific page) and only handles 404 errors.
What if there were a way to have robust error handling, that was dynamic in the sense that content editors could control the content on the error page?
That would be nice. But it would also need to be extensible, as some sites may require other custom error pages in addition to the basic 404 and 500 pages such as HTTP 401 (UNAUTHORIZED) or maybe HTTP 403 (FORBIDDEN). A good setup would also allow us serve unique error pages based on a multi-site setup in Umbraco in case the sites have different skins / styles, so we could maintain the look and feel of the site for the current user.
I set out on a journey to hit these marks, as Umbraco’s native error handling left more to be desired from a developers & site owners perspective. After reading and implementing the code found in this article, your Umbraco site will be armed with a flexible, robust, extensible, custom application error handler that will:
- serve dynamic content controlled through the CMS
- handle error codes within Umbraco and fatal IIS errors that can’t reach Umbraco
- handle multisite specific error pages
- provide specific HTTP status codes to the browser based on the type of error received
- provide a platform for easily creating and handling new error codes
I’ve started with a brand-new install of Umbraco through Nuget and am going to provide each snippet of code necessary to achieve that sweet piece of error handling heaven I’ve previously described.
How It's Done
First things first – we are going to need a create a new document type to house our error page specific content. This will be called an “Error Page” and for illustrative purposes, I’ve given it an icon of a red window with an “X” inside of it. At the very least, this page should have a grid layout, or properties that allow it to receive content as this is what your users will leverage for controlling the content on the specific error pages.
After this is complete, we will need to create a new folder within the root of our website called ‘Errors’ (this isn’t completely necessary. If you don’t create it, the code will create the folder itself). This is where our error page HTML will be stored and pulled from. In the event that Umbraco isn’t able to serve the error from the application, we will grab a static copy of the backoffice error page. These will always be kept up to date because every time a user publishes one of our new “Error Pages”, a new copy of the content will be fetched and stored in this folder.
Next, we will edit the web.config file to add in a few pieces that allow our custom application handler to serve the errors for the application, and to also provide the correct status codes and responses. The first thing is to find your <httpErrors> section if you have one and replace it with:
<httpErrors errorMode="Custom" existingResponse="PassThrough"> <remove statusCode="404" subStatusCode="-1" /> <error statusCode="404" responseMode="ExecuteURL" subStatusCode="-1" path="/MissingPath.aspx" /> </httpErrors>
Note: If you don’t have an httpErrors section defined, you can take the snippet above and place it at the bottom of the <system.webServer> section of your web.config.
This piece tells IIS to pass the response through to the application, and we also use a little trick to tell the application to hit a ‘/MissingPath.aspx’ page when a 404 is found, which will result in a 404 itself that can be caught by our error handler.
Next up is to find the <customErrors> section of your web.config and to replace it with this, which will setup the redirect to our custom error page and preserve our status code instead of redirecting with a 302:
<customErrors mode="Off" redirectMode="ResponseRewrite" />
After that you will need to find the <httpModules> section of the web.config, and add our HTTP module to the list of other modules:
<add name="ErrorHandlingModule" type="marathon.error.ErrorHandlingModule, marathon.error" />
As well as the <modules> section of the web.config:
<remove name="ErrorHandlingModule" /> <add name="ErrorHandlingModule" type="marathon.error.ErrorHandlingModule, marathon.error" />
Keep in mind, that with any HTTP module, the type portion of the string will be dependent on what project you place your error handling module in, as well as the namespace / class that you use. In my case, I created a separate project called ‘marathon.error’ which I’ve included as a zipped link below, as there are a handful of models, services, and controllers that I will save you from copying and pasting. I will however go over some of these components briefly.
Before we leave the web.config though, one last section you will want to find is <location path=”umbraco”>, underneath the <urlCompression> section within this <location> node, add this snippet:
<httpErrors errorMode="DetailedLocalOnly" existingResponse="PassThrough" />
This will allow errors in the backoffice to get passed through to the little error panel that you may be akin to seeing from time to time instead of being swallowed up by the error handling module.
This is the HTTP module that ties into the Application End Request and Application Error events. In the Application End Request method, we basically test the request against a handful of filename extensions such as jpg, png, doc, pdf, js, css, and html. This circumvents the issue with Umbraco sites where hitting a static resource wouldn’t always trigger the site 404 but would display a message along the lines of ‘The requested resource could not be found’.
The Application Error method is where we can setup additional logic flow to capture other error messages, but for now we will just set it to default the StatusCode of the current error to 500. This is because 404’s by default are either caught by the last chance content finder, or it is a static asset that is caught on the end request and won’t make it to the Application Error method. One other thing in this method you will notice is that depending on the debug value set in the web.config, it will either present the dreaded yellow screen of death with a comprehensive stack trace and error or it will gracefully transfer the user to the correct error page. This is so that developers can have meaningful screens and errors while in development, but that once the code has launched and the debug is set to false, the custom error pages will be shown instead.
Services / DynamicErrorPageService.cs
This is the method that runs when one of our custom “Error Page” document types gets published. After doing some processing, it kicks off a new thread which will wait 15 seconds then proceed to fetch the error page HTML through a web request via the WebClient class and write it into our ‘Errors’ folder that we created earlier within the web project.
Services / DomainMappingService.cs
This is where we iterate over the whole site to determine the layout of the backoffice and map out nodes that have custom domains attached to them, which then get written to a JSON file in /Config/domains.config.js. This allows us to efficiently access the current site setup in regard to Multisites and domains.
ContentFinders / LastChanceContentFinder.cs
This is the last chance content finder for the site. Whenever a request comes through Umbraco where it can’t locate a node after running through all previous content finders, this component will be used last and will transfer the user to the correct 404 page.
UmbracoEventHandlers / *.cs
These are the application started and application starting event handlers that allow us to setup the dynamic error page service to run when our “Error Page” document type is published, along with setting up the last chance content finder mentioned above.
Once you’ve edited your web.config to reflect the changes at the beginning of this article, go ahead and download the marathon.error project file. Right-click your solution and hit Add > Existing Project…in Visual Studio, then navigate to the unzipped marathon.error project and import the .csproj file. Once that is loaded up in your solution, add a reference to the project by right-clicking your web project, Add > Reference > Projects (in the left-hand side of the window), check the marathon.error project, and hit OK.
At this point, you’re ready to build your solution – verify that your bin folder now has the built reference to marathon.error.dll and then refresh your site to ensure that it still comes up properly.
Next you will want to add domains to each of your site instances. Even if you have just one site and it is not a multisite, it should be bound with any domains you use on it. In most of my cases, I will have 2 domains bound to a single site, one for my local development IIS instance e.g. mysite.local, and a second domain for the development server e.g. mysite.marathonus.dev.
Once your domains are added, create your error specific pages below each site instance. Error pages in the backoffice should have the name of the status code that they will handle - e.g. content for our not found error page will be called “404” and content for our internal server error page will be named “500”. After this is complete, recycle your app pool and verify that your /Config/domains.config.js file has the different domains listed along with their respective ‘home’ node. You can then publish each of your error pages and allow the code to fetch the error page from Umbraco, retrieve the HTML for the page, and write it to the /Errors folder. As long as your debug is set to false, then voilà– you now have robust and dynamic error handling for your site!