Monday, December 01, 2008
Posted on Monday, December 01, 2008 10:11:31 PM (Mountain Standard Time, UTC-07:00)  Comments [2] | 
Categories: .NET | ASP.NET MVC | Dojo

Continuing my series of posts on using Dojo with the ASP.NET MVC framework, this time I'm going to focus on sending a "complex" object back to a controller. In my previous article, I "posted" an object to a controller by simply decomposing the object into a set of properties which were sent across as Form elements (name and age in the example), which were then matched up to inbound arguments with the same name. While useful, this can become limiting if your RIA is doing more complex things, and you want to send back more complex data - i.e. a class that has a property that's an array of objects.

In this example, I use the same "simple" employee object, but instead of decomposing it, I send it across as a JSON string which is then re-hydrated back into a full .NET class.

Dojo Code

function SendEmployee(){

    var responseNode = dojo.byId("responseComplex");
    var payLoad = { 
        "Name":dojo.byId("usernameComplex").value , 
        "Age":parseInt(dojo.byId("ageComplex").value)        
        };     
    var request = {"employeeJson":dojo.toJson(payLoad)};
    
     dojo.xhrPost({
        url: '<%= Page.ResolveClientUrl("~/Service/CreateComplex") %>',
        handleAs: 'json',
        timeout:3000,
        content: request,               
        contentType: "application/x-www-form-urlencoded",
        load: function(person,args){                                     
            responseNode.innerHTML ="Response: " + person.Name + " " + person.Age;
        },
        error: function(error,args){                           
            responseNode.innerHtml("Error!",error);
        }
    });
}

Controller Code

Instead of having multiple arguments on the Controller method, we just has one string argument - employeeJSON. ASP.NET MVC still maps up the posted form element to the argument name, and passes it in for us. One the string is in the method, we need to de-serialize it. For this I've used the DataContractSerializer

/// <summary>
/// Creates the complex.
/// </summary>
/// <param name="employeeJson">The employee json.</param>
/// <returns></returns>
public ActionResult CreateComplex(string employeeJson)
{
    DataContractJsonSerializer ser = new DataContractJsonSerializer(typeof(Employee));
    MemoryStream ms = new MemoryStream(Encoding.Unicode.GetBytes(employeeJson));
    Employee e = (Employee)ser.ReadObject(ms);
    e.Age = e.Age + 5;
    return Json(e, "text/json-comment-filtered");
}        

DataContractSerializer is quite easy to use - just include System.Runtime.Serialization, and markup your class with the [DataContract] and [DataMember] attributes as shown below.

using System.Runtime.Serialization;

namespace ajaxmvc.Models
{
    [DataContract]
    public class Employee
    {
        /// <summary>
        /// Gets or sets the id.
        /// </summary>
        /// <value>The id.</value>
        [DataMember]
        public Int32 Id { get; set; }

        /// <summary>
        /// Gets or sets the name.
        /// </summary>
        /// <value>The name.</value>
        [DataMember]
        public string Name {get;set;}

        /// <summary>
        /// Gets or sets the age.
        /// </summary>
        /// <value>The age.</value>
        [DataMember]    
        public int Age{get;set;}

        /// <summary>
        /// Initializes a new instance of the <see cref="Employee"/> class.
        /// </summary>
        /// <param name="name">The name.</param>
        /// <param name="age">The age.</param>
        public Employee(string name, int age)
        {
            _name = name;
            _age = age;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="Employee"/> class.
        /// </summary>
        /// <param name="name">The name.</param>
        /// <param name="age">The age.</param>
        /// <param name="id">The id.</param>
        public Employee(string name, int age, int id)
        {
            _name = name;
            _age = age;
            _id = id;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="Employee"/> class.
        /// </summary>
        public Employee() { }
    }
}

Code Smell...

Although this works, I'm not a big fan of having controller methods that accept serialized strings - this makes unit testing more painful. However, a trade-off of sorts can be made in that you can have a strongly typed controller method, and a JSON version of the same method. The Json version accepts the serialized Json string, de-serializes it and passed it over to the strongly typed method. You can then write all sorts of useful unit tests against the strongly typed version. In an ideal world, we'd have the Json de-serialization occur somewhere in the pipeline before we hit the controller - I'm hoping that this may be achievable via custom ModelBinders, but have not had a chance to research this yet.

Tuesday, December 02, 2008 12:11:37 PM (Mountain Standard Time, UTC-07:00)
Dave, check out my JsonBinder class again, easy unit-testing is practically the point... :P

//controller code
public ActionResult getAge([JsonBinder]Employee emp){...}

//unit test code
Employee emp = new Employee("bob", 66, 1);
ViewResult result = controller.getAge(emp) as ViewResult;
ViewDataDictionary viewData = result.ViewData;
Assert.AreEqual(66, viewData["employee_age"]);

(http://mapwrecker.wordpress.com/2008/11/14/aspnet-mvc-ext-wcf-v20/)
Monday, December 08, 2008 2:42:40 PM (Mountain Standard Time, UTC-07:00)
I was wondering if you could post how you can call an .NET method within the javascript on the client. I need to send json data to a .net object to be inserted into a database and would like to know how to do that.

Thanks in advance.
Tuong Do
Comments are closed.