11

This is a follow up to a previous question that I had before about passing an error back to the client, but also pertains to the ModelState.

Has anyone successful used the Nerd Dinner approach, but with Ajax? So Nerd Dinner does an update as so.

[AcceptVerbs(HttpVerbs.Post)]
    public ActionResult Edit(int id, FormCollection formValues) 
{
    Dinner dinner = dinnerRepository.GetDinner(id);
    try 
    {
        UpdateModel(dinner);
        dinnerRepository.Save();
        return RedirectToAction("Details", new { id=dinner.DinnerID });
    }
    catch 
    {
        foreach (var issue in dinner.GetRuleViolations()) {
        ModelState.AddModelError(issue.PropertyName, issue.ErrorMessage);
    }
        return View(dinner);
    }
}

Using jQuery $.ajax

function hijack(form, callback, errorFunction, format) {
    $.ajax({
        url: form.action,
        type: form.method,
        dataType: format,
        data: $(form).serialize(),
        success: callback,
        error: function(xhr, textStatus, errorThrown) {
            errorFunction(xhr, textStatus, errorThrown);
        }
    });
}

Ajax, the "try" part of the controller becomes

    try 
{
    UpdateModel(dinner);
    dinnerRepository.Save();
    return PartialView("PartialDetails", new { id=dinner.DinnerID });
}

, but what do you do about the catch part?

A simple error handling solution to send back an error would be

catch(Exception ex)
{
    Response.StatusCode = 500;                
    return Content("An Error occured.");
    //throw ex;
}

, but that doesn't pass through the robust modelstate built into MVC. I thought of a number of options, but I really want 2 things:

  1. I want the error to be handled in jQuery's error attribute.
  2. I want to use built in ASP.Net MVC validation logic as much as possible.

Is this possible? If not, what are the best alternatives that you know of?

Many thanks.

Update I haven't marked this as answered yet, because I haven't yet implemented what I think will work best.

I have decided that I don't really like the success => send refreshed list, failure => send error message approach that I was taking. I did this to reduce the number of calls, but a refreshed list is really being set to the page. Trying to do both tightly binds the popup to its overall page.

I am going to add a custom jQuery event refresh the master page list when the dialog closes. In essence, it's the observer pattern. I like the idea that the page says to the popup "tell me when you're done" (aka closed), without having to tell the popup why. It does require an additional call, but I don't see that as a big issue.

I'm still not sure how well that I like/dislike server-side validation and I'm considering going with client-side only validation. While server side validation seems like clean layering, it also has a number of problems, including:

1) It puts quality checks at the end, instead of the beginning. An analogy to manufacturing would be a car that's tested when it arrives at the dealer, instead at the points in the process where it's being built.
2) It violates the intent of Ajax. Ajax isn't just about sending asynchronous events, it's also about sending only what I need and receiving only what I need. Sending back the entire modelstate in order to provide error details doesn't seem to go with Ajax.

What I'm thinking about doing is having client-side only validation, but that server code and a custom viewmodel can be used to tell the client how to dynamically create those validation rules.

I also suspect that a dynamic language like IronRuby or IronPython might offer a more elegant way to solve these problems, but it could be a little longer before I look into that possibility.

Community
  • 1
  • 1
John
  • 3,332
  • 5
  • 33
  • 55
  • well i think it really depends on the scenario, if doing 2 request is not a problem i ll go for that. Personally doing a lot of validation with JavaScript on the client side is not something that i love (i dont know why, but i see js like something not very trusted/secure/equally implemented(in this one jquery save the day)), specially because sometimes you just cant do all the validations on the client side, you must use some kind of sever side checks, like (this entity already exists in DB?), and do support js disabled clients, but as i said at the beginning it depends on the scenario. – JOBG Jan 28 '10 at 18:17
  • I agree that you can't do all the validation on the client side, it just seems like the validation framework seems geared mainly towards field errors (too long, not a date, etc.). At least in the examples that I've seen. When you're checking user input, Javascript seems the appropriate place to do the checking. – John Feb 17 '10 at 17:10
  • I've firmed up my decision about making 2 calls, instead of one, because it de-couples the details partial view from the page view beneath it. I want the popup to say back to the page view "I'm closed", but not know anything about what the page view is going to do. jQuery should make that possible without much effort. That enables me to not worry that changes to my details partial view will affect the page and vice versa. – John Feb 17 '10 at 17:20

2 Answers2

4

If I understand what you are trying to do, my first answer will be no, you cannot use the model state as it is through an Ajax request.

