MVC,jQuery和处理错误的最佳实践

时间:2009-08-04 16:01:15

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

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

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

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

所以:

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

您是否有关于如何使控制器操作在正常调用和通过Ajax时正常工作的提示?在编写动作方法时,这是一个令人讨厌的问题。由于返回类型,我可能需要不同的结果,具体取决于调用者。如果没有两种行动方法,有没有这样做呢?

处理MVC操作错误的一般策略是什么?你重定向到错误页面了很多吗?你重定向到另一个页面?

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

由于

7 个答案:

答案 0 :(得分:3)

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

答案 1 :(得分: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,所以写这个可能会有更优雅的方式。

答案 2 :(得分:1)

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

也就是说,我在其他语言中使用了许多MVC框架,并完成了不少django项目。随着时间的推移,我有点使用template inclusion来演变一种模式,用django来处理这类事情。

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

我想在ASP.NET MVC世界中,UserControls可能相当于partials? This article展示了如何实现与我所描述的类似的东西(尽管如此,这篇文章似乎过时了,事情可能会发生变化)。

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

答案 3 :(得分:1)

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

答案 4 :(得分:0)

我们从不重定向(为什么让用户反复按'后退'按钮以防他们不理解需要输入特定字段的内容?),我们现场显示错误,无论是AJAX还是不是(页面上“点”的位置完全取决于你,对于所有AJAX请求,我们只是在页面顶部显示一个彩色条,就像stackoverflow对于第一个使用者一样,只有我们不会推送剩下的内容下)。

对于表单验证,您可以同时执行服务器端和客户端。我们总是尝试在一个独特的容器(客户端)中显示表单顶部的服务器端错误 - 在提交的相关字段旁边。一旦页面从服务器返回服务器端验证错误,用户最初只会看到服务器端的,所以没有重复。

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

答案 5 :(得分:0)

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

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

答案 6 :(得分:0)

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

不幸的是,这种方法不适用于AJAX发布的表单。

对于AJAX规则,它就像在返回的JSON对象中添加Errors数组一样简单。在您使用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);
  }
}