Windows Phone 7 Line of Business App Dev :: Building a WCF REST + JSON Service

| September 27, 2010 | 5 Comments

Let’s start at the server and work our way to the phone.  Anyone who knows me is probably aware that I come from a wireless background so I’m always obsessed with things like coverage, bandwidth and latency when it comes to building mobile systems.  The only thing I assume in mobile development is frequent network dropouts and 28.8 kb/s modem speeds.  Think Compuserve.  For device apps to be successful, they must pre-fetch the data they need and cache it offline so a user can keep working when the network is not around.  This is not typical SOA, calling Web Services on-demand to help drive your application.  If your connectivity is that perfect, then build a mobile web site.  Additionally, they must first save captured data locally and only try to upload it to the server when network connectivity is available.  Never get caught in the trap of assuming ubiquitous network availability.

Networking + Wire Protocols

If you’ve looked at the Networking in Silverlight for Windows Phone 7 documentation on MSDN, you might have noticed that a number of things are missing:

  • WCF RIA Services
  • WCF Data Services (OData)
  • Duplex Communication over HTTP
  • Sockets
  • UDP Multicast Client
  • NTLM Auth
  • RSS/ATOM Feeds
  • Custom Bindings
  • JSON Serialization

 

I decided to focus on the networking capabilities that did work out of the box so that I could continue to build large-scale mobile/wireless solutions for our customers.  We’ve got SOAP and REST + Basic Authentication.  That’s right, you can add a Service Reference to an ASMX or WCF Web Service and you can pass your credentials to IIS with Basic Auth as long as you remember to wrap that clear text with SSL.  What I’m truly interested in is using the smallest, most efficient wire protocol possible to traverse all those unreliable wireless networks.  I’ve looked at various ways to do something simple like return a short list of Customers to a Windows Phone 7 device.  Some wire protocols are more efficient than others.  For instance:

  • An OData list of just 8 Customers from a WCF Data Service was extremely verbose and weighed in at a whopping 8.5 kb.  Yikes!
  • The same list of 8 Customers using SOAP and a DataSet dropped us down to 3 kb but still too big for my taste.
    • <?xml version="1.0" encoding="utf-8"?>
      <DataSet xmlns="
      http://tempuri.org/">
        <xs:schema id="NewDataSet" xmlns="" xmlns:xs="
      http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
          <xs:element name="NewDataSet" msdata:IsDataSet="true" msdata:UseCurrentLocale="true">
            <xs:complexType>
              <xs:choice minOccurs="0" maxOccurs="unbounded">
                <xs:element name="Driver">
                  <xs:complexType>
                    <xs:sequence>
                      <xs:element name="DriverId" type="xs:int" minOccurs="0" />
                      <xs:element name="DistributionCenterId" type="xs:int" minOccurs="0" />
                      <xs:element name="FirstName" type="xs:string" minOccurs="0" />
                      <xs:element name="LastName" type="xs:string" minOccurs="0" />
                    </xs:sequence>
                  </xs:complexType>
                </xs:element>
              </xs:choice>
            </xs:complexType>
          </xs:element>
        </xs:schema>
        <diffgr:diffgram xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" xmlns:diffgr="urn:schemas-microsoft-com:xml-diffgram-v1">
          <NewDataSet xmlns="">
            <Driver diffgr:id="Driver1" msdata:rowOrder="0">
              <DriverId>1</DriverId>
              <DistributionCenterId>1</DistributionCenterId>
              <FirstName>Rob</FirstName>
              <LastName>Tiffany</LastName>
            </Driver>
            <Driver diffgr:id="Driver2" msdata:rowOrder="1">
              <DriverId>2</DriverId>
              <DistributionCenterId>1</DistributionCenterId>
              <FirstName>Loke Uei</FirstName>
              <LastName>Tan</LastName>
            </Driver>
            <Driver diffgr:id="Driver3" msdata:rowOrder="2">
              <DriverId>3</DriverId>
              <DistributionCenterId>1</DistributionCenterId>
              <FirstName>Dan</FirstName>
              <LastName>Bouie</LastName>
            </Driver>
            <Driver diffgr:id="Driver4" msdata:rowOrder="3">
              <DriverId>4</DriverId>
              <DistributionCenterId>1</DistributionCenterId>
              <FirstName>John</FirstName>
              <LastName>Dietz</LastName>
            </Driver>
            <Driver diffgr:id="Driver5" msdata:rowOrder="4">
              <DriverId>5</DriverId>
              <DistributionCenterId>2</DistributionCenterId>
              <FirstName>Derek</FirstName>
              <LastName>Snyder</LastName>
            </Driver>
            <Driver diffgr:id="Driver6" msdata:rowOrder="5">
              <DriverId>6</DriverId>
              <DistributionCenterId>2</DistributionCenterId>
              <FirstName>Steve</FirstName>
              <LastName>Hegenderfer</LastName>
            </Driver>
            <Driver diffgr:id="Driver7" msdata:rowOrder="6">
              <DriverId>7</DriverId>
              <DistributionCenterId>2</DistributionCenterId>
              <FirstName>Chip</FirstName>
              <LastName>Vollers</LastName>
            </Driver>
            <Driver diffgr:id="Driver8" msdata:rowOrder="7">
              <DriverId>8</DriverId>
              <DistributionCenterId>2</DistributionCenterId>
              <FirstName>James</FirstName>
              <LastName>Pratt</LastName>
            </Driver>
          </NewDataSet>
        </diffgr:diffgram>
      </DataSet>

  • Switching to REST + XML dropped us to a much leaner 1.2 kb.
    • <ArrayOfDriver xmlns=http://schemas.datacontract.org/2004/07/ContosoWcfService.Models

      xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
      <Driver>
      <DistributionCenterId>1</DistributionCenterId>
      <DriverId>1</DriverId>
      <FirstName>Rob</FirstName>
      <LastName>Tiffany</LastName>
      </Driver>
      <Driver>
      <DistributionCenterId>1</DistributionCenterId>
      <DriverId>2</DriverId>
      <FirstName>Loke Uei</FirstName>
      <LastName>Tan</LastName>
      </Driver>
      <Driver>
      <DistributionCenterId>1</DistributionCenterId>
      <DriverId>3</DriverId>
      <FirstName>Dan</FirstName>
      <LastName>Bouie</LastName>
      </Driver>
      <Driver>
      <DistributionCenterId>1</DistributionCenterId>
      <DriverId>4</DriverId>
      <FirstName>John</FirstName>
      <LastName>Dietz</LastName>
      </Driver>
      <Driver>
      <DistributionCenterId>2</DistributionCenterId>
      <DriverId>5</DriverId>
      <FirstName>Derek</FirstName>
      <LastName>Snyder</LastName>
      </Driver>
      <Driver>
      <DistributionCenterId>2</DistributionCenterId>
      <DriverId>6</DriverId>
      <FirstName>Steve</FirstName>
      <LastName>Hegenderfer</LastName>
      </Driver>
      <Driver>
      <DistributionCenterId>2</DistributionCenterId>
      <DriverId>7</DriverId>
      <FirstName>Chip</FirstName>
      <LastName>Vollers</LastName>
      </Driver>
      <Driver>
      <DistributionCenterId>2</DistributionCenterId>
      <DriverId>8</DriverId>
      <FirstName>James</FirstName>
      <LastName>Pratt</LastName>
      </Driver>
      </ArrayOfDriver>

  • Switching my REST service encoding from XML to JSON to us down to a mere 639 bytes.
    • [{"DistributionCenterId":1,"DriverId":1,"FirstName":"Rob","LastName":"Tiffany"},{"DistributionCenterId":1,"DriverId":2,"FirstName":"Loke Uei","LastName":"Tan"},{"DistributionCenterId":1,"DriverId":3,"FirstName":"Dan","LastName":"Bouie"},{"DistributionCenterId":1,"DriverId":4,"FirstName":"John","LastName":"Dietz"},{"DistributionCenterId":2,"DriverId":5,"FirstName":"Derek","LastName":"Snyder"},{"DistributionCenterId":2,"DriverId":6,"FirstName":"Steve","LastName":"Hegenderfer"},{"DistributionCenterId":2,"DriverId":7,"FirstName":"Chip","LastName":"Vollers"},{"DistributionCenterId":2,"DriverId":8,"FirstName":"James","LastName":"Pratt"}]

 

