RSS

Posting complex JavaScript types to MVC, using Ajax

22 Jul

This post demonstrates how to send Complex client types to MVC using JSON. If you require the same information but for ASP.NET WebForms, you can go and read this post instead.

Background
Sending data to MVC using jQuery is something that I wrote about in several blog posts. Just as a quick reminder, this post discusses the basics of sending data, including simple-typed arrays. The problem is that I had to send over an array of complex-typed objects. Originally, this was intended to be a post which performs some sort of a work around by sending a JSON string to the server, and uses a JavaScriptSerializer & dynamics to retrieve the objects. But, after Googling some more it turns out by sending and receiving data in a certain way, MVC will perform this process out of the box, by binding the sent data to your custom server side classes. For simple types, sending data to the server is very “forgiving” and easy. But if you require to send complex types which involve JSON strings – you have to be more strict and follow “the rules”.

If you’re just into the solution, click here. If you would like to see some basic examples and the problem explained, read on.

Basic implementation
Here’s a reminder on how to send simple types, slightly complex types and simple typed arrays. First, this is the server side code:

    public class HomeController : Controller
    {
        public ActionResult Index() { return View("Test"); }

        public ActionResult GotSimple(string name, int age)
        {
            return Content(string.Format("name: {0}; age: {1}", name, age));
        }

        public ActionResult GotArrays(string[] names, int[] ages)
        {
            return Content(string.Format("names: {0}; ages: {1}",
                string.Join(",", names),
                string.Join(",", ages)));
        }

        public ActionResult GotComplexType(string name, int age)
        {
            return Content(string.Format("name: {0}; age: {1}", name, age));
        }
    }

And this is the client code (excluding the buttons html):

<script type="text/javascript">// <![CDATA[
        $(function () {
            $('#sendSimple').click(function () {
                 $.get("@Url.Action("GotSimple")",
                     {
                        name: "Joe" ,
                        age: 20
                     },
                     function (result){
                         alert("Server replied: " + result);
                     }
                 );
            });

            $('#sendArray').click(function () {
                 $.get("@Url.Action("GotArrays")",
                     $.param({
                        names: ["Joe", "Jane"],
                        ages: [20,30]
                     }, true),
                     function (result){
                         alert("Server replied: " + result);
                     }
                 );
            });

            $('#sendComplexType').click(function () {
                var Joe = { name: 'Joe', age: 20};
                $.get("@Url.Action("GotComplexType")",
                    Joe,
                    function (result){
                        alert("Server replied: " + result);
                    }
                );
            });
        });
// ]]></script>

There are three examples here:

  • ‘Simple’ demonstrates sending two simple types and the server receives them “as-is”.
  • ‘Array’ demonstrates that you can send simple typed arrays using $.param with the traditional flag set to true. The server receives them as expected.
  • ‘ComplexType’ shows an even cooler example, that you can send an simple object and receive it’s properties on the server side similarly to the ‘Simple’ example.

Note the traffic sent over to the server: ‘Simple’ (1) and ‘Complex’ (3) send the data exactly the same way, so clearly the server handles the sent data in a similar way. ‘Array’ (2) sends the data in duplicate keys which is received and interpreted by MVC as arrays.

Moreover: MVC also supports receiving the arguments directly to a custom class (e.g. Person), which is really cool:


public ActionResult GotComplexType(Person person)
{
   return Content(string.Format("name: {0}; age: {1}", person.name, person.age));
}

public class Person
{
   public string name { get; set; }
   public int age { get; set; }
}

