How to make custom error pages work in ASP.NET MVC 4
Asked Answered
A

11

270

I want a custom error page shown for 500, 404 and 403. Here's what I have done:

  1. Enabled custom errors in the web.config as follows:

    <customErrors mode="On" 
                  defaultRedirect="~/Views/Shared/Error.cshtml">
    
        <error statusCode="403" 
               redirect="~/Views/Shared/UnauthorizedAccess.cshtml" />
    
        <error statusCode="404" 
               redirect="~/Views/Shared/FileNotFound.cshtml" />
    
    </customErrors>
    
  2. Registered HandleErrorAttribute as a global action filter in the FilterConfig class as follows:

    public static void RegisterGlobalFilters(GlobalFilterCollection filters)
    {
        filters.Add(new CustomHandleErrorAttribute());
        filters.Add(new AuthorizeAttribute());
    }
    
  3. Created a custom error page for each of the above messages. The default one for 500 was already available out of the box.

  4. Declared in each custom error page view that the model for the page is System.Web.Mvc.HandleErrorInfo

For 500, it shows the custom error page. For others, it doesn't.

Is there something I am missing?

It does look like this is not all there is to displaying custom errors as I read through the code in the OnException method of the HandleErrorAttribute class and it is handling only 500.

What do I have to do to handle other errors?

Americana answered 16/12, 2012 at 20:23 Comment(3)
What's weird with this setup is that your redirecting to views, not controller actions. Who is supposed to render those views and pass in a model, for example? Just thinking.Enceinte
Most of the answers here either don't handle all cases or cause the web server to respond in an "incorrect" way i.e. redirecting to an error page rather than returning an error response. If you care about the server responding in a way expected of web servers then there is quite a detailed article about it here: benfoster.io/blog/aspnet-mvc-custom-error-pages. Be warned that it is not so straightforward as the answers here so if you want an easy answer just use one of the ones below instead.Contraoctave
Here is another great article on various techniques for asp.net error handling dusted.codes/…Coauthor
O
383

My current setup (on MVC3, but I think it still applies) relies on having an ErrorController, so I use:

<system.web>
    <customErrors mode="On" defaultRedirect="~/Error">
      <error redirect="~/Error/NotFound" statusCode="404" />
    </customErrors>
</system.web>

And the controller contains the following:

public class ErrorController : Controller
{
    public ViewResult Index()
    {
        return View("Error");
    }
    public ViewResult NotFound()
    {
        Response.StatusCode = 404;
        return View("NotFound");
    }
}

And the views just the way you implement them. I tend to add a bit of logic though, to show the stack trace and error information if the application is in debug mode. So Error.cshtml looks something like this:

@model System.Web.Mvc.HandleErrorInfo
@{
    Layout = "_Layout.cshtml";
    ViewBag.Title = "Error";
}
<div class="list-header clearfix">
    <span>Error</span>
</div>
<div class="list-sfs-holder">
    <div class="alert alert-error">
        An unexpected error has occurred. Please contact the system administrator.
    </div>
    @if (Model != null && HttpContext.Current.IsDebuggingEnabled)
    {
        <div>
            <p>
                <b>Exception:</b> @Model.Exception.Message<br />
                <b>Controller:</b> @Model.ControllerName<br />
                <b>Action:</b> @Model.ActionName
            </p>
            <div style="overflow:scroll">
                <pre>
                    @Model.Exception.StackTrace
                </pre>
            </div>
        </div>
    }