Based on the title of this article, it’s probably obvious that I decided to go with REST + JSON because my customers care about speed, efficiency and reduced data costs.  I know I said JSON Serialization isn’t supported out of the box but that’s only partially true since you get the DataContractJsonSerializer to work with WCF objects encoded with JSON.  In this article, I’m going to walk you through the WCF project I built in Visual Studio 2010 so let’s get started.

Models

Since we won’t be generating a client-side proxy for your phone via adding a Service Reference or using slsvcutil.exe, then you can’t count on getting auto-generated Model objects.  This means you actually get to write some more code and build them yourself.  The Models you build on the server for your WCF Service will be the same one’s you copy to your Windows Phone 7 project so you’re client code will know what to do with them when they arrive. In order to ensure that your class and it’s properties are properly serialized for transport over the air, don’t forget to use the DataContract() and DataMember() attributes to make the magic happen.  Since I want my models to be Silverlight-friendly when it comes to data binding, I implement the INotifyPropertyChanged Interface.  While this may not seem important for the business entities you’re creating on the server, your Silverlight client needs this so I’m doing it now since I will be copying these same classes to my Windows Phone 7 project later.  The net of all this is that you add a PropertyChangedEventHandler to each class and you fire that event in each property’s setter.  This facilitates new or changed data in a bound control to update the model automatically.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Runtime.Serialization;
using System.ComponentModel;

