【问题标题】:Best Practices for MVC, jQuery and Handling ErrorsMVC、jQuery 和处理错误的最佳实践
【发布时间】:2010-11-16 17:39:01
【问题描述】:

有没有人有优雅的方法来处理 ASP.Net MVC 中的错误?在处理对控制器操作的请求时,我经常遇到问题,其中操作可用于普通请求和 AJAX 请求。我遇到的问题是找到一种优雅的方式来处理这些问题。

例如,我如何处理验证错误?理想情况下,我想通过 AJAX 将表单提交到服务器,然后返回操作引发的任何错误并将它们显示在页面上,但是当客户端关闭 JavaScript 时,同样可以通过正常的回发工作。我知道我可以在输入时使用 jQuery Validation Plugin,但它不一样,考虑到对要验证的数据的限制将在两个地方指定(我的 nHibernate 验证映射和 JavaScript 文件),这也不理想.

当用户请求一个不存在的记录时怎么办?我应该重定向到 404 页面吗?如果请求是通过 Ajax 发出的(例如,加载到对话框中)怎么办。

所以:

您如何处理使用 Ajax 调用控制器操作时引发的错误?尤其是模型状态错误(即验证)。我可以通过 JSON 发送吗?

您有关于如何使控制器动作在正常调用和通过 Ajax 时正常工作的技巧吗?在编写操作方法时,这是一个烦人的问题。由于返回类型,我可能想要一个不同的结果,具体取决于调用者。无论如何,如果没有两个动作方法就可以做到这一点吗?

您在 MVC 上处理操作中的错误的一般策略是什么?您是否经常重定向到错误页面?您是否重定向到另一个页面?

编辑:我认为问题的一部分是我希望发生不同的事情,所以如果出现错误,我希望停止任何进展,直到它修复并因此发回错误。否则我可能想更新页面的不同区域。但是,如果我有一个 return,我怎么知道它是成功还是失败,而不将它包装成一个具有指示属性的对象(从而使使用局部视图变得更加困难)

谢谢

【问题讨论】:

  • 我也想听听你第二个问题的答案。过去我在试图解决这个问题时遇到了一些重大困难。
  • 可以检测你的请求是否是AJAX调用并返回合适的数据。请参阅我的(粗略)示例。

标签: jquery asp.net-mvc ajax error-handling


【解决方案1】:

AJAX 调用不是页面刷新,所以我绝对不会重定向到 403、404 等。我会显示一个客户端对话框,适当地解释请求的意外结果。您的控制器可以将验证失败的结果返回给 AJAX 调用,其方式与返回成功数据的方式类似,因此您的对话框也可以显示此场景中所需的任何内容。

【讨论】:

  • 在这种情况下,对话框会在成功时关闭,但无论如何,如果返回的数据在成功和错误之间不同,你会怎么做
  • 如果成功,关闭对话框,如果失败,显示适当的消息?
  • AdamRalph 的建议正是我们所做的。我构建了一个模态返回的自定义视图“_NotificationView”,在ajax返回时我检查html是否为空白,如果是,我关闭对话框,否则我向用户显示消息并保持对话框打开。
【解决方案2】:

您有关于如何使控制器动作在正常调用和通过 Ajax 时正常工作的技巧吗?这是编写动作方法时的一个恼人问题。

是的,是的,我愿意。我们还解决了一个类似的问题——我们希望应用程序有一堆表单,这些表单通常可以通过 ajax 调用,但可以正常命中。此外,我们不希望 javascript 中出现一大堆重复的逻辑。无论如何,我们想出的技术是使用一对 ActionFilterAttributes 来拦截表单,然后使用一点 javascript 来连接表单以进行 ajax 处理。

首先,对于 ajax 请求,我们只是想换出母版页:

    private readonly string masterToReplace;

    /// <summary>
    /// Initializes an Ajax Master Page Switcharoo
    /// </summary>
    /// <param name="ajaxMaster">Master page for ajax requests</param>
    public AjaxMasterPageInjectorAttribute(string ajaxMaster)
    {
        this.masterToReplace = ajaxMaster;
    }

    public override void OnResultExecuting(ResultExecutingContext filterContext)
    {
        if (!filterContext.HttpContext.Request.IsAjaxRequest() || !(filterContext.Result is ViewResult)) return;
        ViewResult vr = (ViewResult) filterContext.Result;
        vr.MasterName = masterToReplace;
    }
}

在返回端,我们使用 xVal 来验证客户端,因此不应该获得太多无效数据,但仍然可以获得一些。为此,我们只使用普通验证并让 aciton 方法返回带有验证消息的表单。成功的帖子通常会获得重定向奖励。无论如何,我们为成功案例做了一点 json 注入:

