9

Instead of getting a HTTP 404 response, I'd like to have a generic 404 Not Found page (HTTP 200). I know you can set that up in MVC 5 with

<customErrors mode="On">
  <error statusCode="404" redirect="~/Error/NotFound" />
</customErrors>

But I can't seem to figure out how to do this in MVC 6. I'm guessing it should be in either the UseMvc routing table, or a custom middleware.

Michael
  • 3,222
  • 2
  • 21
  • 22
  • 1
    Never had to do it, but I was curious what's different, this seems to make sense: http://benfoster.io/blog/aspnet-mvc-custom-error-pages in a convoluted sort of way. – RandomUs1r Apr 02 '15 at 19:48
  • Is it that `` just isn't working? Check out my answer here (in case it's relevant): http://stackoverflow.com/questions/28989026/how-do-i-catch-the-http-status-code-of-server-errors-into-a-generic-custom-error/28991628#28991628 If you're using IIS7+ then `` is the better way to catch and handle 404's etc via the web config and a custom view. – scgough Apr 03 '15 at 07:26

6 Answers6

13

Startup.cs:

public class Startup
{
    public void Configure(IApplicationBuilder app)
    {
        // PICK YOUR FLAVOR.

        // app.UseErrorPage(ErrorPageOptions.ShowAll);
        app.UseStatusCodePages(); // There is a default response but any of the following can be used to change the behavior.

        // app.UseStatusCodePages(context => context.HttpContext.Response.SendAsync("Handler, status code: " + context.HttpContext.Response.StatusCode, "text/plain"));
        // app.UseStatusCodePages("text/plain", "Response, status code: {0}");
        // app.UseStatusCodePagesWithRedirects("~/errors/{0}"); // PathBase relative
        // app.UseStatusCodePagesWithRedirects("/base/errors/{0}"); // Absolute
        // app.UseStatusCodePages(builder => builder.UseWelcomePage());
        // app.UseStatusCodePagesWithReExecute("/errors/{0}");
    }
} 

project.json:

"dependencies": {
    "Microsoft.AspNet.Diagnostics": "1.0.0-*",
    // ....
}

note that, if you are hosting on IIS (or azure), the web.config does still works and it's big maybe needed in some hosting scenarios. (it should be placed inside wwwroot folder

Bart Calixto
  • 19,210
  • 11
  • 78
  • 114
5

You can handle 404 errors with the UseStatusCodePagesWithReExecute but you need to set the status code at the end of your configure pipeline first.

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

        if (string.Equals(_env.EnvironmentName, "Development", StringComparison.OrdinalIgnoreCase))
        {
            app.UseDeveloperExceptionPage();
            app.UseRuntimeInfoPage();
        } else
        {
            app.UseExceptionHandler("/Error");
        }
        app.UseStatusCodePagesWithReExecute("/Error/Status/{0}");

        app.UseStaticFiles();

        app.UseMvc();

        app.Use((context, next) =>
        {
            context.Response.StatusCode = 404;
            return next();
        });
    }

This will set the status code to 404 if nothing in the pipeline can handle it (which is what you want).

Then you can easily capture and handle the response any way you want in your controller.

[Route("error")]
public class ErrorController : Controller
{

[Route("Status/{statusCode}")]
    public IActionResult StatusCode(int statusCode)
    {

            //logic to generate status code response

            return View("Status", model);
    }
}
  • thanks for this! however in my testing, I didn't need the catch at the end of the pipeline for 404 (using Core 1.0) – Sean Jul 21 '16 at 16:17
3

If anyone is looking to create custom 404 pages with HTML, you can do this:

Startup.cs

public class Startup
{
    public void Configure(IApplicationBuilder app)
    {        
        app.UseStatusCodePagesWithReExecute("/errors/{0}");
    }
}

project.json

"dependencies": {
    "Microsoft.AspNet.Diagnostics": "1.0.0-*",
    // ....
}

ErrorsController.cs

public class ErrorsController : Controller
{
    // ....

    [Route("/Error/{statusCode}")]
    public IActionResult ErrorRoute(string statusCode)
    {
        if (statusCode == "500" | statusCode == "404")
        {
            return View(statusCode);
        }

        return View("~/Views/Errors/Default.cshtml");
    }
}

All that's left after this is to create a 404.cshtml, 500.cshtml, and Default.cshtml in your Errors view folder and customize the HTML to your liking.

smulholland2
  • 1,143
  • 2
  • 14
  • 28
  • in the startup you have "/errors/{0}" (plural "errors") and in attribute `[Route("/Error/{statusCode}")]` (singular "error"). It won't work. – Mariusz Jamro Apr 27 '17 at 11:22
1

In the Configure function of the Startup class, call:

app.UseErrorPage(ErrorPageOptions.ShowAll);

more production ready:

    if (string.Equals(env.EnvironmentName, "Development", StringComparison.OrdinalIgnoreCase))
    {
        app.UseBrowserLink();
        app.UseErrorPage(ErrorPageOptions.ShowAll);
        app.UseDatabaseErrorPage(DatabaseErrorPageOptions.ShowAll);
    }
    else
    {
        // Add Error handling middleware which catches all application specific errors and
        // send the request to the following path or controller action.
        app.UseErrorHandler("/Home/Error");
    }
Malgaur
  • 1,842
  • 15
  • 17
0

There is a middleware called StatusCodePagesMiddleware which I believe was added post-Beta3.

Sample: https://github.com/aspnet/Diagnostics/blob/dev/samples/StatusCodePagesSample/Startup.cs

Kiran
  • 56,921
  • 15
  • 176
  • 161
0

If you have a standard ASP.NET 5 Web application template project you can get a really nice solution that will handle both keeping the error status code at the same time as you serve the cshtml file of your choice.

Startup.cs, in the Configure method

Replace

app.UseExceptionHandler("/Error");

with

app.UseStatusCodePagesWithReExecute("/Home/Errors/{0}");

Then remove the following catchall:

app.Run(async (context) => 
{ 
   var logger = loggerFactory.CreateLogger("Catchall Endpoint"); 
   logger.LogInformation("No endpoint found for request {path}", context.Request.Path); 
   await context.Response.WriteAsync("No endpoint found - try /api/todo."); 
});

In HomeController.cs

Replace the current Error method with the following one:

public IActionResult Errors(string id) 
{ 
  if (id == "500" | id == "404") 
  { 
    return View($"~/Views/Home/Error/{id}.cshtml"); 
  }

  return View("~/Views/Shared/Error.cshtml"); 
}

That way you can add error files you feel the users can benefit from (while search engines and such still get the right status codes)

Now add a folder named Error in ~Views/Home folder Create two .cshtml files. Name the first one 404.cshtml and the second one 500.cshtml.

Give them some useful content and the start the application and write something like http://localhost:44306/idonthaveanendpointhere and confirm the setup works.

Also press F12 developer tools and confirm the status code is 404.

superjos
  • 12,189
  • 6
  • 89
  • 134
Magnus Karlsson
  • 3,549
  • 3
  • 31
  • 57