17

UPD: Here is the full solution for error handling

I've got plain vanilla MVC4 web-project. Nothing added, nothing deleted, just created a new project in Visual Studio.

In web.config I've added custom error page handler:

<customErrors mode="On" defaultRedirect="~/Content/Error.htm" redirectMode="ResponseRewrite" />

and the ~/Content/Error.htm file is:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>OOPS! Error Occurred. Sorry about this.</title>
</head>
<body>
    <h2>OOPS! Error Occurred</h2>
</body>
</html>

Whenever I get a 404 error on the site, Error.htm is served as plaintext in Firefox and Chrome:

HTML is displayed as plain text

Fiddler says that error page is served without content-type header which leads browsers to render the page as plain-text:

No Content-type header

Is there a way to force IIS server error pages with content-type header??

p.s. the actual problem is in a complex MVC4 project, that has it's own error handling in Global.asax. But I found that some errors don't go through the ASP pipe and handled only by IIS. Like dot at the end of the url. Solution with < httpErrors /> does serve correct responses, but our custom error handling in Global.asax, Application_Error() is not getting called this way.

UPD Seems like I can't win this war. IE displays that html properly rendered, Firefox and Chrome shows as plain text. When I switch to plain-text, Firefox and IE shows white-space correctly, IE swallows white-space and tries to render html. If I try serve an image as error page, Firefox and Chrome show image. IE shows this: IE9 is on crack! Facepalm!

Community
  • 1
  • 1
trailmax
  • 34,305
  • 22
  • 140
  • 234

4 Answers4

16

Use .aspx instead of .htm for error pages (rename htm to aspx).

<customErrors mode="On" defaultRedirect="~/Content/Error.aspx" redirectMode="ResponseRewrite" />
vvucetic
  • 479
  • 7
  • 15
  • We are on MVC. aspx is dirty -) We fall down to htm only in the worst case scenario, when everything else fails. And aspx also adds point of failure. – trailmax Mar 20 '13 at 21:40
  • 1
    I was using ResponseRewrite with MVC as fall down and as you already noted, content type is not right, same problem you have. Renaming from htm to aspx fixes that problem so its kind a workaround for this (i would say) bug. I think that is not that messy but its up to you. Another way to do that is to write your custom handler or something like that. – vvucetic Apr 21 '13 at 00:58
  • html re-named as *.aspx can work.. I'll have a go on that tomorrow! – trailmax Apr 21 '13 at 23:37
  • 4
    I just added the following code and it's working fine with an .htm file in asp.net mvc hosted in Azure : `protected void Application_Error(object sender, EventArgs e) { HttpContext.Current.Response.ContentType = "text/html; charset=UTF-8"; }` – Guillaume Roy Nov 25 '14 at 21:52
11

Apparently, <customErrors> is a mess to get working. If you're determined to use it, Ben Foster has a great write-up on the subject: http://benfoster.io/blog/aspnet-mvc-custom-error-pages

If you want to use .cshtml pages, your best bet is probably to ditch <customErrors> and handle errors in Global.asax.cs:

protected void Application_Error(object sender, EventArgs e)
{
    var exception = Server.GetLastError();
    if (exception != null)
    {
        Response.Clear();
        HttpException httpException = exception as HttpException;
        RouteData routeData = new RouteData();
        routeData.Values.Add("controller", "Error");
        if (httpException == null)
        {
            routeData.Values.Add("action", "Unknown");
        }
        else
        {
            switch (httpException.GetHttpCode())
            {
                case 404:               // Page not found.
                    routeData.Values.Add("action", "NotFound");
                    break;

                default:
                    routeData.Values.Add("action", "Unknown");
                    break;
            }
        }


        // Pass exception details to the target error View.
        routeData.Values.Add("Error", exception);
        // Clear the error on server.
        Server.ClearError();
        // Avoid IIS7 getting in the middle
        Response.TrySkipIisCustomErrors = true;
        // Ensure content-type header is present
        Response.Headers.Add("Content-Type", "text/html");
        // Call target Controller and pass the routeData.
        IController errorController = new ErrorController();
        errorController.Execute(new RequestContext(new HttpContextWrapper(Context), routeData));
    }
}

Of course, you would also need to add an ErrorController with the appropriate methods and .cshtml views.

public class ErrorController : Controller
{
    public ActionResult Index()
    {// your implementation
    }

    public ActionResult Unknown(Exception error)
    {// your implementation 
    }

    public ActionResult NotFound(Exception error)
    {// your implementation         
    }
}
Daniel
  • 920
  • 1
  • 11
  • 22
  • In the customErrors tag, you specify some page or pages: e.g., ... The point is that customErrors is failing to pass a Content-Type header (for whatever reason) - and this code will ensure that header is present and correct. – Daniel Jul 25 '14 at 22:30
  • `index.cshtml` is not a controller action which you refer to. – trailmax Jul 26 '14 at 17:12
  • I didn't say it was... but you can refer to a .cshtml page in your defaultRedirect. The point of my reply was that you don't have to use an .aspx page to get customErrors to work. – Daniel Jul 27 '14 at 02:10
  • Should have started from that, rather than from showing controller code. – trailmax Jul 27 '14 at 20:09
  • Well, dug a little deeper and it looks like customErrors really does force you to use .aspx (or plain text) - so I updated my post to show how you could get .cshtml working. I just wanted people to know there are ways to handle custom errors without resorting to .aspx - since we all know what a pain .aspx can be. In our case, we include our main style page as part of our custom error, and it's just not worth it to have to maintain a .cshtml and an .aspx version of it. HTH. – Daniel Jul 28 '14 at 17:55
  • Nah, I used `Application_Error` for a while: it is a pain and does not always work. I have seen Ben's article and I have written a blog-post myself about this. Best to avoid `*.cshtml` here. – trailmax Jul 29 '14 at 23:23
  • 1
    Your opinion. Would be interested in any hard examples of where it "does not always work". I think people should be aware of their options and pick what works best for them. In our case, that's to use .cshtml files here. – Daniel Jul 30 '14 at 00:10
  • From what I remember (removed it about a year ago), does not always work - exceptions before MVC pipeline, i.e. 404. `Response.TrySkipIisCustomErrors` is somewhat not reliable. – trailmax Jul 30 '14 at 00:15
  • 1
    I was using custom error handling in Global.asax.cs which works great for my application. Wasn't using `Response.Headers.Add("Content-Type", "text/html");` so one of my error pages served as text and this obviously solves it. – Josh Jun 08 '15 at 02:11
6

This is a known bug apparently and Microsoft's suggestion is in line with spiatrax's idea of renaming htm/html to aspx. In my case I also had to include

<% Response.StatusCode = 400 %>

in the .aspx page.

For more information: http://connect.microsoft.com/VisualStudio/feedback/details/507171/

Kevin Farrugia
  • 6,431
  • 4
  • 38
  • 61
  • Thanks for noting that the solution aspx returns the wrong statusCode, but returning 400 for all kinds of errors is wrong too. Is there a way to return the original status code (without creating a page for every error?) – Alleo Nov 15 '13 at 16:38
  • In my case I have a 400.aspx, a 500.aspx and a 404.aspx; each one returning the respective error code. I know, unfortunately it's not the most reusable piece of code. – Kevin Farrugia Dec 04 '13 at 09:38
0

I added the content-type header on custom header under system.webServer/httpprotocol section and that fixed it.

<customHeaders><remove name="Content-Type" /><add name="ContentType"value="text/html" /></customHeaders>
Dino
  • 7,779
  • 12
  • 46
  • 85