MediaWizards Grimoire

Sep 30, 2022 - Huw Reddick

Handling Errors in Umbraco

This article outlines how I handle errors in Umbraco 10.

What I have done is create a CustomError Document type, following the Umbraco docs for a custom 404 page, using the same template I created some more error pages, an errorNoAcess and errorHandler. The CustomError Document type just basic template with a title and message.

I discovered that depending how the error is raised/returned, you could get either an exception or a http statuscode, so you have to handle both :)

So, I created my own exceptionhandler midddleware to handle the exceptions and then added some code to my startup.cs to handle the status codes.

using System.Net;
using Microsoft.AspNetCore.Http;

namespace MediaWiz.Core.MiddleWare
{
    public class ExceptionHandlingMiddleware
    {
        private readonly RequestDelegate _next;

        public ExceptionHandlingMiddleware(RequestDelegate next)
        {
            _next = next;
        }

        public async Task InvokeAsync(HttpContext context)
        {
            var allowedcodes = new[] { 200, 301, 302, 401,403,404,500 };
            try
            {
                await _next(context);
                if (!allowedcodes.Contains(context.Response.StatusCode))
                {
                    context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
                    context.Response.Redirect("/errorHandler");

                }
            }
            catch (UnauthorizedAccessException)
            {
                context.Response.Clear();
                context.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
                context.Response.Redirect("/errorNoAccess");
            }
            catch (Exception ex)
            {
                if (context.Response.StatusCode is 404)
                {
                    //may be from an exception in startup, so lets just let it get handled by the default 404;
                }
                else
                {
                    context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
                    context.Response.Redirect("/errorHandler");
                }

            }
        }
    }
}

I also added an extension method to register the middleware.

public static class MiddleWareExtensions
{
    public static IApplicationBuilder MyExceptionHandler(this IApplicationBuilder builder)
    {
        return builder.UseMiddleware<ExceptionHandlingMiddleware>();
    }
}

I then registered it in my startup.cs and added some code to handle errors returned as statuscodes in the response

        public void Configure(IApplicationBuilder app, IWebHostEnvironment env,IBackgroundJobClient backgroundJobClient, IRecurringJobManager recurringJobManager)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.MyExceptionHandler();
            }

            app.Use(async (context, next) =>
            {
                context.Response.Headers.Add("X-Xss-Protection", "1; mode=block");
                context.Response.Headers.Add("Strict-Transport-Security", "max-age=31536000");
                context.Response.Headers.Add("X-Content-Type-Options", "nosniff");
                context.Response.Headers.Add("X-Frame-Options", "SAMEORIGIN");
                await next();

                if (context.Response.StatusCode == 404)
                {
                    context.Request.Path = "/error404/";
                    await next();
                }
                if (context.Response.StatusCode is 401 or 403)
                {
                    context.Request.Path = "/errorNoAccess/";
                    await next();
                }
                //This is a BadRequest so use the default handler (500)
                if (context.Response.StatusCode == 500)
                {
                    context.Request.Path = "/errorHandler/";
                    await next();
                }
            });

            app.UseAuthentication();
            app.UseRouting();

            app.UseUmbraco()
                .WithMiddleware(u =>
                {
                    u.UseBackOffice();
                    u.UseWebsite();
                })
                .WithEndpoints(u =>
                {
                    u.UseInstallerEndpoints();
                    u.UseBackOfficeEndpoints();
                    u.UseWebsiteEndpoints();
                });

        }

In this blog post I explain how to implement an email validation flow for Member registration.

In part 2 of my Implementing a Forgot password for members I explain how to implement the IMemberMailService to send the reset password email.

How to implement a ForgotPassword process for Umbraco members in Umbraco 9+

Custom views give you complete control over how a Block is rendered in the backoffice and this enables you to give a better representation of the content. In this article I will explain how I created a custom Block view based on the fullcalendar.io javascript library to display events in the backoffice.

These are my experiences of creating an Umbraco package for the MediaWiz Forums, using package targets, razor class libraries, static web assets and template views.

Many thanks go to Kevin Jump and Luuk Peters without whose help I would probably have given up.