namespace ContosoWcfService.Models
{
    [DataContract()]
    public class Customer : INotifyPropertyChanged
    {
        public Customer() { }

        private int customerId;
        private int distributionCenterId;
        private int routeId;
        private string name;
        private string streetAddress;
        private string city;
        private string stateProvince;
        private string postalCode;

        [DataMember()]
        public int CustomerId
        {
            get { return customerId; }
            set
            {
                customerId = value;
                NotifyPropertyChanged("CustomerId");
            }
        }

        [DataMember()]
        public int DistributionCenterId
        {
            get { return distributionCenterId; }
            set
            {
                distributionCenterId = value;
                NotifyPropertyChanged("DistributionCenterId");
            }
        }

        [DataMember()]
        public int RouteId
        {
            get { return routeId; }
            set
            {
                routeId = value;
                NotifyPropertyChanged("RouteId");
            }
        }

        [DataMember()]
        public string Name
        {
            get { return name; }
            set
            {
                name = value;
                NotifyPropertyChanged("Name");
            }
        }

        [DataMember()]
        public string StreetAddress
        {
            get { return streetAddress; }
            set
            {
                streetAddress = value;
                NotifyPropertyChanged("StreetAddress");
            }
        }

        [DataMember()]
        public string City
        {
            get { return city; }
            set
            {
                city = value;
                NotifyPropertyChanged("City");
            }
        }

        [DataMember()]
        public string StateProvince
        {
            get { return stateProvince; }
            set
            {
                stateProvince = value;
                NotifyPropertyChanged("StateProvince");
            }
        }

        [DataMember()]
        public string PostalCode
        {
            get { return postalCode; }
            set
            {
                postalCode = value;
                NotifyPropertyChanged("PostalCode");
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;
        private void NotifyPropertyChanged(String propertyName)
        {
            if (null != PropertyChanged)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }

    }
}

Contract

In WCF, it’s all about contract-first development so you have to start out with a public Interface.  This is your Service Contract that exposes one or more Operation Contracts.  Since we’re going with REST, and I’m retrieving data, this means I’m going to use a GET request.  The WebGet attribute allows me to specify this easily.  Had I been Inserting, Updating or Deleting data, the WebInvoke attribute would’ve helped me with my POST, PUT or DELETE.  Next up is the use of the UriTemplate.  This allows me to point to REST/SEO friendly URIs to perform my operations rather than calling web methods that are buried inside a single URI.  In this case, I just want to retrieve a list of Customers so I set the UriTemplate = “/Customers.”  Since we’re trying to avoid the kind of extra baggage you get with SOAP, I set the BodyStyle == Bare which prevents extra XML wrapping of our parameters and return values.  For my ResponseFormat property, I set the WebMessageFormat to Json instead of XML in order to send the absolute minimum amount of data over the air.  Last but not least, I finish things up with my GetCustomers() method that returns an ObservableCollection of Customer objects to the Windows Phone 7 client.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Web;
using System.Text;
using ContosoWcfService.Models;
using System.Collections.ObjectModel;

namespace ContosoWcfService
{
    [ServiceContract]
    public interface IRestData
    {
        [OperationContract]
        [WebGet(UriTemplate = "/Customers", BodyStyle = WebMessageBodyStyle.Bare, ResponseFormat = WebMessageFormat.Json)]
        ObservableCollection<Customer> GetCustomers();

    }

}

Service Code

Now it’s time to implement my IRestData Interface with the RestData class.  Inside this class, I have my GetCustomers() method which connects to SQL Server, retrieves a list of Customers and sends it back to Windows Phone 7.  As you can see in the code below, I’m not using the Entity Framework but rather I’m just querying SQL Server directly.  Normally, I would call a stored procedure for even better performance but for clarity I’m showing the actual SELECT statement.  The key to this method is how I create new Customer objects and hydrate them with data as I loop through the SqlDataReader.  I then add them to the ObservableCollection which gets returned to the phone with wireless-friendly JSON encoding.

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

using ContosoWcfService.Models;
using System.Data.SqlClient;
using System.Web.Configuration;
using System.Collections.ObjectModel;

namespace ContosoWcfService
{
    public class RestData : IRestData
    {
        //Get the Database Connection string
        private string _connectionString = WebConfigurationManager.ConnectionStrings["ContosoBottlingConnectionString"].ConnectionString;

        public ObservableCollection<Customer> GetCustomers()
        {
            SqlConnection _cn = new SqlConnection(_connectionString);
            SqlCommand _cmd = new SqlCommand();
            _cmd.CommandText = "SELECT CustomerId, DistributionCenterId, RouteId, Name, StreetAddress, City, StateProvince, PostalCode FROM Customer";

            try
            {
                _cn.Open();
                _cmd.Connection = _cn;

                ObservableCollection<Customer> _customerList = new ObservableCollection<Customer>();

                SqlDataReader _dr = _cmd.ExecuteReader();
                while (_dr.Read())
                {
                    Customer _customer = new Customer();
                    _customer.CustomerId = Convert.ToInt32(_dr["CustomerId"]);
                    _customer.DistributionCenterId = Convert.ToInt32(_dr["DistributionCenterId"]);
                    _customer.RouteId = Convert.ToInt32(_dr["RouteId"]);
                    _customer.Name = Convert.ToString(_dr["Name"]);
                    _customer.StreetAddress = Convert.ToString(_dr["StreetAddress"]);
                    _customer.City = Convert.ToString(_dr["City"]);
                    _customer.StateProvince = Convert.ToString(_dr["StateProvince"]);
                    _customer.PostalCode = Convert.ToString(_dr["PostalCode"]);

                    //Add to List
                    _customerList.Add(_customer);
                }
                return _customerList;
            }
            catch (Exception ex)
            {
                throw new Exception(ex.Message);
            }
            finally
            {
                _cmd.Dispose();
                _cn.Close();
            }
        }

Web.config

A WCF Service just wouldn’t be complete without a Web.config file.  The big takeway here is I’m storing my connection string to SQL Server and I’ve created a webHttp endpoint behavior for my REST services.  Down in my service name I point to my endpoint behavior, set my binding == webHttpBinding, and set my contract == IRestData.  

<?xml version="1.0" encoding="UTF-8"?>
<configuration>

  <system.web>
    <compilation debug="true" targetFramework="4.0" />
  </system.web>

  <connectionStrings>
    <add name="ContosoBottlingConnectionString" connectionString="Data Source=RTIFFANY3\SQLEXPRESS;Initial Catalog=ContosoBottling;Integrated Security=True" providerName="System.Data.SqlClient" />
  </connectionStrings>
  <system.serviceModel>
    <behaviors>
      <serviceBehaviors>
        <behavior>
          <!– To avoid disclosing metadata information, set the value below to false and remove the metadata endpoint above before deployment –>
          <serviceMetadata httpGetEnabled="true" />
          <!– To receive exception details in faults for debugging purposes, set the value below to true.  Set to false before deployment to avoid disclosing exception information –>
          <serviceDebug includeExceptionDetailInFaults="true" />
        </behavior>
      </serviceBehaviors>

      <endpointBehaviors>
        <behavior name="REST">
          <webHttp />
        </behavior>

        <behavior name="HelpBehavior">
          <webHttp helpEnabled="true" />
        </behavior>

      </endpointBehaviors>

    </behaviors>
    <services>
      <service name="ContosoWcfService.RestData">
        <endpoint address="" behaviorConfiguration="REST" binding="webHttpBinding" contract="ContosoWcfService.IRestData" />
      </service>
    </services>
    <serviceHostingEnvironment multipleSiteBindingsEnabled="true" />

  </system.serviceModel>
<system.webServer>
    <modules runAllManagedModulesForAllRequests="true" />
        <directoryBrowse enabled="true" />
  </system.webServer>
</configuration>

Conclusion

I hope this helps get you started down the road to building wireless bandwidth efficient WCF Services with REST and JSON.  Keep in mind that you can build these same types of services in Windows Azure as well along with connections to SQL Azure.  In the next article, I’ll show you how to consume these types of services from your Windows Phone 7 project

Keep coding,

Rob

Tags: , , , , , , , , , , , , , , , ,

Category: Windows Phone 7

About the Author ()

A mobile strategist and cloud architect at Microsoft, Rob has spent his career as an entrepreneur, advisor, teacher, developer, speaker, and author of bestselling books on mobile and wireless technologies. A pioneer of the smartphone revolution, he drove the development of the mobile app ecosystem from its earliest days and co-founded the world’s first cloud-based mobile device management company.

Comments (5)

Trackback URL | Comments RSS Feed

  1. Patrick says:

    Just adding this – if you need the ContosoBottling db referenced by Rob’s post, you can get it from his (apparently) publically shared SkyDrive, here:

    http://cid-8b9c82da88af61fc.skydrive.live.com/browse.aspx/Public

  2. Scott says:

    Excellent examples Rob! I had always assumed that a SOAP envelope was larger than Odata, but I’ve always gone with JSON regardless. A neat technique that I like to use to cut down the JSON even more, is to use:ex [DataMember(Name="DCID")] for the DistributionCenterID property.

  3. We’re a gaggle of volunteers and starting a brand new scheme in our community. Your web site offered us with valuable information to work on. You’ve done an impressive process and our entire group can be grateful to you.

Leave a Reply