在控制器中处理异常可能会导致大量的重复代码。更好的方法是在扩展HandleErrorAttribute
的操作筛选器中处理异常。在那里,您可以记录异常,然后重定向到显示友好消息指示用户出现问题的页面。
但是,在某些情况下,您需要在控制器方法中处理异常,例如,当您可以从异常中恢复并向用户显示适当的消息时,例如您的业务层引发的异常表示提供的值无效。在这种情况下,您应该捕获特定的异常并向用户显示相同的视图以及适当的消息。
编辑:
public class CustomErrorHandlerAttribute : HandleErrorAttribute
{
public override void OnException(ExceptionContext filterContext)
{
var logger = log4net.LogManager.GetLogger("SomeLoggerHere");
logger.Error("An unhandled error occurred", filterContext.Exception);
if (filterContext.HttpContext.Request.IsAjaxRequest())
{
filterContext.HttpContext.Response.Clear();
filterContext.HttpContext.Response.Status = "500 Internal Server Error";
filterContext.Result = new JsonResult { Data = new { ErrorMessage = filterContext.Exception.Message } };
filterContext.ExceptionHandled = true;
}
else
{
base.OnException(filterContext);
}
}
编辑2: 然后你可以像这样使用该属性:
[CustomErrorHandler]
public class AnyController : Controller
{
...
}
[HttpPost]
public ActionResult Create(OrderViewModel model)
{
if (!ModelState.IsValid)
return View(model);
try
{
repository.Save(model);
unitOfWork.Commit();
return RedirectToAction("Index");
}
catch (Exception exc)
{
_loggingService.Error(exc);
ModelState.AddModelError("KeyUsedInView", exc.Message); // or, show a generic error.
}
return View(model);
}
注意:
ILoggingService
。这还意味着您可以将其他功能添加到日志记录服务中(例如向支持部门发送电子邮件)。ModelState.AddModelError
添加错误,以便视图可以显示它们。您还可以使用自定义异常,这些异常可能更友好,并且可以向用户显示它们。对于较低级别的错误(SQL等),可以在ModelState中添加通用错误(“对不起,发生了错误,请稍后再试”)。这并不像看起来那么容易。
如果您需要集中式异常处理,最快的方法是在控制器中重写OnException方法。
[NonAction]
protected override void OnException(ExceptionContext filterContext)
{
this.Session["ErrorException"] = filterContext.Exception;
if (filterContext.Exception.GetType() == typeof(PEDException))
{
// Mark exception as handled
filterContext.ExceptionHandled = true;
// ... logging, etc
// Redirect
filterContext.Result = this.RedirectToAction( "ShowError", "Errors");
}
base.OnException(filterContext);
}
从这个方法中可以看到,我已经捕获了所有未处理的PEDException异常,如果您的bl引发自定义异常,我认为拥有一个带有OnException方法的基本控制器可能是一个很好的解决方案,但是在某些情况下,这可能潜在地危险。通常来说,最好定义一个定制的属性(扩展ErrorAttributeFilter),以避免出现许多其他问题(例如缓存问题,您的操作根本不会执行,而属性始终会执行)。
请参见此处获取更多信息。
虽然我同意将异常记录操作集中在基础控制器级别以保持一致性,但我认为应该考虑一种标准的方式来显示用户友好型的UI错误消息,并与记录要求相结合。在控制器级别捕获并记录的异常可以被包装成“友好异常”,然后传递给一个中央异常处理器。共享视图文件夹中的error.cshtml文件是处理和显示包装了在控制器级别发生并已记录下来的“实际异常”的“友好异常”的好地方。
我正在使用自定义基础控件并设置HandleErrorAttribute,通过从Global.asax Application_Start事件调用FilterConfig类中的RegisterGolbalFilters方法。
Base_Controller代码
public class Base_Controller : Controller
{
protected override void OnException(ExceptionContext filterContext)
{
Exception e = filterContext.Exception;
//Custom Exception Logging Here
//Log Exception e
//Elmah.Mvc.ElmahController ec = new Elmah.Mvc.ElmahController();
base.OnException(filterContext);
}
}
FilterConfig.cs 代码
public class FilterConfig
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new HandleErrorAttribute());
}
}
Global.asax 应用程序启动代码
void Application_Start(object sender, EventArgs e)
{
// Code that runs on application startup
AreaRegistration.RegisterAllAreas();
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
}
那部分是相当标准的... 接下来你所做的才真正对用户有所影响。 我使用一个错误处理类来集中处理异常,并根据控制器和操作显示基于上下文的“友好”消息。在下面的示例代码中,我将catch块移动到Views/Shared文件夹中的error.cshtml页面中,以保持简单。下面的代码只是示例代码,因为友好的错误消息将根据应用程序上下文而改变,您可能希望将异常处理移入类中以便于维护。
//Check for Transport Exception with "Actual Exception" stored
//in the inner exception property
if (Model.Exception.InnerException != null)
{
errFriendly = Model.Exception.Message;
modelEx = Model.Exception.InnerException;
}
else
{
modelEx = Model.Exception;
}
try
{
throw modelEx;
}
catch (System.Data.SqlClient.SqlException ex)
{
//Display Landing page friendly error for exception caused by home controller
//Display generic data access error for all other controllers/actions
if (Model.ActionName == "Index" && Model.ControllerName == "Home")
{errFriendly = "Landing page cannot display product data...";}
else
{errFriendly = "Problem Accessing Data...";}
errType = ex.GetType().ToString();
errActual = ex.Message;
}
如需下载完整的代码示例,请参阅博客文章:http://www.prodataman.com/Blog/Post/119/MVC-Custom-Exception-Handling
protected void Application_Error(object sender, EventArgs e)
{
if (Request.Url.ToString().StartsWith("http://localhost:"))
return;
string msg;
Exception ex = Server.GetLastError().GetBaseException();
StringBuilder sb = new StringBuilder();
sb.AppendLine("Exception Found");
sb.AppendLine("Timestamp: " + System.DateTime.Now.ToString());
sb.AppendLine("Error in: " + Request.Url.ToString());
sb.AppendLine("Browser Version: " + Request.UserAgent.ToString());
sb.AppendLine("User IP: " + Request.UserHostAddress.ToString());
sb.AppendLine("Error Message: " + ex.Message);
sb.AppendLine("Stack Trace: " + ex.StackTrace);
msg = sb.ToString();
Server.ClearError();
YourMailHelper.SendException("Your Site Exception", msg);
Response.Redirect("~/Error.html");
}
这真的取决于你想要实现什么。
对于简单的场景,比如在任何错误情况下显示自定义消息,你可以在 web.config 中使用老式的自定义错误配置。
请注意,这将用于甚至没有到达控制器的错误。比如当 URL 中存在未正确编码的特殊值时。
HandleError 属性或您自己的自定义属性,允许您在其他场景中获得更精细的控制。
请注意,如果您想将自定义处理错误属性应用于所有控制器,可以将其作为全局操作筛选器应用。这样,您就不需要显式地将其应用于每个控制器。