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
.
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:
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:
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
:
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
Note: If you look at the
dev
branch in the Diagnostics GitHub repo you'll notice that the interface actually does containOriginalQueryString
. 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.