views:

46

answers:

3

Here's how I have it defined (locally, on my development machine):

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

And I have the [HandleError] attribute:

[Authorize]
[HandleError]
public class HomeController : Controller
{ // etc.

Yet when I type in http://localhost:1986/blah, I get the following error:

The resource cannot be found. Description: HTTP 404. The resource you are looking for (or one of its dependencies) could have been removed, had its name changed, or is temporarily unavailable. Please review the following URL and make sure that it is spelled correctly.

Requested URL: /NotFound.aspx

The URL it's trying to go to is as you would expect: http://localhost:1986/NotFound.aspx?aspxerrorpath=/blah

So it IS attempting to go to the custom error file -- however it can't find it. I do have NotFound.aspx in the Shared directory -- same place as the Error.aspx supplied by Microsoft as a default. Why can't it find it?

+2  A: 

If the Error.aspx and NotFound.aspx are in the shared directory is there a controller wired to served them? If you do not have some sort of controller route configured to serve the files then the fact that they are in the shared folder is irrelevant.

You have a few options, you could create an ErrorController which will handle the requests for those views and define routes pointing to those controller actions:

[OutputCache(CacheProfile = "Default", VaryByParam = "none")]
public class ErrorController : DefaultAreaBaseController
{
    public ViewResult ServiceUnavailable() {
        Response.StatusCode = (int)HttpStatusCode.ServiceUnavailable;

        return View("ServiceUnavailable");
    }

    public ViewResult ServerError() {
        Response.StatusCode = (int)HttpStatusCode.InternalServerError;

        return View("ServerError");
    }

