如何正确处理ASP中的404。净MVC吗?

时间:2022-11-29 16:13:51

I am using RC2

我用RC2

Using URL Routing:

使用URL路由:

routes.MapRoute(
    "Error",
     "{*url}",
     new { controller = "Errors", action = "NotFound" }  // 404s
);

The above seems to take care of requests like this (assuming default route tables setup by initial MVC project): "/blah/blah/blah/blah"

上面的方法似乎可以处理这样的请求(假设初始MVC项目设置了默认路由表):“/blah/blah/blah/blah”

Overriding HandleUnknownAction() in the controller itself:

在控制器本身中重写HandleUnknownAction():

// 404s - handle here (bad action requested
protected override void HandleUnknownAction(string actionName) {
    ViewData["actionName"] = actionName;
    View("NotFound").ExecuteResult(this.ControllerContext);
}  

However the previous strategies do not handle a request to a Bad/Unknown controller. For example, I do not have a "/IDoNotExist", if I request this I get the generic 404 page from the web server and not my 404 if I use routing + override.

但是,前面的策略不处理对坏/未知控制器的请求。例如,我没有“/IDoNotExist”,如果我请求它,我将从web服务器获得通用的404页面,而不是使用routing + override的404页面。

So finally, my question is: Is there any way to catch this type of request using a route or something else in the MVC framework itself?

最后,我的问题是:在MVC框架本身中是否存在使用路由或其他方式捕获此类请求的方法?

OR should I just default to using Web.Config customErrors as my 404 handler and forget all this? I assume if I go with customErrors I'll have to store the generic 404 page outside of /Views due to the Web.Config restrictions on direct access.

或者我应该默认使用Web。设置自定义错误作为我的404处理程序,然后忘记所有这些?我假设如果使用customErrors,我必须将通用的404页面存储在/Views之外,这是由于Web的缘故。对直接访问的配置限制。

19 个解决方案

#1


246  

The code is taken from http://blogs.microsoft.co.il/blogs/shay/archive/2009/03/06/real-world-error-hadnling-in-asp-net-mvc-rc2.aspx and works in ASP.net MVC 1.0 as well

代码来自http://blogs.microsoft.co.il/blogs/shay/archive/2009/03/06/real-world-error- hadlin -asp-net-mvc-rc2。aspx和在ASP.net MVC 1.0中工作

Here's how I handle http exceptions:

下面是我处理http异常的方法:

