使用JQuery Ajax进行MVC5错误处理

时间:2015-07-08 18:59:53

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

我们说我有一个带有ViewModel的标准表单设置并且验证是这样的。

视图模型

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

控制器

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

    },
    error: function () {

    }
});

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

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

if (ModelState.IsValid)

通过这种方式,我在客户端添加一些javascript以在提交之前进行验证,但我仍然希望ViewModel中的数据注释作为备份。 当它到达控制器时,它仍然使用if (ModelState.IsValid) { eventRecord.EventName = model.EventName; db.SaveChanges(); } else { //What can I do here to signify an error? } 进行检查。如果它无效,则数据不会像设计的那样写入数据库。

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

<customErrors mode="On">

使用更多信息进行更新

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

@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>

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

throw new HttpException(500, "ModelState Invalid");

再次更新

这是另一个更新处理所有响应的部分。 在我的控制器中,我把它放在else语句Open中(否则 - 意味着ModelState无效)

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

enter image description here

6 个答案:

答案 0 :(得分:4)

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

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

关于你想要处理什么以及返回多少细节的许多选项,但重点是你的控制器应该提供适合它所执行的实际操作的响应。

<强>更新

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

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

答案 1 :(得分:2)

因为你想要回复错误内容,我建议你返回一个JSON响应(替代是部分视图,但这意味着让你的JS使用委托处理程序,重置表单验证等)。在这种情况下,如果POST是AJAX,您将要检测并返回JSON,否则返回普通视图/重定向。如果所有验证都应在客户端完成,并且没有错误文本,则可能会返回异常结果并使用错误处理程序进行.ajax()调用以更新页面。我发现浏览器支持获取错误的响应文本是不一致的,所以如果你想要实际的错误,你会想要用JSON中的消息返回200 OK响应。我的选择可能取决于确切的用例 - 例如,如果有几个错误我只能检测到服务器端,我可能会使用带有错误内容的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.');     
});

答案 2 :(得分:1)

如果您要创建RESTful服务,则应返回相应的http代码,以指示数据未保存。可能是400.

否则我会说返回任何有意义的东西并在客户端检查该值以确定呼叫是否失败。

答案 3 :(得分:0)

您需要的是对代码进行一些更改。因为您使用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 () {

}

});

答案 4 :(得分:0)

您可以在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

答案 5 :(得分:0)

对于我独特的情况,我决定采取不同的方法。由于我在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.
    }
});

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