Patrick Desjardins Blog
Patrick Desjardins picture from a conference

Two differents approaches to save data from Javascript to Asp.Net MVC

Posted on: 2013-11-07

They are more than two approaches to send data from Javascript with Ajax to Asp.Net MVC. Today, I'll present you one that use dynamic and one that use DTO (data transfert object). The first approach is faster because it requires less code but is dynamic which open the door to several problems. Before anything, let see the Javascript that will be used by both approaches.

$(document).on("click", "#btnSaveOrder", function () { 
  var button = $(this); $(button).attr('disabled', 'disabled'); 
  $('.loadingAnimationfixed').show(); var listIdOrdered = []; 
  $('#sessiongrid tbody tr').each(function () { 
    listIdOrdered.push($(this).attr("data-workoutsessionid")); 
});

var toSave = { 'OrderedWorkoutSessionList': listIdOrdered ,'WorkoutId': $('#Id').val() };

$.ajax( { 
  url: "/WorkoutSession/SaveWorkoutSessionOrder", 
  type: "POST", 
  data: JSON.stringify(toSave), 
  success: function (response, status, xhr) { successMessage(response.Status); }, 
  error: function (XMLHttpRequest, textStatus, errorThrown) { 
    if (errorThrown == "Forbidden") { 
      errorMessage("Forbidden access"); 
    } else {
      errorMessage('An error occurred please retry.'); 
    } 
  } , 
  complete: function () { $(button).removeAttr('disabled'); $('.loadingAnimationfixed').hide(); } 
});

return false; //Prevent button to submit the form });

The code bind with JQuery a click event to the button with the ID "btnSaveOrder". We disable the button and display a loading animation division. This is so far good practice. It disallows the user to click twice on the button and it displays to the user what's going on. Next, we are building from the user interface a list of id. The situation here is that we need to save a list of ID to the database in a specific order. The line that initialize the tosave variable contains a list of all ID which is ordered, then a variable of the current workout. So, we have an object that contain a workout ID with a list of workout session ID that is ordered as FIFO (first in first out) list. This object need to be sent to the server to be saved. This is done by the JQuery Ajax method. First, we specify the URL and the method to contact the server. Second, we specify the object to send and we transform the Json into a string. Finally, we display a success message or an error message. In both case, we enable the button and remove the animation with the complete method. From this code, we know that from the server side we need to have a POST action that is called SaveWorkoutSessionOrder which take a class with 2 properties, one is a list of integer and one is an integer.

The first approach to handle this is to go fast and to not create a class that contain a list of integer and one integer. We can do it by using the dynamic keyword.

[HttpPost] public JsonResult SaveWorkoutSessionOrder() { 
  dynamic json = JsonConvert.DeserializeObject(Request.Form.Get(0));

  int order = 1; 
  var workout = new Workout { Id = json.WorkoutId }; 
  workout.Sessions = new List<WorkoutSession>();

  foreach (var id in json.OrderedWorkoutSessionList) { 
    workout.Sessions.Add(new WorkoutSession { Id = id, Order = order++}); 
  }

  try { 
    ServiceFactory.Workout.UpdateSessionOrderOnly(workout); 
  } catch (DataNotFoundException) { 
    throw new HttpException((int)HttpStatusCode.Forbidden,"Cannot update the session"); 
  }
  return Json(new { Status="Saved" }); 
}

The first thing we remark is that the method doesn't contain any parameter. This is because we won't allow the model binder to act but deserialize it manually inside the method. This is what the first line of the method is doing with the JsonConvert.DeserializeObject. We need to get the Request object and to get the first element of the form which is the one sent by Javascript with the Ajax calls. The return value of the deserialization go to a dynamic variable. This allow us to skip the creation of a concrete class for the list of integer and integer. Then, we can use the variable but you won't have any intellisence. You need to be sure to write correctly the property name of this dynamic variable with the same name as the Javascript prototype. If you do, everything will be fine in the execution. Nevertheless, this is not an optimal way to develop a enterprise application because of many problem. Yes, it's faster, but the cost is heavier in the long term.

As you can see, we do not have a parameter to this controller's action. Even if it's not dramatic, it's harder to unit test because it requires to mock the Request object. The second problem is with the dynamic itself. The use of dynamic weak the entire method. The problem is that error is not detected until execution. If you are unit testing with a full coverage this method, you shouldn't have a problem. The last problem is that we are deserializing inside the controller's action instead of delegating the process to the Model Binder. Every mapping between http request and controller's methods are done by the Model Binder. The cohesion of your controller is compromise and may come a problem later on in the maintenance phase of your system.

To solution is to change the controller to take a parameter that will be deserialized by the Model Binder of Asp.Net MVC and will give us a strongly typed object. This one will be easily testable since we will be able to pass in our tests a parameter.

[HttpPost]
public JsonResult SaveWorkoutSessionOrder(WorkoutSessionOrder workoutSessionOrder) {
  int order = 1;
  var workout = new Workout {
    Id = workoutSessionOrder.WorkoutId
   };
   workout.Sessions = new List<WorkoutSession>();

  foreach (var id in workoutSessionOrder.OrderedWorkoutSessionList) {
    workout.Sessions.Add(new WorkoutSession { Id = id, Order = order++});
  }

  try {
    ServiceFactory.Workout.UpdateSessionOrderOnly(workout);
  } catch (DataNotFoundException) {
    throw new HttpException((int)HttpStatusCode.Forbidden,"Cannot update the session");
  }
  return Json(new { Status="Saved" });
}

As you can see, the first signature as changed to have a parameter. That mean that we need to provide this data from the Javascript. In fact, we were sending this object previously but this time we have to specify the data type to indicate to Asp.Net MVC framework what information we are sending to deserialize. To mark the data sent to be JSON format, we need to add this line in the Ajax's call : contentType: 'application/json'.

$(document).on("click", "#btnSaveOrder", function () {
  var button = $(this); $(button).attr('disabled', 'disabled');
  $('.loadingAnimationfixed').show(); var listIdOrdered = [];
  $('#sessiongrid tbody tr').each(function () {
    listIdOrdered.push($(this).attr("data-workoutsessionid"));
  });

var toSave = {
  'OrderedWorkoutSessionList': listIdOrdered
  ,'WorkoutId': $('#Id').val()
 };

$.ajax( { url: "/WorkoutSession/SaveWorkoutSessionOrder",
type: "POST",
 data: JSON.stringify(toSave),
 contentType: 'application/json',
 success: function (response, status, xhr) {
  successMessage(response.Status);
  },
  error: function (XMLHttpRequest, textStatus, errorThrown) {
     if (errorThrown == "Forbidden") {
      errorMessage("Forbidden access");
    } else {
        errorMessage('An error occurred please retry.');
     }
   } ,
 complete: function () {
            $(button).removeAttr('disabled');
            $('.loadingAnimationfixed').hide();
   }
  }
);

That's it! Now we have seen two different approaches. The last one is the one you should use every time because it's strongly typed, easier to test and won't compile with possible error like the one with Dynamic which will crash on execution if a typo is done.