protected void Application_Error(object sender, EventArgs e)
{
   Exception exception = Server.GetLastError();
   // Log the exception.

   ILogger logger = Container.Resolve<ILogger>();
   logger.Error(exception);

   Response.Clear();

   HttpException httpException = exception as HttpException;

   RouteData routeData = new RouteData();
   routeData.Values.Add("controller", "Error");

   if (httpException == null)
   {
       routeData.Values.Add("action", "Index");
   }
   else //It's an Http Exception, Let's handle it.
   {
       switch (httpException.GetHttpCode())
       {
          case 404:
              // Page not found.
              routeData.Values.Add("action", "HttpError404");
              break;
          case 500:
              // Server error.
              routeData.Values.Add("action", "HttpError500");
              break;

           // Here you can handle Views to other error codes.
           // I choose a General error template  
           default:
              routeData.Values.Add("action", "General");
              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; 

  // Call target Controller and pass the routeData.
  IController errorController = new ErrorController();
  errorController.Execute(new RequestContext(    
       new HttpContextWrapper(Context), routeData));
}

#2


251  

Requirements for 404

The following are my requirements for a 404 solution and below i show how i implement it:

下面是我对404解决方案的要求,下面我将展示如何实现它:

  • I want to handle matched routes with bad actions
  • 我想用坏的动作来处理匹配的路线。
  • I want to handle matched routes with bad controllers
  • 我想用坏的控制器处理匹配的路由
  • I want to handle un-matched routes (arbitrary urls that my app can't understand) - i don't want these bubbling up to the Global.asax or IIS because then i can't redirect back into my MVC app properly
  • 我想处理不匹配的路由(我的应用不能理解的任意url)——我不希望这些溢出到全局。asax或IIS,因为这样我就不能正确地重定向到我的MVC应用
  • I want a way to handle in the same manner as above, custom 404s - like when an ID is submitted for an object that does not exist (maybe deleted)
  • 我想要一种与上面相同的处理方式,自定义404s——类似于为不存在的对象提交ID(可能被删除)
  • I want all my 404s to return an MVC view (not a static page) to which i can pump more data later if necessary (good 404 designs) and they must return the HTTP 404 status code
  • 我希望所有的404s都返回一个MVC视图(不是一个静态页面),如果必要的话,我可以在以后再增加更多的数据(好的404设计),并且它们必须返回HTTP 404状态码。

Solution

I think you should save Application_Error in the Global.asax for higher things, like unhandled exceptions and logging (like Shay Jacoby's answer shows) but not 404 handling. This is why my suggestion keeps the 404 stuff out of the Global.asax file.

我认为您应该在全局中保存Application_Error。asax用于更高级的事情,如未处理的异常和日志记录(如Shay Jacoby的答案所示),而不是404处理。这就是为什么我的建议将404代码排除在全局之外。asax文件。

Step 1: Have a common place for 404-error logic

This is a good idea for maintainability. Use an ErrorController so that future improvements to your well designed 404 page can adapt easily. Also, make sure your response has the 404 code!

这对于可维护性来说是一个好主意。使用一个ErrorController,以便将来对设计良好的404页面的改进能够很容易地适应。另外,确保您的响应具有404代码!

public class ErrorController : MyController
{
    #region Http404

    public ActionResult Http404(string url)
    {
        Response.StatusCode = (int)HttpStatusCode.NotFound;
        var model = new NotFoundViewModel();
        // If the url is relative ('NotFound' route) then replace with Requested path
        model.RequestedUrl = Request.Url.OriginalString.Contains(url) & Request.Url.OriginalString != url ?
            Request.Url.OriginalString : url;
        // Dont get the user stuck in a 'retry loop' by
        // allowing the Referrer to be the same as the Request
        model.ReferrerUrl = Request.UrlReferrer != null &&
            Request.UrlReferrer.OriginalString != model.RequestedUrl ?
            Request.UrlReferrer.OriginalString : null;

        // TODO: insert ILogger here

        return View("NotFound", model);
    }
    public class NotFoundViewModel
    {
        public string RequestedUrl { get; set; }
        public string ReferrerUrl { get; set; }
    }

    #endregion
}

Step 2: Use a base Controller class so you can easily invoke your custom 404 action and wire up HandleUnknownAction

404s in ASP.NET MVC need to be caught at a number of places. The first is HandleUnknownAction.

404年代的ASP。NET MVC需要在许多地方被捕获。第一个是HandleUnknownAction。

The InvokeHttp404 method creates a common place for re-routing to the ErrorController and our new Http404 action. Think DRY!

InvokeHttp404方法为重新路由到ErrorController和我们新的Http404操作创建了一个公共位置。想干!

public abstract class MyController : Controller
{
    #region Http404 handling

    protected override void HandleUnknownAction(string actionName)
    {
        // If controller is ErrorController dont 'nest' exceptions
        if (this.GetType() != typeof(ErrorController))
            this.InvokeHttp404(HttpContext);
    }

    public ActionResult InvokeHttp404(HttpContextBase httpContext)
    {
        IController errorController = ObjectFactory.GetInstance<ErrorController>();
        var errorRoute = new RouteData();
        errorRoute.Values.Add("controller", "Error");
        errorRoute.Values.Add("action", "Http404");
        errorRoute.Values.Add("url", httpContext.Request.Url.OriginalString);
        errorController.Execute(new RequestContext(
             httpContext, errorRoute));

        return new EmptyResult();
    }

    #endregion
}

Step 3: Use Dependency Injection in your Controller Factory and wire up 404 HttpExceptions

Like so (it doesn't have to be StructureMap):

就像这样(它不必是StructureMap):

MVC1.0 example:

MVC1.0例子:

public class StructureMapControllerFactory : DefaultControllerFactory
{
    protected override IController GetControllerInstance(Type controllerType)
    {
        try
        {
            if (controllerType == null)
                return base.GetControllerInstance(controllerType);
        }
        catch (HttpException ex)
        {
            if (ex.GetHttpCode() == (int)HttpStatusCode.NotFound)
            {
                IController errorController = ObjectFactory.GetInstance<ErrorController>();
                ((ErrorController)errorController).InvokeHttp404(RequestContext.HttpContext);

                return errorController;
            }
            else
                throw ex;
        }

        return ObjectFactory.GetInstance(controllerType) as Controller;
    }
}

MVC2.0 example:

MVC2.0例子:

    protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
    {
        try
        {
            if (controllerType == null)
                return base.GetControllerInstance(requestContext, controllerType);
        }
        catch (HttpException ex)
        {
            if (ex.GetHttpCode() == 404)
            {
                IController errorController = ObjectFactory.GetInstance<ErrorController>();
                ((ErrorController)errorController).InvokeHttp404(requestContext.HttpContext);

                return errorController;
            }
            else
                throw ex;
        }

        return ObjectFactory.GetInstance(controllerType) as Controller;
    }

I think its better to catch errors closer to where they originate. This is why i prefer the above to the Application_Error handler.

我认为最好在更接近错误起源的地方捕捉错误。这就是为什么我更喜欢上面的Application_Error处理程序。

This is the second place to catch 404s.

这是第二次乘404s。

Step 4: Add a NotFound route to Global.asax for urls that fail to be parsed into your app

This route should point to our Http404 action. Notice the url param will be a relative url because the routing engine is stripping the domain part here? That is why we have all that conditional url logic in Step 1.

该路由应该指向我们的Http404操作。注意,url param将是一个相对url,因为路由引擎正在剥离这里的域部分?这就是为什么我们在步骤1中有所有的条件url逻辑。

        routes.MapRoute("NotFound", "{*url}", 
            new { controller = "Error", action = "Http404" });

This is the third and final place to catch 404s in an MVC app that you don't invoke yourself. If you don't catch unmatched routes here then MVC will pass the problem up to ASP.NET (Global.asax) and you don't really want that in this situation.

这是第三个也是最后一个你不需要自己调用的MVC应用程序捕获404s的地方。如果您没有在这里捕获到不匹配的路由,那么MVC将把问题传递给ASP。NET (Global.asax),在这种情况下,你并不想要它。

Step 5: Finally, invoke 404s when your app can't find something

Like when a bad ID is submitted to my Loans controller (derives from MyController):

比如当一个坏的ID提交给我的贷款控制器(来自MyController):

    //
    // GET: /Detail/ID

    public ActionResult Detail(int ID)
    {
        Loan loan = this._svc.GetLoans().WithID(ID);
        if (loan == null)
            return this.InvokeHttp404(HttpContext);
        else
            return View(loan);
    }

It would be nice if all this could be hooked up in fewer places with less code but i think this solution is more maintainable, more testable and fairly pragmatic.

如果所有这些都可以用更少的代码连接到更少的地方,那就太好了,但是我认为这个解决方案更易于维护,更易于测试,而且相当实用。

Thanks for the feedback so far. I'd love to get more.

谢谢你的反馈。我想要更多。

NOTE: This has been edited significantly from my original answer but the purpose/requirements are the same - this is why i have not added a new answer

注意:这与我最初的答案有很大的不同,但是目的/要求是一样的——这就是为什么我没有添加新的答案

#3


221  

ASP.NET MVC doesn't support custom 404 pages very well. Custom controller factory, catch-all route, base controller class with HandleUnknownAction - argh!

ASP。NET MVC不太支持自定义404页面。自定义控制器工厂,包罗万象的路线,基本控制器类与HandleUnknownAction - argh!

IIS custom error pages are better alternative so far:

IIS自定义错误页面是目前更好的选择:

web.config

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

ErrorController

public class ErrorController : Controller
{
    public ActionResult PageNotFound()
    {
        Response.StatusCode = 404;
        return View();
    }
}

Sample Project

#4


151  

Quick Answer / TL;DR

如何正确处理ASP中的404。净MVC吗?

For the lazy people out there:

对于那些懒惰的人:

Install-Package MagicalUnicornMvcErrorToolkit -Version 1.0

Then remove this line from global.asax

然后从global.asax删除这条线

GlobalFilters.Filters.Add(new HandleErrorAttribute());

And this is only for IIS7+ and IIS Express.

这只是IIS7+和IIS Express的。

If you're using Cassini .. well .. um .. er.. awkward ... 如何正确处理ASP中的404。净MVC吗?

如果你在用卡西尼。嗯. .嗯. .呃. .尴尬……


Long, explained answer

I know this has been answered. But the answer is REALLY SIMPLE (cheers to David Fowler and Damian Edwards for really answering this).

我知道这已经得到了回答。但是答案很简单(为大卫·福勒和达米安·爱德华兹的回答欢呼)。

There is no need to do anything custom.

没有必要做任何定制。

For ASP.NET MVC3, all the bits and pieces are there.

ASP。NET MVC3,所有的位和位都在那里。

Step 1 -> Update your web.config in TWO spots.

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

and

<system.webServer>
    <httpErrors errorMode="Custom">
      <remove statusCode="404" subStatusCode="-1" />
      <error statusCode="404" path="/NotFound" responseMode="ExecuteURL" />
      <remove statusCode="500" subStatusCode="-1" />
      <error statusCode="500" path="/ServerError" responseMode="ExecuteURL" />
    </httpErrors>    

...
<system.webServer>
...
</system.web>

Now take careful note of the ROUTES I've decided to use. You can use anything, but my routes are

现在仔细注意我决定使用的路线。你可以用任何东西,但我的路线是

  • /NotFound <- for a 404 not found, error page.
  • /NotFound <-用于404 not found, error page。
  • /ServerError <- for any other error, include errors that happen in my code. this is a 500 Internal Server Error
  • /ServerError <-对于任何其他错误,包括在我的代码中发生的错误。这是一个500个内部服务器错误

See how the first section in <system.web> only has one custom entry? The statusCode="404" entry? I've only listed one status code because all other errors, including the 500 Server Error (ie. those pesky error that happens when your code has a bug and crashes the user's request) .. all the other errors are handled by the setting defaultRedirect="/ServerError" .. which says, if you are not a 404 page not found, then please goto the route /ServerError.

参见 <系统中的第一部分。web> 只有一个自定义条目吗?statusCode = " 404 "条目吗?我只列出了一个状态码,因为所有其他错误,包括500服务器错误。当您的代码有错误并崩溃用户的请求时发生的那些令人讨厌的错误。所有其他错误都由设置defaultRedirect="/ServerError"处理。也就是说,如果没有找到404页面,请转到route /ServerError。

Ok. that's out of the way.. now to my routes listed in global.asax

好的。那是不可能的。现在回到我在global.asax列出的路线

Step 2 - Creating the routes in Global.asax

Here's my full route section..

这是我的全程路线。

public static void RegisterRoutes(RouteCollection routes)
{
    routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
    routes.IgnoreRoute("{*favicon}", new {favicon = @"(.*/)?favicon.ico(/.*)?"});

    routes.MapRoute(
        "Error - 404",
        "NotFound",
        new { controller = "Error", action = "NotFound" }
        );

    routes.MapRoute(
        "Error - 500",
        "ServerError",
        new { controller = "Error", action = "ServerError"}
        );

    routes.MapRoute(
        "Default", // Route name
        "{controller}/{action}/{id}", // URL with parameters
        new {controller = "Home", action = "Index", id = UrlParameter.Optional}
        );
}

That lists two ignore routes -> axd's and favicons (ooo! bonus ignore route, for you!) Then (and the order is IMPERATIVE HERE), I have my two explicit error handling routes .. followed by any other routes. In this case, the default one. Of course, I have more, but that's special to my web site. Just make sure the error routes are at the top of the list. Order is imperative.

它列出了两个忽略的路由——> axd和favicon(哇!奖励忽略路线,为你!)然后(这里的顺序是命令式的),我有两个明确的错误处理路径。然后是其他路线。这里是默认值。当然,我还有更多,但这对我的网站来说是特别的。只要确保错误路径在列表的顶部。秩序是必要的。

Finally, while we are inside our global.asax file, we do NOT globally register the HandleError attribute. No, no, no sir. Nadda. Nope. Nien. Negative. Noooooooooo...

最后,当我们进入我们的全球。asax文件,我们不全局注册HandleError属性。不,不,先生,纳达。不。年兽。负的。Noooooooooo……

Remove this line from global.asax

从global.asax删除这条线

GlobalFilters.Filters.Add(new HandleErrorAttribute());

Step 3 - Create the controller with the action methods

Now .. we add a controller with two action methods ...

现在. .我们添加了一个具有两个操作方法的控制器……

public class ErrorController : Controller
{
    public ActionResult NotFound()
    {
        Response.StatusCode = (int)HttpStatusCode.NotFound;
        return View();
    }

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

        // Todo: Pass the exception into the view model, which you can make.
        //       That's an exercise, dear reader, for -you-.
        //       In case u want to pass it to the view, if you're admin, etc.
        // if (User.IsAdmin) // <-- I just made that up :) U get the idea...
        // {
        //     var exception = Server.GetLastError();
        //     // etc..
        // }

        return View();
    }

    // Shhh .. secret test method .. ooOOooOooOOOooohhhhhhhh
    public ActionResult ThrowError()
    {
        throw new NotImplementedException("Pew ^ Pew");
    }
}

Ok, lets check this out. First of all, there is NO [HandleError] attribute here. Why? Because the built in ASP.NET framework is already handling errors AND we have specified all the shit we need to do to handle an error :) It's in this method!

好的,让我们看看这个。首先,这里没有[HandleError]属性。为什么?因为内建的ASP。NET框架已经处理了错误,我们已经指定了处理错误所需的所有垃圾:)它在这个方法中!

Next, I have the two action methods. Nothing tough there. If u wish to show any exception info, then u can use Server.GetLastError() to get that info.

接下来,我有两个动作方法。没什么困难。如果您希望显示任何异常信息,则可以使用Server.GetLastError()获取该信息。

Bonus WTF: Yes, I made a third action method, to test error handling.

额外的WTF:是的,我做了第三个动作方法来测试错误处理。

Step 4 - Create the Views

And finally, create two views. Put em in the normal view spot, for this controller.

最后,创建两个视图。将em放在这个控制器的法线视图中。

如何正确处理ASP中的404。净MVC吗?

Bonus comments

  • You don't need an Application_Error(object sender, EventArgs e)
  • 您不需要Application_Error(对象发送方,EventArgs e)
  • The above steps all work 100% perfectly with Elmah. Elmah fraking wroxs!
  • 以上步骤在Elmah中100%完美。Elmah fraking wrox电台!

And that, my friends, should be it.

我的朋友们,应该是这样。

Now, congrats for reading this much and have a Unicorn as a prize!

现在,恭喜你读了这么多书,并有一只独角兽作为奖品!

如何正确处理ASP中的404。净MVC吗?

#5


80  

I've investigated A LOT on how to properly manage 404s in MVC (specifically MVC3), and this, IMHO is the best solution I've come up with:

我已经研究了很多如何在MVC(尤其是MVC3)中正确管理404s,这是我提出的最好的解决方案:

In global.asax:

在global.asax:

public class MvcApplication : HttpApplication
{
    protected void Application_EndRequest()
    {
        if (Context.Response.StatusCode == 404)
        {
            Response.Clear();

            var rd = new RouteData();
            rd.DataTokens["area"] = "AreaName"; // In case controller is in another area
            rd.Values["controller"] = "Errors";
            rd.Values["action"] = "NotFound";

            IController c = new ErrorsController();
            c.Execute(new RequestContext(new HttpContextWrapper(Context), rd));
        }
    }
}

ErrorsController:

ErrorsController:

public sealed class ErrorsController : Controller
{
    public ActionResult NotFound()
    {
        ActionResult result;

        object model = Request.Url.PathAndQuery;

        if (!Request.IsAjaxRequest())
            result = View(model);
        else
            result = PartialView("_NotFound", model);

        return result;
    }
}

(Optional)

(可选)

Explanation:

解释:

AFAIK, there are 6 different cases that an ASP.NET MVC3 apps can generate 404s.

AFAIK,有6种不同的ASP情况。NET MVC3应用程序可以生成404s。

(Automatically generated by ASP.NET Framework:)

(自动生成的ASP。净框架:)

(1) An URL does not find a match in the route table.

(1) URL在路由表中找不到匹配项。

(Automatically generated by ASP.NET MVC Framework:)

(自动生成的ASP。NET MVC框架:)

(2) An URL finds a match in the route table, but specifies a non-existent controller.

(2) URL在路由表中查找匹配项,但指定不存在的控制器。

(3) An URL finds a match in the route table, but specifies a non-existant action.

(3) URL在路由表中查找匹配项,但指定不存在的操作。

(Manually generated:)

(手动生成:)

(4) An action returns an HttpNotFoundResult by using the method HttpNotFound().

(4)使用HttpNotFound()方法返回HttpNotFoundResult。

(5) An action throws an HttpException with the status code 404.

(5)操作抛出一个具有状态代码404的HttpException。

(6) An actions manually modifies the Response.StatusCode property to 404.

(6)操作手动修改响应。StatusCode财产为404。

Normally, you want to accomplish 3 objectives:

通常,你想完成三个目标:

(1) Show a custom 404 error page to the user.

(1)向用户显示自定义404错误页面。

(2) Maintain the 404 status code on the client response (specially important for SEO).

(2)维护客户端响应上的404状态码(对SEO尤为重要)。

(3) Send the response directly, without involving a 302 redirection.

(3)直接发送响应,不涉及302重定向。

There are various ways to try to accomplish this:

有很多方法可以做到这一点:

(1)

(1)

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

Problems with this solution:

问题解决方案:

  1. Does not comply with objective (1) in cases (1), (4), (6).
  2. (1)、(4)、(6)不符合目标(1)。
  3. Does not comply with objective (2) automatically. It must be programmed manually.
  4. 不自动符合目标(2)。必须手动编程。
  5. Does not comply with objective (3).
  6. 不符合目标(3)。

(2)

(2)

<system.webServer>
    <httpErrors errorMode="Custom">
        <remove statusCode="404"/>
        <error statusCode="404" path="App/Errors/NotFound" responseMode="ExecuteURL"/>
    </httpErrors>
</system.webServer>

Problems with this solution:

问题解决方案:

  1. Only works on IIS 7+.
  2. 只能在IIS 7+上工作。
  3. Does not comply with objective (1) in cases (2), (3), (5).
  4. (2)、(3)、(5)不符合目标(1)。
  5. Does not comply with objective (2) automatically. It must be programmed manually.
  6. 不自动符合目标(2)。必须手动编程。

(3)

(3)

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

Problems with this solution:

问题解决方案:

  1. Only works on IIS 7+.
  2. 只能在IIS 7+上工作。
  3. Does not comply with objective (2) automatically. It must be programmed manually.
  4. 不自动符合目标(2)。必须手动编程。
  5. It obscures application level http exceptions. E.g. can't use customErrors section, System.Web.Mvc.HandleErrorAttribute, etc. It can't only show generic error pages.
  6. 它模糊了应用程序级http异常。不能使用customErrors部分System.Web.Mvc。HandleErrorAttribute等,它不能只显示一般的错误页面。

(4)

(4)

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

and

<system.webServer>
    <httpErrors errorMode="Custom">
        <remove statusCode="404"/>
        <error statusCode="404" path="App/Errors/NotFound" responseMode="ExecuteURL"/>
    </httpErrors>
</system.webServer>

Problems with this solution:

问题解决方案:

  1. Only works on IIS 7+.
  2. 只能在IIS 7+上工作。
  3. Does not comply with objective (2) automatically. It must be programmed manually.
  4. 不自动符合目标(2)。必须手动编程。
  5. Does not comply with objective (3) in cases (2), (3), (5).
  6. (2)、(3)、(5)不符合目标(3)。

People that have troubled with this before even tried to create their own libraries (see http://aboutcode.net/2011/02/26/handling-not-found-with-asp-net-mvc3.html). But the previous solution seems to cover all the cases without the complexity of using an external library.

以前遇到过这种问题的人甚至尝试过创建自己的库(参见http://aboutcode.net/2011/02/26/handling-not-found- asp-net-mvc3.html)。但是之前的解决方案似乎涵盖了所有的情况,而没有使用外部库的复杂性。

#6


13  

I really like cottsaks solution and think its very clearly explained. my only addition was to alter step 2 as follows

我真的很喜欢棉胶溶液,而且认为它的解释很清楚。我唯一添加的是将步骤2更改为如下所示

public abstract class MyController : Controller
{

    #region Http404 handling

    protected override void HandleUnknownAction(string actionName)
    {
        //if controller is ErrorController dont 'nest' exceptions
        if(this.GetType() != typeof(ErrorController))
        this.InvokeHttp404(HttpContext);
    }

    public ActionResult InvokeHttp404(HttpContextBase httpContext)
    {
        IController errorController = ObjectFactory.GetInstance<ErrorController>();
        var errorRoute = new RouteData();
        errorRoute.Values.Add("controller", "Error");
        errorRoute.Values.Add("action", "Http404");
        errorRoute.Values.Add("url", httpContext.Request.Url.OriginalString);
        errorController.Execute(new RequestContext(
             httpContext, errorRoute));

        return new EmptyResult();
    }

    #endregion
}

Basically this stops urls containing invalid actions AND controllers from triggering the exception routine twice. eg for urls such as asdfsdf/dfgdfgd

基本上,这将阻止包含无效操作和控制器的url两次触发异常例程。如asdfsdf/dfgdfgd等url

#7


6  

The only way I could get @cottsak's method to work for invalid controllers was to modify the existing route request in the CustomControllerFactory, like so:

使@cottsak的方法对无效控制器起作用的唯一方法是修改CustomControllerFactory中的现有路由请求,如下所示:

public class CustomControllerFactory : DefaultControllerFactory
{
    protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
    {
        try
        {
            if (controllerType == null)
                return base.GetControllerInstance(requestContext, controllerType); 
            else
                return ObjectFactory.GetInstance(controllerType) as Controller;
        }
        catch (HttpException ex)
        {
            if (ex.GetHttpCode() == (int)HttpStatusCode.NotFound)
            {
                requestContext.RouteData.Values["controller"] = "Error";
                requestContext.RouteData.Values["action"] = "Http404";
                requestContext.RouteData.Values.Add("url", requestContext.HttpContext.Request.Url.OriginalString);

                return ObjectFactory.GetInstance<ErrorController>();
            }
            else
                throw ex;
        }
    }
}

I should mention I'm using MVC 2.0.

我应该提到我正在使用MVC 2.0。

#8


4  

Here is another method using MVC tools which you can handle requests to bad controller names, bad route names, and any other criteria you see fit inside of an Action method. Personally, I prefer to avoid as many web.config settings as possible, because they do the 302 / 200 redirect and do not support ResponseRewrite (Server.Transfer) using Razor views. I'd prefer to return a 404 with a custom error page for SEO reasons.

这是另一个使用MVC工具的方法,您可以处理对坏控制器名、坏路由名和任何其他您认为适合操作方法的条件的请求。就我个人而言,我更喜欢避免太多的网络。尽可能地设置配置,因为它们执行302 / 200重定向,不支持使用Razor视图进行ResponseRewrite (Server.Transfer)。由于SEO的原因,我宁愿返回一个带有自定义错误页面的404页面。

Some of this is new take on cottsak's technique above.

其中一些是对上述科特萨克技术的新理解。

This solution also uses minimal web.config settings favoring the MVC 3 Error Filters instead.

这个解决方案还使用了最少的web。配置设置偏向于MVC 3错误过滤器。

Usage

Just throw a HttpException from an action or custom ActionFilterAttribute.

只需从操作或自定义ActionFilterAttribute中抛出HttpException。

Throw New HttpException(HttpStatusCode.NotFound, "[Custom Exception Message Here]")

Step 1

Add the following setting to your web.config. This is required to use MVC's HandleErrorAttribute.

在您的web.config中添加以下设置。这需要使用MVC的HandleErrorAttribute。

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

Step 2

Add a custom HandleHttpErrorAttribute similar to the MVC framework's HandleErrorAttribute, except for HTTP errors:

添加一个自定义HandleHttpErrorAttribute,类似于MVC框架的HandleErrorAttribute,除了HTTP错误:

<AttributeUsage(AttributeTargets.All, AllowMultiple:=True)>
Public Class HandleHttpErrorAttribute
    Inherits FilterAttribute
    Implements IExceptionFilter

    Private Const m_DefaultViewFormat As String = "ErrorHttp{0}"

    Private m_HttpCode As HttpStatusCode
    Private m_Master As String
    Private m_View As String

    Public Property HttpCode As HttpStatusCode
        Get
            If m_HttpCode = 0 Then
                Return HttpStatusCode.NotFound
            End If
            Return m_HttpCode
        End Get
        Set(value As HttpStatusCode)
            m_HttpCode = value
        End Set
    End Property

    Public Property Master As String
        Get
            Return If(m_Master, String.Empty)
        End Get
        Set(value As String)
            m_Master = value
        End Set
    End Property

    Public Property View As String
        Get
            If String.IsNullOrEmpty(m_View) Then
                Return String.Format(m_DefaultViewFormat, Me.HttpCode)
            End If
            Return m_View
        End Get
        Set(value As String)
            m_View = value
        End Set
    End Property

    Public Sub OnException(filterContext As System.Web.Mvc.ExceptionContext) Implements System.Web.Mvc.IExceptionFilter.OnException
        If filterContext Is Nothing Then Throw New ArgumentException("filterContext")

        If filterContext.IsChildAction Then
            Return
        End If

        If filterContext.ExceptionHandled OrElse Not filterContext.HttpContext.IsCustomErrorEnabled Then
            Return
        End If

        Dim ex As HttpException = TryCast(filterContext.Exception, HttpException)
        If ex Is Nothing OrElse ex.GetHttpCode = HttpStatusCode.InternalServerError Then
            Return
        End If

        If ex.GetHttpCode <> Me.HttpCode Then
            Return
        End If

        Dim controllerName As String = filterContext.RouteData.Values("controller")
        Dim actionName As String = filterContext.RouteData.Values("action")
        Dim model As New HandleErrorInfo(filterContext.Exception, controllerName, actionName)

        filterContext.Result = New ViewResult With {
            .ViewName = Me.View,
            .MasterName = Me.Master,
            .ViewData = New ViewDataDictionary(Of HandleErrorInfo)(model),
            .TempData = filterContext.Controller.TempData
        }
        filterContext.ExceptionHandled = True
        filterContext.HttpContext.Response.Clear()
        filterContext.HttpContext.Response.StatusCode = Me.HttpCode
        filterContext.HttpContext.Response.TrySkipIisCustomErrors = True
    End Sub
End Class

Step 3

Add Filters to the GlobalFilterCollection (GlobalFilters.Filters) in Global.asax. This example will route all InternalServerError (500) errors to the Error shared view (Views/Shared/Error.vbhtml). NotFound (404) errors will be sent to ErrorHttp404.vbhtml in the shared views as well. I've added a 401 error here to show you how this can be extended for additional HTTP error codes. Note that these must be shared views, and they all use the System.Web.Mvc.HandleErrorInfo object as a the model.

在Global.asax中添加过滤器到GlobalFilterCollection (globalfilters.filter .Filters)。这个示例将把所有内部服务器错误(500)错误路由到错误共享视图(视图/共享/Error.vbhtml)。NotFound(404)错误将被发送到ErrorHttp404。共享视图中的vbhtml。我在这里添加了一个401错误,向您展示如何扩展这个错误代码以获得更多的HTTP错误代码。注意,这些视图必须是共享的,它们都使用System.Web.Mvc。HandleErrorInfo对象作为模型。

filters.Add(New HandleHttpErrorAttribute With {.View = "ErrorHttp401", .HttpCode = HttpStatusCode.Unauthorized})
filters.Add(New HandleHttpErrorAttribute With {.View = "ErrorHttp404", .HttpCode = HttpStatusCode.NotFound})
filters.Add(New HandleErrorAttribute With {.View = "Error"})

Step 4

Create a base controller class and inherit from it in your controllers. This step allows us to handle unknown action names and raise the HTTP 404 error to our HandleHttpErrorAttribute.

创建一个基控制器类,并在控制器中继承它。这个步骤允许我们处理未知的操作名称,并将HTTP 404错误提高到HandleHttpErrorAttribute。

Public Class BaseController
    Inherits System.Web.Mvc.Controller

    Protected Overrides Sub HandleUnknownAction(actionName As String)
        Me.ActionInvoker.InvokeAction(Me.ControllerContext, "Unknown")
    End Sub

    Public Function Unknown() As ActionResult
        Throw New HttpException(HttpStatusCode.NotFound, "The specified controller or action does not exist.")
        Return New EmptyResult
    End Function
End Class

Step 5

Create a ControllerFactory override, and override it in your Global.asax file in Application_Start. This step allows us to raise the HTTP 404 exception when an invalid controller name has been specified.

创建一个ControllerFactory覆盖,并在全局环境中覆盖它。在Application_Start asax文件。这一步允许我们在指定无效的控制器名时引发HTTP 404异常。

Public Class MyControllerFactory
    Inherits DefaultControllerFactory

    Protected Overrides Function GetControllerInstance(requestContext As System.Web.Routing.RequestContext, controllerType As System.Type) As System.Web.Mvc.IController
        Try
            Return MyBase.GetControllerInstance(requestContext, controllerType)
        Catch ex As HttpException
            Return DependencyResolver.Current.GetService(Of BaseController)()
        End Try
    End Function
End Class

'In Global.asax.vb Application_Start:

controllerBuilder.Current.SetControllerFactory(New MyControllerFactory)

Step 6

Include a special route in your RoutTable.Routes for the BaseController Unknown action. This will help us raise a 404 in the case where a user accesses an unknown controller, or unknown action.

在你的路线上包括一条特别的路线。基本控制器未知动作的路由。这将帮助我们在用户访问未知控制器或未知操作的情况下生成404。

'BaseController
routes.MapRoute( _
    "Unknown", "BaseController/{action}/{id}", _
    New With {.controller = "BaseController", .action = "Unknown", .id = UrlParameter.Optional} _
)

Summary

This example demonstrated how one can use the MVC framework to return 404 Http Error Codes to the browser without a redirect using filter attributes and shared error views. It also demonstrates showing the same custom error page when invalid controller names and action names are specified.

这个示例演示了如何使用MVC框架向浏览器返回404 Http错误代码,而不使用过滤器属性和共享错误视图进行重定向。它还演示了在指定无效的控制器名和操作名时显示相同的自定义错误页。

I'll add a screenshot of an invalid controller name, action name, and a custom 404 raised from the Home/TriggerNotFound action if I get enough votes to post one =). Fiddler returns a 404 message when I access the following URLs using this solution:

我将添加一个无效的控制器名、动作名的屏幕截图,以及一个自定义的404从Home/TriggerNotFound动作中生成,如果我得到足够的选票发布一个=)。当我使用此解决方案访问以下url时,Fiddler将返回404消息:

/InvalidController
/Home/InvalidRoute
/InvalidController/InvalidRoute
/Home/TriggerNotFound

cottsak's post above and these articles were good references.

上述科特萨克的文章和这些文章都是很好的参考。

#9


4  

My shortened solution that works with unhandled areas, controllers and actions:

我缩短的解决方案,适用于未处理的区域、控制器和操作:

  1. Create a view 404.cshtml.

    cshtml 404.创建一个视图。

  2. Create a base class for your controllers:

    为控制器创建一个基类:

    public class Controller : System.Web.Mvc.Controller
    {
        protected override void HandleUnknownAction(string actionName)
        {
            Http404().ExecuteResult(ControllerContext);
        }
    
        protected virtual ViewResult Http404()
        {
            Response.StatusCode = (int)HttpStatusCode.NotFound;
            return View("404");
        }
    }
    
  3. Create a custom controller factory returning your base controller as a fallback:

    创建一个自定义控制器工厂,返回您的基本控制器作为回退:

    public class ControllerFactory : DefaultControllerFactory
    {
        protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
        {
            if (controllerType != null)
                return base.GetControllerInstance(requestContext, controllerType);
    
            return new Controller();
        }
    }
    
  4. Add to Application_Start() the following line:

    向Application_Start()添加以下行:

    ControllerBuilder.Current.SetControllerFactory(typeof(ControllerFactory));
    

#10


3  

In MVC4 WebAPI 404 can be handle in the following way,

在MVC4中,WebAPI 404可以按以下方式处理,

COURSES APICONTROLLER

课程APICONTROLLER

    // GET /api/courses/5
    public HttpResponseMessage<Courses> Get(int id)
    {
        HttpResponseMessage<Courses> resp = null;

        var aCourse = _courses.Where(c => c.Id == id).FirstOrDefault();

        resp = aCourse == null ? new HttpResponseMessage<Courses>(System.Net.HttpStatusCode.NotFound) : new HttpResponseMessage<Courses>(aCourse);

        return resp;
    }

HOME CONTROLLER

家控制器

public ActionResult Course(int id)
{
    return View(id);
}

VIEW

视图

<div id="course"></div>
<script type="text/javascript">
    var id = @Model;
    var course = $('#course');
    $.ajax({    
        url: '/api/courses/' + id,
        success: function (data) {
            course.text(data.Name);
        },
        statusCode: {
            404: function() 
            {
                course.text('Course not available!');    
            }
        }
    });
</script>

GLOBAL

全球

public static void RegisterRoutes(RouteCollection routes)
{
    routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

    routes.MapHttpRoute(
        name: "DefaultApi",
        routeTemplate: "api/{controller}/{id}",
        defaults: new { id = RouteParameter.Optional }
    );

    routes.MapRoute(
        name: "Default",
        url: "{controller}/{action}/{id}",
        defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
    );
}

RESULTS

结果

如何正确处理ASP中的404。净MVC吗?

#11


2  

Try NotFoundMVC on nuget. It works , no setup.

试穿NotFoundMVC nuget。它是有效的,没有设置。

#12


2  

My solution, in case someone finds it useful.

我的解决方案,以防有人发现它有用。

In Web.config:

在web . config:

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

In Controllers/ErrorController.cs:

在控制器/ ErrorController.cs:

public class ErrorController : Controller
{
    public ActionResult PageNotFound()
    {
        if(Request.IsAjaxRequest()) {
            Response.StatusCode = (int)HttpStatusCode.NotFound;
            return Content("Not Found", "text/plain");
        }

        return View();
    }
}

Add a PageNotFound.cshtml in the Shared folder, and that's it.

添加一个PageNotFound。共享文件夹中的cshtml,就是这样。

#13


2  

It seems to me that the standard CustomErrors configuration should just work however, due to the reliance on Server.Transfer it seems that the internal implementation of ResponseRewrite isn't compatible with MVC.

在我看来,由于对服务器的依赖,标准的CustomErrors配置应该能够正常工作。ResponseRewrite的内部实现似乎与MVC不兼容。

This feels like a glaring functionality hole to me, so I decided to re-implement this feature using a HTTP module. The solution below allows you to handle any HTTP status code (including 404) by redirecting to any valid MVC route just as you would do normally.

这对我来说是一个明显的功能漏洞,所以我决定使用HTTP模块重新实现这个特性。下面的解决方案允许您像往常一样重定向到任何有效的MVC路由,从而处理任何HTTP状态代码(包括404)。

<customErrors mode="RemoteOnly" redirectMode="ResponseRewrite">
    <error statusCode="404" redirect="404.aspx" />
    <error statusCode="500" redirect="~/MVCErrorPage" />
</customErrors>

This has been tested on the following platforms;

在以下平台上进行了测试;

  • MVC4 in Integrated Pipeline Mode (IIS Express 8)
  • MVC4集成管道模式(IIS Express 8)
  • MVC4 in Classic Mode (VS Development Server, Cassini)
  • MVC4经典模式(VS开发服务器,卡西尼)
  • MVC4 in Classic Mode (IIS6)
  • MVC4经典模式(IIS6)

Benefits

好处

  • Generic solution which can be dropped into any MVC project
  • 通用的解决方案,可以放到任何MVC项目中
  • Enables support for traditional custom errors configuration
  • 支持传统的自定义错误配置
  • Works in both Integrated Pipeline and Classic modes
  • 可以在集成管道和经典模式下工作

The Solution

解决方案

namespace Foo.Bar.Modules {

    /// <summary>
    /// Enables support for CustomErrors ResponseRewrite mode in MVC.
    /// </summary>
    public class ErrorHandler : IHttpModule {

        private HttpContext HttpContext { get { return HttpContext.Current; } }
        private CustomErrorsSection CustomErrors { get; set; }

        public void Init(HttpApplication application) {
            System.Configuration.Configuration configuration = WebConfigurationManager.OpenWebConfiguration("~");
            CustomErrors = (CustomErrorsSection)configuration.GetSection("system.web/customErrors");

            application.EndRequest += Application_EndRequest;
        }

        protected void Application_EndRequest(object sender, EventArgs e) {

            // only handle rewrite mode, ignore redirect configuration (if it ain't broke don't re-implement it)
            if (CustomErrors.RedirectMode == CustomErrorsRedirectMode.ResponseRewrite && HttpContext.IsCustomErrorEnabled) {

                int statusCode = HttpContext.Response.StatusCode;

                // if this request has thrown an exception then find the real status code
                Exception exception = HttpContext.Error;
                if (exception != null) {
                    // set default error status code for application exceptions
                    statusCode = (int)HttpStatusCode.InternalServerError;
                }

                HttpException httpException = exception as HttpException;
                if (httpException != null) {
                    statusCode = httpException.GetHttpCode();
                }

                if ((HttpStatusCode)statusCode != HttpStatusCode.OK) {

                    Dictionary<int, string> errorPaths = new Dictionary<int, string>();

                    foreach (CustomError error in CustomErrors.Errors) {
                        errorPaths.Add(error.StatusCode, error.Redirect);
                    }

                    // find a custom error path for this status code
                    if (errorPaths.Keys.Contains(statusCode)) {
                        string url = errorPaths[statusCode];

                        // avoid circular redirects
                        if (!HttpContext.Request.Url.AbsolutePath.Equals(VirtualPathUtility.ToAbsolute(url))) {

                            HttpContext.Response.Clear();
                            HttpContext.Response.TrySkipIisCustomErrors = true;

                            HttpContext.Server.ClearError();

                            // do the redirect here
                            if (HttpRuntime.UsingIntegratedPipeline) {
                                HttpContext.Server.TransferRequest(url, true);
                            }
                            else {
                                HttpContext.RewritePath(url, false);

                                IHttpHandler httpHandler = new MvcHttpHandler();
                                httpHandler.ProcessRequest(HttpContext);
                            }

                            // return the original status code to the client
                            // (this won't work in integrated pipleline mode)
                            HttpContext.Response.StatusCode = statusCode;

                        }
                    }

                }

            }

        }

        public void Dispose() {

        }


    }

}

Usage

使用

Include this as the final HTTP module in your web.config

将其作为web.config中的最后一个HTTP模块

  <system.web>
    <httpModules>
      <add name="ErrorHandler" type="Foo.Bar.Modules.ErrorHandler" />
    </httpModules>
  </system.web>

  <!-- IIS7+ -->
  <system.webServer>
    <modules>
      <add name="ErrorHandler" type="Foo.Bar.Modules.ErrorHandler" />
    </modules>
  </system.webServer>

For those of you paying attention you will notice that in Integrated Pipeline mode this will always respond with HTTP 200 due to the way Server.TransferRequest works. To return the proper error code I use the following error controller.

注意了的人会注意到,在集成的管道模式中,由于服务器的方式,它总是使用HTTP 200进行响应。TransferRequest作品。要返回正确的错误代码,我使用以下错误控制器。

public class ErrorController : Controller {

    public ErrorController() { }

    public ActionResult Index(int id) {
        // pass real error code to client
        HttpContext.Response.StatusCode = id;
        HttpContext.Response.TrySkipIisCustomErrors = true;

        return View("Errors/" + id.ToString());
    }

}

#14


2  

Dealing with errors in ASP.NET MVC is just a pain in the butt. I tried a whole lot of suggestions on this page and on other questions and sites and nothing works good. One suggestion was to handle errors on web.config inside system.webserver but that just returns blank pages.

处理ASP中的错误。NET MVC只是一个令人头痛的问题。我在这个页面和其他问题和网站上尝试了很多建议,但没有什么是好的。一个建议是处理web上的错误。系统内配置。但它只返回空页面。

My goal when coming up with this solution was to;

我提出这个解决方案的目的是;

  • NOT REDIRECT
  • 不重定向
  • Return PROPER STATUS CODES not 200/Ok like the default error handling
  • 返回正确的状态码而不是200/Ok,如默认的错误处理

Here is my solution.

这是我的解决方案。

1.Add the following to system.web section

1。将以下内容添加到系统中。网络部分

   <system.web>
     <customErrors mode="On" redirectMode="ResponseRewrite">
      <error statusCode="404"  redirect="~/Error/404.aspx" />
      <error statusCode="500" redirect="~/Error/500.aspx" />
     </customErrors>
    <system.web>

The above handles any urls not handled by routes.config and unhandled exceptions especially those encountered on the views. Notice I used aspx not html. This is so I can add a response code on the code behind.

以上处理的url不是由路由处理的。配置和未处理的异常,特别是在视图中遇到的异常。注意,我使用的是aspx而不是html。这样我就可以在后面的代码中添加响应代码。

2. Create a folder called Error (or whatever you prefer) at the root of your project and add the two webforms. Below is my 404 page;

2。在项目的根目录中创建一个名为Error(或您喜欢的任何内容)的文件夹,并添加这两个webforms。下面是我的404页面;

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="404.aspx.cs" Inherits="Myapp.Error._404" %>

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title >Page Not found</title>
    <link href="<%=ResolveUrl("~/Content/myapp.css")%>" rel="stylesheet" />
</head>
<body>
    <div class="top-nav">
      <a runat="server" class="company-logo" href="~/"></a>
    </div>
    <div>
        <h1>404 - Page Not found</h1>
        <p>The page you are looking for cannot be found.</p>
        <hr />
        <footer></footer>
    </div>
</body>
</html>

And on the code behind I set the response code

在后面的代码中,我设置了响应代码

protected void Page_Load(object sender, EventArgs e)
{
    Response.StatusCode = 404;
}

Do the same for the 500 page

500页也是这样吗

3.To handle errors within the controllers. There's many ways to do it. This is what worked for me. All my controllers inherit from a base controller. In the base controller, I have the following methods

3所示。处理控制器中的错误。有很多方法可以做到。这对我起了作用。所有的控制器都继承自基控制器。在基本控制器中,我有以下方法

protected ActionResult ShowNotFound()
{
    return ShowNotFound("Page not found....");
}

protected ActionResult ShowNotFound(string message)
{
    return ShowCustomError(HttpStatusCode.NotFound, message);
}

protected ActionResult ShowServerError()
{
    return ShowServerError("Application error....");
}

protected ActionResult ShowServerError(string message)
{
    return ShowCustomError(HttpStatusCode.InternalServerError, message);
}

protected ActionResult ShowNotAuthorized()
{
    return ShowNotAuthorized("You are not allowed ....");

}

protected ActionResult ShowNotAuthorized(string message)
{
    return ShowCustomError(HttpStatusCode.Forbidden, message);
}

protected ActionResult ShowCustomError(HttpStatusCode statusCode, string message)
{
    Response.StatusCode = (int)statusCode;
    string title = "";
    switch (statusCode)
    {
        case HttpStatusCode.NotFound:
            title = "404 - Not found";
            break;
        case HttpStatusCode.Forbidden:
            title = "403 - Access Denied";
            break;
        default:
            title = "500 - Application Error";
            break;
    }
    ViewBag.Title = title;
    ViewBag.Message = message;
    return View("CustomError");
}

4.Add the CustomError.cshtml to your Shared views folder. Below is mine;

4所示。添加CustomError。cshtml到您的共享视图文件夹。下面是我的;

<h1>@ViewBag.Title</h1>
<br />
<p>@ViewBag.Message</p>

Now in your application controller you can do something like this;

在你的应用程序控制器中你可以这样做;

public class WidgetsController : ControllerBase
{
  [HttpGet]
  public ActionResult Edit(int id)
  {
    Try
    {
       var widget = db.getWidgetById(id);
       if(widget == null)
          return ShowNotFound();
          //or return ShowNotFound("Invalid widget!");
       return View(widget);
    }
    catch(Exception ex)
    {
       //log error
       logger.Error(ex)
       return ShowServerError();
    }
  }
}

Now for the caveat. It won't handle static file errors. So if you have a route such as example.com/widgets and the user changes it to example.com/widgets.html, they will get the IIS default error page so you have to handle IIS level errors some other way.

现在的警告。它不会处理静态文件错误。如果你有一个路径,比如example.com/widgets用户把它改成example.com/widgets.html,他们会得到IIS默认错误页面,所以你必须用其他方法处理IIS级别的错误。

#15


1  

Posting an answer since my comment was too long...

因为我的评论太长了,所以我发布了一个答案……

It's both a comment and questions to the unicorn post/answer:

这是对独角兽的评论和问题/回答:

https://*.com/a/7499406/687549

https://*.com/a/7499406/687549

I prefer this answer over the others for it's simplicity and the fact that apparently some folks at Microsoft were consulted. I got three questions however and if they can be answered then I will call this answer the holy grail of all 404/500 error answers on the interwebs for an ASP.NET MVC (x) app.

我更喜欢这个答案而不是其他答案,因为它很简单,而且微软的一些员工显然也被咨询过。我有三个问题,如果它们能被回答,我将把这个答案称为网络上所有404/500个错误答案的圣杯。净MVC(x)应用程序。

@Pure.Krome

@Pure.Krome

  1. Can you update your answer with the SEO stuff from the comments pointed out by GWB (there was never any mentioning of this in your answer) - <customErrors mode="On" redirectMode="ResponseRewrite"> and <httpErrors errorMode="Custom" existingResponse="Replace">?

    你能从GWB的评论(你的回答中从来没有提到过这个)中使用SEO来更新你的答案吗? - ?

  2. Can you ask your ASP.NET team friends if it is okay to do it like that - would be nice to have some confirmation - maybe it's a big no-no to change redirectMode and existingResponse in this way to be able to play nicely with SEO?!

    你能问问你的ASP吗?网络团队的朋友们,如果可以这样做——如果能得到一些确认就太好了——也许以这种方式改变redirectMode和现有的响应就能很好地使用SEO?!

  3. Can you add some clarification surrounding all that stuff (customErrors redirectMode="ResponseRewrite", customErrors redirectMode="ResponseRedirect", httpErrors errorMode="Custom" existingResponse="Replace", REMOVE customErrors COMPLETELY as someone suggested) after talking to your friends at Microsoft?

    你能不能在和你在微软的朋友聊天后,对这些东西(customErrors redirectMode="ResponseRewrite", customErrors redirectMode="ResponseRedirect", httpErrors errorMode="Custom" existingResponse="Replace",完全按照别人的建议删除定制错误)做一些澄清?

As I was saying; it would be supernice if we could make your answer more complete as this seem to be a fairly popular question with 54 000+ views.

我说的;如果我们能让你的回答更完整,那就太好了,因为这似乎是一个很受欢迎的问题,浏览量超过5.4万。

Update: Unicorn answer does a 302 Found and a 200 OK and cannot be changed to only return 404 using a route. It has to be a physical file which is not very MVC:ish. So moving on to another solution. Too bad because this seemed to be the ultimate MVC:ish answer this far.

更新:独角兽回答做302找到和200 OK,不能更改为仅返回404使用的路径。它必须是一个不太符合MVC的物理文件。那么我们再来看另一个解。太糟糕了,因为这似乎是最终的MVC:ish。

#16


1  

Adding my solution, which is almost identical to Herman Kan's, with a small wrinkle to allow it to work for my project.

加上我的解决方案,几乎和赫尔曼·菅直人的方案一样,有一个小的皱纹,让它可以为我的项目工作。

Create a custom error controller:

创建一个自定义错误控制器:

public class Error404Controller : BaseController
{
    [HttpGet]
    public ActionResult PageNotFound()
    {
        Response.StatusCode = 404;
        return View("404");
    }
}

Then create a custom controller factory:

然后创建一个自定义控制器工厂:

public class CustomControllerFactory : DefaultControllerFactory
{
    protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
    {
        return controllerType == null ? new Error404Controller() : base.GetControllerInstance(requestContext, controllerType);
    }
}

Finally, add an override to the custom error controller:

最后,向自定义错误控制器添加一个覆盖:

protected override void HandleUnknownAction(string actionName)
{
    var errorRoute = new RouteData();
    errorRoute.Values.Add("controller", "Error404");
    errorRoute.Values.Add("action", "PageNotFound");
    new Error404Controller().Execute(new RequestContext(HttpContext, errorRoute));
}

And that's it. No need for Web.config changes.

就是这样。不需要网络。配置更改。

#17


1  

1) Make abstract Controller class.

1)制作抽象控制器类。

