ASP.MVC HandleError属性不起作用
我知道这是一个常见问题,但我已经抓住了许多讨论而没有结果。
我正在尝试使用HandleError ASP.MVC attrbiute来处理错误。 我正在使用MVC 4。
我的错误页面位于Views / Shared / Error.cshtml中,看起来像这样:
Test error page Error.
An error occurred while processing your request.
App-Start文件夹中的My FilterConfig.cs是:
public class FilterConfig { public static void RegisterGlobalFilters(GlobalFilterCollection filters) { filters.Add(new HandleErrorAttribute()); } }
我的控制器:
public class TestController : Controller { [HandleError(View = "Error")] public ActionResult Index() { throw new Exception("oops"); } }
最后我的Web.config in有以下节点:
当我调用控制器操作时,我得到一个带有以下文本的白色屏幕:
‘/’应用程序中的服务器错误。
运行时错误说明:处理请求时发生exception。 此外,执行第一个exception的自定义错误页面时发生另一个exception。 请求已终止。
如果未在Web.config中设置defaultRedirect =“Error”,则会出现以下文本的黄色屏幕:
‘/’应用程序中的服务器错误。
运行时错误说明:服务器上发生应用程序错误。 此应用程序的当前自定义错误设置可防止查看应用程序错误的详细信息。
详细信息:要在本地服务器计算机上查看此特定错误消息的详细信息,请在位于当前Web应用程序根目录中的“web.config”配置文件中创建标记。 然后,此标记应将其“mode”属性设置为“RemoteOnly”。 要使详细信息可在远程计算机上查看,请将“mode”设置为“Off”。
注意:通过修改应用程序配置标记的“defaultRedirect”属性以指向自定义错误页面URL,可以将自动查看的当前错误页面替换为自定义错误页面。
有人知道什么是错的吗?
编辑:
错误是由使用强类型布局引起的。 抛出错误时,MVC的error handling机制正在创建HandleErrorInfo对象,该对象将传递给Error视图。 但是,如果我们使用强类型布局,则类型不匹配。
我的解决方案是在Global.asax中使用Application_Error方法,下面的SBirthare对此进行了完美描述。
多年来,我一直在努力顺利地在ASP.NET MVC中实现“处理自定义错误”。
我之前已经成功使用过Elmah,然而却被大量需要处理和测试的案例所淹没(即本地与IIS)。
最近我的一个项目现在正在使用,我使用了以下方法(似乎在本地和生产环境中工作正常)。
我根本没有在web.config中指定customErrors
或任何设置。
我重写Application_Error
并处理所有情况,在ErrorController
调用特定的操作。
我分享这个,如果它有帮助,也得到反馈(虽然事情正在发挥作用,你永远不知道什么时候开始破坏;))
的Global.asax.cs
public class MvcApplication : System.Web.HttpApplication { protected void Application_Start() { AreaRegistration.RegisterAllAreas(); WebApiConfig.Register(GlobalConfiguration.Configuration); FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters); RouteConfig.RegisterRoutes(RouteTable.Routes); BundleConfig.RegisterBundles(BundleTable.Bundles); AuthConfig.RegisterAuth(); } protected void Application_Error(object sender, EventArgs e) { System.Diagnostics.Trace.WriteLine("Enter - Application_Error"); var httpContext = ((MvcApplication)sender).Context; var currentRouteData = RouteTable.Routes.GetRouteData(new HttpContextWrapper(httpContext)); var currentController = " "; var currentAction = " "; if (currentRouteData != null) { if (currentRouteData.Values["controller"] != null && !String.IsNullOrEmpty(currentRouteData.Values["controller"].ToString())) { currentController = currentRouteData.Values["controller"].ToString(); } if (currentRouteData.Values["action"] != null && !String.IsNullOrEmpty(currentRouteData.Values["action"].ToString())) { currentAction = currentRouteData.Values["action"].ToString(); } } var ex = Server.GetLastError(); if (ex != null) { System.Diagnostics.Trace.WriteLine(ex.Message); if (ex.InnerException != null) { System.Diagnostics.Trace.WriteLine(ex.InnerException); System.Diagnostics.Trace.WriteLine(ex.InnerException.Message); } } var controller = new ErrorController(); var routeData = new RouteData(); var action = "CustomError"; var statusCode = 500; if (ex is HttpException) { var httpEx = ex as HttpException; statusCode = httpEx.GetHttpCode(); switch (httpEx.GetHttpCode()) { case 400: action = "BadRequest"; break; case 401: action = "Unauthorized"; break; case 403: action = "Forbidden"; break; case 404: action = "PageNotFound"; break; case 500: action = "CustomError"; break; default: action = "CustomError"; break; } } else if (ex is AuthenticationException) { action = "Forbidden"; statusCode = 403; } httpContext.ClearError(); httpContext.Response.Clear(); httpContext.Response.StatusCode = statusCode; httpContext.Response.TrySkipIisCustomErrors = true; routeData.Values["controller"] = "Error"; routeData.Values["action"] = action; controller.ViewData.Model = new HandleErrorInfo(ex, currentController, currentAction); ((IController)controller).Execute(new RequestContext(new HttpContextWrapper(httpContext), routeData)); } }
ErrorController.cs
public class ErrorController : Controller { public ActionResult PageNotFound() { Response.StatusCode = (int)HttpStatusCode.NotFound; return View(); } public ActionResult CustomError() { Response.StatusCode = (int)HttpStatusCode.InternalServerError; return View(); } }
这就是我的全部。 没有注册HandleErrorAttribute
。
我发现这种方法不那么容易混淆和扩展。 希望这有助于某人。
将customErrors
设置为on
应足以在本地查看结果。
当您在全局注册HandleErrorAttribute
,您不需要使用它来修饰您的action方法,因为它将默认应用。
public class TestController : Controller { public ActionResult Index() { throw new Exception("oops"); return View(); } }
只要您在HandleErrorAttribute
中注册了HandleErrorAttribute
filterConfig
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
在Global.asax.cs
中的Application_Start()
中它应该可以工作。
如果您要创建自定义错误页面,我建议您阅读此博文
Mvc自定义错误页面
我在几乎所有的应用程序中使用DI。 即使您不使用dependency injection – 它对于MVC(Web API)应用程序的全局exception处理程序也非常有用。
我喜欢@ SBirthare的方法 – 但我会把它放在任何IoC都能解决的类中。
我更喜欢Autofac – 但是将@ SBirthare的技术与一些DI相结合应该可以为您提供一个集中的地方来配置您的exception处理 – 而且还能够注册不同类型的exception处理(如果您需要)。
这就是我传统上做的事情:
public abstract class ExceptionHandlerService : IExceptionHandlerService { ILoggingService _loggingSerivce; protected ExceptionHandlerService(ILoggingService loggingService) { //Doing this allows my IoC component to resolve whatever I have //configured to log "stuff" _loggingService = loggingService; } public virtual void HandleException(Exception exception) { //I use elmah a alot - and this can handle WebAPI //or Task.Factory ()=> things where the context is null if (Elmah.ErrorSignal.FromCurrentContext() != null) { Elmah.ErrorSignal.FromCurrentContext().Raise(exception); } else { ErrorLog.GetDefault(null).Log(new Error(exception)); } _loggingService.Log("something happened", exception) } }
现在你需要注册这个
builder.RegisterType().As
在MVC应用程序中 - 您需要实现一个实现IExceptionFilter的类
public class CustomHandleError : IExceptionFilter { private readonly IExceptionHandlerService _exceptionHandlerService; public CustomHandleError(IExceptionHandlerService exceptionHandlerService) { _exceptionHandlerService = exceptionHandlerService; } public void OnException(ExceptionContext filterContext) { _exceptionHandlerService.HandleException(filterContext.Exception); } }
在Autofac中注册filter
builder.Register(ctx => new CustomHandleError(ctx.Resolve())).AsExceptionFilterFor();
我总是定义一个我所有其他控制器派生自的BaseController。 您可以使用相同的技术定义授权filter。 现在所有控制器都是安全的并且处理exception
现在你不需要任何类的属性 - 代码在一个地方。
我没有任何尝试catch的任何地方,所以我们可以在exception处理程序捕获exception时保留堆栈跟踪。
如果你将这种技术与@ SBirthare结合使用 -
public abstract class ExceptionHandlerService : IExceptionHandlerService { ILoggingService _loggingSerivce; protected ExceptionHandlerService(ILoggingService loggingService) { //Doing this allows my IoC component to resolve whatever I have //configured to log "stuff" _loggingService = loggingService; } public virtual void HandleException(Exception exception) { //I use elmah a alot - and this can handle WebAPI //or Task.Factory ()=> things where the context is null if (Elmah.ErrorSignal.FromCurrentContext() != null) { Elmah.ErrorSignal.FromCurrentContext().Raise(exception); } else { ErrorLog.GetDefault(null).Log(new Error(exception)); } _loggingService.Log("something happened", exception) //re-direct appropriately var controller = new ErrorController(); var routeData = new RouteData(); var action = "CustomError"; var statusCode = 500; statusCode = exception.GetHttpCode(); switch (exception.GetHttpCode()) { case 400: action = "BadRequest"; break; case 401: action = "Unauthorized"; break; case 403: action = "Forbidden"; break; case 404: action = "PageNotFound"; break; case 500: action = "CustomError"; break; default: action = "CustomError"; break; } //I didn't add the Authentication Error because that should be a separate filter that Autofac resolves. var httpContext = ((MvcApplication)sender).Context; httpContext.ClearError(); httpContext.Response.Clear(); httpContext.Response.StatusCode = statusCode; httpContext.Response.TrySkipIisCustomErrors = true; routeData.Values["controller"] = "Error"; routeData.Values["action"] = action; controller.ViewData.Model = new HandleErrorInfo(ex, currentController, currentAction); ((IController)controller).Execute(new RequestContext(new HttpContextWrapper(httpContext), routeData)); }
}
这实现了同样的目的 - 但现在您使用dependency injection,您可以注册多个ExceptionHandler并根据exception类型解析服务。
我无法弄清楚HandleErrorAttribute实际上做了什么。 它似乎什么都不做。
无论如何,在OnException()中只需要4行代码就可以使其表现得像我预期的那样:
// Copyright(c) 2016 Google Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); you may not // use this file except in compliance with the License. You may obtain a copy of // the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the // License for the specific language governing permissions and limitations under // the License. using System.Web.Mvc; namespace GoogleCloudSamples { internal class CustomHandleErrorAttribute : HandleErrorAttribute { public override void OnException(ExceptionContext filterContext) { // Why oh Why doesn't base.OnException(filterContext) do this? ViewDataDictionary viewData = new ViewDataDictionary(filterContext); filterContext.Result = new ViewResult() { ViewName = "Error", ViewData = viewData }; filterContext.HttpContext.Response.StatusCode = 500; filterContext.ExceptionHandled = true; } } public class FilterConfig { public static void RegisterGlobalFilters(GlobalFilterCollection filters) { filters.Add(new CustomHandleErrorAttribute()); } } }
解:
首先,删除web.config文件中的“defaultRedirect”属性。
其次,在你的FilterConfig.cs文件中,我发现有些人正在引用一些MVC模板中的“HandleErrorAttribute”的“自定义类”版本,一些开发人员已经在线创建了其他版本。 这些将断开ORIGINAL MVC HandlerErrorAttribute类与默认的错误视图页面。
您可以通过确保使用“using”语句在FilterConfig文件中引用ORIGINAL Microsoft MVC HandleErrorAttribute来解决此问题,如下所示,向其添加全局“错误”视图以确保现在再次调用该页面。 见下文….
using System.Web.Mvc;//add this to make sure you are referencing the MVC version public class FilterConfig { public static void Configure(System.Web.Mvc.GlobalFilterCollection filters) { // Note I added {View = "Error"}. This applies the Error View Page to all Actions in all Controller classes filters.Add(new HandleErrorAttribute { View = "Error" }); } }
这将全局将共享视图文件夹中的“Error.cshtml”视图分配给抛出的每个exception,当然除了404和其他服务器错误。 您可以通过以上开发人员概述的其他方式处理的那些。 但是,这应该将您的.NETexception路由到自定义错误页面。 – 斯托克利