【问题标题】:MVC5 Error Handling with JQuery Ajax使用 JQuery Ajax 处理 MVC5 错误
【发布时间】:2015-09-26 21:08:26
【问题描述】:

假设我有一个带有 ViewModel 和类似验证的标准表单设置。

视图模型

public class EditEventViewModel
{
    public int EventID { get; set; }
    [StringLength(10)]
    public string EventName { get; set; }
}

视图中的表单

@using (Html.BeginForm(null, null, FormMethod.Post, new {id="editEventForm"}))
{
    @Html.AntiForgeryToken()

    @Html.LabelFor(model => model.EventName)
    @Html.EditorFor(model => model.EventName)
    @Html.ValidationMessageFor(model => model.EventName)
}

控制器

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit([Bind(Include = "EventName")] EventViewModel model)
{
    //Get the specific record to be updated
    var eventRecord = (from e in db.Event
                       where e.EventID == model.EventID
                       select e).SingleOrDefault();

    //Update the data
    if (ModelState.IsValid)
    {
        eventRecord.EventName = model.EventName;
        db.SaveChanges();            
    }

    return RedirectToAction("Index");
}

现在,如果我进行常规表单提交并输入字符串长度超过 10 的 EventName,将触发模型错误,并在视图中的验证消息中通知我。

但是,我更喜欢像这样使用 JQuery AJax 提交表单。

$.ajax({
    type: "POST",
    url: "/EditEvent/Edit",
    data: $('#editEventForm').serialize(),
    success: function () {

    },
    error: function () {

    }
});

通过这种方式,我在客户端添加了一些 javascript 以在提交之前进行验证,但我仍然希望 ViewModel 中的数据注释作为备份。 当它到达控制器时,它仍然使用if (ModelState.IsValid) 进行检查。如果无效,则数据不会按设计写入数据库。

现在,我想知道在使用 JQuery 发布时,如果 ModelState 无效有效,我该怎么办。这不会触发常规验证,那么我该怎么做才能发回发生错误的信息?

if (ModelState.IsValid)
{
    eventRecord.EventName = model.EventName;
    db.SaveChanges();            
}
else
{
    //What can I do here to signify an error?
}

更新更多信息

我已经在 Web.config 中设置了自定义错误

<customErrors mode="On">

这会将错误路由到 Views/Shared/Error.cshtml 文件,我在该文件中输出有关收到请求的错误的信息。 无论如何我的控制器中的模型状态错误(或任何错误)都可以发送到这里吗?

@model System.Web.Mvc.HandleErrorInfo

@{
    Layout = null;
    ViewBag.Title = "Error";
}

<h1 class="text-danger">Error.</h1>
<h2 class="text-danger">An error occurred while processing your request.</h2>

<p>
    Controller: @Model.ControllerName   <br />
    Action: @Model.ActionName           <br />
    Exception: @Model.Exception.Message
</p>

再次更新

这是另一个处理所有响应片段的更新。 在我的控制器中,我将它放在 else 语句 throw new HttpException(500, "ModelState Invalid"); 中(else - 意味着 ModelState 无效)

这会触发我在 Web.config 中的自定义错误发送到 Views/Shared/Error.cshtml,(有点)但这只会像这样在 FireBug 中显示。实际页面不会去任何地方。知道我如何在父页面上得到这个吗? 如果这没有意义,我一直在使用here 描述的设置发送到我的自定义错误页面。这是一个 AJAX 调用的事实使得这项工作有点不同。

【问题讨论】:

  • 我唯一能想到的就是序列化模型错误并将它们显示在successstackoverflow.com/questions/2845852/…
  • 我已经成功地使控制器动作返回了一个响应对象,其中包括模型编辑器模板的 HTML 呈现,然后让客户端用新的编辑器 HTML 替换表单内容.这样,您可以利用 MVC 的内置错误显示和编辑器模板,同时仍然让您的页面以 AJAXy 方式运行。但是,设置它是一项相当大的工作。
  • 抛出 500 响应并不是真正的用户友好,并且在这种情况下是错误处理的错误实现。您需要向用户返回一条消息,让他们知道发生了验证错误并可能包含这些消息。在下面检查我的答案。
  • 也许我想错了。除了验证问题(由数据注释设置的问题)之外,还有什么会导致模型错误的吗?我已经在客户端使用 javascript 来通知用户。我只是想抓住任何可能在这一点上漏掉的错误,因为之前没有在 ModeState IsValid 上设置 else 语句。

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