public abstract class MyController:Controller
{
    public ActionResult NotFound()
    {
        Response.StatusCode = 404;
        return View("NotFound");
    }

    protected override void HandleUnknownAction(string actionName)
    {
        this.ActionInvoker.InvokeAction(this.ControllerContext, "NotFound");
    }
    protected override void OnAuthorization(AuthorizationContext filterContext) { }
}  

2) Make inheritence from this abstract class in your all controllers

2)在所有控制器中继承这个抽象类

public class HomeController : MyController
{}  

3) And add a view named "NotFound" in you View-Shared folder.

并在视图共享文件夹中添加名为“NotFound”的视图。

#18


0  

I went through most of the solutions posted on this thread. While this question might be old, it is still very applicable to new projects even now, so I spent quite a lot of time reading up on the answers presented here as well as else where.

我浏览了大多数在这个线程上发布的解决方案。虽然这个问题可能是旧的,但即使是现在,它仍然适用于新项目,所以我花了大量的时间阅读这里和其他地方给出的答案。

As @Marco pointed out the different cases under which a 404 can happen, I checked the solution I compiled together against that list. In addition to his list of requirements, I also added one more.

正如@Marco指出了404可能发生的不同情况,我根据这个列表检查了一起编译的解决方案。除了他的要求清单之外,我还增加了一个。

  • The solution should be able to handle MVC as well as AJAX/WebAPI calls in the most appropriate manner. (i.e. if 404 happens in MVC, it should show the Not Found page and if 404 happens in WebAPI, it should not hijack the XML/JSON response so that the consuming Javascript can parse it easily).
  • 解决方案应该能够以最合适的方式处理MVC以及AJAX/WebAPI调用。(例如,如果404发生在MVC中,它应该显示未找到的页面,如果404发生在WebAPI中,它不应该劫持XML/JSON响应,以便使用的Javascript能够轻松解析它)。

