ASP.NET MVC - Passing JSON to controller actions and populating controls on demand

Wednesday, April 01, 2009 by ASP.NET MVC Team | Comments 6

As you probably know some of our controls support web service load on demand (a.k.a. client-side databinding). Also in ASP.NET MVC we can call controller actions via Ajax. In this blog post I will show you how to populate RadComboBox and RadTreeView on demand by using a controller action which returns JsonResult. Let's start with RadComboBox:

Configure RadComboBox for Load on Demand

I have added the RadComboBox control to the default Index.aspx view and configured it like this:

<telerik:RadComboBox runat="server" ID="RadComboBox1" EnableLoadOnDemand="true"
ShowMoreResultsBox="true" EmptyMessage="Type here ..." Height="200px">
<
WebServiceSettings Path="Home" Method="LoadItems" />
</telerik:RadComboBox>

The important thing here is that RadComboBox is configured as if it were using a web service - it will call the Home controller's LoadItems method. Internally RadComboBox us using ASP.NET Ajax to call the specified URL and populate from the returned JSON.

Passing JSON to Controller Methods

RadComboBox is passing a RadComboBoxContext object to web service methods. This object is used by the developer for various reasons - filtering, paging etc. Consider the following method:

[WebMethod]
public RadComboBoxData LoadItems(RadComboBoxContext context)
{
}

In a regular web service the context parameter would be automatically deserialized from the JSON passed by RadComboBox. In ASP.NET MVC you should deserialize the JSON parameter yourself. I did it by using a custom ActionFilter. Here is the implementation of the filter:

public class JsonParamFilter : ActionFilterAttribute
{
public string Param { get; set; }
public Type TargetType { get; set; }

public override void OnActionExecuting(ActionExecutingContext filterContext)
{
string contentType = filterContext.HttpContext.Request.ContentType;

if (string.IsNullOrEmpty(contentType))
return;

if (!contentType.Contains("application/json"))
return;

string paramValue;
using (var reader = new StreamReader(filterContext.HttpContext.Request.InputStream))
paramValue = reader.ReadToEnd();

var serializer = new JavaScriptSerializer();
var rawResult = (IDictionary<string, object>) serializer.DeserializeObject(paramValue);

var deserializeMethod = serializer.GetType()
.GetMethod("ConvertToType")
.MakeGenericMethod(TargetType);

filterContext.ActionParameters[Param] =
deserializeMethod.Invoke(serializer, new[] {rawResult[Param]});
}
}

Here is how to use it:

[JsonParamFilter(Param = "context", TargetType=typeof(RadComboBoxContext))]
public JsonResult LoadItems(RadComboBoxContext context)
{
}

Implementing the Load On Demand Method

There is only one thing left - to implement the method and return a RadComboBoxData object in JSON format. There is a tricky part though - ASP.NET Ajax expects a web service to return a JSON in this format: {d:{}}. This is easily done by returning an anonymous object with a single "d" property containing the desired result:

[JsonParamFilter(Param = "context", TargetType = typeof (RadComboBoxContext))]
public JsonResult LoadItems(RadComboBoxContext context)
{
RadComboBoxData result = new RadComboBoxData();
//fill the result return Json(new {d = result});
}

And that's it. RadComboBox has been successfully tricked that it is populated from a web service!

combobox 

Populating RadTreeView on Demand From MVC Controller Action

The implementation is exactly the same:

  1. Configure RadTreeView for web service load on demand
  2. <telerik:RadTreeView runat="server" ID="RadTreeView1">
    <WebServiceSettings Path="Home" Method="LoadNodes" />
    <
    Nodes>
    <telerik:RadTreeNode Text="Fuller" ExpandMode="WebService" />
    </Nodes>
    </telerik:RadTreeView>
  3. Decorate the controller action with the JsonParamFilter so the JSON parameter is automatically deserialized
  4. [JsonParamFilter(Param = "node", TargetType = typeof (RadTreeNodeData))]
    public JsonResult LoadNodes(RadTreeNodeData node)
    {
    }
  5. Implement the action method and return the result
  6. [JsonParamFilter(Param = "node", TargetType = typeof (RadTreeNodeData))]
    public JsonResult LoadNodes(RadTreeNodeData node)
    {
    List<RadTreeNodeData> result = new List<RadTreeNodeData>();

    //fill the result return Json(new {d = result.ToArray()});
    }
     

