blog post image
Andrew Lock avatar

Andrew Lock

~3 min read

Retrieving the path that generated an error with the StatusCodePages Middleware

In my previous post, I showed how to use the re-execute features of the StatusCodePagesMiddleware to generate custom error pages for status-code errors. This allows you to easily create custom error pages for common error status codes like 404 or 500.

Re-executing the middleware pipeline

The re-executing approach using UseStatusCodePagesWithReExecute is generally a better approach than using UseStatusCodePagesWithRedirects as it generates the custom error page in the same request that caused it. This allows you to return the correct error code in response to the original request. This is more 'correct' from an HTTP/SEO/semantic point of view, but it also means the context of the original request is maintained when you generate the error.

In this quick post, I show how you can use this context to obtain the original path that triggered the error status code when the middleware pipeline is re-executed.

Setting up the status code pages middleware

I'll start by adding the StatusCodePagesMiddleware as I did in my previous post. I'm using the same UseStatusCodePagesWithReExecute as before, and providing the error status code when the pipeline is re-executed using a statusCode querystring parameter:

public void Configure(IApplicationBuilder app)  
{
    app.UseDeveloperExceptionPage();

    app.UseStatusCodePagesWithReExecute("/Home/Error", "?statusCode={0}");

    app.UseStaticFiles();

    app.UseMvc(routes =>
    {
        routes.MapRoute(
            name: "default",
            template: "{controller=Home}/{action=Index}/{id?}");
    });
}

The corresponding action method that gets invoked is:

public class HomeController : Controller  
{
    public IActionResult Error(int? statusCode = null)
    {
        if (statusCode.HasValue &&
        {
            if (statusCode == 404 || statusCode == 500)
            {
                var viewName = statusCode.ToString();
                return View(viewName);
            }
        }
        return View();
    }
}

This gives me customised error pages for 404 and 500 status codes:

400 error code reponse

Retrieving the original error path

This technique lets you customise the response returned when a URL generates an error status code, but on occasion you may want to know the original path that actually caused the error. From the flow diagram at the top of the page, I want to know the /Home/Problem URL when the HomeController.Error action is executing.

Luckily, the StatusCodePagesMiddleware stores a request-feature with the original path on the HttpContext. You can access it from the Features property:

public class HomeController : Controller
{
    public IActionResult Error(int? statusCode = null)
    {
        var feature = HttpContext.Features.Get<IStatusCodeReExecuteFeature>();
        ViewData["ErrorUrl"] = feature?.OriginalPath;

        if (statusCode.HasValue &&
        {
            if (statusCode == 404 || statusCode == 500)
            {
                var viewName = statusCode.ToString();
                return View(viewName);
            }
        }
        return View();
    }
}

Adding this to the Error method means you can display or log the path, depending on your needs:

Displaying path

Note that I've used the null propagator syntax ?. to retrieve the path, as the feature will only be added if the StatusCodePagesMiddleware is re-executing the pipeline. This will avoid any null reference exceptions if the action is executed without using the StatusCodePagesMiddleware, for example by directly requesting /Home/Error?statusCode=404:

Blank path

Retrieving additional information

The StatusCodePagesMiddleware sets an IStatusCodeReExecuteFeature on the HttpContext when it re-executes the pipeline. This interface exposes two properties; the original path, as you have already seen along with the PathBase

public interface IStatusCodeReExecuteFeature
{
    string OriginalPathBase { get; set; }
    string OriginalPath { get; set; }
}

The one property it doesn't (currently) expose is the original querystring. However the concrete type that is actually set by the middleware is the StatusCodeReExecuteFeature. This contains an additional property OriginalQuerystring:

public interface StatusCodeReExecuteFeature
{
    string OriginalPathBase { get; set; }
    string OriginalPath { get; set; }
    string OriginalPath { get; set; }
}

If you're willing to add some coupling to this implementation in your code, you can access these properties by safely casting the IStatusCodeReExecuteFeature to a StatusCodeReExecuteFeature. For example:

var feature = HttpContext.Features.Get<IStatusCodeReExecuteFeature>();
var reExecuteFeature = feature as StatusCodeReExecuteFeature
ViewData["ErrorPathBase"] = reExecuteFeature?.OriginalPathBase;
ViewData["ErrorQuerystring"] = reExecuteFeature?.OriginalQueryString;

This lets you display/log the complete path that gave you the error, including the querystring

Logging querystring

Note: If you look at the dev branch in the Diagnostics GitHub repo you'll notice that the interface actually does contain OriginalQueryString. This will be coming with .NET Core 2.0 / ASP.NET Core 2.0, as it is a breaking change. It'll make the above scenario that little bit easier though

Summary

The StatusCodePagesMiddleware is just one of the pieces needed to provide graceful handling of errors in your application. The re-execute approach is a great way to include custom layouts in your application, but it can obscure the origin of the error. Obviously, logging the error where it is generated provides the best context, but the IStatusCodeReExecuteFeature can be useful for easily retrieving the source of the error when generating the final response.

Andrew Lock | .Net Escapades
Want an email when
there's new posts?