This solution is 2 fold:

这个解决方案是2倍:

First part of it comes from @Guillaume at https://*.com/a/27354140/2310818. Their solution takes care of any 404 that were caused due to invalid route, invalid controller and invalid action.

第一部分来自@Guillaume, https://*.com/a/27354140/2310818。他们的解决方案负责处理由于无效路由、无效控制器和无效操作而导致的任何404错误。

The idea is to create a WebForm and then make it call the NotFound action of your MVC Errors Controller. It does all of this without any redirect so you will not see a single 302 in Fiddler. The original URL is also preserved, which makes this solution fantastic!

我们的想法是创建一个WebForm,然后让它调用MVC错误控制器的NotFound操作。它在没有任何重定向的情况下完成了所有这些,所以你在Fiddler中看不到一个302。原始的URL也被保留了,这使得这个解决方案非常棒!


Second part of it comes from @Germán at https://*.com/a/5536676/2310818. Their solution takes care of any 404 returned by your actions in the form of HttpNotFoundResult() or throw new HttpException()!

第二部分来自@German, https://*.com/a/5536676/2310818。他们的解决方案负责处理您的操作以HttpNotFoundResult()或抛出新的HttpException()形式返回的任何404 !

The idea is to have a filter look at the response as well as the exception thrown by your MVC controllers and to call the appropriate action in your Errors Controller. Again this solution works without any redirect and the original url is preserved!

其思想是让一个过滤器查看响应以及MVC控制器抛出的异常,并在错误控制器中调用适当的操作。同样,这个解决方案在没有任何重定向的情况下工作,原始的url被保留了!


As you can see, both of these solutions together offer a very robust error handling mechanism and they achieve all the requirements listed by @Marco as well as my requirements. If you would like to see a working sample or a demo of this solution, please leave in the comments and I would be happy to put it together.

如您所见,这两个解决方案一起提供了一个非常健壮的错误处理机制,它们实现了@Marco列出的所有需求以及我的需求。如果您想看到这个解决方案的工作示例或演示,请在评论中留下,我很乐意把它放在一起。

#19