</div>
Organogenesis answered 16/12, 2012 at 21:57 Comment(21)
Did you have to put anything in your Application_Error in your Global.asax for this Pablo ?Lyophobic
This should not need any additional steps... in fact, Error.cshtml should already exists in Views/Shared so just edit it to the above.Groat
The code in the controller does not seem to execute from my experience. MVC4 - throwing a System.Exception in a different controller will make the Error.cshtml file render, but not through the ErrorController. Anyone else experiencing this?Forsythia
I should add that my comment above is only true when the line filters.Add(new HandleErrorAttribute()) is present in RegisterGlobalFilters()Forsythia
For anyone else who found this helpful, but needed more context; The <customErrors> tag goes inside <system.web> in web.config.Leopoldine
Using this approach you will also get the following for @Model.Exception.Message (: Message: The view 'FileNotFound' or its master was not found or no view engine supports the searched locations. The following locations were searched: ~/Views/Error/FileNotFound.aspx ~/Views/Error/FileNotFound.ascx ~/Views/Shared/FileNotFound.aspx ~/Views/Shared/FileNotFound.ascx ~/Views/Error/FileNotFound.cshtml ~/Views/Error/FileNotFound.vbhtml ~/Views/Shared/FileNotFound.cshtml ~/Views/Shared/FileNotFound.vbhtmlInsufferable
The code above doesn't look for view named "FileNotFound", are you sure that's not something in your code?Organogenesis
Also keep in mind that the NotFound action is indeed using a view named "NotFound", which you should certainly create.Organogenesis
You forgot to add (HandleErrorInfo exception) to the NotFound action to pass into the view.Ishtar
When I set this up, I just get a yellow screen of death with the message An exception occurred while processing your request. Additionally, another exception occurred while executing the custom error page for the first exception. The request has been terminated. Germanophile
Update for others- apparently my issue was happening because I had redirectMode="ResponseRewrite" on the CustomerErrors elementGermanophile
I also recieved this error. I was just about to kill myself when I found out why it didn't work. I had forgotten I was using the Authorize filter attribute so if you're using this, don't forget to add [AllowAnonymous] on your error action.Intersection
Anyone making the mistake I did - this code assumes it's in the same path as the _Layout.cshtml file. You need to change the @{ Layout = "~/Views/Shared/_Layout.cshtml";Fixate
Please for the love of god ignore the comment saying //you may want to set this to 200 in the code. DO NOT DO THAT!Parkerparkhurst
Putting the response.statuscode for me makes it go to the default ASP.NET 404 error page. I copied this answer exactly. I'm wondering if there is another setting I need to change. If I remove the response.status code everything works fine. I'm not sure I need the status code but I was wondering if the browser needed it or something.Licorice
I had to add the suggestion of <system.webServer> <httpErrors existingResponse="PassThrough" /> in addition to the aboveJourney
System.Web.Mvc.HandleErrorInfo model is NULL!Whippletree
Would be nice if this didn't perform a redirect to the error page URL before returning the error response. This is not what would be expected of most web servers.Contraoctave
How to simulate an error thrown by IIS. Be it 500 or 504. What to do in ASP.Net MVC - 5 code to simulate the exception from IIS so that I can test my custom error pageLutyens
If you're configuring custom error page for 404 using httpErrors section in web.config, and you don't return 200 from the controller, IIS returns 404 without any content.Monogenic
In WebConfig mode="RemoteOnly" instead of mode="On". And in production you will see your nice Error page while in Debug you will still see the usual Programmer's friendly error page. You can still use mode="On" to check hou the page is going to look like while in debug mode :)Demise
P
44

I've done pablo solution and I always had the error (MVC4)

The view 'Error' or its master was not found or no view engine supports the searched location.

To get rid of this, remove the line

 filters.Add(new HandleErrorAttribute());

in FilterConfig.cs

Picturize answered 7/4, 2014 at 18:2 Comment(3)
I looked everywhere to resolve this. This finally had the answer. I knew why it was doing it but for the heck of me I couldn't, without thinking drastically like what other people have said. I imagine I share 360Airwalk's pain when I say thank you for pointing this out. Legend!Dollarfish
This is one option and Error controller works fine. But it seems when you register filters in FilterConfig.cs, it looks for Error.cshtml in shared and original controllers' view folders. When you change the Error.cshtml to anything other than that our custom ErrorController works. But there is a place you can add this registration and it is global.asax.cs. If you add mentioned line in RegisterGlobalFilters(GlobalFilterCollection filters) function in global.asax.cs and remove from FilterConfig.cs, it works.Lunate
I think it is related to order of filter registrations. Keep the error controller and move filter registration to global.asax.cs. public static void RegisterGlobalFilters(GlobalFilterCollection filters) { filters.Add(new HandleErrorAttribute()); }Lunate
S
26

I do something that requires less coding than the other solutions posted.

First, in my web.config, I have the following:

<customErrors mode="On" defaultRedirect="~/ErrorPage/Oops">
   <error redirect="~/ErrorPage/Oops/404" statusCode="404" />
   <error redirect="~/ErrorPage/Oops/500" statusCode="500" />
</customErrors>

And the controller (/Controllers/ErrorPageController.cs) contains the following:

public class ErrorPageController : Controller
{
    public ActionResult Oops(int id)
    {
        Response.StatusCode = id;

        return View();
    }
}

And finally, the view contains the following (stripped down for simplicity, but it can conta:

@{ ViewBag.Title = "Oops! Error Encountered"; }

<section id="Page">
  <div class="col-xs-12 well">
    <table cellspacing="5" cellpadding="3" style="background-color:#fff;width:100%;" class="table-responsive">
      <tbody>
        <tr>
          <td valign="top" align="left" id="tableProps">
            <img width="25" height="33" src="~/Images/PageError.gif" id="pagerrorImg">
          </td>
          <td width="360" valign="middle" align="left" id="tableProps2">
            <h1 style="COLOR: black; FONT: 13pt/15pt verdana" id="errortype"><span id="errorText">@Response.Status</span></h1>
          </td>
        </tr>
        <tr>
          <td width="400" colspan="2" id="tablePropsWidth"><font style="COLOR: black; FONT: 8pt/11pt verdana">Possible causes:</font>
          </td>
        </tr>
        <tr>
          <td width="400" colspan="2" id="tablePropsWidth2">
            <font style="COLOR: black; FONT: 8pt/11pt verdana" id="LID1">
                            <hr>
                            <ul>
                                <li id="list1">
                                    <span class="infotext">
                                        <strong>Baptist explanation: </strong>There
                                        must be sin in your life. Everyone else opened it fine.<br>
                                    </span>
                                </li>
                                <li>
                                    <span class="infotext">
                                        <strong>Presbyterian explanation: </strong>It's
                                        not God's will for you to open this link.<br>
                                    </span>
                                </li>
                                <li>
                                    <span class="infotext">
                                        <strong> Word of Faith explanation:</strong>
                                        You lack the faith to open this link. Your negative words have prevented
                                        you from realizing this link's fulfillment.<br>
                                    </span>
                                </li>
                                <li>
                                    <span class="infotext">
                                        <strong>Charismatic explanation: </strong>Thou
                                        art loosed! Be commanded to OPEN!<br>
                                    </span>
                                </li>
                                <li>
                                    <span class="infotext">
                                        <strong>Unitarian explanation:</strong> All
                                        links are equal, so if this link doesn't work for you, feel free to
                                        experiment with other links that might bring you joy and fulfillment.<br>
                                    </span>
                                </li>
                                <li>
                                    <span class="infotext">
                                        <strong>Buddhist explanation:</strong> .........................<br>
                                    </span>
                                </li>
                                <li>
                                    <span class="infotext">
                                        <strong>Episcopalian explanation:</strong>
                                        Are you saying you have something against homosexuals?<br>
                                    </span>
                                </li>
                                <li>
                                    <span class="infotext">
                                        <strong>Christian Science explanation: </strong>There
                                        really is no link.<br>
                                    </span>
                                </li>
                                <li>
                                    <span class="infotext">
                                        <strong>Atheist explanation: </strong>The only
                                        reason you think this link exists is because you needed to invent it.<br>
                                    </span>
                                </li>
                                <li>
                                    <span class="infotext">
                                        <strong>Church counselor's explanation:</strong>
                                        And what did you feel when the link would not open?
                                    </span>
                                </li>
                            </ul>
                            <p>
                                <br>
                            </p>
                            <h2 style="font:8pt/11pt verdana; color:black" id="ietext">
                                <img width="16" height="16" align="top" src="~/Images/Search.gif">
                                HTTP @Response.StatusCode - @Response.StatusDescription <br>
                            </h2>
                        </font>
          </td>
        </tr>
      </tbody>
    </table>
  </div>
</section>

It's just as simple as that. It could be easily extended to offer more detailed error info, but ELMAH handles that for me & the statusCode & statusDescription is all that I usually need.

Sanction answered 15/11, 2014 at 11:41 Comment(2)
I think the redirect in the .config file of "~/ErrorPage/Oops/404" probably should be "~/ErrorPage/Oops?404" right? At least that's what worked for me. Maybe that just depends on the routing.Rainbow
How to simulate an error thrown by IIS. Be it 500 or 504. What to do in ASP.Net MVC - 5 code to simulate the exception from IIS so that I can test my custom error pageLutyens
U
17

There seem to be a number of steps here jumbled together. I'll put forward what I did from scratch.

  1. Create the ErrorPage controller

    public class ErrorPageController : Controller
    {
        public ActionResult Index()
        {
            return View();
        }
    
        public ActionResult Oops(int id)
        {
            Response.StatusCode = id;
            return View();
        }
    }
    
  2. Add views for these two actions (right click -> Add View). These should appear in a folder called ErrorPage.

  3. Inside App_Start open up FilterConfig.cs and comment out the error handling filter.

    public static void RegisterGlobalFilters(GlobalFilterCollection filters)
    {
        // Remove this filter because we want to handle errors ourselves via the ErrorPage controller
        //filters.Add(new HandleErrorAttribute());
    }
    
  4. Inside web.config add the following <customerErrors> entries, under System.Web

    <customErrors mode="On" defaultRedirect="~/ErrorPage/Oops">
        <error redirect="~/ErrorPage/Oops/404" statusCode="404" />
        <error redirect="~/ErrorPage/Oops/500" statusCode="500" />
    </customErrors>
    
  5. Test (of course). Throw an unhandled exception in your code and see it go to the page with id 500, and then use a URL to a page that does not exist to see 404.

Underprivileged answered 28/5, 2015 at 9:11 Comment(5)
I was getting this error An exception occurred while processing your request. Additionally, another exception occurred while executing the custom error page for the first exception. The request has been terminated. All I picked up from your code is in the web.config file, I added <error redirect = "~/ControllerName/ActionName" statusCode="404"/> and it worked fine:) Rest of the code was from @Pablo's answer. I am using MVC 5 and entity framework 6. I did not remove filters.Add(new HandleErrorAttribute()) from FilterConfig.csOgden
How to simulate an error thrown by IIS. Be it 500 or 504. What to do in ASP.Net MVC - 5 code to simulate the exception from IIS so that I can test my custom error pageLutyens
Also, how to throw unhandled exception (step 5). I am new to coding please guide.Lutyens
Still doesn't work for me? What about the routing? Do I need to add Routing for Error page too? If I hit page: localhost:84/Enforcer/blah I get redirected to: localhost:84/Enforcer/Enforcer/Error/NotFound?aspxerrorpath=/… The error page looks like a standard error page provided by Asp.NET. Any ideas?Vigilant
The customerrors element in the webconfig should be habndling this. Your (project-created) default route code should work fine.Underprivileged
S
11

I would Recommend to use Global.asax.cs File.

 protected void Application_Error(Object sender, EventArgs e)
{
    var exception = Server.GetLastError();
    if (exception is HttpUnhandledException)
    {
        Server.Transfer("~/Error.aspx");
    }
    if (exception != null)
    {
        Server.Transfer("~/Error.aspx");
    }
    try
    {
        // This is to stop a problem where we were seeing "gibberish" in the
        // chrome and firefox browsers
        HttpApplication app = sender as HttpApplication;
        app.Response.Filter = null;
    }
    catch
    {
    }
}
Sullen answered 11/2, 2014 at 4:47 Comment(3)
I didn't think you could do a Server.Transfer() in MVC. Are you thinking the OP has a mixed site?Rasp
why should we use Application_Error in mvc? We have options like [handleerror] attribut with redirection url options. Is there any specific advantage with application_error to that?Mouthy
We should use HandleErrorAttribute in MVC and by overriding OnException method, we can handle them in a much better wayBroody
R
10

Building on the answer posted by maxspan, I've put together a minimal sample project on GitHub showing all the working parts.

Basically, we just add an Application_Error method to global.asax.cs to intercept the exception and give us an opportunity to redirect (or more correctly, transfer request) to a custom error page.

    protected void Application_Error(Object sender, EventArgs e)
    {
        // See https://mcmap.net/q/108570/-how-to-make-custom-error-pages-work-in-asp-net-mvc-4
        // for additional context on use of this technique

        var exception = Server.GetLastError();
        if (exception != null)
        {
            // This would be a good place to log any relevant details about the exception.
            // Since we are going to pass exception information to our error page via querystring,
            // it will only be practical to issue a short message. Further detail would have to be logged somewhere.

            // This will invoke our error page, passing the exception message via querystring parameter
            // Note that we chose to use Server.TransferRequest, which is only supported in IIS 7 and above.
            // As an alternative, Response.Redirect could be used instead.
            // Server.Transfer does not work (see https://support.microsoft.com/en-us/kb/320439 )
            Server.TransferRequest("~/Error?Message=" + exception.Message);
        }

    }

Error Controller:

/// <summary>
/// This controller exists to provide the error page
/// </summary>
public class ErrorController : Controller
{
    /// <summary>
    /// This action represents the error page
    /// </summary>
    /// <param name="Message">Error message to be displayed (provided via querystring parameter - a design choice)</param>
    /// <returns></returns>
    public ActionResult Index(string Message)
    {
        // We choose to use the ViewBag to communicate the error message to the view
        ViewBag.Message = Message;
        return View();
    }

}

Error page View:

<!DOCTYPE html>

<html>
<head>
    <title>Error</title>
</head>
<body>

    <h2>My Error</h2>
    <p>@ViewBag.Message</p>
</body>
</html>

Nothing else is involved, other than disabling/removing filters.Add(new HandleErrorAttribute()) in FilterConfig.cs

public class FilterConfig
{
    public static void RegisterGlobalFilters(GlobalFilterCollection filters)
    {
        //filters.Add(new HandleErrorAttribute()); // <== disable/remove
    }
}

While very simple to implement, the one drawback I see in this approach is using querystring to deliver exception information to the target error page.

Rabassa answered 23/8, 2015 at 17:2 Comment(0)
P
4

I had everything set up, but still couldn't see proper error pages for status code 500 on our staging server, despite the fact everything worked fine on local development servers.

I found this blog post from Rick Strahl that helped me.

I needed to add Response.TrySkipIisCustomErrors = true; to my custom error handling code.

Polemics answered 8/5, 2015 at 1:57 Comment(1)
@Shaun314 You mean where do you put that code? In the action that handles the request. You can see examples in that blog post.Polemics
U
2

Here is my solution. Use [ExportModelStateToTempData] / [ImportModelStateFromTempData] is uncomfortable in my opinion.

~/Views/Home/Error.cshtml:

@{
    ViewBag.Title = "Error";
    Layout = "~/Views/Shared/_Layout.cshtml";
}

<h2>Error</h2>
<hr/>

<div style="min-height: 400px;">

    @Html.ValidationMessage("Error")

    <br />
    <br />

    <button onclick="Error_goBack()" class="k-button">Go Back</button>
    <script>
        function Error_goBack() {
            window.history.back()
        }
    </script>

</div>

~/Controllers/HomeController.sc:

public class HomeController : BaseController
{
    public ActionResult Index()
    {
        return View();
    }

    public ActionResult Error()
    {
        return this.View();
    }

    ...
}

~/Controllers/BaseController.sc:

public class BaseController : Controller
{
    public BaseController() { }

    protected override void OnActionExecuted(ActionExecutedContext filterContext)
    {
        if (filterContext.Result is ViewResult)
        {
            if (filterContext.Controller.TempData.ContainsKey("Error"))
            {
                var modelState = filterContext.Controller.TempData["Error"] as ModelState;
                filterContext.Controller.ViewData.ModelState.Merge(new ModelStateDictionary() { new KeyValuePair<string, ModelState>("Error", modelState) });
                filterContext.Controller.TempData.Remove("Error");
            }
        }
        if ((filterContext.Result is RedirectResult) || (filterContext.Result is RedirectToRouteResult))
        {
            if (filterContext.Controller.ViewData.ModelState.ContainsKey("Error"))
            {
                filterContext.Controller.TempData["Error"] = filterContext.Controller.ViewData.ModelState["Error"];
            }
        }

        base.OnActionExecuted(filterContext);
    }
}

~/Controllers/MyController.sc:

public class MyController : BaseController
{
    public ActionResult Index()
    {
        return View();
    }

    public ActionResult Details(int id)
    {
        if (id != 5)
        {
            ModelState.AddModelError("Error", "Specified row does not exist.");
            return RedirectToAction("Error", "Home");
        }
        else
        {
            return View("Specified row exists.");
        }
    }
}

I wish you successful projects ;-)