/// <summary>
/// Intercepts the response and stuffs in Json commands if the request is ajax and the request returns a RedirectToRoute result.
/// </summary>
public class JsonUpdateInterceptorAttribute : ActionFilterAttribute 
{
    public override void OnActionExecuted(ActionExecutedContext filterContext)
    {
        if (filterContext.HttpContext.Request.IsAjaxRequest())
        {
            JsonResult jr = new JsonResult();
            if (filterContext.Result is RedirectResult)
            {
                RedirectResult rr = (RedirectResult) filterContext.Result;
                jr.Data = new {command = "redirect", content = rr.Url};
            }
            if (filterContext.Result is RedirectToRouteResult)
            {
                RedirectToRouteResult rrr = (RedirectToRouteResult) filterContext.Result;
                VirtualPathData vpd = RouteTable.Routes.GetVirtualPath(filterContext.RequestContext, rrr.RouteValues);
                jr.Data = new {command = "redirect", content = vpd.VirtualPath};
            }
            if (jr.Data != null)
            {
                filterContext.Result = jr;
            }
        }
    }
}

最后一个技巧是使用一个小的 javascript 对象将所有内容联系在一起:

function AjaxFormSwitcher(form, outputTarget, doValidation) {
    this.doValidation = doValidation;
    this.outputTarget = outputTarget;
    this.targetForm = form;
}

AjaxFormSwitcher.prototype.switchFormToAjax = function() {
    var afs = this;
    var opts = {
        beforeSubmit: this.doValidation ? afs.checkValidation : null,
        complete: function(xmlHttp, status){ afs.processResult(afs, xmlHttp, status); },
        clearForm: false
    };
    this.targetForm.ajaxForm(opts);
}

AjaxFormSwitcher.prototype.checkValidation = function(formData, jqForm, options) {
    jqForm.validate();
    return jqForm.valid();
}

AjaxFormSwitcher.prototype.processResult = function(afs, xmlHttp, status) {
    if (xmlHttp == null) return;
    var r = xmlHttp;
    var c = r.getResponseHeader("content-type");
    if (c.match("json") != null) {
        var json = eval("(" + r.responseText + ")");
        afs.processJsonRedirect(json);
    }
    if (c.match("html") != null) {
        afs.outputTarget.html(r.responseText);
    }
}

AjaxFormSwitcher.prototype.processJsonRedirect = function(data) {
    if (data!=null) {
        switch (data.command) {
            case 'redirect':
                window.location.href = data.content;
                break;
        }
    }
}

那个小脚本处理诸如连接表单以执行 ajax 和处理结果(json 命令或显示在目标中的 html)之类的事情。诚然,我不擅长编写 javascript,所以可能有一种更优雅的方式来编写它。

【讨论】:

  • 不错的帖子,有一些有用的想法。
  • “妈妈!Phineas 和 Ferb 在 Stack Overflow 上回答问题!”
  • 这是迪斯尼的儿童节目(是的,我有一个孩子),他们有一个连续的笑话,他们说,“是的,是的,我愿意”。
【解决方案3】:

免责声明:我没有做过太多的 ASP.NET MVC 编程。

也就是说,我已经使用了许多其他语言的 MVC 框架,并且完成了相当多的 django 项目。随着时间的推移,我已经进化出一种模式来处理 django 中的这类事情,使用template inclusion

基本上,我创建了一个包含表单(以及任何验证错误)的部分模板,该模板包含在主模板中。视图(或控制器,在您的情况下)然后在这两个模板之间进行选择:AJAX 请求获取部分模板,常规请求获取完整模板。在主模板中,我使用jQuery form plugin 通过 AJAX 提交表单,并简单地将当前表单替换为来自服务器的数据:如果验证失败,我会得到一个带有突出显示字段的表单,并且顶部有一个错误列表;如果 POST 成功,表单将替换为成功消息,或者更适合您的情况。

我猜在 ASP.NET MVC 世界中,UserControls 可能相当于部分? This article 展示了如何实现类似于我所描述的内容(不过,这篇文章似乎过时了,事情可能已经改变了)。

要回答您的第二个问题,我认为您不应该在“找不到页面”上进行重定向——一个返回 302,另一个应该返回 404;一个不是错误条件,另一个是。如果您丢失了状态代码,您的 javascript 会变得更加复杂(因为您必须以某种方式测试实际返回的数据,才能弄清楚发生了什么)。这些twoposts 应该会给你一些关于如何实现HTTP 友好的错误页面的想法。 Django 做了类似的事情(引发 Http404 异常,返回 404 状态代码并可选地呈现 404.html 模板)。