0  

I have gone through all articles but nothing works for me: My requirement user type anything in your url custom 404 page should show.I thought it is very straight forward.But you should understand handling of 404 properly:

我已经浏览了所有的文章,但是没有一篇对我有用:我的需求用户在您的url自定义404页面中输入任何内容。我觉得很直接。但是您应该正确地理解404的处理:

 <system.web>
    <customErrors mode="On" redirectMode="ResponseRewrite">
      <error statusCode="404" redirect="~/PageNotFound.aspx"/>
    </customErrors>
  </system.web>
<system.webServer>
    <httpErrors errorMode="Custom">
      <remove statusCode="404"/>
      <error statusCode="404" path="/PageNotFound.html" responseMode="ExecuteURL"/>
    </httpErrors>
</system.webServer>

I found this article very helpfull.should be read at once.Custome error page-Ben Foster

我发现这篇文章很有帮助。应该立即阅读。Custome错误page-Ben福斯特

#1


246  

The code is taken from http://blogs.microsoft.co.il/blogs/shay/archive/2009/03/06/real-world-error-hadnling-in-asp-net-mvc-rc2.aspx and works in ASP.net MVC 1.0 as well

代码来自http://blogs.microsoft.co.il/blogs/shay/archive/2009/03/06/real-world-error- hadlin -asp-net-mvc-rc2。aspx和在ASP.net MVC 1.0中工作

Here's how I handle http exceptions:

下面是我处理http异常的方法:

protected void Application_Error(object sender, EventArgs e)
{
   Exception exception = Server.GetLastError();
   // Log the exception.

   ILogger logger = Container.Resolve<ILogger>();
   logger.Error(exception);

   Response.Clear();

   HttpException httpException = exception as HttpException;

   RouteData routeData = new RouteData();
   routeData.Values.Add("controller", "Error");

   if (httpException == null)
   {
       routeData.Values.Add("action", "Index");
   }
   else //It's an Http Exception, Let's handle it.
   {
       switch (httpException.GetHttpCode())
       {
          case 404:
              // Page not found.
              routeData.Values.Add("action", "HttpError404");
              break;
          case 500:
              // Server error.
              routeData.Values.Add("action", "HttpError500");
              break;

           // Here you can handle Views to other error codes.
           // I choose a General error template  
           default:
              routeData.Values.Add("action", "General");
              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; 

  // Call target Controller and pass the routeData.
  IController errorController = new ErrorController();
  errorController.Execute(new RequestContext(    
       new HttpContextWrapper(Context), routeData));
}

#2


251  

Requirements for 404

The following are my requirements for a 404 solution and below i show how i implement it:

下面是我对404解决方案的要求,下面我将展示如何实现它:

  • I want to handle matched routes with bad actions
  • 我想用坏的动作来处理匹配的路线。
  • I want to handle matched routes with bad controllers
  • 我想用坏的控制器处理匹配的路由
  • I want to handle un-matched routes (arbitrary urls that my app can't understand) - i don't want these bubbling up to the Global.asax or IIS because then i can't redirect back into my MVC app properly
  • 我想处理不匹配的路由(我的应用不能理解的任意url)——我不希望这些溢出到全局。asax或IIS,因为这样我就不能正确地重定向到我的MVC应用
  • I want a way to handle in the same manner as above, custom 404s - like when an ID is submitted for an object that does not exist (maybe deleted)
  • 我想要一种与上面相同的处理方式,自定义404s——类似于为不存在的对象提交ID(可能被删除)
  • I want all my 404s to return an MVC view (not a static page) to which i can pump more data later if necessary (good 404 designs) and they must return the HTTP 404 status code
  • 我希望所有的404s都返回一个MVC视图(不是一个静态页面),如果必要的话,我可以在以后再增加更多的数据(好的404设计),并且它们必须返回HTTP 404状态码。

Solution

I think you should save Application_Error in the Global.asax for higher things, like unhandled exceptions and logging (like Shay Jacoby's answer shows) but not 404 handling. This is why my suggestion keeps the 404 stuff out of the Global.asax file.

我认为您应该在全局中保存Application_Error。asax用于更高级的事情,如未处理的异常和日志记录(如Shay Jacoby的答案所示),而不是404处理。这就是为什么我的建议将404代码排除在全局之外。asax文件。

Step 1: Have a common place for 404-error logic

This is a good idea for maintainability. Use an ErrorController so that future improvements to your well designed 404 page can adapt easily. Also, make sure your response has the 404 code!

这对于可维护性来说是一个好主意。使用一个ErrorController,以便将来对设计良好的404页面的改进能够很容易地适应。另外,确保您的响应具有404代码!

public class ErrorController : MyController
{
    #region Http404

    public ActionResult Http404(string url)
    {
        Response.StatusCode = (int)HttpStatusCode.NotFound;
        var model = new NotFoundViewModel();
        // If the url is relative ('NotFound' route) then replace with Requested path
        model.RequestedUrl = Request.Url.OriginalString.Contains(url) & Request.Url.OriginalString != url ?
            Request.Url.OriginalString : url;
        // Dont get the user stuck in a 'retry loop' by
        // allowing the Referrer to be the same as the Request
        model.ReferrerUrl = Request.UrlReferrer != null &&
            Request.UrlReferrer.OriginalString != model.RequestedUrl ?
            Request.UrlReferrer.OriginalString : null;

        // TODO: insert ILogger here

        return View("NotFound", model);
    }
    public class NotFoundViewModel
    {
        public string RequestedUrl { get; set; }
        public string ReferrerUrl { get; set; }
    }

    #endregion
}

Step 2: Use a base Controller class so you can easily invoke your custom 404 action and wire up HandleUnknownAction

404s in ASP.NET MVC need to be caught at a number of places. The first is HandleUnknownAction.

404年代的ASP。NET MVC需要在许多地方被捕获。第一个是HandleUnknownAction。

The InvokeHttp404 method creates a common place for re-routing to the ErrorController and our new Http404 action. Think DRY!

InvokeHttp404方法为重新路由到ErrorController和我们新的Http404操作创建了一个公共位置。想干!

public abstract class MyController : Controller
{
    #region Http404 handling

    protected override void HandleUnknownAction(string actionName)
    {
        // If controller is ErrorController dont 'nest' exceptions
        if (this.GetType() != typeof(ErrorController))
            this.InvokeHttp404(HttpContext);
    }

    public ActionResult InvokeHttp404(HttpContextBase httpContext)
    {
        IController errorController = ObjectFactory.GetInstance<ErrorController>();
        var errorRoute = new RouteData();
        errorRoute.Values.Add("controller", "Error");
        errorRoute.Values.Add("action", "Http404");
        errorRoute.Values.Add("url", httpContext.Request.Url.OriginalString);
        errorController.Execute(new RequestContext(
             httpContext, errorRoute));

        return new EmptyResult();
    }

    #endregion
}

Step 3: Use Dependency Injection in your Controller Factory and wire up 404 HttpExceptions

Like so (it doesn't have to be StructureMap):

就像这样(它不必是StructureMap):

MVC1.0 example:

MVC1.0例子:

public class StructureMapControllerFactory : DefaultControllerFactory
{
    protected override IController GetControllerInstance(Type controllerType)
    {
        try
        {
            if (controllerType == null)
                return base.GetControllerInstance(controllerType);
        }
        catch (HttpException ex)
        {
            if (ex.GetHttpCode() == (int)HttpStatusCode.NotFound)
            {
                IController errorController = ObjectFactory.GetInstance<ErrorController>();
                ((ErrorController)errorController).InvokeHttp404(RequestContext.HttpContext);

                return errorController;
            }
            else
                throw ex;
        }

        return ObjectFactory.GetInstance(controllerType) as Controller;
    }
}

MVC2.0 example:

MVC2.0例子:

    protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
    {
        try
        {
            if (controllerType == null)
                return base.GetControllerInstance(requestContext, controllerType);
        }
        catch (HttpException ex)
        {
            if (ex.GetHttpCode() == 404)
            {
                IController errorController = ObjectFactory.GetInstance<ErrorController>();
                ((ErrorController)errorController).InvokeHttp404(requestContext.HttpContext);

                return errorController;
            }
            else
                throw ex;
        }

        return ObjectFactory.GetInstance(controllerType) as Controller;
    }

I think its better to catch errors closer to where they originate. This is why i prefer the above to the Application_Error handler.

我认为最好在更接近错误起源的地方捕捉错误。这就是为什么我更喜欢上面的Application_Error处理程序。

This is the second place to catch 404s.

这是第二次乘404s。

Step 4: Add a NotFound route to Global.asax for urls that fail to be parsed into your app

This route should point to our Http404 action. Notice the url param will be a relative url because the routing engine is stripping the domain part here? That is why we have all that conditional url logic in Step 1.

该路由应该指向我们的Http404操作。注意,url param将是一个相对url,因为路由引擎正在剥离这里的域部分?这就是为什么我们在步骤1中有所有的条件url逻辑。

        routes.MapRoute("NotFound", "{*url}", 
            new { controller = "Error", action = "Http404" });

This is the third and final place to catch 404s in an MVC app that you don't invoke yourself. If you don't catch unmatched routes here then MVC will pass the problem up to ASP.NET (Global.asax) and you don't really want that in this situation.

这是第三个也是最后一个你不需要自己调用的MVC应用程序捕获404s的地方。如果您没有在这里捕获到不匹配的路由,那么MVC将把问题传递给ASP。NET (Global.asax),在这种情况下,你并不想要它。

Step 5: Finally, invoke 404s when your app can't find something

Like when a bad ID is submitted to my Loans controller (derives from MyController):

比如当一个坏的ID提交给我的贷款控制器(来自MyController):

    //
    // GET: /Detail/ID

    public ActionResult Detail(int ID)
    {
        Loan loan = this._svc.GetLoans().WithID(ID);
        if (loan == null)
            return this.InvokeHttp404(HttpContext);
        else
            return View(loan);
    }

It would be nice if all this could be hooked up in fewer places with less code but i think this solution is more maintainable, more testable and fairly pragmatic.

如果所有这些都可以用更少的代码连接到更少的地方,那就太好了,但是我认为这个解决方案更易于维护,更易于测试,而且相当实用。

Thanks for the feedback so far. I'd love to get more.

谢谢你的反馈。我想要更多。

NOTE: This has been edited significantly from my original answer but the purpose/requirements are the same - this is why i have not added a new answer

注意:这与我最初的答案有很大的不同,但是目的/要求是一样的——这就是为什么我没有添加新的答案

#3


221  

ASP.NET MVC doesn't support custom 404 pages very well. Custom controller factory, catch-all route, base controller class with HandleUnknownAction - argh!

ASP。NET MVC不太支持自定义404页面。自定义控制器工厂,包罗万象的路线,基本控制器类与HandleUnknownAction - argh!

IIS custom error pages are better alternative so far:

IIS自定义错误页面是目前更好的选择:

web.config

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

ErrorController

public class ErrorController : Controller
{
    public ActionResult PageNotFound()
    {
        Response.StatusCode = 404;
        return View();
    }
}

Sample Project

#4


151  

Quick Answer / TL;DR

如何正确处理ASP中的404。净MVC吗?

For the lazy people out there:

对于那些懒惰的人:

Install-Package MagicalUnicornMvcErrorToolkit -Version 1.0

Then remove this line from global.asax

然后从global.asax删除这条线

GlobalFilters.Filters.Add(new HandleErrorAttribute());

And this is only for IIS7+ and IIS Express.

这只是IIS7+和IIS Express的。

If you're using Cassini .. well .. um .. er.. awkward ... 如何正确处理ASP中的404。净MVC吗?

如果你在用卡西尼。嗯. .嗯. .呃. .尴尬……


Long, explained answer

I know this has been answered. But the answer is REALLY SIMPLE (cheers to David Fowler and Damian Edwards for really answering this).

我知道这已经得到了回答。但是答案很简单(为大卫·福勒和达米安·爱德华兹的回答欢呼)。

There is no need to do anything custom.

没有必要做任何定制。

For ASP.NET MVC3, all the bits and pieces are there.

ASP。NET MVC3,所有的位和位都在那里。

Step 1 -> Update your web.config in TWO spots.

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

and

<system.webServer>
    <httpErrors errorMode="Custom">
      <remove statusCode="404" subStatusCode="-1" />
      <error statusCode="404" path="/NotFound" responseMode="ExecuteURL" />
      <remove statusCode="500" subStatusCode="-1" />
      <error statusCode="500" path="/ServerError" responseMode="ExecuteURL" />
    </httpErrors>    

...
<system.webServer>
...
</system.web>

Now take careful note of the ROUTES I've decided to use. You can use anything, but my routes are

现在仔细注意我决定使用的路线。你可以用任何东西,但我的路线是

  • /NotFound <- for a 404 not found, error page.
  • /NotFound <-用于404 not found, error page。
  • /ServerError <- for any other error, include errors that happen in my code. this is a 500 Internal Server Error
  • /ServerError <-对于任何其他错误,包括在我的代码中发生的错误。这是一个500个内部服务器错误

See how the first section in <system.web> only has one custom entry? The statusCode="404" entry? I've only listed one status code because all other errors, including the 500 Server Error (ie. those pesky error that happens when your code has a bug and crashes the user's request) .. all the other errors are handled by the setting defaultRedirect="/ServerError" .. which says, if you are not a 404 page not found, then please goto the route /ServerError.

参见 <系统中的第一部分。web> 只有一个自定义条目吗?statusCode = " 404 "条目吗?我只列出了一个状态码,因为所有其他错误,包括500服务器错误。当您的代码有错误并崩溃用户的请求时发生的那些令人讨厌的错误。所有其他错误都由设置defaultRedirect="/ServerError"处理。也就是说,如果没有找到404页面,请转到route /ServerError。

Ok. that's out of the way.. now to my routes listed in global.asax

好的。那是不可能的。现在回到我在global.asax列出的路线

Step 2 - Creating the routes in Global.asax

Here's my full route section..

这是我的全程路线。

public static void RegisterRoutes(RouteCollection routes)
{
    routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
    routes.IgnoreRoute("{*favicon}", new {favicon = @"(.*/)?favicon.ico(/.*)?"});

    routes.MapRoute(
        "Error - 404",
        "NotFound",
        new { controller = "Error", action = "NotFound" }
        );

    routes.MapRoute(
        "Error - 500",
        "ServerError",
        new { controller = "Error", action = "ServerError"}
        );

    routes.MapRoute(
        "Default", // Route name
        "{controller}/{action}/{id}", // URL with parameters
        new {controller = "Home", action = "Index", id = UrlParameter.Optional}
        );
}

That lists two ignore routes -> axd's and favicons (ooo! bonus ignore route, for you!) Then (and the order is IMPERATIVE HERE), I have my two explicit error handling routes .. followed by any other routes. In this case, the default one. Of course, I have more, but that's special to my web site. Just make sure the error routes are at the top of the list. Order is imperative.

它列出了两个忽略的路由——> axd和favicon(哇!奖励忽略路线,为你!)然后(这里的顺序是命令式的),我有两个明确的错误处理路径。然后是其他路线。这里是默认值。当然,我还有更多,但这对我的网站来说是特别的。只要确保错误路径在列表的顶部。秩序是必要的。

Finally, while we are inside our global.asax file, we do NOT globally register the HandleError attribute. No, no, no sir. Nadda. Nope. Nien. Negative. Noooooooooo...

最后,当我们进入我们的全球。asax文件,我们不全局注册HandleError属性。不,不,先生,纳达。不。年兽。负的。Noooooooooo……

Remove this line from global.asax

从global.asax删除这条线

GlobalFilters.Filters.Add(new HandleErrorAttribute());

Step 3 - Create the controller with the action methods

Now .. we add a controller with two action methods ...

现在. .我们添加了一个具有两个操作方法的控制器……

public class ErrorController : Controller
{
    public ActionResult NotFound()
    {
        Response.StatusCode = (int)HttpStatusCode.NotFound;
        return View();
    }

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

        // Todo: Pass the exception into the view model, which you can make.
        //       That's an exercise, dear reader, for -you-.
        //       In case u want to pass it to the view, if you're admin, etc.
        // if (User.IsAdmin) // <-- I just made that up :) U get the idea...
        // {
        //     var exception = Server.GetLastError();
        //     // etc..
        // }

        return View();
    }

    // Shhh .. secret test method .. ooOOooOooOOOooohhhhhhhh
    public ActionResult ThrowError()
    {
        throw new NotImplementedException("Pew ^ Pew");
    }
}

Ok, lets check this out. First of all, there is NO [HandleError] attribute here. Why? Because the built in ASP.NET framework is already handling errors AND we have specified all the shit we need to do to handle an error :) It's in this method!

好的,让我们看看这个。首先,这里没有[HandleError]属性。为什么?因为内建的ASP。NET框架已经处理了错误,我们已经指定了处理错误所需的所有垃圾:)它在这个方法中!

Next, I have the two action methods. Nothing tough there. If u wish to show any exception info, then u can use Server.GetLastError() to get that info.

接下来,我有两个动作方法。没什么困难。如果您希望显示任何异常信息,则可以使用Server.GetLastError()获取该信息。

Bonus WTF: Yes, I made a third action method, to test error handling.

额外的WTF:是的,我做了第三个动作方法来测试错误处理。

Step 4 - Create the Views

And finally, create two views. Put em in the normal view spot, for this controller.

最后,创建两个视图。将em放在这个控制器的法线视图中。

如何正确处理ASP中的404。净MVC吗?

Bonus comments

  • You don't need an Application_Error(object sender, EventArgs e)
  • 您不需要Application_Error(对象发送方,EventArgs e)
  • The above steps all work 100% perfectly with Elmah. Elmah fraking wroxs!
  • 以上步骤在Elmah中100%完美。Elmah fraking wrox电台!

And that, my friends, should be it.

我的朋友们,应该是这样。

Now, congrats for reading this much and have a Unicorn as a prize!

现在,恭喜你读了这么多书,并有一只独角兽作为奖品!

如何正确处理ASP中的404。净MVC吗?

#5


80  

I've investigated A LOT on how to properly manage 404s in MVC (specifically MVC3), and this, IMHO is the best solution I've come up with:

我已经研究了很多如何在MVC(尤其是MVC3)中正确管理404s,这是我提出的最好的解决方案:

In global.asax:

在global.asax:

public class MvcApplication : HttpApplication
{
    protected void Application_EndRequest()
    {
        if (Context.Response.StatusCode == 404)
        {
            Response.Clear();

            var rd = new RouteData();
            rd.DataTokens["area"] = "AreaName"; // In case controller is in another area
            rd.Values["controller"] = "Errors";
            rd.Values["action"] = "NotFound";

            IController c = new ErrorsController();
            c.Execute(new RequestContext(new HttpContextWrapper(Context), rd));
        }
    }
}

ErrorsController:

ErrorsController:

public sealed class ErrorsController : Controller
{
    public ActionResult NotFound()
    {
        ActionResult result;

        object model = Request.Url.PathAndQuery;

        if (!Request.IsAjaxRequest())
            result = View(model);
        else
            result = PartialView("_NotFound", model);

        return result;
    }
}

(Optional)

(可选)

Explanation:

解释:

AFAIK, there are 6 different cases that an ASP.NET MVC3 apps can generate 404s.

AFAIK,有6种不同的ASP情况。NET MVC3应用程序可以生成404s。

(Automatically generated by ASP.NET Framework:)

(自动生成的ASP。净框架:)

(1) An URL does not find a match in the route table.

(1) URL在路由表中找不到匹配项。

(Automatically generated by ASP.NET MVC Framework:)

(自动生成的ASP。NET MVC框架:)

(2) An URL finds a match in the route table, but specifies a non-existent controller.

(2) URL在路由表中查找匹配项,但指定不存在的控制器。

(3) An URL finds a match in the route table, but specifies a non-existant action.

(3) URL在路由表中查找匹配项,但指定不存在的操作。

(Manually generated:)

(手动生成:)

(4) An action returns an HttpNotFoundResult by using the method HttpNotFound().

(4)使用HttpNotFound()方法返回HttpNotFoundResult。

(5) An action throws an HttpException with the status code 404.

(5)操作抛出一个具有状态代码404的HttpException。

(6) An actions manually modifies the Response.StatusCode property to 404.

(6)操作手动修改响应。StatusCode财产为404。

Normally, you want to accomplish 3 objectives:

通常,你想完成三个目标:

(1) Show a custom 404 error page to the user.

(1)向用户显示自定义404错误页面。

(2) Maintain the 404 status code on the client response (specially important for SEO).

(2)维护客户端响应上的404状态码(对SEO尤为重要)。

(3) Send the response directly, without involving a 302 redirection.

(3)直接发送响应,不涉及302重定向。

There are various ways to try to accomplish this:

有很多方法可以做到这一点:

(1)

(1)

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

Problems with this solution:

问题解决方案:

  1. Does not comply with objective (1) in cases (1), (4), (6).
  2. (1)、(4)、(6)不符合目标(1)。
  3. Does not comply with objective (2) automatically. It must be programmed manually.
  4. 不自动符合目标(2)。必须手动编程。
  5. Does not comply with objective (3).
  6. 不符合目标(3)。

(2)

(2)

<system.webServer>
    <httpErrors errorMode="Custom">
        <remove statusCode="404"/>
        <error statusCode="404" path="App/Errors/NotFound" responseMode="ExecuteURL"/>
    </httpErrors>
</system.webServer>

Problems with this solution:

问题解决方案:

  1. Only works on IIS 7+.
  2. 只能在IIS 7+上工作。
  3. Does not comply with objective (1) in cases (2), (3), (5).
  4. (2)、(3)、(5)不符合目标(1)。
  5. Does not comply with objective (2) automatically. It must be programmed manually.
  6. 不自动符合目标(2)。必须手动编程。

(3)

(3)

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

Problems with this solution:

问题解决方案:

  1. Only works on IIS 7+.
  2. 只能在IIS 7+上工作。
  3. Does not comply with objective (2) automatically. It must be programmed manually.
  4. 不自动符合目标(2)。必须手动编程。
  5. It obscures application level http exceptions. E.g. can't use customErrors section, System.Web.Mvc.HandleErrorAttribute, etc. It can't only show generic error pages.
  6. 它模糊了应用程序级http异常。不能使用customErrors部分System.Web.Mvc。HandleErrorAttribute等,它不能只显示一般的错误页面。

(4)

(4)

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

and

<system.webServer>
    <httpErrors errorMode="Custom">
        <remove statusCode="404"/>
        <error statusCode="404" path="App/Errors/NotFound" responseMode="ExecuteURL"/>
    </httpErrors>
</system.webServer>

Problems with this solution:

问题解决方案:

  1. Only works on IIS 7+.
  2. 只能在IIS 7+上工作。
  3. Does not comply with objective (2) automatically. It must be programmed manually.
  4. 不自动符合目标(2)。必须手动编程。
  5. Does not comply with objective (3) in cases (2), (3), (5).
  6. (2)、(3)、(5)不符合目标(3)。

People that have troubled with this before even tried to create their own libraries (see http://aboutcode.net/2011/02/26/handling-not-found-with-asp-net-mvc3.html). But the previous solution seems to cover all the cases without the complexity of using an external library.

以前遇到过这种问题的人甚至尝试过创建自己的库(参见http://aboutcode.net/2011/02/26/handling-not-found- asp-net-mvc3.html)。但是之前的解决方案似乎涵盖了所有的情况,而没有使用外部库的复杂性。

#6


13  

I really like cottsaks solution and think its very clearly explained. my only addition was to alter step 2 as follows

我真的很喜欢棉胶溶液,而且认为它的解释很清楚。我唯一添加的是将步骤2更改为如下所示

public abstract class MyController : Controller
{

    #region Http404 handling

    protected override void HandleUnknownAction(string actionName)
    {
        //if controller is ErrorController dont 'nest' exceptions
        if(this.GetType() != typeof(ErrorController))
        this.InvokeHttp404(HttpContext);
    }

    public ActionResult InvokeHttp404(HttpContextBase httpContext)
    {
        IController errorController = ObjectFactory.GetInstance<ErrorController>();
        var errorRoute = new RouteData();
        errorRoute.Values.Add("controller", "Error");
        errorRoute.Values.Add("action", "Http404");
        errorRoute.Values.Add("url", httpContext.Request.Url.OriginalString);
        errorController.Execute(new RequestContext(
             httpContext, errorRoute));

        return new EmptyResult();
    }

    #endregion
}

Basically this stops urls containing invalid actions AND controllers from triggering the exception routine twice. eg for urls such as asdfsdf/dfgdfgd

基本上,这将阻止包含无效操作和控制器的url两次触发异常例程。如asdfsdf/dfgdfgd等url

#7


6  

The only way I could get @cottsak's method to work for invalid controllers was to modify the existing route request in the CustomControllerFactory, like so:

使@cottsak的方法对无效控制器起作用的唯一方法是修改CustomControllerFactory中的现有路由请求,如下所示:

public class CustomControllerFactory : DefaultControllerFactory
{
    protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
    {
        try
        {
            if (controllerType == null)
                return base.GetControllerInstance(requestContext, controllerType); 
            else
                return ObjectFactory.GetInstance(controllerType) as Controller;
        }
        catch (HttpException ex)
        {
            if (ex.GetHttpCode() == (int)HttpStatusCode.NotFound)
            {
                requestContext.RouteData.Values["controller"] = "Error";
                requestContext.RouteData.Values["action"] = "Http404";
                requestContext.RouteData.Values.Add("url", requestContext.HttpContext.Request.Url.OriginalString);

                return ObjectFactory.GetInstance<ErrorController>();
            }
            else
                throw ex;
        }
    }
}

I should mention I'm using MVC 2.0.

我应该提到我正在使用MVC 2.0。

#8


4  

Here is another method using MVC tools which you can handle requests to bad controller names, bad route names, and any other criteria you see fit inside of an Action method. Personally, I prefer to avoid as many web.config settings as possible, because they do the 302 / 200 redirect and do not support ResponseRewrite (Server.Transfer) using Razor views. I'd prefer to return a 404 with a custom error page for SEO reasons.

这是另一个使用MVC工具的方法,您可以处理对坏控制器名、坏路由名和任何其他您认为适合操作方法的条件的请求。就我个人而言,我更喜欢避免太多的网络。尽可能地设置配置,因为它们执行302 / 200重定向,不支持使用Razor视图进行ResponseRewrite (Server.Transfer)。由于SEO的原因,我宁愿返回一个带有自定义错误页面的404页面。

Some of this is new take on cottsak's technique above.

其中一些是对上述科特萨克技术的新理解。

This solution also uses minimal web.config settings favoring the MVC 3 Error Filters instead.

这个解决方案还使用了最少的web。配置设置偏向于MVC 3错误过滤器。

Usage

Just throw a HttpException from an action or custom ActionFilterAttribute.

只需从操作或自定义ActionFilterAttribute中抛出HttpException。

Throw New HttpException(HttpStatusCode.NotFound, "[Custom Exception Message Here]")

Step 1

Add the following setting to your web.config. This is required to use MVC's HandleErrorAttribute.

在您的web.config中添加以下设置。这需要使用MVC的HandleErrorAttribute。

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

Step 2

Add a custom HandleHttpErrorAttribute similar to the MVC framework's HandleErrorAttribute, except for HTTP errors:

添加一个自定义HandleHttpErrorAttribute,类似于MVC框架的HandleErrorAttribute,除了HTTP错误:

<AttributeUsage(AttributeTargets.All, AllowMultiple:=True)>
Public Class HandleHttpErrorAttribute
    Inherits FilterAttribute
    Implements IExceptionFilter

    Private Const m_DefaultViewFormat As String = "ErrorHttp{0}"

    Private m_HttpCode As HttpStatusCode
    Private m_Master As String
    Private m_View As String

    Public Property HttpCode As HttpStatusCode
        Get
            If m_HttpCode = 0 Then
                Return HttpStatusCode.NotFound
            End If
            Return m_HttpCode
        End Get
        Set(value As HttpStatusCode)
            m_HttpCode = value
        End Set
    End Property

    Public Property Master As String
        Get
            Return If(m_Master, String.Empty)
        End Get
        Set(value As String)
            m_Master = value
        End Set
    End Property

    Public Property View As String
        Get
            If String.IsNullOrEmpty(m_View) Then
                Return String.Format(m_DefaultViewFormat, Me.HttpCode)
            End If
            Return m_View
        End Get
        Set(value As String)
            m_View = value
        End Set
    End Property

    Public Sub OnException(filterContext As System.Web.Mvc.ExceptionContext) Implements System.Web.Mvc.IExceptionFilter.OnException
        If filterContext Is Nothing Then Throw New ArgumentException("filterContext")

        If filterContext.IsChildAction Then
            Return
        End If

        If filterContext.ExceptionHandled OrElse Not filterContext.HttpContext.IsCustomErrorEnabled Then
            Return
        End If

        Dim ex As HttpException = TryCast(filterContext.Exception, HttpException)
        If ex Is Nothing OrElse ex.GetHttpCode = HttpStatusCode.InternalServerError Then
            Return
        End If

        If ex.GetHttpCode <> Me.HttpCode Then
            Return
        End If

        Dim controllerName As String = filterContext.RouteData.Values("controller")
        Dim actionName As String = filterContext.RouteData.Values("action")
        Dim model As New HandleErrorInfo(filterContext.Exception, controllerName, actionName)

        filterContext.Result = New ViewResult With {
            .ViewName = Me.View,
            .MasterName = Me.Master,
            .ViewData = New ViewDataDictionary(Of HandleErrorInfo)(model),
            .TempData = filterContext.Controller.TempData
        }
        filterContext.ExceptionHandled = True
        filterContext.HttpContext.Response.Clear()
        filterContext.HttpContext.Response.StatusCode = Me.HttpCode
        filterContext.HttpContext.Response.TrySkipIisCustomErrors = True
    End Sub
End Class

Step 3

Add Filters to the GlobalFilterCollection (GlobalFilters.Filters) in Global.asax. This example will route all InternalServerError (500) errors to the Error shared view (Views/Shared/Error.vbhtml). NotFound (404) errors will be sent to ErrorHttp404.vbhtml in the shared views as well. I've added a 401 error here to show you how this can be extended for additional HTTP error codes. Note that these must be shared views, and they all use the System.Web.Mvc.HandleErrorInfo object as a the model.

在Global.asax中添加过滤器到GlobalFilterCollection (globalfilters.filter .Filters)。这个示例将把所有内部服务器错误(500)错误路由到错误共享视图(视图/共享/Error.vbhtml)。NotFound(404)错误将被发送到ErrorHttp404。共享视图中的vbhtml。我在这里添加了一个401错误,向您展示如何扩展这个错误代码以获得更多的HTTP错误代码。注意,这些视图必须是共享的,它们都使用System.Web.Mvc。HandleErrorInfo对象作为模型。

filters.Add(New HandleHttpErrorAttribute With {.View = "ErrorHttp401", .HttpCode = HttpStatusCode.Unauthorized})
filters.Add(New HandleHttpErrorAttribute With {.View = "ErrorHttp404", .HttpCode = HttpStatusCode.NotFound})
filters.Add(New HandleErrorAttribute With {.View = "Error"})

Step 4

Create a base controller class and inherit from it in your controllers. This step allows us to handle unknown action names and raise the HTTP 404 error to our HandleHttpErrorAttribute.

创建一个基控制器类,并在控制器中继承它。这个步骤允许我们处理未知的操作名称,并将HTTP 404错误提高到HandleHttpErrorAttribute。

Public Class BaseController
    Inherits System.Web.Mvc.Controller

    Protected Overrides Sub HandleUnknownAction(actionName As String)
        Me.ActionInvoker.InvokeAction(Me.ControllerContext, "Unknown")
    End Sub

    Public Function Unknown() As ActionResult
        Throw New HttpException(HttpStatusCode.NotFound, "The specified controller or action does not exist.")
        Return New EmptyResult
    End Function
End Class

Step 5

Create a ControllerFactory override, and override it in your Global.asax file in Application_Start. This step allows us to raise the HTTP 404 exception when an invalid controller name has been specified.

创建一个ControllerFactory覆盖,并在全局环境中覆盖它。在Application_Start asax文件。这一步允许我们在指定无效的控制器名时引发HTTP 404异常。

Public Class MyControllerFactory
    Inherits DefaultControllerFactory

    Protected Overrides Function GetControllerInstance(requestContext As System.Web.Routing.RequestContext, controllerType As System.Type) As System.Web.Mvc.IController
        Try
            Return MyBase.GetControllerInstance(requestContext, controllerType)
        Catch ex As HttpException
            Return DependencyResolver.Current.GetService(Of BaseController)()
        End Try
    End Function
End Class

'In Global.asax.vb Application_Start:

controllerBuilder.Current.SetControllerFactory(New MyControllerFactory)

Step 6

Include a special route in your RoutTable.Routes for the BaseController Unknown action. This will help us raise a 404 in the case where a user accesses an unknown controller, or unknown action.

在你的路线上包括一条特别的路线。基本控制器未知动作的路由。这将帮助我们在用户访问未知控制器或未知操作的情况下生成404。

'BaseController
routes.MapRoute( _
    "Unknown", "BaseController/{action}/{id}", _
    New With {.controller = "BaseController", .action = "Unknown", .id = UrlParameter.Optional} _
)

Summary

This example demonstrated how one can use the MVC framework to return 404 Http Error Codes to the browser without a redirect using filter attributes and shared error views. It also demonstrates showing the same custom error page when invalid controller names and action names are specified.

这个示例演示了如何使用MVC框架向浏览器返回404 Http错误代码,而不使用过滤器属性和共享错误视图进行重定向。它还演示了在指定无效的控制器名和操作名时显示相同的自定义错误页。

I'll add a screenshot of an invalid controller name, action name, and a custom 404 raised from the Home/TriggerNotFound action if I get enough votes to post one =). Fiddler returns a 404 message when I access the following URLs using this solution:

我将添加一个无效的控制器名、动作名的屏幕截图,以及一个自定义的404从Home/TriggerNotFound动作中生成,如果我得到足够的选票发布一个=)。当我使用此解决方案访问以下url时,Fiddler将返回404消息:

/InvalidController
/Home/InvalidRoute
/InvalidController/InvalidRoute
/Home/TriggerNotFound

cottsak's post above and these articles were good references.

上述科特萨克的文章和这些文章都是很好的参考。

#9


4  

My shortened solution that works with unhandled areas, controllers and actions:

我缩短的解决方案,适用于未处理的区域、控制器和操作:

  1. Create a view 404.cshtml.

    cshtml 404.创建一个视图。

  2. Create a base class for your controllers:

    为控制器创建一个基类:

    public class Controller : System.Web.Mvc.Controller
    {
        protected override void HandleUnknownAction(string actionName)
        {
            Http404().ExecuteResult(ControllerContext);
        }
    
        protected virtual ViewResult Http404()
        {
            Response.StatusCode = (int)HttpStatusCode.NotFound;
            return View("404");
        }
    }
    
  3. Create a custom controller factory returning your base controller as a fallback:

    创建一个自定义控制器工厂,返回您的基本控制器作为回退:

    public class ControllerFactory : DefaultControllerFactory
    {
        protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
        {
            if (controllerType != null)
                return base.GetControllerInstance(requestContext, controllerType);
    
            return new Controller();
        }
    }
    
  4. Add to Application_Start() the following line:

    向Application_Start()添加以下行:

    ControllerBuilder.Current.SetControllerFactory(typeof(ControllerFactory));
    

#10


3  

In MVC4 WebAPI 404 can be handle in the following way,

在MVC4中,WebAPI 404可以按以下方式处理,

COURSES APICONTROLLER

课程APICONTROLLER

    // GET /api/courses/5
    public HttpResponseMessage<Courses> Get(int id)
    {
        HttpResponseMessage<Courses> resp = null;

        var aCourse = _courses.Where(c => c.Id == id).FirstOrDefault();

        resp = aCourse == null ? new HttpResponseMessage<Courses>(System.Net.HttpStatusCode.NotFound) : new HttpResponseMessage<Courses>(aCourse);

        return resp;
    }

HOME CONTROLLER

家控制器

public ActionResult Course(int id)
{
    return View(id);
}

VIEW

视图

<div id="course"></div>
<script type="text/javascript">
    var id = @Model;
    var course = $('#course');
    $.ajax({    
        url: '/api/courses/' + id,
        success: function (data) {
            course.text(data.Name);
        },
        statusCode: {
            404: function() 
            {
                course.text('Course not available!');    
            }
        }
    });
</script>

GLOBAL

全球

public static void RegisterRoutes(RouteCollection routes)
{
    routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

    routes.MapHttpRoute(
        name: "DefaultApi",
        routeTemplate: "api/{controller}/{id}",
        defaults: new { id = RouteParameter.Optional }
    );

    routes.MapRoute(
        name: "Default",
        url: "{controller}/{action}/{id}",
        defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
    );
}

RESULTS

结果

如何正确处理ASP中的404。净MVC吗?

#11


2  

Try NotFoundMVC on nuget. It works , no setup.

试穿NotFoundMVC nuget。它是有效的,没有设置。

#12


2  

My solution, in case someone finds it useful.

我的解决方案,以防有人发现它有用。

In Web.config:

在web . config:

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

In Controllers/ErrorController.cs:

在控制器/ ErrorController.cs:

public class ErrorController : Controller
{
    public ActionResult PageNotFound()
    {
        if(Request.IsAjaxRequest()) {
            Response.StatusCode = (int)HttpStatusCode.NotFound;
            return Content("Not Found", "text/plain");
        }

        return View();
    }
}

Add a PageNotFound.cshtml in the Shared folder, and that's it.

添加一个PageNotFound。共享文件夹中的cshtml,就是这样。

#13


2  

It seems to me that the standard CustomErrors configuration should just work however, due to the reliance on Server.Transfer it seems that the internal implementation of ResponseRewrite isn't compatible with MVC.

在我看来,由于对服务器的依赖,标准的CustomErrors配置应该能够正常工作。ResponseRewrite的内部实现似乎与MVC不兼容。

This feels like a glaring functionality hole to me, so I decided to re-implement this feature using a HTTP module. The solution below allows you to handle any HTTP status code (including 404) by redirecting to any valid MVC route just as you would do normally.

这对我来说是一个明显的功能漏洞,所以我决定使用HTTP模块重新实现这个特性。下面的解决方案允许您像往常一样重定向到任何有效的MVC路由,从而处理任何HTTP状态代码(包括404)。

<customErrors mode="RemoteOnly" redirectMode="ResponseRewrite">
    <error statusCode="404" redirect="404.aspx" />
    <error statusCode="500" redirect="~/MVCErrorPage" />
</customErrors>

This has been tested on the following platforms;

在以下平台上进行了测试;

  • MVC4 in Integrated Pipeline Mode (IIS Express 8)
  • MVC4集成管道模式(IIS Express 8)
  • MVC4 in Classic Mode (VS Development Server, Cassini)
  • MVC4经典模式(VS开发服务器,卡西尼)
  • MVC4 in Classic Mode (IIS6)
  • MVC4经典模式(IIS6)

Benefits

好处

  • Generic solution which can be dropped into any MVC project
  • 通用的解决方案,可以放到任何MVC项目中
  • Enables support for traditional custom errors configuration
  • 支持传统的自定义错误配置
  • Works in both Integrated Pipeline and Classic modes
  • 可以在集成管道和经典模式下工作

The Solution

解决方案

namespace Foo.Bar.Modules {

    /// <summary>
    /// Enables support for CustomErrors ResponseRewrite mode in MVC.
    /// </summary>
    public class ErrorHandler : IHttpModule {

        private HttpContext HttpContext { get { return HttpContext.Current; } }
        private CustomErrorsSection CustomErrors { get; set; }

        public void Init(HttpApplication application) {
            System.Configuration.Configuration configuration = WebConfigurationManager.OpenWebConfiguration("~");
            CustomErrors = (CustomErrorsSection)configuration.GetSection("system.web/customErrors");

            application.EndRequest += Application_EndRequest;
        }

        protected void Application_EndRequest(object sender, EventArgs e) {

            // only handle rewrite mode, ignore redirect configuration (if it ain't broke don't re-implement it)
            if (CustomErrors.RedirectMode == CustomErrorsRedirectMode.ResponseRewrite && HttpContext.IsCustomErrorEnabled) {

                int statusCode = HttpContext.Response.StatusCode;

                // if this request has thrown an exception then find the real status code
                Exception exception = HttpContext.Error;
                if (exception != null) {
                    // set default error status code for application exceptions
                    statusCode = (int)HttpStatusCode.InternalServerError;
                }

                HttpException httpException = exception as HttpException;
                if (httpException != null) {
                    statusCode = httpException.GetHttpCode();
                }

                if ((HttpStatusCode)statusCode != HttpStatusCode.OK) {

                    Dictionary<int, string> errorPaths = new Dictionary<int, string>();

                    foreach (CustomError error in CustomErrors.Errors) {
                        errorPaths.Add(error.StatusCode, error.Redirect);
                    }

                    // find a custom error path for this status code
                    if (errorPaths.Keys.Contains(statusCode)) {
                        string url = errorPaths[statusCode];

                        // avoid circular redirects
                        if (!HttpContext.Request.Url.AbsolutePath.Equals(VirtualPathUtility.ToAbsolute(url))) {

                            HttpContext.Response.Clear();
                            HttpContext.Response.TrySkipIisCustomErrors = true;

                            HttpContext.Server.ClearError();

                            // do the redirect here
                            if (HttpRuntime.UsingIntegratedPipeline) {
                                HttpContext.Server.TransferRequest(url, true);
                            }
                            else {
                                HttpContext.RewritePath(url, false);

                                IHttpHandler httpHandler = new MvcHttpHandler();
                                httpHandler.ProcessRequest(HttpContext);
                            }

                            // return the original status code to the client
                            // (this won't work in integrated pipleline mode)
                            HttpContext.Response.StatusCode = statusCode;

                        }
                    }

                }

            }

        }

        public void Dispose() {

        }


    }

}

Usage

使用

Include this as the final HTTP module in your web.config

将其作为web.config中的最后一个HTTP模块

  <system.web>
    <httpModules>
      <add name="ErrorHandler" type="Foo.Bar.Modules.ErrorHandler" />
    </httpModules>
  </system.web>

  <!-- IIS7+ -->
  <system.webServer>
    <modules>
      <add name="ErrorHandler" type="Foo.Bar.Modules.ErrorHandler" />
    </modules>
  </system.webServer>

For those of you paying attention you will notice that in Integrated Pipeline mode this will always respond with HTTP 200 due to the way Server.TransferRequest works. To return the proper error code I use the following error controller.

注意了的人会注意到,在集成的管道模式中,由于服务器的方式,它总是使用HTTP 200进行响应。TransferRequest作品。要返回正确的错误代码,我使用以下错误控制器。

public class ErrorController : Controller {

    public ErrorController() { }

    public ActionResult Index(int id) {
        // pass real error code to client
        HttpContext.Response.StatusCode = id;
        HttpContext.Response.TrySkipIisCustomErrors = true;

        return View("Errors/" + id.ToString());
    }

}

#14


2  

Dealing with errors in ASP.NET MVC is just a pain in the butt. I tried a whole lot of suggestions on this page and on other questions and sites and nothing works good. One suggestion was to handle errors on web.config inside system.webserver but that just returns blank pages.

处理ASP中的错误。NET MVC只是一个令人头痛的问题。我在这个页面和其他问题和网站上尝试了很多建议,但没有什么是好的。一个建议是处理web上的错误。系统内配置。但它只返回空页面。

My goal when coming up with this solution was to;

我提出这个解决方案的目的是;

  • NOT REDIRECT
  • 不重定向
  • Return PROPER STATUS CODES not 200/Ok like the default error handling
  • 返回正确的状态码而不是200/Ok,如默认的错误处理

Here is my solution.

这是我的解决方案。

1.Add the following to system.web section

1。将以下内容添加到系统中。网络部分

   <system.web>
     <customErrors mode="On" redirectMode="ResponseRewrite">
      <error statusCode="404"  redirect="~/Error/404.aspx" />
      <error statusCode="500" redirect="~/Error/500.aspx" />
     </customErrors>
    <system.web>

The above handles any urls not handled by routes.config and unhandled exceptions especially those encountered on the views. Notice I used aspx not html. This is so I can add a response code on the code behind.

以上处理的url不是由路由处理的。配置和未处理的异常,特别是在视图中遇到的异常。注意,我使用的是aspx而不是html。这样我就可以在后面的代码中添加响应代码。

2. Create a folder called Error (or whatever you prefer) at the root of your project and add the two webforms. Below is my 404 page;

2。在项目的根目录中创建一个名为Error(或您喜欢的任何内容)的文件夹,并添加这两个webforms。下面是我的404页面;

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="404.aspx.cs" Inherits="Myapp.Error._404" %>

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title >Page Not found</title>
    <link href="<%=ResolveUrl("~/Content/myapp.css")%>" rel="stylesheet" />
</head>
<body>
    <div class="top-nav">
      <a runat="server" class="company-logo" href="~/"></a>
    </div>
    <div>
        <h1>404 - Page Not found</h1>
        <p>The page you are looking for cannot be found.</p>
        <hr />
        <footer></footer>
    </div>
</body>
</html>

And on the code behind I set the response code

在后面的代码中,我设置了响应代码

protected void Page_Load(object sender, EventArgs e)
{
    Response.StatusCode = 404;
}

Do the same for the 500 page

500页也是这样吗

3.To handle errors within the controllers. There's many ways to do it. This is what worked for me. All my controllers inherit from a base controller. In the base controller, I have the following methods

3所示。处理控制器中的错误。有很多方法可以做到。这对我起了作用。所有的控制器都继承自基控制器。在基本控制器中,我有以下方法

protected ActionResult ShowNotFound()
{
    return ShowNotFound("Page not found....");
}

protected ActionResult ShowNotFound(string message)
{
    return ShowCustomError(HttpStatusCode.NotFound, message);
}

protected ActionResult ShowServerError()
{
    return ShowServerError("Application error....");
}

protected ActionResult ShowServerError(string message)
{
    return ShowCustomError(HttpStatusCode.InternalServerError, message);
}

protected ActionResult ShowNotAuthorized()
{
    return ShowNotAuthorized("You are not allowed ....");

}

protected ActionResult ShowNotAuthorized(string message)
{
    return ShowCustomError(HttpStatusCode.Forbidden, message);
}

protected ActionResult ShowCustomError(HttpStatusCode statusCode, string message)
{
    Response.StatusCode = (int)statusCode;
    string title = "";
    switch (statusCode)
    {
        case HttpStatusCode.NotFound:
            title = "404 - Not found";
            break;
        case HttpStatusCode.Forbidden:
            title = "403 - Access Denied";
            break;
        default:
            title = "500 - Application Error";
            break;
    }
    ViewBag.Title = title;
    ViewBag.Message = message;
    return View("CustomError");
}

4.Add the CustomError.cshtml to your Shared views folder. Below is mine;

4所示。添加CustomError。cshtml到您的共享视图文件夹。下面是我的;

<h1>@ViewBag.Title</h1>
<br />
<p>@ViewBag.Message</p>

Now in your application controller you can do something like this;

在你的应用程序控制器中你可以这样做;

public class WidgetsController : ControllerBase
{
  [HttpGet]
  public ActionResult Edit(int id)
  {
    Try
    {
       var widget = db.getWidgetById(id);
       if(widget == null)
          return ShowNotFound();
          //or return ShowNotFound("Invalid widget!");
       return View(widget);
    }
    catch(Exception ex)
    {
       //log error
       logger.Error(ex)
       return ShowServerError();
    }
  }
}

Now for the caveat. It won't handle static file errors. So if you have a route such as example.com/widgets and the user changes it to example.com/widgets.html, they will get the IIS default error page so you have to handle IIS level errors some other way.

现在的警告。它不会处理静态文件错误。如果你有一个路径,比如example.com/widgets用户把它改成example.com/widgets.html,他们会得到IIS默认错误页面,所以你必须用其他方法处理IIS级别的错误。

#15


1  

Posting an answer since my comment was too long...

因为我的评论太长了,所以我发布了一个答案……

It's both a comment and questions to the unicorn post/answer:

这是对独角兽的评论和问题/回答:

https://*.com/a/7499406/687549

https://*.com/a/7499406/687549

I prefer this answer over the others for it's simplicity and the fact that apparently some folks at Microsoft were consulted. I got three questions however and if they can be answered then I will call this answer the holy grail of all 404/500 error answers on the interwebs for an ASP.NET MVC (x) app.

我更喜欢这个答案而不是其他答案,因为它很简单,而且微软的一些员工显然也被咨询过。我有三个问题,如果它们能被回答,我将把这个答案称为网络上所有404/500个错误答案的圣杯。净MVC(x)应用程序。

@Pure.Krome

@Pure.Krome

  1. Can you update your answer with the SEO stuff from the comments pointed out by GWB (there was never any mentioning of this in your answer) - <customErrors mode="On" redirectMode="ResponseRewrite"> and <httpErrors errorMode="Custom" existingResponse="Replace">?

    你能从GWB的评论(你的回答中从来没有提到过这个)中使用SEO来更新你的答案吗? - ?

  2. Can you ask your ASP.NET team friends if it is okay to do it like that - would be nice to have some confirmation - maybe it's a big no-no to change redirectMode and existingResponse in this way to be able to play nicely with SEO?!

    你能问问你的ASP吗?网络团队的朋友们,如果可以这样做——如果能得到一些确认就太好了——也许以这种方式改变redirectMode和现有的响应就能很好地使用SEO?!

  3. Can you add some clarification surrounding all that stuff (customErrors redirectMode="ResponseRewrite", customErrors redirectMode="ResponseRedirect", httpErrors errorMode="Custom" existingResponse="Replace", REMOVE customErrors COMPLETELY as someone suggested) after talking to your friends at Microsoft?

    你能不能在和你在微软的朋友聊天后,对这些东西(customErrors redirectMode="ResponseRewrite", customErrors redirectMode="ResponseRedirect", httpErrors errorMode="Custom" existingResponse="Replace",完全按照别人的建议删除定制错误)做一些澄清?

As I was saying; it would be supernice if we could make your answer more complete as this seem to be a fairly popular question with 54 000+ views.

我说的;如果我们能让你的回答更完整,那就太好了,因为这似乎是一个很受欢迎的问题,浏览量超过5.4万。

Update: Unicorn answer does a 302 Found and a 200 OK and cannot be changed to only return 404 using a route. It has to be a physical file which is not very MVC:ish. So moving on to another solution. Too bad because this seemed to be the ultimate MVC:ish answer this far.

更新:独角兽回答做302找到和200 OK,不能更改为仅返回404使用的路径。它必须是一个不太符合MVC的物理文件。那么我们再来看另一个解。太糟糕了,因为这似乎是最终的MVC:ish。

#16


1  

Adding my solution, which is almost identical to Herman Kan's, with a small wrinkle to allow it to work for my project.

加上我的解决方案,几乎和赫尔曼·菅直人的方案一样,有一个小的皱纹,让它可以为我的项目工作。

Create a custom error controller:

创建一个自定义错误控制器:

public class Error404Controller : BaseController
{
    [HttpGet]
    public ActionResult PageNotFound()
    {
        Response.StatusCode = 404;
        return View("404");
    }
}

Then create a custom controller factory:

然后创建一个自定义控制器工厂:

public class CustomControllerFactory : DefaultControllerFactory
{
    protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
    {
        return controllerType == null ? new Error404Controller() : base.GetControllerInstance(requestContext, controllerType);
    }
}

Finally, add an override to the custom error controller:

最后,向自定义错误控制器添加一个覆盖:

protected override void HandleUnknownAction(string actionName)
{
    var errorRoute = new RouteData();
    errorRoute.Values.Add("controller", "Error404");
    errorRoute.Values.Add("action", "PageNotFound");
    new Error404Controller().Execute(new RequestContext(HttpContext, errorRoute));
}

And that's it. No need for Web.config changes.

就是这样。不需要网络。配置更改。

#17


1  

1) Make abstract Controller class.

1)制作抽象控制器类。