【解决方案1】:

现在,您的控制器只是吞下任何错误 - 如果模型无效,它就不会保存,也不会向调用者提供任何反馈。您可以通过实际返回错误来解决此问题并让它向 jQuery 返回错误:

return new HttpStatusCodeResult(HttpStatusCode.BadRequest, "Some meaningful message");

关于您想要处理的内容以及返回多少细节的选项很多,但重点是您的控制器应该提供适合其执行的实际操作的响应。

更新

响应您的“再次更新”部分 - 我不会返回 500 - 这意味着“内部服务器错误”。如果您要返回错误,则 400(错误请求)可能更合适。无论如何,您的 ajax 调用的问题是它正在接收来自 Web 服务器(而不是您的主浏览器窗口)的错误响应。如果我不得不猜测,错误正在服务器端处理,而您的 jquery 正在接收来自您的自定义错误的 html 响应。

如果您打算保留自动错误处理,您应该只将它用于未处理的错误。因此,在您的控制器中,您将通过返回指示此状态的非错误响应来处理无效模型(我认为其他人提到了 json 响应)。然后,所有响应都会成功,但内容会告诉您的应用程序如何表现(适当地重定向等......)。

【讨论】:

  • 对,你完全了解情况。如果你手动调用这样的错误,那么 JQuery AJax“错误”函数会选择它,我可以在那里做一些事情。不确定您是否在最后看到了添加的部分,但有没有办法可以像其他错误一样将其路由到错误页面?
  • 好吧,您的控制器可以返回错误视图,但这不适用于您的 jquery 调用 - jquery 将通过错误页面的内容获得成功的响应。我之前没有使用过customErrors,所以如果没有一些实验我无法确定。我希望 jquery 得到错误响应并决定做什么——它可能只是在页面上显示一些东西,或者它可能重定向到错误页面。通过让控制器返回错误,您可以让消费者做出决定。
  • 这一切都说得通。正如我在上面对哈立德的回应。我想知道除了验证之外的任何其他事情是否会导致需要捕获的模型错误。如果只是验证,那么也许这些不需要以这种方式捕获。我想我需要尝试除以零错误之类的东西,看看它是如何被捕获和返回的。
  • modelstate 只会反映与您的模型的冲突(不正确的数据类型、缺少必填字段等)。如果您的控制器代码有任何其他错误(例如除以零)并且您不处理它,则会引发异常并且您可能会收到 500 响应(现在这是一个真正的内部服务器错误)。
  • 感谢您的帮助。我关心的是(并且您忽略了此验证部分)是如何在该控制器中获取任何错误以重定向到错误页面。如果我以非 ajax 方式调用该控制器/操作,它将转到我的 View/Shared/Error.cshtml 页面,但是通过 AJAX 调用(如我在上图中发布的那样),响应被困在 FireBug 中(例如) 而主页面什么也不做。
【解决方案2】:

因为您想要返回错误内容,我建议返回 JSON 响应(替代方法是部分视图,但这意味着让您的 JS 使用委托处理程序、重置表单验证等)。在这种情况下,如果 POST 是 AJAX,您将需要检测并返回 JSON,否则返回普通视图/重定向。如果所有验证都应该在客户端完成并且没有错误文本也没关系,您可能会返回异常结果并使用.ajax() 调用的错误处理程序来更新页面。不过,我发现浏览器对获取错误响应文本的支持是不一致的,因此,如果您想要实际的错误,您需要返回 200 OK 响应以及 JSON 中的消息。我的选择可能取决于确切的用例——例如,如果有几个我只能检测到服务器端的错误,我可能会使用带有错误内容的 OK 响应。如果只有少数或所有错误应该在客户端处理,那么我会走异常路线。

不应为此使用或不需要自定义错误处理程序。