【讨论】:

    【解决方案4】:

    Ajax 和普通请求的操作选择器属性

    区分 Ajax 请求和普通请求的最佳方法是编写自定义操作选择器属性 (AjaxOnlyAtribute) 并提供两种操作方法,每个方法处理自己的情况。

    [HttpPost]
    [AjaxOnly]
    [ActionName("Add")]
    public ActionResult AddAjax(Entity data) { ... }
    
    [HttpPost]
    [ActionName("Add")]
    public ActionResult AddNormal(Entity data) { ... }
    

    通过这种方式,您可以避免代码分支并保持较小的代码占用空间,同时还可以保持对代码的控制。仅将配对操作提供给需要它们的操作。

    处理 Ajax 请求中的验证错误

    处理 Ajax 调用中的验证错误(或基本上任何其他错误)可以使用异常操作过滤器来完成。我写了一个名为ModelStateExceptionAttribute 的特别的。这就是它的完成方式:

    [HandleModelStateException]
    public ActionResult SomeAjaxAction(Data data)
    {
        if (!this.ModelState.IsValid)
        {
            throw new ModelStateException(this.ModelState);
        }
        // usual code from here on
    }
    

    您可能已经使用 jQuery 发出了 Ajax 请求,因此错误 ajax 函数可以轻松处理此类错误:

    $.ajax({
        type: "POST",
        url: "someURL",
        success: function(data, status, xhr) {
            // handle success
        },
        error: function(xhr, status, err) {
            // handle error
        }
    });
    

    您可以阅读有关此方法的详细博客文章here

    【讨论】:

      【解决方案5】:

      我们从不重定向(为什么要让用户反复按“返回”按钮,以防他们不明白需要在特定字段中输入什么?),我们会在现场显示错误,无论是不是 AJAX(页面上“点”的位置完全取决于您,对于所有 AJAX 请求,我们只在页面顶部显示一个彩色条,就像 stackoverflow 对先到者所做的那样,只有我们的不推送其余内容下)。

      至于表单验证,您可以在服务器端和客户端进行。我们总是尝试在客户端的独特容器中在表单顶部显示服务器端错误 - 就在提交时有问题的字段旁边。一旦页面从服务器返回并出现服务器端验证错误,用户最初只会看到服务器端的,因此不会出现重复。

      对于在两个地方指定的数据,我不确定我是否理解,因为我从未处理过 nHibernate 验证映射。

      【讨论】:

      • 他不想定义他的验证逻辑两次:他已经使用他的 nHibernate 验证映射属性(应用于类属性)定义了它,并且不想在他的 JQuery 中再做一次客户端javascript。我假设他希望能够从他的 nHibernate 映射中的验证创建客户端 javascript 验证。
      • 是的,我知道这种支持将存在于 MVC 2 中,但在那之前。
      • 嗯.. 那么 nHibernate 会被认为是服务器端的吗?尽管如此,在我的理解中,客户端验证应该始终补充服务器端验证,以防止不正确的数据流入数据库,主要是因为以后修复以及出于安全目的可能真的很乏味。
      • 是的,服务器端已经完成。这真的不是什么大问题。
      【解决方案6】:

      保留两个错误视图,一个用于页面的正常(完整)显示,另一个仅在 XML 或 JSON 中呈现错误。

      永远不要验证客户端,因为用户可以轻松绕过它。对于实时验证,只需向服务器运行一个 AJAX 请求进行验证,这样您只需编写一次验证代码。

      【讨论】:

        【解决方案7】:

        我过去曾使用过 xVal,以及一些自主开发的基于反射的 JS 规则生成器。这个想法是您定义一次规则(在您的情况下,通过 nHibernate),HTML 帮助程序反映您的属性并动态生成客户端验证代码(使用 jQuery.validation 插件)。拥有响应式客户端 UI 的正确方法,同时仍强制执行服务器端验证。

        很遗憾,此方法不适用于 AJAX 发布的表单。

        对于 AJAX 规则,只需将 Errors 数组添加到返回的 JSON 对象中即可。在您使用 AJAX 的任何地方,只需检查错误的长度(这就是 ModelState.IsValid 所做的一切)并显示错误。您可以使用IsAjaxRequest method 来检测 AJAX 调用:

        public ActionResult PostForm(MyModel thing)
        {
          UpdateModel(thing);
        
          if (this.Request.IsAjaxRequest() == false)
          {
            return View();
          }
          else
          {
            foreach(var error in ModelState.Errors)
            {
              MyJsonObject.Errors.Add(error.Message); 
            }
            return JsonResult(MyJsonObject);
          }
        }
        

        【讨论】:

          猜你喜欢
          • 2016-06-06
          • 1970-01-01
          • 1970-01-01
          • 2011-05-30
          • 1970-01-01
          • 2018-09-12
          • 2017-11-26
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多