How do I display custom error pages in Asp.Net Mvc 3?
Asked Answered
G

4

23

I want all 401 errors to be be redirected to a custom error page. I have initially setup the following entry in my web.config.

<customErrors defaultRedirect="ErrorPage.aspx" mode="On">
  <error statusCode="401" redirect="~/Views/Shared/AccessDenied.aspx" />
</customErrors>

When using IIS Express I receive the stock IIS Express 401 error page.

In the event when I do not use IIS Express a blank page is returned. Using Google Chrome's Network tab to inspect the response, I see that while the page is blank a 401 status is returned in the headers

What I have tried thus far is using suggestions from this SO answer since I am using IIS Express but to no avail. I have tried using a combination <custom errors> and <httpErrors> with no luck - the standard error or blank page is still displayed.

The httpErrors section looks like this at the moment based on the link from the above SO question ( I also found another very promising answer however no luck - blank response)

<system.webServer>
  <httpErrors  errorMode="DetailedLocalOnly" existingResponse="PassThrough" >
    <remove statusCode="401"  />
    <error statusCode="401" path="/Views/Shared/AccessDenied.htm" />
  </httpErrors>

 <!-- 
 <httpErrors  errorMode="Custom" 
             existingResponse="PassThrough" 
             defaultResponseMode="ExecuteURL">
      <remove statusCode="401"  />
  <error statusCode="401" path="~/Views/Shared/AccessDenied.htm" 
         responseMode="File" />
 </httpErrors>
 -->
</system.webServer>

I have even modified the applicationhost.config file and modified <httpErrors lockAttributes="allowAbsolutePathsWhenDelegated,defaultPath"> to <httpErrors lockAttributes="allowAbsolutePathsWhenDelegated"> based on information from iis.net. During the course of my endeavours I also managed to stumbled upon this error as described in another SO question.

How do I display custom error pages in Asp.Net Mvc 3?

Additional info

The following controller actions have been decorated with the Authorize attribute for a specific user.

[HttpGet]
[Authorize(Users = "domain\\userXYZ")]
public ActionResult Edit() 
{
   return GetSettings();
}

[HttpPost]
[Authorize(Users = "domain\\userXYZ")]
public ActionResult Edit(ConfigurationModel model, IList<Shift> shifts)
{
    var temp = model;
    model.ConfiguredShifts = shifts;
    EsgConsole config = new EsgConsole();

    config.UpdateConfiguration(model.ToDictionary());
    return RedirectToAction("Index");
}
Guadalupe answered 18/7, 2011 at 12:44 Comment(6)
I would also like to know how to get custom errors displayed (in my case a 403 error) - 500 errors work fine...Amendatory
Made a small library to make this easier. It's available: github.com/Buildstarted/ErrlusionPortfolio
Just thought you might be interested in seeing this SO postPoling
@Poling that post actually sorts out the problems I was having, not sure for the OP. Also none of the answers to this question mention all the configuration options needed, so it's difficult to award the bounty...Amendatory
Bounty awarded but none of the answers were what I was looking for, better answers in @CBRRacer's linkAmendatory
I belive this was answered on this post #5227291Displease
A
34

I use these steps:

// in Global.asax.cs:
        protected void Application_Error(object sender, EventArgs e) {

            var ex = Server.GetLastError().GetBaseException();

            Server.ClearError();
            var routeData = new RouteData();
            routeData.Values.Add("controller", "Error");
            routeData.Values.Add("action", "Index");

            if (ex.GetType() == typeof(HttpException)) {
                var httpException = (HttpException)ex;
                var code = httpException.GetHttpCode();
                routeData.Values.Add("status", code);
            } else {
                routeData.Values.Add("status", 500);
            }

            routeData.Values.Add("error", ex);

            IController errorController = new Kavand.Web.Controllers.ErrorController();
            errorController.Execute(new RequestContext(new HttpContextWrapper(Context), routeData));
        }

        protected void Application_EndRequest(object sender, EventArgs e) {
            if (Context.Response.StatusCode == 401) { // this is important, because the 401 is not an error by default!!!
                throw new HttpException(401, "You are not authorised");
            }
        }

AND:

// in Error Controller:
    public class ErrorController : Controller {

        public ActionResult  Index(int status, Exception error) {
            Response.StatusCode = status;
            return View(status);
        }

        protected override void Dispose(bool disposing) {
            base.Dispose(disposing);
        }
    }

AND the index view in Error folder:

@* in ~/Views/Error/Index.cshtml: *@

@model Int32    
@{
    Layout = null;
}    
<!DOCTYPE html>    
<html>
<head>
    <title>Kavand | Error</title>
</head>
<body>
    <div>
        There was an error with your request. The error is:<br />
        <p style=" color: Red;">
        @switch (Model) {
            case 401: {
                    <span>Your message goes here...</span>
                }
                break;
            case 403: {
                    <span>Your message goes here...</span>
                }
                break;
            case 404: {
                    <span>Your message goes here...</span>
                }
                break;
            case 500: {
                    <span>Your message goes here...</span>
                }
                break;
            //and more cases for more error-codes...
            default: {
                    <span>Unknown error!!!</span>
                }
                break;
        }
        </p>
    </div>
</body>
</html>

AND -the final step:

<!-- in web.config: -->