public abstract class MyController:Controller
{
    public ActionResult NotFound()
    {
        Response.StatusCode = 404;
        return View("NotFound");
    }

    protected override void HandleUnknownAction(string actionName)
    {
        this.ActionInvoker.InvokeAction(this.ControllerContext, "NotFound");
    }
    protected override void OnAuthorization(AuthorizationContext filterContext) { }
}  

2) Make inheritence from this abstract class in your all controllers

2)在所有控制器中继承这个抽象类

public class HomeController : MyController
{}  

3) And add a view named "NotFound" in you View-Shared folder.

并在视图共享文件夹中添加名为“NotFound”的视图。

#18


0  

I went through most of the solutions posted on this thread. While this question might be old, it is still very applicable to new projects even now, so I spent quite a lot of time reading up on the answers presented here as well as else where.

我浏览了大多数在这个线程上发布的解决方案。虽然这个问题可能是旧的,但即使是现在,它仍然适用于新项目,所以我花了大量的时间阅读这里和其他地方给出的答案。

As @Marco pointed out the different cases under which a 404 can happen, I checked the solution I compiled together against that list. In addition to his list of requirements, I also added one more.

正如@Marco指出了404可能发生的不同情况,我根据这个列表检查了一起编译的解决方案。除了他的要求清单之外,我还增加了一个。

  • The solution should be able to handle MVC as well as AJAX/WebAPI calls in the most appropriate manner. (i.e. if 404 happens in MVC, it should show the Not Found page and if 404 happens in WebAPI, it should not hijack the XML/JSON response so that the consuming Javascript can parse it easily).
  • 解决方案应该能够以最合适的方式处理MVC以及AJAX/WebAPI调用。(例如,如果404发生在MVC中,它应该显示未找到的页面,如果404发生在WebAPI中,它不应该劫持XML/JSON响应,以便使用的Javascript能够轻松解析它)。

