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.
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); } }); }
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() { } } }
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.
I'm Dave and this is my blog. I'm usually writing about .NET Software Development, ArcGIS, or Agile Practices, but other stuff does creep in from time to time. I hope you find something of use, and feel free to contact me if you have any questions. You can also check out my profile on LinkedIn
dojo.DTSAgile.com is our technology preview / demo site. As I and my team cook up cool things we post them here.
ArcDeveloper.net is a site that hosts a set of open source projects related to ArcGIS. This includes Tile Cache for .NET (TC4N) and Feature Server for .NET (FS4N). Come over and check it out!
Assembla is a free service that provides Subversion source control, wikis and work Tracking. The ArcDeveloper project is run from here. It rocks. Check them out today.
Agilistas is a LinkedIn group focused on discussing and promoting Agile practices. Everyone is welcome to join in the conversation as we evolve the process of creating software to make it more enjoyable for all involved.