Undirected answered 5/1, 2015 at 19:35 Comment(0)
B
2

You can get errors working correctly without hacking global.cs, messing with HandleErrorAttribute, doing Response.TrySkipIisCustomErrors, hooking up Application_Error, or whatever:

In system.web (just the usual, on/off)

<customErrors mode="On">
  <error redirect="/error/401" statusCode="401" />
  <error redirect="/error/500" statusCode="500" />
</customErrors>

and in system.webServer

<httpErrors existingResponse="PassThrough" />

Now things should behave as expected, and you can use your ErrorController to display whatever you need.

Botticelli answered 14/12, 2015 at 12:45 Comment(3)
How to simulate an error thrown by IIS. Be it 500 or 504. What to do in ASP.Net MVC - 5 code to simulate the exception from IIS so that I can test my custom error pageLutyens
@Lutyens temporarily change your code to throw an exception.Agata
Didn't make a difference for me. I'm not taken to my custom error page upon an exception or 404 not found error.Overissue
G
1

In web.config add this under system.webserver tag as below,

<system.webServer>
<httpErrors errorMode="Custom" existingResponse="Replace">
  <remove statusCode="404"/>
  <remove statusCode="500"/>
  <error statusCode="404" responseMode="ExecuteURL" path="/Error/NotFound"/>
  <error statusCode="500" responseMode="ExecuteURL"path="/Error/ErrorPage"/>