<customErrors mode="Off"/>
Avalokitesvara answered 8/9, 2011 at 1:36 Comment(4)
this seems to work, the exception is annoying when debugging with Windows authentication and I did have to add httpErrors existingResponse="PassThrough" to the web config when testing on a server. Thank you though..Ramos
its throwing error CS0151: A switch expression or case label must be a bool, char, string, integral, enum, or corresponding nullable type..help plz??Detrition
This only works for me when debugging. How can I get it to work on the server? I still see the default error pages on the server. IIS has Error Pages Settings Mode = Off.Spend
I don't know. May be your server's configuration is different. I'm using this for about 3 years in many websites. It works just fine. Sorry ):Avalokitesvara
F
11

I was never able to get CustomErrors in a web.config and MVC to play nice together, so I gave up. I do this instead.

In global.asax:

protected void Application_Error()
    {
        var exception = Server.GetLastError();
        var httpException = exception as HttpException;
        Response.Clear();
        Server.ClearError();
        var routeData = new RouteData();
        routeData.Values["controller"] = "Errors";
        routeData.Values["action"] = "General";
        routeData.Values["exception"] = exception;
        Response.StatusCode = 500;
        if (httpException != null)
        {
            Response.StatusCode = httpException.GetHttpCode();
            switch (Response.StatusCode)
            {
                case 403:
                    routeData.Values["action"] = "Http403";
                    break;
                case 404:
                    routeData.Values["action"] = "Http404";
                    break;
            }
        }
        // Avoid IIS7 getting in the middle
        Response.TrySkipIisCustomErrors = true;
        IController errorsController = new GNB.LG.StrategicPlanning.Website.Controllers.ErrorsController();
        HttpContextWrapper wrapper = new HttpContextWrapper(Context);
        var rc = new RequestContext(wrapper, routeData);
        errorsController.Execute(rc);
    }

In ErrorsController:

public class ErrorsController
{
    public ActionResult General(Exception exception)
    {
        // log the error here
        return View(exception);
    }

    public ActionResult Http404()
    {
        return View("404");
    }

    public ActionResult Http403()
    {
        return View("403");
    }
}

In web.config:

<customErrors mode="Off" />

That's worked for me no matter where or how the error is created. 401 isn't handled there right now but you could add it pretty easily.

Fideism answered 18/7, 2011 at 12:52 Comment(4)
thanks for the answer - however I have seen this approach and for some reason it did not appeal to me.Guadalupe
@Guadalupe me neither, but it works. :) I found trying to set it up with CustomErrors in web.config that depending on where/how the error is thrown, it doesn't always work.Fideism
Great answer - except I would also add an if (!HttpContext.Current.IsDebuggingEnabled) around the block in Application_Error so the errors still show while debugging.Matless
+1, very helpful and working answer, almost for all kinda errors (I searched for a week to control Errors in web.config but didn't find a complete solution)Hypersonic
L
9

Maybe I'm missing something, but MVC has a default global ErrorHandlerAttribute that uses custom errors. This is explained quite well here.

public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
    filters.Add(new HandleErrorAttribute());
}

All you need to do is turn on custom errors in the config, and then setup custom error redirects, preferably to a static HTML file (in case there's errors with the app).

<customErrors mode="On" defaultRedirect="errors.htm">
    <error statusCode="404" redirect="errors404.htm"/>
</customErrors>

If you prefer could also point to a custom Controller to display the errors. In the following example I've just used the default routing to a Controller named Error, with an action called Index, and string parameter named id (to receive the errorcode). You could of course use any routing you desire. Your example isn't working because you are trying to link directly into the Views directory without going via a Controller. MVC .NET doesn't serve requests to the Views folder directly.

<customErrors mode="On" defaultRedirect="/error/index/500">
    <error statusCode="404" redirect="/error/index/404"/>
</customErrors>

The ErrorHandlerAttribute can also be used extensively with Controllers/Actions to redirect errors to named Views related to the Controller. For example to show the View named MyArgumentError when a exception of type ArgumentException occurs you could use:

[ControllerAction,ExceptionHandler("MyArgumentError",typeof(ArgumentException))]
public void Index()
{
   // some code that could throw ArgumentExcepton
}

Of course another option is to update the stock Error page in Shared.

Larva answered 9/9, 2011 at 23:28 Comment(2)
The problem is that IIS express already handles those other status codes (not the 500), so what you're suggesting doesn't always work (depends on the IIS setup). IIS just displays its own page, and we never make it through to the view in asp.Amendatory
True, that's just IIS config though which is a different issue. The MVC error handling should be done as above.Larva
E
0

Looking at the first part of your web.config there, you're pointing to an .aspx page directly. When I setup my error pages I pointed directly to a controller and action. For example:

<customErrors mode="On" defaultRedirect="~/Error/UhOh">
  <error statusCode="404" redirect="~/Error/NotFound" />
  <error statusCode="403" redirect="~/Error/AccessDenied" />
</customErrors>

And I had an Error controller with all the required actions. I don't think MVC plays well with direct calls to .aspx pages.

Educt answered 14/9, 2011 at 0:31 Comment(1)
It does work with aspx pages, in fact if you are using rewrite mode then you have to use static content files (not actions)Enterostomy

© 2022 - 2024 — McMap. All rights reserved.