带有状态结果的MVC

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit([Bind(Include = "EventName")] EventViewModel model)
{
    //Get the specific record to be updated
    var eventRecord = (from e in db.Event
                       where e.EventID == model.EventID
                       select e).SingleOrDefault();

    if (eventRecord == null)
    {
        if (Request.IsAjaxRequest())
        {
            return new HttpStatusCodeResult(HttpStatusCode.NotFound, "Event not found.");
        }

        ModelState.AddModelError("EventID", "Event not found.");
    }

    //Update the data
    if (ModelState.IsValid)
    {
        eventRecord.EventName = model.EventName;
        db.SaveChanges();            

        if (Request.IsAjaxRequest())
        {
            return Json(new { Url = Url.Action("Index") });
        }

        return RedirectToAction("Index");
    }

    if (Request.IsAjaxRequest())
    {
        return new HttpStatusCodeResult(HttpStatusCode.BadRequest, /* ... collate error messages ... */ "" );
    }

    return View(model);
}

带有状态结果的示例 JS

$.ajax({
   type: "POST",
   url: "/EditEvent/Edit",
   data: $('#editEventForm').serialize(),
})
.done(function(result) {
     window.location = result.Url;
})
.fail(function(xhr) {
    switch (xhr.status) {  // examples, extend as needed
       case 400:
          alert('some data was invalid. please check for errors and resubmit.');
          break;
       case 404:
          alert('Could not find event to update.');
          break;
    }     
});

带有错误内容的MVC

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit([Bind(Include = "EventName")] EventViewModel model)
{
    //Get the specific record to be updated
    var eventRecord = (from e in db.Event
                       where e.EventID == model.EventID
                       select e).SingleOrDefault();

    if (eventRecord == null)
    {
        if (Request.IsAjaxRequest())
        {
            return Json(new { Status = false, Message = "Event not found." });
        }

        ModelState.AddModelError("EventID", "Event not found.");
    }

    //Update the data
    if (ModelState.IsValid)
    {
        eventRecord.EventName = model.EventName;
        db.SaveChanges();            

        if (Request.IsAjaxRequest())
        {
            return Json(new { Status = true, Url = Url.Action("Index") });
        }

        return RedirectToAction("Index");
    }

    if (Request.IsAjaxRequest())
    {
        return Json(new 
        {
            Status = false,
            Message = "Invalid data",
            Errors = ModelState.Where((k,v) => v.Errors.Any())
                               .Select((k,v) => new
                               {
                                   Property = k,
                                   Messages = v.Select(e => e.ErrorMessage)
                                               .ToList()
                               })
                               .ToList()
        });
    }

    return View(model);
}

带有错误内容的示例 JS

$.ajax({
   type: "POST",
   url: "/EditEvent/Edit",
   data: $('#editEventForm').serialize(),
})
.done(function(result) {
     if (result.Status)
     {
         window.Location = result.Url;
     }
     // otherwise loop through errors and show the corresponding validation messages
})
.fail(function(xhr) {
    alert('A server error occurred.  Please try again later.');     
});

【讨论】:

    【解决方案3】:

    如果您正在创建一个 RESTful 服务,您应该返回一个适当的 http 代码,表明数据没有保存。大概是400。

    否则我会说返回任何对您有意义的值,并在客户端检查该值以确定调用是否失败。

    【讨论】:

      【解决方案4】:

      您需要对代码稍作改动。因为您使用 ajax 通信将数据发送到您不需要使用表单发布的服务器。相反,您可以将操作方法​​的返回类型更改为 JsonResult 并使用 Json() 方法发送数据处理的结果。

      [HttpPost]
      [ValidateAntiForgeryToken]
      public JsonResult Edit([Bind(Include = "EventName")] EventViewModel model)
      {
          //Get the specific record to be updated
          var eventRecord = (from e in db.Event
                         where e.EventID == model.EventID
                         select e).SingleOrDefault();
      
          //Update the data
          if (ModelState.IsValid)
          {
              eventRecord.EventName = model.EventName;
              db.SaveChanges();  
              return Json(new {Result=true});          
          } 
          else
          {
              return Json(new {Result=false});
          }
      }
      

      现在您可以使用此操作方法进行数据处理了。

      $.ajax({
      type: "POST",
      url: "/EditEvent/Edit",
      data: $('#editEventForm').serialize(),
      success: function (d) {
         var r = JSON.parse(d);
         if(r.Result == 'true'){
             //Wohoo its valid data and processed. 
             alert('success');
         }
         else{
             alert('not success');
             location.href = 'Index';
         }
      },
      error: function () {
      
      }
      
      });
      

      【讨论】:

        【解决方案5】:

        您可以在 viewbag 中发回一条错误消息,其中包含有关导致错误的项目的消息:

        foreach (ModelState modelState in ViewData.ModelState.Values)
                    {
                        foreach (ModelError error in modelState.Errors)
                        {
                        //Add the error.Message to a local variable
                        }
                    }
        
        ViewBag.ValidationErrorMessage = //the error message
        

        【讨论】:

          【解决方案6】:

          针对我的独特情况,我决定采取不同的方法。由于我在 Web.config 中设置了自定义错误,因此我能够自动处理任何非 Ajax 请求错误并将该信息发送到 Views/Shared/Error.cshtml。 推荐阅读here

          我还能够使用here 描述的方法处理 MVC 之外的应用程序错误。

          我还安装了 ELMAH 以在我的 SQL 数据库中记录任何错误。有关herehere 的信息。

          我的初衷是在发送 Ajax 请求时以相同的方式捕获控制器中的错误,我猜你做不到。

          当我在我的应用程序中发布表单时,首先在客户端使用 javascript 验证检查表单。我知道哪些字段我不想为空,以及要接受多少个字符和什么类型的数据。如果我发现一个不符合该条件的字段,我会将有关该错误的信息发送到 JQuery 对话框以向用户显示该信息。如果满足我的所有条件,那么我最终将表单提交给控制器和操作。

          因此,如果发生错误,我决定在控制器中抛出异常。如果客户端验证发现提交的数据没有问题并且控制器仍然出错,那么我肯定希望记录该错误。请记住,我还将 ViewModel 上的数据注释设置为客户端验证的备份。抛出异常将触发 JQuery 错误函数,并会触发 ELMAH 记录此错误。

          我的控制器看起来像这样。

          // POST: EditEvent/Edit
          //Method set to void because it does not return a value
          [HttpPost]
          [ValidateAntiForgeryToken]
          public void Edit([Bind(Include = "EventID, EventName")] EditEventViewModel model)
          {
              //Get the specific record to be updated
              var eventRecord = (from e in db.Event
                                 where e.EventID == model.EventID
                                 select e).SingleOrDefault();
          
              //******************************************************************//
              //*** Not really sure if exceptions like this below need to be    **//
              //*** manually called because they can really bog down the code   **//
              //*** if you're checking every query.  Errors caused from         **//
              //*** this being null will still trigger the Ajax error functiton **//
              //*** and get caught by the ELMAH logger, so I'm not sure if I    **//
              //*** should waste my time putting all of these in.  Thoughts?    **//
              //******************************************************************//
          
          
              //If the record isn't returned from the query
              if (eventRecord == null)
              {
                  //Triggers JQuery Ajax Error function
                  //Triggers ELMAH to log the error
                  throw new HttpException(400, "Event Record Not Found");
              }
          
              //If there's something wrong with the ModelState
              if (!ModelState.IsValid)
              {
                  //Triggers JQuery Ajax Error function
                  //Triggers ELMAH to log the error
                  throw new HttpException(400, "ModelState Invalid");
              }
          
              //Update the data if there are no exceptions
              eventRecord.EventName = model.EventName;
              db.SaveChanges();
          }
          

          带有数据注释的 ViewModel 如下所示。

          public class EditEventViewModel
          {
              public int EventID { get; set; } 
              [Required]
              [StringLength(10)]
              public string EventName { get; set; }
          }
          

          JQuery Ajax 调用如下所示

          $.ajax({
              type: "POST",
              url: "/EditEvent/Edit",
              data: $('#editEventForm').serialize(),
              success: function () {
                  //Triggered if everything is fine and data was written
              },
              error: function () {
                  //Triggered with the thrown exceptions in the controller
                  //I will probably just notify the user on screen here that something happened.
                  //Specific details are stored in the ELMAH log, the user doesn't need that information.
              }
          });
          

          我使用了每个人的帖子中的信息。感谢大家的帮助。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2012-02-25
            • 2011-03-25
            • 1970-01-01
            • 1970-01-01
            • 2011-08-24
            • 2012-04-20
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多