6 Comments

  • Don Stuber 01 May 2009
    This question does not directly have to do with JSON, but for you to have written the above, you may be able to help.

    I have successfully implemented two web services to populate two RadComboBox's.  I appreciate the elegant simplicity of Telerik's design.  However, yesterday I discoverd that it's important to give the server-side web service code additional information, more than just the characters that the user typed that get passed in Context.  Please comment on these possibilities:

    1.  I could concatenate the parameters onto Context.Text.  Instead of just "typedChars", I would separate with ":" and make it into "typedChars:param1:param2".  To accomplish this, I would need to customize the client side call to the web service.  How can I customize the Javascript code in order to modify the Context.Text value?

    2. Put "param1" and "param2" into Session.  However, the web server side doesn't appear to have access to Session.  Is there a way to access Session?

    3. Write "param1" and "param2" into the SQL Server database.  They are fixed when the user logs on, so I know them well before the user types characters into the RadComboBox.  However, there would be several pairs of such values, two for each logged on browser user, and I need a way to identify the correct browser user.  What is common between the information the server-side page code knows and what the server-side web server code knows?

    4. Any other method you may be aware of.

    TIA,
    Don
  • Atanas Korchev 12 May 2009
    To pass custom data to the server you can use the context. Check the following for additional info:
    http://www.telerik.com/help/aspnet-ajax/combo_loadondemandwebservice.html
  • AN 27 Aug 2009
    Hi ,

    I am Trying to call a web service method and passing the RadComboBoxContext context in the AJAX WCF Service method .I am Assigning the Context in the OnClientItemsRequesting Event.

     

    function OnClientItemsRequesting(sender, args)

     

    {

    alert(args.get_text());

    args.get_context()[

    "TextVal"] = args.get_text();

     

    alert(args.get_context()[

    "TextVal"]);

     

    }

    I am  getting the context count as 0.in WCF Function .

    Any Suggestion why?

  • Laurent Fabre 17 Apr 2010
    For some reason this method appears to be broken under framework 4.0. This seems to fix it :

    var deserializeMethod = serializer.GetType().GetMethod("ConvertToType", new Type[] { typeof(object) }).MakeGenericMethod(TargetType);
  • Nick Foster 23 Apr 2010
    Thanks Laurent, I just came across this problem after upgrading my app to .NET 4.0 / MVC2

    For the VB heads out there this line works:

    Dim deserializeMethod = serializer.GetType().GetMethod("ConvertToType"New Type() {GetType(Object)}).MakeGenericMethod(TargetType) 
  • Roxanne 25 May 2010
    Hi,
    I am trying to display the treeview using a partial view, after performing a GET on the partial view:
      $.get('/Home/TV',{},
            function(data) {
                $('#tv').html(data),
                "html"});
     The treeview does get displayed, but the loadnodes functionality does not work anymore. The method simply does not get called.

    The partial view looks like this:
    <%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl" %>
    <%@ Register Assembly="Telerik.Web.UI" Namespace="Telerik.Web.UI" TagPrefix="telerik" %>

    <link href="../../Content/telerik/skins/default/TreeView.Default.css" rel="stylesheet"
        type="text/css" />
    <link href="../../Content/telerik/skins/TreeView.css" rel="stylesheet" type="text/css" />


    <telerik:RadScriptManager runat="server" ID="RadScriptManager1" />
    <fieldset style="width: 300px;">
        <legend>RadTreeView</legend>
        <telerik:RadTreeView runat="server" ID="RadTreeView1">
            <WebServiceSettings Path="Home" Method="LoadNodes" />
            <Nodes>
                <telerik:RadTreeNode Text="Fuller" ExpandMode="WebService" />
            </Nodes>
        </telerik:RadTreeView>
    </fieldset>


    If I call directly Html.RenderPartial on the partial view in the Index page, the LoadNodes works fine.

    Any ideas why does LoadNodes stop working and what to do to get it to work?

Add comment

  1. Formatting options
       
     
     
     
     
       
  2. (optional, emails won't be shown on public pages)
  3. (optional)