控制器中的异常处理(ASP.NET MVC)

26

当你的控制器中的一个操作调用了自己代码引发了异常,应该如何处理?我看到很多最佳实践的例子中根本没有try-catch语句。例如从存储库访问数据:

public ViewResult Index()
{
    IList<CustomModel> customModels = _customModelRepository.GetAll();
    return View(customModels);
}

如果我们使用像 Entity Framework 这样的 ORM,显然这段代码可能会抛出异常,如果调用的是它无法访问的数据库。

然而,我所能看到的是,异常将会传播并向用户显示一个不友好的错误消息。

我知道 HandleError 属性,但我了解它主要用于在发生未处理的异常时重定向到错误页面。

当然,这段代码可以被包裹在 try-catch 语句中,但是如果你有更多逻辑,它并不容易分离。

public ViewResult Index()
{
    if (ValidationCheck())
    {
        IList<CustomModel> customModels = new List<CustomModel>();
        try
        {
            customModels = _customModelRepository.GetAll();
        }
        catch (SqlException ex)
        {
            // Handle exception
        }

        if (CustomModelsAreValid(customModels))
            // Do something
        else
            // Do something else
    }

    return View();
}

之前我已经将可能会抛出异常的代码,例如数据库调用,提取到了一个名为 DataProvider 的类中,该类处理错误并返回消息以供显示给用户。

我在思考如何最好地处理这个问题?我并不总是想返回到错误页面,因为一些异常不应该这样做。相反,应该使用普通视图显示向用户显示错误消息。我的以前的方法正确吗,还是有更好的解决方案?

4个回答

24
我有三个方法来显示更加用户友好的信息:
  1. 利用全局异常处理程序。在MVC中,可以使用Global.asax中的Application_Error方法。点击这里了解如何使用:http://msdn.microsoft.com/zh-cn/library/24395wz3(v=vs.100).aspx
  2. 将Exception类派生为UserFriendlyException类。我尽可能在我的所有基础服务类中抛出UserFriendlyException而不是普通的Exception。我总是尝试在这些自定义异常中放置用户可理解的消息。主要目的是能够在Application_Error方法中对异常进行类型检查。对于UserFriendlyExceptions,我只使用我在服务底层设置的用户友好消息,例如“嘿!91度不是有效的纬度值!”。如果是普通异常,则是一些我没有处理的情况,因此我会显示更通用的错误消息,例如“糟糕,发生了错误!我们将尽最大努力修复它!”。
  3. 我还创建了一个ErrorController,负责呈现用户友好的视图或JSON。这是从Application_Error方法调用其方法的控制器。

编辑:

我想提一下 ASP.NET Web API,因为它与此密切相关。由于 Web API 端点的使用者不一定是浏览器,所以我喜欢稍微不同地处理错误。 我仍然使用“FriendlyException”(#2),但是不会重定向到 ErrorController,而是让所有端点返回包含一个错误属性的某种基础类型。 因此,如果异常一直冒泡到 Web API 控制器,我确保将该错误放入 API 响应的错误属性中。 此错误消息将是从 API 控制器依赖的类中冒出的友好消息,或者如果异常类型不是 FriendlyException,则为通用消息。 这样,消费客户端只需检查 API 响应的错误属性是否为空即可。 如果存在错误,则显示消息,如果没有,则继续进行正常操作。 好处是,由于友好消息的概念,消息可能比通用的“错误!” 消息更有意义。 我在使用 Xamarin 编写移动应用程序时使用此策略,其中我可以在我的 Web 服务和 iOS / Android 应用程序之间共享 C# 类型。

1
我也同意Patrick Desjardins的回答:覆盖OnException是处理错误的好方法。特别是如果它在一个基本控制器中,所有其他控制器都继承自该控制器。 - NovaJoe

20

使用Asp.Net MVC,您还可以为控制器覆盖OnException方法。

protected override void OnException(ExceptionContext filterContext)
{
    if (filterContext.ExceptionHandled)
    {
        return;
    }
    filterContext.Result = new ViewResult
    {
        ViewName = ...
    };
    filterContext.ExceptionHandled = true;
}

如果您想要的话,这将允许您重定向到一个自定义错误页面,并提供与异常相关的消息。


可能会有一些人反对:这个答案有什么问题吗? - Askolein
1
@Askolein 我也不理解。如果你像我一样认为这是一个可能的解决方案,可以点赞将其设置回0。 - Patrick Desjardins
4
我同意这是一个好的答案。在自定义基本控制器中重写OnException方法,并让所有其他控制器都继承它。结合用户友好异常的概念,这将是一个不错的解决方案。 - NovaJoe
这个问题类似于这个问题,有什么解决方法吗? - Jack
一个警告 - OnException不能捕获控制器引发的所有异常(遗憾),只能捕获在Actions和一些与Action相关的方法中引发的异常。因此,在controller.initialize()中引发的异常将不会被捕获,但在controller.OnActionExecuting()中引发的异常将会被捕获。 - EamonnM

3
我使用了一个OnException重写,因为我有几个项目引用了一个处理错误的控制器的控制器:
Security/HandleErrorsController.cs
protected override void OnException(ExceptionContext filterContext) 
{
    MyLogger.Error(filterContext.Exception);   //method for log in EventViewer

    if (filterContext.ExceptionHandled)
        return;

    filterContext.HttpContext.Response.StatusCode = (int)System.Net.HttpStatusCode.InternalServerError;

    filterContext.Result = new JsonResult
    {
        Data = new
        {
            Success = false, 
            Error = "Please report to admin.",
            ErrorText = filterContext.Exception.Message,
            Stack = filterContext.Exception.StackTrace
        },
        JsonRequestBehavior = JsonRequestBehavior.AllowGet
    };
    filterContext.ExceptionHandled = true;
}

5
将异常内容(例如消息和堆栈跟踪)直接返回给用户是一个非常糟糕的想法。 - Justin Skiles
从安全角度考虑,将异常返回给用户是一种不良实践。 - CervEd

0

像这样的问题并不是很有建设性,因为答案总是“取决于情况”,因为处理错误的方式有很多种。

许多人喜欢使用HandleError方法,因为任何异常基本上都是无法恢复的。我的意思是,如果您无法返回对象,那么您将怎么做?您仍然会向他们显示错误,对吧?

问题在于,您想如何显示错误。如果显示错误页面可以接受,那么HandleError就可以正常工作,并提供一个方便的地方来记录错误。如果您正在使用Ajax或想要更高级的东西,则需要开发一种方法来实现。

您谈到了一个DataProvider类。那基本上就是您的Repository。为什么不将其构建到您的存储库中呢?


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接