Wednesday, July 2, 2008

HowTo use Castle Monorail and jQuery to exchange JSON data

Define a Controller as follows

[Layout("default")]
public class JsonController : SmartDispatcherController
{
    public void Index()
    { }
 
    /// <summary>"Manual" or "traditional" way to return 
    /// JSON formatted data</summary>
    public void GetStates(string countryCode)
    {
        State[] states = FetchAllByCountry(countryCode);
 
        string js = Context.Services
                       .JSONSerializer
                       .Serialize(states);
 
        Response.ContentType = "application/json";
        RenderText(js);
    }
 
    /// <summary>
    /// Return JSON formatted data by using an attribute
    /// One can even filter the list of fields that should
    /// be serialized
    /// </summary>
    /// <returns>the JSON formatted list of states</returns>
    [return: JSONReturnBinder(Properties = "Id,Name")]
    public State[] GetStates_Ex(string countryCode)
    {
        return FetchAllByCountry(countryCode);
    }
 
    private static State[] FetchAllByCountry(string countryCode)
    {...}
}

The State entity is defined as

public class State
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Description { get; set; }
}

The JSON output generated by the method GetStates is similar to this

[{"Id":1,"Name":"ZH","Description":"Zürich"},
 {"Id":2,"Name":"TG","Description":"Thurgau"},
 {"Id":3,"Name":"SG","Description":"St. Gallen"}]

and the output from GetStates_Ex is similar to this

[{"Id":1,"Name":"ZH"},
 {"Id":2,"Name":"TG"},
 {"Id":3,"Name":"SG"}]

Note that in the latter case the field Description is not included as expected!

In the index.brail template (I'm using the brail view engine) use the following java script block

<script>
    jQuery(document).ready(function(){
        jQuery("#traditional").click(function(){
            jQuery.getJSON("${UrlHelper.For({@action:@GetStates})}",
                function(data){
                    jQuery.each(data, function(i,item){
                        jQuery("<div/>").text(item.Name)
                            .appendTo("#states");
                    });
                });
        });
        
        jQuery("#withAttr").click(function(){
            jQuery.getJSON("${UrlHelper.For({@action:@GetStates_Ex})}",
                function(data){
                    jQuery.each(data, function(i,item){
                        jQuery("<div/>").text(item.Name)
                            .appendTo("#states");
                    });
                });
        });
    });
</script>

and the following HTML

<button id="traditional" >Traditional</button>&nbsp;
<button id="withAttr" >with Attribute</button>&nbsp;
<br />
<div id="states"></div>

Clicking on either the first or the second button will make an Ajax call to the controller and return a JSON serialized list of states. When returning from the call the names of the states are displayed in the div with id="states".