处理asp.net mvc中的错误和异常

时间:2011-10-27 22:12:42

标签: asp.net-mvc error-handling

在asp.net mvc结构中处理错误和捕获异常的可接受方法是什么? 普遍的共识是异常泡沫化。 那么什么层(视图或控制器)你会处理异常(捕获/显示用户友好的文本等)。我的预感是它是在控制器中完成的? 编辑: 我想避免在每个控制器操作中重复相同的错误处理代码 ..因此,我正在寻找一个简洁的示例,说明如何在不重复相同代码的情况下实现错误处理。

7 个答案:

答案 0 :(得分:16)

处理控制器中的异常可能会导致大量重复代码。更好的方法是在扩展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
{
...
}

答案 1 :(得分:3)

你的预感是正确的。 Definetely需要是控制器。这是一个例子:

[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);
}

备注:

  • 首先检查ModelState。如果无效,请返回。搞定它。
  • 不要不断更新日志记录服务。使用单例实例,并使用DI将其注入控制器,以便您在界面上工作,例如ILoggingService。这也意味着您可以将其他功能添加到日志记录服务中(例如,通过电子邮件发送支持)。
  • 您的较低层(服务,存储库等)可能会抛出错误(自定义或内置),因此控制器捕获它们非常重要,因为它是“聚合器”,负责什么用于客户端和服务器之间的流程。
  • 使用ModelState.AddModelError添加错误,以便View可以显示错误。您还可以使用可能是用户友好的自定义例外,并将其显示给用户。对于较低级别的错误(SQL等),您只需向ModelState添加一般错误(“抱歉,发生错误。请稍后再试”)。

答案 2 :(得分:2)

它看起来并不那么容易。

如果需要集中式异常处理,最快的方法是覆盖Controller中的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)以避免很多其他问题(例如,使用缓存,根本不会执行您的操作,而始终会执行该属性)。 p>

请参阅 here 了解更多信息

答案 3 :(得分:1)

您可以创建自定义基本控制器并从Base Controller类继承。然后覆盖customcontroller中的OnException。然后让每个控制器继承自新的自定义基本控制器。

或者,您可以覆盖global.asax

中的Application_Error事件

答案 4 :(得分:0)

这实际上取决于你的目标。

对于在任何错误上显示自定义消息的简单方案,您可以在web.config中使用旧的自定义错误配置。

请注意,这将用于甚至无法到达控制器的错误。就像在URL中没有正确编码特殊值的问题时一样。

HandleError属性或您自己的自定义属性允许您在其他方案中获得所需的更精细控件。

请注意,如果要将自定义句柄错误属性应用于所有控制器,可以将其应用为全局操作过滤器。这样您就不需要将它明确地应用于每个控制器。

答案 5 :(得分:0)

我通常会覆盖Global.asax文件中的Application_Error,并将用户重定向到每个异常的通用异常页面,然后发送包含一些详细信息的电子邮件。很简单。这是我通常使用的:

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");
}

答案 6 :(得分:0)

虽然我同意异常日志记录操作应集中在BASE控制器级别以保持一致性...我还认为除了日志记录要求外,还应考虑在UI中显示用户友好错误消息的标准方法。在控制器级别捕获并记录的异常可以包含在“友好异常”中。并传递给中央异常处理器。共享视图文件夹中的error.cshtml文件是处理和显示"友好异常"的好地方。包装"实际例外"发生并在Controller级别记录并传递给error.cshtml。

我使用自定义基本控件并使用从Global.asax Application_Start事件调用的FilterConfig类中的RegisterGolbalFilters方法设置HandleErrorAttribute

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 Application_Start Code

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);

}

那部分非常标准......你接下来做的事情确实对用户产生了影响。 我使用错误处理类来集中处理异常并显示"友好"基于导致异常的Controller和Action向用户发送基于上下文的消息。在下面的示例代码中,我已将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