This actually works (You can read about it here, under “JavaScript and AJAX Improvements”.

Complex typed arrays

But… when I wanted to send an array of complex types, this was more of a problem. First, lets review the client code:

            $('#sendComplexTypeArrays').click(function () {
                var Joe = { name: 'Joe', age: 20};
                var Jane = { name: 'Jane', age: 30};
                $.get("@Url.Action("GotComplexTypeArrays")",
                    $.param({
                        people: [Joe,Jane]
                    }, true),
                    function (result){
                        alert("Server replied: " + result);
                    }
                );
            });

And here is how it is sent over by the browser:

Clearly, this is not what I wanted. OK, let’s try to remove the traditional flag from $.param:

Looks more promising. Let’s review the server side code this time:

public ActionResult GotComplexTypeArrays(dynamic data)
{
    return Content("");
}

Unfortunately “data” is just an object, nothing more, and I did not get what I wanted.

Solution
I Googled and found this post which gave me a good direction. Turns out that MVC will know how to parse your sent data from the client on the server side, but there are a few catches I had to overcome before this properly worked:

  1. (Client) You must send a JSON content-type in the form of: ‘application/json’.
  2. (Client) The data must be POSTed and in JSON format (use JSON.stringify).
  3. (Server) The server data type must match that of the client:
    • Property names must match (although it seems like case-sensitivity is not an issue);
    • Client arrays must match a server IEnumerable type (array, list etc.);
    • Client data type must match the server side data type to a certain extent or you’re risking losing data (e.g. can’t send a client string and receive it as an int on the server side.)
  4. (Server) All properties must be, well, Properties (e.g. get;set; implemented). Can’t use global fields. They should also be public.
  5. (Server) Apparently the name of the argument cannot be identical to one of the properties. I’m not sure why, but this seem to confuse the binder and results in ‘null’. So you cannot call your argument ‘people’, and have a ‘people’ property in that class at the same time. At least not in the immediate class of the argument.

So after making a few changes to my code, here is the client side:

$.ajaxSetup({
    dataType: 'json',
    contentType: 'application/json, charset=utf-8'
});

The above code ensures that Ajax calls sent to the server are with a JSON content-type, and that JSON is to be received from the server (you may choose to add more global jQuery Ajax settings here such as error handling, or POST method etc.).

Important: if you don’t need all of your Ajax calls to be in JSON format, you can simply use the dataType and contentType in explicit Ajax calls. Just replace $.post with $.ajax and use POST.

$('#sendComplexTypes').click(function () {
    var Joe = { name: 'Joe', age: 20};
    var Jane = { name: 'Jane', age: 30};

    $.post("@Url.Action("GotComplexTypes")",
        JSON.stringify({ people: [ Joe,Jane ]}),
        function (result){
            alert("Server replied: " + result);
        }
    );
});

The code above converts the data into JSON format, and will require a ‘people’ property on the server side for binding purposes.

Below is the modified server side code. It receives a ‘data’ (not ‘people’ – remember?), performs binding to the different attributes, matching name to name and data type to data type. Properties and not global fields.

public ActionResult GotComplexTypes(MyPeople data)
{
    return Json(string.Format("People: {0}", data.People.Count()), JsonRequestBehavior.AllowGet);
}

public class MyPeople
{
    public IEnumerable<Person> People { get; set; }
}

public class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
}

Here’s the traffic shown which can help understand whether you’re POSTing JSON strings correctly:

Summary
Apart for being a really cool feature, to be able to bind JSON strings to server side objects, it can save a lot of effort if you thought of doing so yourself. However, this was no picnic. If you don’t follow “the rules” specified above, expect a hard-time and lots of frustration. But don’t despair – it’s worthwhile once it works, and after the first time, its supposed to get much easier.

 
4 Comments

Posted by on 22/07/2012 in Software Development

 

Tags: , , ,

4 responses to “Posting complex JavaScript types to MVC, using Ajax

  1. Andrey

    12/01/2014 at 19:04

    this works for me, thank you

     
  2. EA builder

    20/06/2014 at 13:11

    Great examples, though I’m not getting the results I’m supposed to get.
    My issue is with sending complex types as JSON. If I use JSON.stringify on the client side, all the field values are received on server as null/empty/0. Without JSON.stringify (if I only send the JS object), I only get the 1st tier field values (not the 2nd tier fields as well).
    Does anybody have any suggestions?

     
  3. EA builder

    20/06/2014 at 13:12

    I was missing “contentType: ‘application/json, charset=utf-8’,” apparently. All good now.

     

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

 
%d bloggers like this: