Kirk Evans Blog

.NET From a Markup Perspective

Using WCF, JSON, LINQ, and AJAX: Passing Complex Types to WCF Services with JSON Encoding

Using WCF, JSON, LINQ, and AJAX: Passing Complex Types to WCF Services with JSON Encoding

Rate This
  • Comments 8

In my previous post, I showed how to use the DataContractSerializer with the classes generated by the LINQ to SQL designer.  As a refresher, here's the service.  It is a trouble ticket lookup service based on the 3 parts of a phone number (referred to as an Ani) which follows the format (NPA)NXX-LINE:

using System;
using System.Collections.Generic;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;
using System.Linq;


[ServiceContract(Name="TroubleTicketService", Namespace="http://blogs.msdn.com/kaevans")]
public interface IService
{
    [OperationContract] 
    IEnumerable<TroubleTicket> GetTroubleTickets(Ani tn);
}

[DataContract(Namespace = "http://blogs.msdn.com/kaevans")]
public class Ani
{
    [DataMember]
    public string NPA { get; set; }
    [DataMember]
    public string NXX { get; set; }
    [DataMember]
    public string Line { get; set; }
}
public class Service : IService
{
    public IEnumerable<TroubleTicket> GetTroubleTickets(Ani tn)
    {
        TroubleTicketsDataContext db = new TroubleTicketsDataContext(@"Server=(local);Integrated Security=true;Initial Catalog=tickets");
        var tickets = from t in db.GetTroubleTickets(tn.NPA, tn.NXX, tn.Line)                      
                      select  t;
        return tickets;   
    }
}

Here's the LINQ to SQL Designer image overlayed with the Server Explorer pane.  I have a simple table, TroubleTicket, that has a stored procedure, GetTroubleTickets, which accept 3 string parameters.  Since we are using this with WCF, make sure to click the designer surface and show the Property Pane to change the "Serialization Mode" property value to "Unidirectional".

Linq to SQL with Stored Procedures 

You can see that there's nothing in there that says anything about JSON support, yet we can configure this service to return JSON over HTTP instead of SOAP.  .NET 3.5 introduces JSON serialization support for services without having to decorate your code with any special attributes, you just configure an endpoint to use the webHttpBinding, and add an endpoint behavior that enables web scripting.

  <system.serviceModel>
    <behaviors>
      <endpointBehaviors>
        <behavior name="AjaxBehavior">
          <enableWebScript />
        </behavior>
      </endpointBehaviors>
    </behaviors>
    <services>
      <service name="Service">
        <endpoint 
          address="" 
          binding="webHttpBinding"
          contract="IService" 
          behaviorConfiguration="AjaxBehavior" />        
      </service>
    </services>
  </system.serviceModel>

Very cool, now our service is exposed and returns JSON over HTTP instead of XML over HTTP!  You can now go to your service endpoint and append "/js" to the querystring to see the JSON proxy that is generated.  For instance, the endpoint URL on my machine is:

http://localhost:49455/WCFServices/Service.svc/js

To call the service, ASP.NET 3.5's built-in AJAX support makes referencing the JavaScript proxy very simple.

    <asp:ScriptManager ID="ScriptManager1" runat="server">
        <Scripts>
            <asp:ScriptReference Path="~/Scripts.js" />
        </Scripts>
        <Services>
            <asp:ServiceReference Path="~/Service.svc" />
        </Services>
    </asp:ScriptManager>

In this example, I am using the ScriptManager control in ASP.NET and pointing to our Service.svc host file.  Thanks to the JavaScript Intellisense features in Visual Studio 2008, I get Intellisense completion so that I can figure out how to reference the proxy correctly.

JavaScript Intellisense for Service Proxies in Visual Studio 2008

The question is, how do you call it?  You will see a ton of blog posts that show how to process the result of a web service call, even how to pass simple types as parameters to a web service proxy, but how do you pass a complex type?  The problem was that I could not figure out how to cons up an Ani type.  It's shown in the Intellisense, but there are no properties defined for it, and you can't use a "new" operator in JavaScript.  After a bit of searching the web, it turns out that calling JSON services looks a whole lot like the object construction syntax introduced in C# 3.0.

function callService(npa, nxx, line)
{        
    var tn = {"NPA" : npa, "NXX" : nxx, "Line" : line};        
    blogs.msdn.com.kaevans.TroubleTicketService.GetTroubleTickets(tn,onServiceCompleted);                   
}

Inside the callService method, we create an Ani class using JSON.  To do that, we create a variable called "tn" that has 3 properties: NPA, NXX, and Line, and we provide the values that were passed into the method.  Then just pass that to the method, and you get your result.

Here is the complete .ASPX source.  There is no code-behind for this page because we are doing all of the processing client-side.

<%@ Page Language="C#" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title>WCF and AJAX Demo</title>    
    <link href="StyleSheet.css" rel="stylesheet" type="text/css" />    
</head>
<body>
    <form id="form1" runat="server">
    <asp:ScriptManager ID="ScriptManager1" runat="server">
        <Scripts>
            <asp:ScriptReference Path="~/Scripts.js" />
        </Scripts>
        <Services>
            <asp:ServiceReference Path="~/Service.svc" />
        </Services>
    </asp:ScriptManager>        
        <div id="query">
            <asp:TextBox ID="npa" runat="server"></asp:TextBox>
            <asp:TextBox ID="nxx" runat="server"></asp:TextBox>
            <asp:TextBox ID="line" runat="server"></asp:TextBox>
            <input type="button" id="button" value="Call Service" onclick="return onClick();" />
        </div>
        <div id="results">
            <asp:Table ID="resultTable" runat="server" EnableViewState="false" Width="100%">
                <asp:TableHeaderRow>
                    <asp:TableHeaderCell>ID</asp:TableHeaderCell>
                    <asp:TableHeaderCell>NPA</asp:TableHeaderCell>
                    <asp:TableHeaderCell>NXX</asp:TableHeaderCell>
                    <asp:TableHeaderCell>LINE</asp:TableHeaderCell>
                    <asp:TableHeaderCell>Description</asp:TableHeaderCell>                
                </asp:TableHeaderRow>        
            </asp:Table>
        </div>    
    </form>
</body>
</html>

The UI is very simple, we have a couple UI elements that gather user input and a button with a JavaScript click handler.  The work is done within our JavaScript (scripts.js, referenced in the ScriptManager control).  That code is also simple, we just call the service and then add rows to the HTML table with the results.

function onClick()
{
    callService($get("npa").value, $get("nxx").value, $get("line").value);
}

function callService(npa, nxx, line)
{        
    var tn = {"NPA" : npa, "NXX" : nxx, "Line" : line};        
    blogs.msdn.com.kaevans.TroubleTicketService.GetTroubleTickets(tn,onServiceCompleted);                   
}

function onServiceCompleted(sender, e)
{
   if(sender.length > 0)
   {
      for(var i=0;i<sender.length;i++)
      {          
          var ticket = sender[i];
          addTableRow(ticket.TicketID, ticket.NPA, ticket.NXX, ticket.Line, ticket.Description, ticket.Status);
      }
   }
}

$ctn = function(text){return document.createTextNode(text);}

function addTableRow(id, npa, nxx, line, description, status)
{
    var table = $get("resultTable"); 

    var row = table.insertRow(table.rows.length);
    var idCell = row.insertCell(0);
    idCell.appendChild($ctn(id));

    var npaCell = row.insertCell(1);
    npaCell.appendChild($ctn(npa));

    var nxxCell = row.insertCell(2);
    nxxCell.appendChild($ctn(nxx));

    var lineCell = row.insertCell(3);
    lineCell.appendChild($ctn(line));

    var descriptionCell = row.insertCell(4);
    descriptionCell.appendChild($ctn(description));

    var statusCell = row.insertCell(5);
    statusCell.appendChild($ctn(status));        
}

If you haven't seen the "$get" shortcut method for ASP.NET AJAX, it is just a global function defintion that is equivalent to document.getElementById(name).  I just use that to query the UI elements for their values and then pass into my callService function.  Notice that I created a shortcut method "$ctn" that replaces the createTextNode functionality, it works the same way. 

That's all there really is to using ASP.NET AJAX and WCF together.  The LINQ inclusion was just icing on the cake.  In all, this took me MUCH less time to develop than if I had tried to use SOAP or REST/POX and process the XML result on the client.  Using JSON encoding significantly shortened my development cycle, and I really didn't have to know anything about JSON except for how to create the class variable to pass to my service.  And the Intellisense in Visual Studio 2008 Beta 2 makes this so much easier to work with.

Man, I think I am getting to be more of a fan of JSON services instead of SOAP based services!  Never thought I would say that.

For more information on the technologies used in this post, here are some great references:

  • Kirk,

    Is there something I have to do to in my web.config file for the Client to know about the webservice?  I keep getting an undefind error when trying to call the Service from my javascript method.  The only thing that could possible be different that I can think of is service configuration in the web.config file.

  • @Matt - Feel free to send me a small sample to my microsoft.com email address (prepend with "kirke") so that I can take a look.

  • Most other WCF/JSON examples indicate the need for an intermediate C# serialization class where all properties have a [DataMember] attribute. I assume this attribute provides guidance to the JSON serialize process. In your example the inbound parameter class is decorated with [DataMember] attributes but this is not necessary for the returned raw LINQ result enumeration.

    Could you explain further how you managed to skip over manual JSON serialization via an intermediate decorated result class, as seen in other examples.

  • @ExtJSdeveloper  -

    When I used the LINQ to SQL designer, I indicated that you should click the design surface, go to the property pane, and change the "Serialization Mode" property value to "Unidirectional".  Behind the scenes, this decorates the LINQ generated types with the DataMember attributes behind the scenes.  I don't need intermediate types because the LINQ types themselves are decorated with DataMember attributes.

    The other key part is that I used the webHttpBinding binding with a behavior "enableWebScript", which causes the DataContractJsonSerializer to be used instead of XmlSerializer or DataContractSerializer.  This is how the returned types are serialized to JSON.

    You can see this illustrated more explicitly in the following post:

    http://blogs.msdn.com/kaevans/archive/2007/09/04/use-linq-and-net-3-5-to-convert-rss-to-json.aspx

  • Just wonder whether the JSON serializer has been tested with IIS 5. I can get some samples to work with IIS 6 and the VS build-in webserver but not IIS 5. I could not access the ../js page at all.

  • .NET 3.5 is not supported on Windows 2000.  From System Requirements section on the download page:

    Supported Operating Systems: Windows Server 2003; Windows Vista; Windows XP

    [via http://www.microsoft.com/downloads/details.aspx?familyid=d2f74873-c796-4e60-91c8-f0ef809b09ee&displaylang=en#Requirements]

  • Kirk,

    It is a Windows XP SP2 machine that has the issue with IIS 5.x.

  • Looks like this is an interesting topic to a lot of people since part 1 of this series made it to the

Page 1 of 1 (8 items)
Leave a Comment
  • Please add 2 and 1 and type the answer here:
  • Post
Translate This Page
Search
Archive
Archives