(C# ASP.NET Core) Writing a Custom Exception Page and the UseExceptionHandler middleware

This tutorial continues our previous tutorial where we learnt that if an application is running under development environment, then ASP.NET Core provides a UseDeveloperExceptionPage middleware that catches un-handled exceptions and provides a detailed information about the snapshot state of the application. But that feature is not recommended for production environment - a developer should provide a custom page to filter out the information that finally reaches the display. Let's learn how!
(Rev. 19-Mar-2024)

Categories | About |     |  

Parveen,

Why do we need a custom exception page

Let us first understand why we need a custom exception page.

  1. The exception page provided by the UseDeveloperExceptionPage middleware is too detailed. It is not advisable to reveal too much information to general public because it could used by hackers. Moreover, this information might not make much sense to an ordinary user.
  2. A custom exception page can help us display a human readable message that could possibly ask the user to contact customer service, or could possibly apologize to the user.
  3. A custom exception page allows us to filter the information before displaying it. We can choose to show some information, while hiding the more sensitive part.

Video Explanation

Please watch the following youtube video:

Configuring a custom error page

Let us see how to configure a custom error page.

Open the program.cs file and set the environment to Production. We could have done this through the launchSettings file also, and through environment variables as well. The whole point is to ensure that our application doesn't run under Development.

Use a conditional if statement to add a middleware called UseExceptionHandler. This middleware -

  1. comes into play if the app is not running under development environment.
  2. accepts the path to the error page that will be shown to the user if an un-handled exception is caught. In our case, the error page is called MyError, and it will be placed in the root directory.
// Visual Studio 2022, .NET 6 and later 

// set the envirnment to production 
var builder = WebApplication.CreateBuilder(new WebApplicationOptions()
    {
        EnvironmentName = Environments.Production
    });

builder.Services.AddRazorPages();

var app = builder.Build();

// this adds a middleware if the 
// environment is but development 
if(!app.Environment.IsDevelopment())
{

  app.UseExceptionHandler("/MyError");

}

app.MapRazorPages();

app.Run();

Add the custom error page

Let us quickly recall the index page that we created in the previous tutorial. Open the solution explorer and double click to open the index cshtml file.

As you can see, this page has an anchor link that is connected to a handler on the backing class.


@page "{handler?}"

@model MyRazorApp.Pages.IndexModel

@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

<p>

  <a asp-page-handler="MyClickHandler">
    Click to throw an exception
  </a>

</p>


  

Let us open the backing class also. As we can see, the click event handler throws an un-handled exception for our testing purposes.

public class IndexModel : PageModel
{

  // generates an exception for testing 
  public void OnGetMyClickHandler()
  {

    throw new Exception("This is a test exception");

  }

}

Now it is time to add an error page.

Open the solution explorer and add a razor page called MyError to the Pages folder.

Open the backing class of this page to see the various parts.

First of all the ResponseCache attribute has been used to prevent caching of this page. This will ensure that the page is always obtained from the server, and therefore, shows the latest error.

Then, we have added a string property called Message. This property will be set in the OnGet handler, and will be later displayed in the markup razor page.

I have used only one property because I wanted to keep the things very simple. In real practice, a more professional look may be desired. So you might want to add more properties to suitably decorate the final html.

using Microsoft.AspNetCore.Diagnostics;

using Microsoft.AspNetCore.Mvc;

using Microsoft.AspNetCore.Mvc.RazorPages;

using System.Text;

namespace MyRazorApp.Pages
{

  [ResponseCache(Duration = 0,
  Location = ResponseCacheLocation.None,
  NoStore = true)]
  public class MyErrorModel : PageModel
  {

    public String? Message { get; set; }

    // onget handler 
    public void OnGet()
    {

      var feature = HttpContext
            .Features
            .Get<IExceptionHandlerPathFeature>();

      var exception = feature?.Error;

      var path = feature?.Path;

      StringBuilder sbError = new StringBuilder();

      if (exception != null)
      {

        sbError.AppendLine("<p>Some error encountered.</p>");

        // or some specific exception messages 
        if (exception is FileNotFoundException)
        {

          sbError.AppendLine("<p>File not found.</p>");

        }

        else if (exception is AccessViolationException)
        {

          sbError.AppendLine("<p>Access not configured.</p>");

        }

        // more else blocks can be added 
      }

      // path to the page that threw exception 
      // you can output any message on 
      // the basis of path 
      if (!String.IsNullOrEmpty(path))
      {

        sbError.AppendLine($"<p>Path {path}</p>");

      }

      Message = sbError.ToString();

    }

  }

}

The OnGet method is the one where the whole processing takes place. Here we can choose to display or hide the details of error and exception.

I will be explaining the general flow, but you can always take your own decisions regarding what to present and what to swallow.

First of all query the HttpContext to get the IExceptionHandlerPathFeature interface.

The Error property of this interface contains the exception instance.

The Path property of this interface contains the relative path of the page where the exception was thrown. This proves very useful in reaching the affected page.

A StringBuilder has been used to build the message.

An if-else ladder can be used to guess the type of exception and accordingly decide on what to add to the final display message.

Similarly, path can be added to the final display.

Next, let us visit the solution explorer to open the markup file.


@page

@model MyRazorApp.Pages.MyErrorModel

<h1>Please contact developer support</h1>

<p>

  @(Html.Raw(Model.Message))

</p>


  

The Message property is rendered in a paragraph (p) tag.

As I have already said, that I wanted to keep the things very simple for tutorial purposes. But you might want to add more properties to suitably decorate the final html.

Run the App

Finally, use Ctrl+F5 to run the application. Allow the home page to open.

Click the link to throw an exception.

We can verify that the unhandled exception reaches our custom error page. And we also verify that it contains just the information that we wanted to show.

This is how a custom error page is used. Thanks!


This Blog Post/Article "(C# ASP.NET Core) Writing a Custom Exception Page and the UseExceptionHandler middleware" by Parveen is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.