Displaying a 404 Not Found Page for ASP.NET Core MVC

After working on few hours for handling 500 and 404 errors, I have implemented below given solution.

For handling 500 Server Side Errors you can use app.UseExceptionHandler middleware but app.UseExceptionHandler middleware only handles unhandled exceptions while 404 isn't an exception. For handling 404 errors I've designed another custom middleware which is checking response status code and if it is 404 then returning user to my custom 404 error page.

if (env.IsDevelopment())
   {
       app.UseDeveloperExceptionPage();
   }
   else
   {
       //Hnadle unhandled exceptions 500 erros
       app.UseExceptionHandler("/Pages500");
       //Handle 404 erros
       app.Use(async (ctx, next) =>
       {
           await next();
           if (ctx.Response.StatusCode == 404 && !ctx.Response.HasStarted)
           {
               //Re-execute the request so the user gets the error page
               ctx.Request.Path = "/Pages404";
               await next();
           }
       });
   }

Note: You must add app.UseExceptionHandler("/Pages500"); middleware at the starting of your Configure method so that it can handle exceptions from all middlewares. The custom middleware can be placed any ware before app.UseEndpoins middleware but still, it's good to place in the beginning of Configure method. /Pages500 and /Pages404 urls are my custom pages, you can design for your application.


One of the best tutorials I found is this: https://joonasw.net/view/custom-error-pages

The summary is here:

1. First add a controller like ErrorController then add this action to it:

[Route("404")]
public IActionResult PageNotFound()
{
    string originalPath = "unknown";
    if (HttpContext.Items.ContainsKey("originalPath"))
    {
        originalPath = HttpContext.Items["originalPath"] as string;
    }
    return View();
}

Note: You can add the action to another existing controller like HomeController.

2. Now add the PageNotFound.cshtml view. Something like this:

@{
    ViewBag.Title = "404";
}

<h1>404 - Page not found</h1>

<p>Oops, better check that URL.</p>

3. And the important part is here. Add this code to Startup class, inside Configure method:

app.Use(async (ctx, next) =>
{
    await next();

    if(ctx.Response.StatusCode == 404 && !ctx.Response.HasStarted)
    {
        //Re-execute the request so the user gets the error page
        string originalPath = ctx.Request.Path.Value;
        ctx.Items["originalPath"] = originalPath;
        ctx.Request.Path = "/error/404";
        await next();
    }
});

Note that it must be added before routing configs like app.UseEndpoints....


You can use fallback in EndPoint in asp.net core Like below (inside app.UseEndpoints) and with razor pages (NotFound is a razor page in Pages folder instead controller)

 endpoints.MapRazorPages();
            
 endpoints.MapFallback( context => {
    context.Response.Redirect("/NotFound");
    return Task.CompletedTask;
  });

Based on this SO item, IIS gets the 404 (and therefore handles it) before it reaches UseStatusCodePagesWithReExecute.

Have you tried this: https://github.com/aspnet/Diagnostics/issues/144? It suggests terminating the request that received a 404 so it does not go to IIS to handle. And here's the code to add to your Startup to do so:

app.Run(context =>
{
   context.Response.StatusCode = 404;
   return Task.FromResult(0);
});

This appears to be an IIS-only issue.