You may be able to emulate the ModelState behavior, to display the errors:

  1. Passing a List<KeyValuePair<string,string>> (property,message) by JSON (this will require you to pass the modelErrors from the ModelState to the new structure) and do the HTML construction of a Validation Summary by JS/jQuery (which I think is over killing solution).

  2. If you are going to the server, and there are any errors, just do a render partial of the Html.ValidationSummary(), pass it through JSON, and prepend it to the form. If everything was OK, just return the PartialDetails view and replace the actual content. This will require some kind of status parameter so you know what is coming back from the server on the ajax callback.

Edit: This last option sounds good but tricky, because you will need to return a partial view in a string form by JSONResult. Here is a question and solution about that hack: How to render an ASP.NET MVC view as a string?.

Personally, I don't think that using the error attribute will do any good at all. I just use it in very specific situations like timeout errors and server exceptions, not app exceptions.

Edit:

Using JSON:

[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Edit(int id, FormCollection formValues)
{
    Dinner dinner = dinnerRepository.GetDinner(id);
    try
    {
        UpdateModel(dinner);
        dinnerRepository.Save();
        return Json(new 
        {
            result = "success",
            html = this.RenderToString("PartialDetails", dinner) 
        });

    }
    catch
    {
        foreach (var issue in dinner.GetRuleViolations())
        {
            ModelState.AddModelError(issue.PropertyName, issue.ErrorMessage);
        }
        return Json(new
        {
            result = "failed",
            html = this.RenderToString("PartialEdit", dinner)
        });
    }
}

Here the result parameter will let you know what action to do in each case, just have to check it on the callback.

CarenRose
  • 1,266
  • 1
  • 12
  • 24
JOBG
  • 4,544
  • 4
  • 26
  • 47
  • Thanks. Part of the challenge, however, is that I'd really prefer to send the results back as PartialView result and not as a JSON result. For 2., are you making 2 calls? One to return success/error and another to deliver the results? That is one option that I was thinking of. – John Dec 10 '09 at 22:21
  • Its really one call with this structure like {"status": [{"code": "error"}], "html" : [{"html":"a lot of html"}]}. If you want it to be just a partialView Result object, why don't just return the hole Edit partial View, in case that the model has errors? and the Details partial View when no errors occurs, the model State will work with no problems that way. – JOBG Dec 11 '09 at 00:43
  • The part that makes my scenario different from most examples is that I want to do two very different actions based on whether it's a success or error. If it's an error, I can return the Edit partial View and recreate the dialog. However, if it's a success, I want to close the dialog and refresh the list on the main page. So the client side code needs to know if it's a success/failure in order to know which object to update -- the dialog or the list. – John Dec 11 '09 at 14:11
  • In that case i would suggest you go for a JSONResult type instead, because a plain PartialView type wont give you any state reference about what happened at the server side, unless you want to do 2 different request one for the stats and another for the partialView. I updated the Post with the Json Approach. – JOBG Dec 11 '09 at 15:01
2

Adding my prefered approach with MVC3. Using your edit method you could do something like

[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Edit(int id, FormCollection formValues) 
{
    Dinner dinner = dinnerRepository.GetDinner(id);
    try 
    {
        UpdateModel(dinner);
        dinnerRepository.Save();
        return Json (new { Success = true, Url = Url.Action("DetailsPartial", dinner), Div = "#DivToUpdateId" }); 
    }
    catch 
    {
        foreach (var issue in dinner.GetRuleViolations()) {
        ModelState.AddModelError(issue.PropertyName, issue.ErrorMessage);
    }
        //I am replacing this with a partial view which will contain the model state errors. For people using MVC 3 with razor they should be able to use their normal views as partials
        return PartialView(dinner);
    }
}

and then you can use a success function like

success: function(data) {
    if (data.Success) {
        $.post(data.Url, function(partial) { 
            $(data.Div).html(partial);
        });
    }
    else
    {
        $('#formDiv').html(data)
    }
}

so basically, if the result is Json then data.Success is true. It then updates the div specified in the json (data.Div) with the results of the action specified in the Url. If the result is not Json its because the post method failed and the formDiv is updated with the partial form that contains the ModelState Errors. This is the approach i use for dialogs but it works pretty well. Obviously with MVC3 i'd change some of the edit method like using TryUpdateModel etc, but just trying to give a example of my approach. By passing the url and posting the results of the method there is no need to try to render html to a string in order to pass a partial.

Manatherin
  • 4,169
  • 5
  • 36
  • 52