This solution is 2 fold:

这个解决方案是2倍:

First part of it comes from @Guillaume at https://*.com/a/27354140/2310818. Their solution takes care of any 404 that were caused due to invalid route, invalid controller and invalid action.

第一部分来自@Guillaume, https://*.com/a/27354140/2310818。他们的解决方案负责处理由于无效路由、无效控制器和无效操作而导致的任何404错误。

The idea is to create a WebForm and then make it call the NotFound action of your MVC Errors Controller. It does all of this without any redirect so you will not see a single 302 in Fiddler. The original URL is also preserved, which makes this solution fantastic!

我们的想法是创建一个WebForm,然后让它调用MVC错误控制器的NotFound操作。它在没有任何重定向的情况下完成了所有这些,所以你在Fiddler中看不到一个302。原始的URL也被保留了,这使得这个解决方案非常棒!


Second part of it comes from @Germán at https://*.com/a/5536676/2310818. Their solution takes care of any 404 returned by your actions in the form of HttpNotFoundResult() or throw new HttpException()!

第二部分来自@German, https://*.com/a/5536676/2310818。他们的解决方案负责处理您的操作以HttpNotFoundResult()或抛出新的HttpException()形式返回的任何404 !

The idea is to have a filter look at the response as well as the exception thrown by your MVC controllers and to call the appropriate action in your Errors Controller. Again this solution works without any redirect and the original url is preserved!

其思想是让一个过滤器查看响应以及MVC控制器抛出的异常,并在错误控制器中调用适当的操作。同样,这个解决方案在没有任何重定向的情况下工作,原始的url被保留了!


As you can see, both of these solutions together offer a very robust error handling mechanism and they achieve all the requirements listed by @Marco as well as my requirements. If you would like to see a working sample or a demo of this solution, please leave in the comments and I would be happy to put it together.

如您所见,这两个解决方案一起提供了一个非常健壮的错误处理机制,它们实现了@Marco列出的所有需求以及我的需求。如果您想看到这个解决方案的工作示例或演示,请在评论中留下,我很乐意把它放在一起。

#19


0  

I have gone through all articles but nothing works for me: My requirement user type anything in your url custom 404 page should show.I thought it is very straight forward.But you should understand handling of 404 properly:

我已经浏览了所有的文章,但是没有一篇对我有用:我的需求用户在您的url自定义404页面中输入任何内容。我觉得很直接。但是您应该正确地理解404的处理:

 <system.web>
    <customErrors mode="On" redirectMode="ResponseRewrite">
      <error statusCode="404" redirect="~/PageNotFound.aspx"/>
    </customErrors>
  </system.web>
<system.webServer>
    <httpErrors errorMode="Custom">
      <remove statusCode="404"/>
      <error statusCode="404" path="/PageNotFound.html" responseMode="ExecuteURL"/>
    </httpErrors>
</system.webServer>

I found this article very helpfull.should be read at once.Custome error page-Ben Foster

我发现这篇文章很有帮助。应该立即阅读。Custome错误page-Ben福斯特