    public new ViewResult NotFound() {
        Response.StatusCode = (int)HttpStatusCode.NotFound;

        return View("NotFound");
    }
}

Or as an alternative, you can create ignore routes pointing at the physical files and place the error pages somewhere other than the Views folder (like your root directory):

routes.IgnoreRoute("Error.aspx/{*pathInfo}");
routes.IgnoreRoute("NotFound.aspx/{*pathInfo}");

Either of these solutions is viable however depending on your configuration using an IgnoreRoute() may be more ideal as it will forgo the need to pipe the request to MVC only to serve a static error page.

Nathan Taylor
No, unfortunately that makes no difference. The weird thing is, this was working yesterday, with just Error.aspx, when I was trying it out, before I set it to RemoteOnly. Today I set it back to On and added the 404 file. Now, it won't work even with Error.aspx if I comment out the 404 tag.
Cynthia
OK, adding the IgnoreRoute and putting the NotFound file in the root directory DOES work -- but ONLY if I change it to a straight html file. That's not what I really want though. I want the infrastructure to remain so the user can try again, or log out, or whatever. I will give the ErrorController a try, though it seems that if you were required to have an ErrorController, Microsoft would have supplied one along with the Error.aspx page. Plus, it DID work yesterday without it. Very strange.
Cynthia
To what Microsoft-supplied Error.aspx are you referring? It's not that an ErrorController is required, but rather that that is how MVC handles *all* requests. Unless you've specifically told the routing engine to ignore a file, MVC requires a Controller, Action, and View to serve any file.
Nathan Taylor
I'm talking about the Error.aspx that shows up in the solution if you select the MVC template when you create a project, along with Index.aspx and Account.aspx and the other things. Usually there is a rudimentary controller provided.
Cynthia
At any rate, it makes sense that it won't work if you put it in IgnoreRoute and try to get it to regard it as a physical file, seeing as it's not a real aspx file like a webforms file (at least that's my current understanding). I have actually gotten the controller to work and forward the errors to my custom pages (yay!). Now I'm looking for a way to provide a little more info to the user about the error (especially in the case of a 500).
Cynthia
+2  A: 

Option One:

is to build an Errors Controller with a "NotFound" view along with a "Unknown" view. This will take anything that is a 500 Server error or a 404 NotFound error and redirect you to the appropriate URL. I don't totally love this solution as the visitor is always redirected to an error page.

http://example.com/Error/Unknown

<customErrors mode="On" defaultRedirect="Error/Unknown">
    <error statusCode="404" redirect="Error/NotFound" />
    <error statusCode="500" redirect="Error/Unknown" />
</customErrors>
  • wwwroot/
    • Controllers
      • Error.cs
    • Views/
      • Error/
      • NotFound.aspx
      • Unknown.aspx

Option Two:

I Definitely don't prefer this method (as it is basically reverting back to web forms, The second option is to simply have a static Error.aspx page and ignore the route in MVC), but it works none the less. What you're doing here is ignoring a "Static" directory, placing your physical Error pages in there, and skirting around MVC.

routes.IgnoreRoute("/Static/{*pathInfo}");  //This will ignore everything in the "Static" directory
  • wwwroot/
    • Controllers/
    • Static/
      • Error.aspx
    • Views/

Option Three:

The third option (THIS IS MY FAVORITE) is to return an Error View from whatever view is catching the error. This would require you to code up Try/Catch blocks along the way for "known" errors and then you can use HandleError for the unknown errors that might creep up. What this will do is preserve the originally requested URL but return the ERROR view.

EXAMPLE:
http://example.com/Products/1234 will show a details page for ProductID 1234
http://example.com/Products/9999 will show a NotFound error page because ProductID 9999 doesn't exist
http://example.com/Errors/NotFound "should" never be shown because you handle those errors individually in your controllers.

Web.Config

<customErrors mode="On">
</customErrors>

Controller

// Use as many or as few of these as you need
[HandleError(ExceptionType = typeof(SqlException), View = "SqlError")]
[HandleError(ExceptionType = typeof(NullReferenceException), View = "NullError")]
[HandleError(ExceptionType = typeof(SecurityException), View = "SecurityError")]
[HandleError(ExceptionType = typeof(ResourceNotFoundException), View = "NotFound")]
Public Class ProductController: Controller{
    public ViewResult Item(string itemID)
    {
        try
        {
            Item item = ItemRepository.GetItem(itemID);
            return View(item);
        }
        catch()
        {
            return View("NotFound");
        }
    }
}

Folder Structure

  • wwwroot/
    • Controllers/
    • Shared/
      • NotFound.aspx
      • NullError.aspx
      • SecurityError.aspx
      • SqlError.aspx
    • Views/

Option Four:

The last option would be that you build your own custom filter for things like ResourceNotFoundException and attach it to your controller class. This will do the exact same thing as above but with the added benefit of sending the error code down the line to the client as well.

Richard Dingwall talks about it on his blog.

rockinthesixstring
Is this what everyone has to do to get this to work? Funny I have not seen it mentioned in the "literature" anywhere.
Cynthia
Well you have to generate your error page somehow. If you're using the View engine to generate the errors, then you'll need the appropriate View and Controller. If you want to simply send the static "Error.aspx" (which in my opinion is not ideal in MVC cause the URL looks different), then you need to ignore the route and place a static page somewhere.
rockinthesixstring
I made an edit.
rockinthesixstring
The last edit I made allows you to send the NotFound view from your Controller.
rockinthesixstring
Ok, I edited one more time.
rockinthesixstring
This is a bit overkill for my application, which does not really have any ways a user could ask for unavailable resources, etc. I don't need quite so much fine detail on my errors -- at least not at this point. :) But I will definitely keep this scheme in mind; thanks for your input.
Cynthia
Remember, there are four different concepts in that post. I have separated them for easier reading
rockinthesixstring
Gotcha. I guess I really implemented the first option -- but I flagged Nathan's as the answer since I saw his first.
Cynthia
A: 

Your mixing web forms and MVC concepts here. Turn custom errors off in the web.config. Then in the HandleError attribute optionally specify the type and view, by default error.aspx is searched for in views\CurrentController then views\shared. Whilst you can get 404 handling working with the HandleError filter you probably want to create a filter just for 404 handling, the reasons and how to are explained in detail here:

http://richarddingwall.name/2008/08/17/strategies-for-resource-based-404-errors-in-aspnet-mvc/

DalSoft