views:

33

answers:

2

Suppose you have the following controller action

[HttpPost]
public ActionResult Save( CustomerModel model )
{
    if (!ModelState.IsValid) {
        //Invalid - redisplay form with errors
        return PartialView("Customer", model);
    }
    try {
        //
        // ...code to save the customer here...
        //
        return PartialView( "ActionCompleted" );
    }
    catch ( Exception ex ) {
        ActionErrorModel aem = new ActionErrorModel() { 
            Message = ex.Message 
        };
        return PartialView( "ActionError", aem );
    }
}

And suppose you call this action using jQuery:

$.ajax({
    type: "post",
    dataType: "html",
    url: "/Customer/Save",
    sync: true,
    data: $("#customerForm").serialize(),
    success: function(response) {
        /*
             ??????
        */
    },
    error: function(response) {
    }
});

I would like to be able to distinguish between the results I am getting to handle them in different ways on the client. In other words how can I understand that the action

  • returned the same model because has not passed validation
  • returned one of the views that represents error info/messages

Any suggestion?

A: 

You could check for an element unique to that view, for example:

$.ajax({
    type: "post",
    dataType: "html",
    url: "/Customer/Save",
    sync: true,
    data: $("#customerForm").serialize(),
    success: function(response) {
        var resp = $(response);
        if($(resp.find("#customer").length) {
          //customer returned
        } else if($(resp.find("#completed").length) {
          //completed view
        } else if($(resp.find("#error").length) {
          //error view
        }
    },
    error: function(response) {
    }
});
Nick Craver
+2  A: 

One way to handle this is to append a custom HTTP header to indicate in which case we are falling:

[HttpPost]
public ActionResult Save( CustomerModel model )
{
    if (!ModelState.IsValid) {
        //Invalid - redisplay form with errors
        Response.AppendHeader("MyStatus", "case 1");
        return PartialView("Customer", model);
    }
    try {
        //
        // ...code to save the customer here...
        //
        Response.AppendHeader("MyStatus", "case 2");
        return PartialView( "ActionCompleted" );
    }
    catch ( Exception ex ) {
        ActionErrorModel aem = new ActionErrorModel() { 
            Message = ex.Message 
        };
        Response.AppendHeader("MyStatus", "case 3");
        return PartialView( "ActionError", aem );
    }
}

And on the client side test this header:

success: function (response, status, xml) {
    var myStatus = xml.getResponseHeader('MyStatus');
    // Now test the value of MyStatus to determine in which case we are
}

The benefit of this is that the custom HTTP header will always be set in the response no matter what content type you've returned. It will also work with JSON, XML, ...

Remark 1: To avoid cluttering you controller action with all those Response.AppendHeader instructions you could write a custom ActionResult allowing you to directly specify the value of this header so that you simply return this.MyPartialView("Customer", model, "case 1")

Remark 2: Remove this sync: true attribute from the request because it makes my eyes hurt (in fact I think you meant async: 'false').

Darin Dimitrov
+1 - Was typing up exactly this, probably the best approach.
Nick Craver
@Darin: very good! I cannot think to a cleaner way!!! Anyway this make me change every action. I am guessing if this could be done in an automatic way mixing with the Nick answer in an ActionFilter attribute... P.S. :) yes it was `async: 'false'`
Lorenzo