Silverlight and WCF Web API Preview 4

The WCF Web API is a pretty exciting new offering from our team. The simplicity of creating and consuming a service is quite compelling. Especially considering that you can take advantage of the infrastructure surrounding HTTP and the web. One of the shortcomings though is the lack of Silverlight support. In Silverlight you can definitely use HTTP requests and responses to talk to WCF Web API services, but it helps to have a nice wrapper class for this.

Joe McBride wrote such a wrapper class and blogged about it here: https://xamlcoder.com/blog/2011/01/23/consuming-wcf-web-rest-apis-in-silverlight/

It's not too difficult to get to the source code. You need to get a Mercurial client like Tortoise Hg. In order to run the sample code, you'll need to install the Async CTP for Visual Studio in addition to the WCF Web API. I found that the sample code was not updated to match the latest version though. So here are the necessary edits.

The first is in the HttpContrib\Http\HttpExtensions.cs. The content-type will contain the encoding so this code:

 public static bool IsJsonContent(this HttpResponseMessage message)
{
  return message.ContentType == MediaType.Json;
}

public static bool IsXmlContent(this HttpResponseMessage message)
{
  return message.ContentType == MediaType.Xml;
}

Needs to be changed to this code:

 public static bool IsJsonContent(this HttpResponseMessage message)
{
  return message.ContentType.StartsWith(MediaType.Json);
}

public static bool IsXmlContent(this HttpResponseMessage message)
{
  return message.ContentType.StartsWith(MediaType.Xml);
}

The next thing is to fix the references in the QueryableSilverlight.Web project. It references the Microsoft.ServiceModel.Http and Microsoft.ServiceModel.WebHttp DLLs. Remove these and replace them with references to Microsoft.ApplicationServer.Http, Microsoft.ApplicationServer.HttpEnhancements, and Microsoft.ApplicationServer.ServiceModel.

The PeopleConfiguration.cs class is no longer needed and should be removed from the project.

Next, change the global.asax from:

 using System;
using System.Web.Routing;
using Microsoft.ServiceModel.Http;

public class Global : System.Web.HttpApplication
{
  protected void Application_Start(object sender, EventArgs e)
  {
    RouteTable.Routes.AddServiceRoute<PeopleResource>(
      "people", 
      new PeopleConfiguration());
  }
}

To:

 using System;
using System.Web.Routing;
using Microsoft.ApplicationServer.Http;
using Microsoft.ApplicationServer.Http.Activation;

public class Global : System.Web.HttpApplication
{
  protected void Application_Start(object sender, EventArgs e)
  {
    RouteTable.Routes.MapServiceRoute<PeopleResource>("people");
  }
}

The PeopleResource class can be simplified a bit. The AspNetCompatibilityRequirements attribute doesn't seem necessary. Also, the Get() method can be simplified by removing the QueryComposition attribute and replacing the IEnumerable return type with an IQueryable return type.

 [ServiceContract]
public class PeopleResource
{
  private static int _totalPeople = 1;
  private static List<Person> _people = new List<Person>()
    {
      new Person { ID = _totalPeople++, Name = "First"},
      new Person { ID = _totalPeople++, Name = "Second" },
      new Person { ID = _totalPeople++, Name = "Third"},
      new Person { ID = _totalPeople++, Name = "Fourth" },
    };

  [WebGet(UriTemplate = "")]
  public IQueryable<Person> Get()
  {
    return _people.AsQueryable();
  }

  [WebInvoke(UriTemplate = "", Method = "POST")]
  public Person Post(Person person)
  {
    if (person == null)
    {
      throw new ArgumentNullException("person");
    }

    person.ID = _totalPeople++;
    _people.Add(person);
    return person;
  }

  [WebInvoke(UriTemplate = "{id}", Method = "PUT")]
  public Person Put(int id, Person person)
  {
    Person localPerson = _people.First(p => p.ID == id);

    localPerson.Name = person.Name;

    return localPerson;
  }

  [WebInvoke(UriTemplate = "{id}", Method = "DELETE")]
  public Person Delete(string id)
  {
    var intId = int.Parse(id, CultureInfo.InvariantCulture);
    Person deleted = _people.First(p => p.ID == intId);

    _people.Remove(deleted);

    return deleted;
  }
}

You can also see that the Put() method is simplified as well. The id is passed as an int instead of a string and it no longer takes an HttpResponseMessage as the last parameter.

And that's really all there is to it! The modified sample is attached to this post.

Samples.Silverlight.zip