asp.net-mvc 如何在 ASP.NET MVC 中正确处理 404?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/619895/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How can I properly handle 404 in ASP.NET MVC?
提问by Brian
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 页面,如果我使用路由 + 覆盖,则不是我的 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.Config customErrors 作为我的 404 处理程序并忘记所有这些吗?我假设如果我使用 customErrors,由于 Web.Config 对直接访问的限制,我必须将通用 404 页面存储在 /Views 之外。
采纳答案by Shay Jacoby
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.aspxand works in ASP.net MVC 1.0 as well
该代码取自http://blogs.microsoft.co.il/blogs/shay/archive/2009/03/06/real-world-error-hadnling-in-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));
}
回答by Matt Kocaj
Requirements for 404
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
- 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)
- 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) andthey mustreturn the HTTP 404 status code
- 我想用错误的动作处理匹配的路由
- 我想用坏控制器处理匹配的路由
- 我想处理不匹配的路由(我的应用程序无法理解的任意 url) - 我不希望这些冒泡到 Global.asax 或 IIS 因为那样我无法正确重定向回我的 MVC 应用程序
- 我想要一种以与上述相同的方式处理自定义 404 的方法 - 就像为不存在的对象(可能已删除)提交 ID 一样
- 我希望我的所有 404 都返回一个 MVC 视图(不是静态页面),如果需要,我可以稍后向其中抽取更多数据(好的 404 设计),并且它们必须返回 HTTP 404 状态代码
Solution
解决方案
I think you should save Application_Errorin the Global.asax for higher things, like unhandled exceptions and logging (like Shay Jacoby's answershows) but not 404 handling. This is why my suggestion keeps the 404 stuff out of the Global.asax file.
我认为您应该Application_Error在 Global.asax 中保存更高的内容,例如未处理的异常和日志记录(如Shay Jacoby 的回答所示),而不是 404 处理。这就是为什么我的建议将 404 内容排除在 Global.asax 文件之外的原因。
Step 1: Have a common place for 404-error logic
第 1 步:为 404 错误逻辑设置一个通用位置
This is a good idea for maintainability. Use an ErrorControllerso that future improvements to your well designed 404 pagecan 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
第 2 步:使用基本 Controller 类,以便您可以轻松调用自定义 404 操作并进行连接 HandleUnknownAction
404s in ASP.NET MVC need to be caught at a number of places. The first is HandleUnknownAction.
ASP.NET MVC 中的 404 需要在许多地方捕获。第一个是HandleUnknownAction。
The InvokeHttp404method creates a common place for re-routing to the ErrorControllerand our new Http404action. 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
第 3 步:在控制器工厂中使用依赖注入并连接 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_Errorhandler.
我认为最好在更接近错误发生的地方捕获错误。这就是为什么我更喜欢上面的Application_Error处理程序。
This is the second place to catch 404s.
这是第二个抓404的地方。
Step 4: Add a NotFound route to Global.asax for urls that fail to be parsed into your app
第 4 步:将 NotFound 路由添加到 Global.asax,用于无法解析到您的应用程序中的 url
This route should point to our Http404action. Notice the urlparam 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参数将是一个相对 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 应用程序中捕获 404 的第三个也是最后一个地方。如果您没有在此处捕获不匹配的路由,那么 MVC 会将问题传递给 ASP.NET (Global.asax),而在这种情况下您并不真正想要这样。
Step 5: Finally, invoke 404s when your app can't find something
第 5 步:最后,当您的应用找不到某些内容时调用 404s
Like when a bad ID is submitted to my Loans controller (derives from MyController):
就像当一个错误的 ID 提交给我的 Loans 控制器时(来自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
注意:这已从我的原始答案中进行了大量编辑,但目的/要求是相同的 - 这就是我没有添加新答案的原因
回答by Pavel Chuchuva
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包罗万象的路由、带有- 啊!
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
示例项目
回答by Pure.Krome
Quick Answer / TL;DR
快速回答 / TL;DR


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 ...

如果你使用的是卡西尼号……好吧……嗯……呃……尴尬……

Long, explained answer
长而解释的答案
I know this has been answered. But the answer is REALLY SIMPLE (cheers to David Fowlerand Damian Edwardsfor 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.
步骤 1 -> 在两个位置更新您的 web.config。
<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
现在仔细记下我决定使用的 ROUTES。你可以使用任何东西,但我的路线是
/NotFound<- for a 404 not found, error page./ServerError<- for any other error, include errors that happen in my code. this is a 500 Internal Server Error
/NotFound<- 对于未找到的 404,错误页面。/ServerError<- 对于任何其他错误,包括在我的代码中发生的错误。这是一个 500 内部服务器错误
See how the first section in <system.web>only has onecustom 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.
看到第一个部分如何<system.web>只有一个自定义条目?该statusCode="404"项目吗?我只列出了一个状态代码,因为所有其他错误,包括500 Server Error(即,当您的代码有错误并导致用户请求崩溃时发生的那些讨厌的错误).. 所有其他错误都由设置处理defaultRedirect="/ServerError".. ,如果你不是404页面没有找到,那么请转到路由/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
步骤 2 - 在 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'sand 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's和favicons(哦!奖励忽略路由,给你!)然后(这里的顺序是必不可少的),我有我的两个显式错误处理路由..后面是任何其他路由。在这种情况下,默认一个。当然,我还有更多,但这对我的网站来说很特别。只需确保错误路由位于列表顶部。秩序势在必行。
Finally, while we are inside our global.asaxfile, we do NOT globally register the HandleError attribute. No, no, no sir. Nadda. Nope. Nien. Negative. Noooooooooo...
最后,当我们在我们的global.asax文件中时,我们不会全局注册 HandleError 属性。不,不,不,先生。纳达。不。念。消极的。呜呜呜呜……
Remove this line from global.asax
删除此行 global.asax
GlobalFilters.Filters.Add(new HandleErrorAttribute());
Step 3 - Create the controller with the action methods
第 3 步 - 使用操作方法创建控制器
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.NETframework 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.
Bonus WTF:是的,我做了第三个动作方法,来测试错误处理。
Step 4 - Create the Views
第 4 步 - 创建视图
And finally, create two views. Put em in the normal view spot, for this controller.
最后,创建两个视图。对于此控制器,将 em 放在正常视图中。


Bonus comments
奖金评论
- You don't need an
Application_Error(object sender, EventArgs e) - The above steps all work 100% perfectly with Elmah. Elmah fraking wroxs!
And that, my friends, should be it.
我的朋友们,应该就是这样。
Now, congrats for reading this much and have a Unicorn as a prize!
现在,恭喜你阅读了这么多,并有一个独角兽作为奖品!


回答by Marco
I've investigated A LOTon 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:
错误控制器:
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 应用程序可以生成 404。
(Automatically generated by ASP.NET Framework:)
(由 ASP.NET Framework 自动生成:)
(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)一个action手动修改Response.StatusCode属性为404。
Normally, you want to accomplish 3 objectives:
通常,您希望实现 3 个目标:
(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:
此解决方案的问题:
- Does not comply with objective (1) in cases (1), (4), (6).
- Does not comply with objective (2) automatically. It must be programmed manually.
- Does not comply with objective (3).
- 在情况 (1)、(4)、(6) 中不符合目标 (1)。
- 不自动符合目标 (2)。它必须手动编程。
- 不符合目标 (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:
此解决方案的问题:
- Only works on IIS 7+.
- Does not comply with objective (1) in cases (2), (3), (5).
- Does not comply with objective (2) automatically. It must be programmed manually.
- 仅适用于 IIS 7+。
- 在情况 (2)、(3)、(5) 中不符合目标 (1)。
- 不自动符合目标 (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:
此解决方案的问题:
- Only works on IIS 7+.
- Does not comply with objective (2) automatically. It must be programmed manually.
- 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.
- 仅适用于 IIS 7+。
- 不自动符合目标 (2)。它必须手动编程。
- 它掩盖了应用程序级别的 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:
此解决方案的问题:
- Only works on IIS 7+.
- Does not comply with objective (2) automatically. It must be programmed manually.
- Does not comply with objective (3) in cases (2), (3), (5).
- 仅适用于 IIS 7+。
- 不自动符合目标 (2)。它必须手动编程。
- 在情况 (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-with-asp-net-mvc3.html)。但是之前的解决方案似乎涵盖了所有情况,而没有使用外部库的复杂性。
回答by Dave Lowe
I really like cottsaks solution and think its very clearly explained. my only addition was to alter step 2 as follows
我真的很喜欢 cottsaks 解决方案,并认为它的解释非常清楚。我唯一的补充是按如下方式更改第 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
回答by Dave K
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。
回答by sky-dev
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 工具的另一种方法,您可以处理对错误控制器名称、错误路由名称以及您认为适合 Action 方法中的任何其他条件的请求。就我个人而言,我更喜欢避免尽可能多的 web.config 设置,因为它们执行 302 / 200 重定向并且不支持Server.Transfer使用 Razor 视图的ResponseRewrite ( )。出于 SEO 的原因,我更愿意返回带有自定义错误页面的 404。
Some of this is new take on cottsak's technique above.
其中一些是对上述 cottsak 技术的新看法。
This solution also uses minimal web.config settings favoring the MVC 3 Error Filters instead.
此解决方案还使用最少的 web.config 设置来支持 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
第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
第2步
Add a custom HandleHttpErrorAttribute similar to the MVC framework's HandleErrorAttribute, except for HTTP errors:
添加一个类似于 MVC 框架的 HandleErrorAttribute 的自定义 HandleHttpErrorAttribute,除了 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
第 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.HandleErrorInfoobject as a the model.
将过滤器添加到 GlobalFilterCollection( GlobalFilters.Filters) 中Global.asax。此示例将所有 InternalServerError (500) 错误路由到 Error 共享视图 ( Views/Shared/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
第 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 中的 Global.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
第 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.
在您的 RoutTable.Routes 中为 BaseController Unknown 操作包含一个特殊的路由。这将帮助我们在用户访问未知控制器或未知操作的情况下引发 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:
如果我获得足够的票数来发布一个 =),我将添加无效控制器名称、操作名称和从 Home/TriggerNotFound 操作引发的自定义 404 的屏幕截图。当我使用此解决方案访问以下 URL 时,Fiddler 返回 404 消息:
/InvalidController
/Home/InvalidRoute
/InvalidController/InvalidRoute
/Home/TriggerNotFound
cottsak's post above and these articles were good references.
cottsak 上面的帖子和这些文章都是很好的参考。
回答by Herman Kan
My shortened solution that works with unhandled areas, controllers and actions:
我的简化解决方案适用于未处理的区域、控制器和操作:
Create a view 404.cshtml.
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"); } }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(); } }Add to
Application_Start()the following line:ControllerBuilder.Current.SetControllerFactory(typeof(ControllerFactory));
创建视图 404.cshtml。
为您的控制器创建一个基类:
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"); } }创建一个自定义控制器工厂,返回您的基本控制器作为后备:
public class ControllerFactory : DefaultControllerFactory { protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType) { if (controllerType != null) return base.GetControllerInstance(requestContext, controllerType); return new Controller(); } }添加到
Application_Start()以下行:ControllerBuilder.Current.SetControllerFactory(typeof(ControllerFactory));
回答by Diganta Kumar
In MVC4 WebAPI 404 can be handle in the following way,
在 MVC4 WebAPI 404 中可以通过以下方式处理,
COURSES APICONTROLLER
课程 API 控制器
// 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
结果



