我正在使用RC2
使用URL路由:
routes.MapRoute( "Error", "{*url}", new { controller = "Errors", action = "NotFound" } // 404s );
以上似乎照顾这样的请求(假设默认路由表由初始MVC项目设置):"/ blah/blah/blah/blah"
覆盖控制器本身中的HandleUnknownAction():
// 404s - handle here (bad action requested protected override void HandleUnknownAction(string actionName) { ViewData["actionName"] = actionName; View("NotFound").ExecuteResult(this.ControllerContext); }
但是,之前的策略不处理对Bad/Unknown控制器的请求.例如,我没有"/ IDoNotExist",如果我请求这个,我从Web服务器获取通用404页面而不是我的404,如果我使用路由+覆盖.
最后,我的问题是: 有没有办法在MVC框架中使用路由或其他东西来捕获这种类型的请求?
或者我应该默认使用Web.Config customErrors作为我的404处理程序并忘记所有这些?我假设如果我使用customErrors,由于Web.Config对直接访问的限制,我必须在/ Views之外存储通用404页面.
代码来自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中也是如此
以下是我处理http异常的方法:
protected void Application_Error(object sender, EventArgs e) { Exception exception = Server.GetLastError(); // Log the exception. ILogger logger = Container.Resolve(); 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)); }
以下是我对404解决方案的要求,下面我将展示如何实现它:
我想处理带有错误操作的匹配路由
我想处理与坏控制器匹配的路由
我想处理不匹配的路由(我的应用无法理解的任意网址) - 我不希望这些冒泡到Global.asax或IIS因为那时我无法正确地重定向回我的MVC应用程序
我想要一种方法来处理与上面相同的方式,自定义404s - 就像为不存在的对象(可能删除)提交ID
我希望我的所有404都返回一个MVC视图(不是静态页面),如果有必要,我可以在以后提取更多数据(好的404设计),并且它们必须返回HTTP 404状态代码
解
我认为你应该Application_Error
在Global.asax中保存更高级的东西,比如未处理的异常和日志记录(比如Shay Jacoby的答案显示),但不是404处理.这就是为什么我的建议将404内容保留在Global.asax文件之外的原因.
这是可维护性的好主意.使用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 }
HandleUnknownAction
ASP.NET MVC中的404s需要在很多地方被捕获.首先是HandleUnknownAction
.
该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(); 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 }
像这样(它不一定是StructureMap):
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).InvokeHttp404(RequestContext.HttpContext); return errorController; } else throw ex; } return ObjectFactory.GetInstance(controllerType) as Controller; } }
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).InvokeHttp404(requestContext.HttpContext); return errorController; } else throw ex; } return ObjectFactory.GetInstance(controllerType) as Controller; }
我认为更好地捕捉到更接近它们起源的错误.这就是为什么我更喜欢上面的Application_Error
处理程序.
这是第二个捕获404s的地方.
这条路线应该指向我们的Http404
行动.请注意,url
param将是一个相对URL,因为路由引擎正在剥离域部分?这就是我们在步骤1中拥有所有条件URL逻辑的原因.
routes.MapRoute("NotFound", "{*url}", new { controller = "Error", action = "Http404" });
这是在你自己不调用的MVC应用程序中捕获404s的第三个也是最后一个.如果你没有在这里捕获不匹配的路由,那么MVC会将问题传递给ASP.NET(Global.asax),在这种情况下你真的不想要它.
就像将错误的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); }
如果所有这些都可以用更少的代码连接在更少的地方,那将是很好的,但我认为这个解决方案更易于维护,更可测试且相当实用.
感谢您的反馈到目前为止.我想要得到更多.
注意:这已经根据我的原始答案进行了大量编辑,但目的/要求是相同的 - 这就是我没有添加新答案的原因
ASP.NET MVC不能很好地支持自定义404页面.定制控制器工厂,全能路线,基础控制器类HandleUnknownAction
- 唉!
到目前为止,IIS自定义错误页面是更好的选择
web.config中ErrorController
public class ErrorController : Controller { public ActionResult PageNotFound() { Response.StatusCode = 404; return View(); } }示例项目
GitHub上的Test404
直播网站
对于那里的懒人:
Install-Package MagicalUnicornMvcErrorToolkit -Version 1.0
然后从中删除此行 global.asax
GlobalFilters.Filters.Add(new HandleErrorAttribute());
这仅适用于IIS7 +和IIS Express.
如果你正在使用卡西尼......那么......嗯..呃......尴尬......
我知道这已经回答了.但答案真的很简单(为David Fowler和Damian Edwards欢呼,真的回答这个问题).
有没有必要做任何定制.
因为ASP.NET MVC3
,所有的零碎都在那里.
和
... ...
现在请仔细注意我决定使用的路线.你可以使用任何东西,但我的路线是
/NotFound
< - 找不到404,错误页面.
/ServerError
< - 对于任何其他错误,包括我的代码中发生的错误.这是500内部服务器错误
看看第一部分中
只有一个自定义条目?这个statusCode="404"
条目?我只列出了一个状态代码,因为所有其他错误,包括500 Server Error
(即,当您的代码有错误并崩溃用户的请求时发生的那些讨厌的错误)..所有其他错误由设置处理defaultRedirect="/ServerError"
... ,如果您没有找到404页面,请转到该路线/ServerError
.
好.这是不合适的..现在我的路线列出来了global.asax
这是我的完整路线部分..
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} ); }
这列出了两个忽略路由 - > axd's
和favicons
(ooo!bonus ignore route,for you!)然后(并且命令是IMPERATIVE HERE),我有两个显式的错误处理路由..后跟任何其他路由.在这种情况下,默认值为1.当然,我有更多,但这对我的网站来说很特别.只需确保错误路由位于列表顶部.秩序是必要的.
最后,当我们在global.asax
文件中时,我们不会全局注册HandleError属性.不,不,不,先生.Nadda.不.粘.负.Noooooooooo ...
从中删除此行 global.asax
GlobalFilters.Filters.Add(new HandleErrorAttribute());
现在..我们添加一个带有两种动作方法的控制器......
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"); } }
好的,我们来看看吧.首先,这里没有 [HandleError]
属性.为什么?因为内置ASP.NET
框架已经处理错误而且我们已经指定了我们需要做的所有狗屎来处理错误:)这是在这个方法中!
接下来,我有两个动作方法.那里没什么难的.如果您希望显示任何异常信息,那么您可以使用Server.GetLastError()
获取该信息.
奖金WTF:是的,我做了第三个动作方法,测试错误处理.
最后,创建两个视图.将em放在普通视点中,对于此控制器.
你不需要 Application_Error(object sender, EventArgs e)
以上步骤与Elmah完全一致.Elmah fraking wroxs!
那个,我的朋友,应该是它.
现在,恭喜阅读这篇文章并获得独角兽作为奖品!
我调查了很多关于如何在MVC妥善管理404 (具体MVC3) ,这一点,恕我直言,是最好的解决方案,我想出来的:
在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; } }
(可选的)
说明:
AFAIK,ASP.NET MVC3应用程序可以生成404的6种不同情况.
(由ASP.NET Framework自动生成:)
(1) URL在路由表中找不到匹配项.
(由ASP.NET MVC框架自动生成:)
(2) URL在路由表中查找匹配项,但指定不存在的控制器.
(3) URL在路由表中查找匹配项,但指定不存在的操作.
(手动生成:)
(4)动作使用方法HttpNotFound()返回HttpNotFoundResult.
(5)一个动作抛出一个状态码为404的HttpException.
(6)动作手动将Response.StatusCode属性修改为404.
通常,您希望实现3个目标:
(1)向用户显示自定义404错误页面.
(2)维护客户端响应的404状态代码(对SEO特别重要).
(3)直接发送响应,不涉及302重定向.
有多种方法可以尝试实现此目的:
(1)
此解决方案的问题:
在情况(1),(4),(6)中不符合目标(1).
不自动符合目标(2).必须手动编程.
不符合目标(3).
(2)
此解决方案的问题:
仅适用于IIS 7+.
在情况(2),(3),(5)中不符合目标(1).
不自动符合目标(2).必须手动编程.
(3)
此解决方案的问题:
仅适用于IIS 7+.
不自动符合目标(2).必须手动编程.
它模糊了应用程序级别的http异常.例如,不能使用customErrors部分,System.Web.Mvc.HandleErrorAttribute等.它不仅可以显示通用错误页面.
(4)
和
此解决方案的问题:
仅适用于IIS 7+.
不自动符合目标(2).必须手动编程.
在情况(2),(3),(5)中不符合目标(3).
在尝试创建自己的库之前,对此感到困扰的人们(请参阅http://aboutcode.net/2011/02/26/handling-not-found-with-asp-net-mvc3.html).但是之前的解决方案似乎涵盖了所有情况,而没有使用外部库的复杂性.
我真的很喜欢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(); 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 }
基本上,这会阻止包含无效操作和控制器的URL触发异常例程两次.例如,对于诸如asdfsdf/dfgdfgd之类的URL
我可以获得@ 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(); } else throw ex; } } }
我应该提到我正在使用MVC 2.0.