Kentico Xperience Design Patterns: Handling Failures - Centralized Exception Handling
Tags: xperience, kentico, aspnetcore, csharp
Published: 2021-11-08 14:54:04

---
title: Kentico Xperience Design Patterns: Handling Failures - Centralized Exception Handling
published: true
description: Learn how to deal with failed operations in Kentico Xperience applications using centralized exception handling
tags: xperience, kentico, aspnetcore, csharp
series: Kentico Xperience Design Patterns
cover_image: https://dev-to-uploads.s3.amazonaws.com/i/hcusgcjdsp4aozej50g9.jpg
---

Even perfectly crafted code can result in runtime failures for reasons that are out of our control 😫. Flaky network connections, data schemas that don't match the deployed code, or the accidental deletion of required data.

As software engineers, we are expected to design for the happy 😊 paths _and_ the 'exceptional' unhappy ☹ ones.

Let's look at a common approach for handling these failures in ASP.NET Core applications, how it applies to Kentico Xperience sites, and where it falls short.

> Note: This is part 1 of a 3 part series on handling failures.

> [Part 2](https://dev.to/seangwright/kentico-xperience-design-patterns-handling-failures-return-errors-dont-throw-them-4apa) and [Part 3](https://dev.to/seangwright/kentico-xperience-design-patterns-handling-failures-the-result-monad-1j25)

## 📚 What Will We Learn?

- [Representing failures as Exceptions](#representing-failures-as-exceptions)
- [How to handle Exceptions](#how-to-respond-to-exceptions)
- [Exception handling as a cross-cutting concern](#cross-cutting-concern)
- [The problems with using Exceptions as control flow](#exceptions-control-flow)

## [Representing Failures as Exceptions](#representing-failures-as-exceptions)

.NET developers are familiar with exceptions. They are part of the [Base Class Library](https://docs.microsoft.com/en-us/dotnet/standard/framework-libraries#base-class-library) (BCL) and can be used to signal that applications have entered into an invalid state, by using the `throw new Exception()` syntax.

However, throwing exceptions is just half of the story. We can also catch exceptions by wrapping some code in the `try/catch` syntax:

```csharp
try
{
   someBusinessOperation();
}
catch (Exception ex)
{
    // log and handle the exception
}
```

Of course, to be able to do the correct thing with a caught exception, we need to know 🧐 what type of exception it is.

We can use the built-in exceptions, like `ArgumentNullException` and `InvalidOperationException`, or even the basic `Exception` type to halt execution when something's gone wrong.

We can also create our own custom exception types, like the following `BadRequestException`:

```csharp
public class BadRequestException : Exception
{
    public BadRequestException(string message) : base(message) { }
}
```

But why go through the effort of making a custom exception type?

Custom exceptions can more closely model our application's domain language and use-cases. The exception types in the BCL have to be use-case agnostic, by definition, because they are 'common' and usable in _any_ .NET application 🤔.

If we were to use the base `Exception` type for all of our applications exceptions, it's going to be hard to determine what we should do when we catch the exception.

When we create domain specific exception types, we can use [exception filters](https://docs.microsoft.com/en-us/dotnet/csharp/fundamentals/exceptions/exception-handling#catch-blocks) - a specific kind of C# pattern matching used with `catch` statements 🤓 - to make sure we execute specific code when different kinds of exceptions happen.

Imagine this scenario...

In a Kentico Xperience application, we have site-wide settings in a custom Page Type, which we'll call `SiteContent`. If this `SiteContent` Page is deleted from the Content Tree, how should our application respond 🤷🏽‍♂️?

When we try to query for the `SiteContent` Page, we could throw a custom `MissingContent` exception if it's not found.

Here's our example exception type:

```csharp
public class MissingContentException : Exception
{
    public string PageType { get; }

    public MissingContentException(string pageType)
        base($"Could not find [{pageType}] Page") =>
        PageType = pageType;
}
```

And, here's our content querying logic:

```csharp
var pages = await pageRetriever.RetrieveAsync<SiteContent>();

if (!pages.Any())
{
    throw new MissingContentException(SiteContent.CLASS_NAME);
}

// ...
```

The nice thing about throwing an exception is that it lets us abort our application execution immediately, preventing additional code errors or failures 💪🏾.

We also don't have to change the type signature of our methods that throw exceptions. This is convenient for blocks of code that could fail for any number of reasons. Our method return type represents the happy path, and the exceptions which the method throws represent all the failure scenarios 😏.

## [How to Handle Exceptions](#how-to-handle-exceptions)

If we throw an exception somewhere, we need to eventually catch that exception and respond accordingly:

```csharp
try
{
    var siteContent = await GetSiteContentPage();
}
catch (MissingContentException ex) 
    when (ex.PageType == SiteContent.CLASS_NAME)
{
    // log and handle
}
```

With this approach we're representing our failed operation (querying for the SiteContent Page) with a custom exception type, and only catching and responding to the exceptions we are interested in - all other exceptions will need to be caught at some higher level in our code.

This all leads to the question - how do we want to handle exceptions we've caught? What information do we want to collect and what do we want to present to the site visitor that was unfortunate enough to trigger this failure 🤔?

In a Kentico Xperience site, the most common way of handling exceptions is going to be logging the failure to the Xperience [Event Log](https://docs.xperience.io/13api/configuration/event-log) and displaying an error page to the visitor 👍🏻.

We can usually achieve this with two middlewares - `UseStatusCodePagesWithReExecute()` and `UseExceptionHandler()`.

### Status Code Pages With Re-Execute

`UseStatusCodePagesWithReExecute` will handle results from Controllers that have 'error' HTTP status codes (eg 400, 500) and let you execute another route, passing the status code as a parameter:

```csharp
public void Configure(IApplicationBuilder app)
{
    // earlier middleware

    app.UseStatusCodePagesWithReExecute(
        "/status-code-error", 
        "?code={0}");

    // later middleware
}
```

The site visitor won't see `/status-code-error` in their address bar 🤨, but the Controller action associated with that route can return some content explaining to the user what happened, based on the `code` query parameter. (`/status-code-error` can be any path as long as it's one your application can handle).

> Read another explanation on these middlewares [on StackOverflow](https://stackoverflow.com/a/51118918/939634).

This middleware will typically be used when we've caught an exception later in the request pipeline - like in an MVC filter or Controller action - and returned a result representing the error, like `StatusCode()` or `NotFound()`:

```csharp
public class HomeController : Controller
{
    public async Task<IActionResult> Index()
    {
        try 
        {
            var data = await myService.GetData();

            return View(new HomeViewModel(data));
        }
        catch (Exception ex)
        {
            eventLogService.LogException(...);

            return StatusCode(500);
        }
    }
}
```

> Note: The `UseStatusCodePagesWithReExecute` middleware does _not_ catch exceptions 😲. It helps to keep the presentation of 'error' status codes separate from the Controllers that return them.

While this lets us handle and log each exception exactly as we'd like for every Controller action, it's also a lot of repeated code when our Kentico Xperience site grows beyond a few Pages 😬.

Fortunately, there's a way handle exceptions as a cross-cutting concern... 😅

## [Exception Handling as a Cross-Cutting Concern](#cross-cutting-concern)

### Exception Handler

The `UseExceptionHandler` middleware wraps the entire request pipeline in a `try`/`catch`, which means any unhandled exceptions will be caught and the developer can specify how they should be handled - like executing a request to another path in the application:

```csharp
public void Configure(IApplicationBuilder app)
{
    // This is the first in / last out middleware
    app.UseExceptionHandler(new ExceptionHandlerOptions
    {
        AllowStatusCode404Response = true,
        ExceptionHandlingPath = "/error"
    });

    // later middleware
}
```

In this example, when an exception is caught the site will execute the `/error` path, which is typically a Controller action, and return its result.

We'll often use the `UseExceptionHandler` and `UseStatusCodePagesWithReExecute` middleware together. The former might return a 'Not Found' Page, keeping the HTTP status code as 404, and we wouldn't want that to be treated as an unhandled exception for the `UseExceptionHandler` middleware to process. `AllowStatusCode404Response = true` ensures the 404 response is allowed through 😮.

The Controller that handles our failure can grab the original `Exception` and request data out of the `IExceptionHandlerPathFeature`, which is where the middleware stores it before calling the Controller action:

```csharp
public class ErrorController : Controller
{
    public IActionResult Index()
    {
        var exceptionFeature = HttpContext
            .Features
            .Get<IExceptionHandlerPathFeature>();

       Exception ex = exceptionFeature.Error;
       string originalPath = exceptionFeature.Path;

       eventLogService.LogException(
           "ErrorController", 
           "ASPNET_EXCEPTION",
           exception);

       var (message, statusCode) = ex switch
       {
           BadRequestException b => 
               ("The page had some problems", 400);
           NotAuthorizedException na =>
               ("You don't have permissions to view this", 403);
           _ => 
               ("We've hit a snag loading the page!", 500);
       };

       HttpContext.Response.StatusCode = statusCode;

       return View(new ErrorViewModel(originalPath, message));
    }
}
```

In this example, we are logging the exception in Xperience's Event Log, creating a friendly, contextual message and HTTP status code based on the type of exception that was caught, and returning a View to the user - all under the URL that caused the original problem - no redirects here 👏🏾!

We've effectively created a central cross-cutting Global Exception Handler that guarantees _any_ exception thrown in our application will be caught, logged, handled, and presented to the visitor as a normal site Page - not an incomprehensible 'developer' error Page.

This seems great... 😁 doesn't it 😟!?

## [The Problems with Using Exceptions as Control Flow](#exceptions-control-flow)

The convenience of throwing (or allowing) exceptions anywhere in our application and then using a Global Exception Handler comes with a real cost and a potential cost.

### It's To Late, Baby

{% youtube VkKxmnrRVHo %}

The real cost is that by the time we're at the Exception Handler, it's too late to do anything to recover. We've consigned ourselves to a failed request and the best we can give to the site visitor is a friendly error and log the Exception.

The problem is not all exceptions are created equal 😕.

What if an exception was thrown trying to get the URL for an image in an image gallery on the Page? Do we want to throw away everything else that was correctly retrieved from the database and show an error page, just because of this single failure? Probably not.

Building a Kentico Xperience application is about choosing the best ways to manage and present content in the system, and keep visitors coming back to the site. We need to more nuanced 😎 with handling failures and treat our approach as a spectrum - some failures are ok, others are not, some require backup content, others can just result in content missing from the Page.

Unfortunately, we lose the ability to be more nuanced if we turn every failure into an exception and then rely on a single, late (in the request lifecycle) point to deal with them.

Exception handling is a cross-cutting concern (like authorization, logging, or caching), which aligns with the `UseExceptionHandler` middleware 👍. At the same time, cross-cutting concerns should be applied with multiple layers, not as a single blanket thrown over the application to hide the ugly parts 😏.

### Abusing Control Flow

While some exceptions represent application states that should immediately halt the request, many are business logic situations we can gracefully (or at least intelligently) recover from.

There are [many](https://docs.microsoft.com/en-us/previous-versions/msp-n-p/ff647790(v=pandp.10)#exception-management) [articles](https://fsharpforfunandprofit.com/posts/exceptions/#should-functions-throw-exceptions-or-return-error-structures) [about using exceptions](https://enterprisecraftsmanship.com/posts/exceptions-for-flow-control/) [to control application flow](https://headsigned.com/posts/stop-using-exceptions-for-flow-control/) ... most recommend against it.

If you choose not to consider this advice, problems will show up in your code base over time 😧.

A developer throws an exception when an array index is a negative number. Then, another developer later throws an exception when the credentials submitted by the login form are invalid. Eventually, half the methods in your app throw exceptions, either directly, or by some method they call, and all of those method signatures have become *untrustworthy* 😑.

What do you think this method does?

```csharp
string[] GetUserRoles();
```

It probably returns all the roles a User is in when the current request is from an authenticated user. But what about when the user hasn't finished sign-up? Or if the request is for an unauthenticated User 🤔?

We'd hope it would return an empty array or maybe an array with a `"Anonymous"` Role... but there's nothing preventing it from throwing an exception! Even if it didn't, it could call other methods and they could throw exceptions!

Method return types should tell you what happens during normal use, and exceptional cases should really be *exceptional*. We want to trust 🤗 the code we write and the code written by others.

Using exceptions as control flow is a leaky abstraction because it forces us to know a lot more about a method than just its signature - potentially its internals, which breaks the idea of encapsulation. It also slowly reinforces our reliance on the Global Exception Handler pattern since _any_ code in our application can `throw` without us knowing 🤦‍♀️!

## Conclusion

In a Kentico Xperience site, we will inevitably run into situations that cause our code to fail to produce the desired result. These could be from exceptions thrown by libraries we are using, or our own code, or they could be from data not matching what we require 🤷🏽‍♂️.

In these cases we can use exceptions to quickly stop the current execution and bubble 🧼 that failure up to another part of our application that is responsible for catching them.

If we make custom exception types, we can match them to the specific scenarios our sites might encounter. And, we can react to each type of exception intelligently 🧐.

In order to not repeat our `try`/`catch` logic across the entire code base, we can centralize exception handling with the `UseExceptionHandler` ASP.NET Core middleware. This gives us a convenient place to convert uncaught exceptions into friendly error pages and log entries 👍🏿.

This convenience comes with some negatives. Our centralized exception handling will often not be located where we need it to if we want to compensate for some failures and let the request continue processing. It also encourages using exceptions as control flow, which results in method signatures only representing the 'happy path' and code that is harder to understand and trust 😖.

In my next post, we'll look at a way of including failures in our method signatures/return types, which keeps our code honest 😇 and allows us to better handle failures.

...

As always, thanks for reading 🙏!

## References

- [.NET Base Class Library](https://docs.microsoft.com/en-us/dotnet/standard/framework-libraries#base-class-library)
- [C# Exception Filters](https://docs.microsoft.com/en-us/dotnet/csharp/fundamentals/exceptions/exception-handling#catch-blocks)
- [Kentico Xperience Event Log](https://docs.xperience.io/13api/configuration/event-log)
- [Status Code vs Exception Handling middleware in ASP.NET Core](https://stackoverflow.com/a/51118918/939634)
- [.NET Documentation - Exception Management](https://docs.microsoft.com/en-us/previous-versions/msp-n-p/ff647790(v=pandp.10)#exception-management)
- [Exceptions vs Error Structures in F#](https://fsharpforfunandprofit.com/posts/exceptions/#should-functions-throw-exceptions-or-return-error-structures)
- [Exceptions for Control Flow](https://enterprisecraftsmanship.com/posts/exceptions-for-flow-control/)
- [Stop Using Exceptions for Control Flow](https://headsigned.com/posts/stop-using-exceptions-for-flow-control/)

---

<figcaption>Photo by <a href="https://unsplash.com/@jordanmadrid">Jordan Madrid
</a> on <a href="https://unsplash.com">Unsplash</a></figcaption>

---

We've put together a list over on [Kentico's GitHub account](https://github.com/Kentico/Home/blob/master/RESOURCES.md) of developer resources. Go check it out!

If you are looking for additional Kentico content, checkout the Kentico or Xperience tags here on DEV.

{% tag kentico %}

{% tag xperience %}

Or my [Kentico Xperience blog series](https://dev.to/seangwright/series), like:

- [Kentico Xperience Xplorations](https://dev.to/seangwright/series/8185)
- [Kentico Xperience MVC Widget Experiments](https://dev.to/seangwright/series/9483)
- [Bits of Xperience](https://dev.to/seangwright/series/8740)