</httpErrors>

and add a controller as,

public class ErrorController : Controller
{
    //
    // GET: /Error/
    [GET("/Error/NotFound")]
    public ActionResult NotFound()
    {
        Response.StatusCode = 404;

        return View();
    }

    [GET("/Error/ErrorPage")]
    public ActionResult ErrorPage()
    {
        Response.StatusCode = 500;

        return View();
    }
}

and add their respected views, this will work definitely I guess for all.

This solution I found it from: Neptune Century

Gladis answered 30/8, 2017 at 8:6 Comment(1)
Persian character encoding messes upLaoag
K
0

It seems i came late to the party, but you should better check this out too.

So in system.web for caching up exceptions within the application such as return HttpNotFound()

  <system.web>
    <customErrors mode="RemoteOnly">
      <error statusCode="404" redirect="/page-not-found" />
      <error statusCode="500" redirect="/internal-server-error" />
    </customErrors>
  </system.web>

and in system.webServer for catching up errors that were caught by IIS and did not made their way to the asp.net framework

 <system.webServer>
    <httpErrors errorMode="DetailedLocalOnly">
      <remove statusCode="404"/>
      <error statusCode="404" path="/page-not-found" responseMode="Redirect"/>
      <remove statusCode="500"/>
      <error statusCode="500" path="/internal-server-error" responseMode="Redirect"/>
  </system.webServer>

In the last one if you worry about the client response then change the responseMode="Redirect" to responseMode="File" and serve a static html file, since this one will display a friendly page with an 200 response code.

Kell answered 11/8, 2017 at 16:38 Comment(1)
Persian character encoding messes upLaoag

© 2022 - 2024 — McMap. All rights reserved.