Consuming WCF Web Apis in Silverlight

Topics: Web Api
Jan 24, 2011 at 12:48 AM
Edited Jan 24, 2011 at 12:49 AM

Hello All,

I created a 'SimpleHttpClient' which can be used in Silverlight to access a WCF Web Api.  It currently can be found as a fork of the WCF HTTP Contrib project.  Currently Silverlight does not provide the ability to create your own custom query provider, so this 'SimpleHttpClient' and classes do not use LINQ directly, though the API is fairly similar (using extension methods).  The client & helper libraries were written from scratch with no direct dependencies on the core http framework.  I'm currently also working on a WP7 compatible version.  If you're interested in seeing improvements to this or have any suggestions I'd love to hear them.  Hopefully this helps in your quest to write and consume REST apis in Silverlight.

https://hg01.codeplex.com/forks/xamlcoder/wcfhttpcontrib

Here are a few sample queries:

 

// Get all people
SimpleHttpClient client = new SimpleHttpClient("http://localhost:1182/people");
client.Accept = MediaType.Xml; // can also use MediaType.Json, which is the default

var query = client.CreateQuery<Person>();

HandleQuery(query);

 

 

 

// Get person by ID
int id;

if (Int32.TryParse(this.uxPersonID.Text, out id))
{
	SimpleHttpClient client = new SimpleHttpClient("http://localhost:1182/people");

	var query = client.CreateQuery<Person>().Where(c => c.ID, id);

	HandleQuery(query);
}

 

 

// Get top 3 people
SimpleHttpClient client = new SimpleHttpClient("http://localhost:1182/people");

var query = client.CreateQuery<Person>().Take(3);

HandleQuery(query);

 

 

 

// Get 3rd person
SimpleHttpClient client = new SimpleHttpClient("http://localhost:1182/people");

var query = client.CreateQuery<Person>().Skip(2).Take(1);

HandleQuery(query);

 

 

 

// Handle query method used above
private void HandleQuery(HttpQuery<Person> query)
{
	var task = query.ExecuteAsync();
	task.ContinueWith(t =>
	{
		Execute.OnUIThread(() =>
		{
			if (!t.IsFaulted && t.IsCompleted && t.Result != null)
			{
				t.Result.Apply(p => { Debug.WriteLine("Person: {0}", p); });
			}
		});
	});
}

 

 

 

// Create new person
Uri uri = new Uri("http://localhost:1182/people");

SimpleHttpClient client = new SimpleHttpClient(uri.ToString());

var contact = new Person { ID = 5, Name = personName.Text };

var stream = contact.WriteObjectAsXml();

var request = new HttpRequestMessage(HttpMethod.Post);
request.Accept = MediaType.Xml;
request.ContentType = MediaType.Xml;
request.RequestUri = uri;
request.Content = stream;

var task = client.SendAsync(request);
task.ContinueWith(t =>
{
	Execute.OnUIThread(() =>
	{
		var person = t.Result.ReadXmlAsObject<Person>();

		if (person != null)
		{
			Debug.WriteLine("Person: {0}", person);
		}
	});
});

 

 

Regards,

Joe

http://xamlcoder.com

Jan 24, 2011 at 4:40 AM
Edited Jan 24, 2011 at 5:29 AM

Note that for this to fully work with Preview 3, make sure you have this patch

http://wcf.codeplex.com/Thread/View.aspx?ThreadId=242523

 

And the UriTemplateOperationSelector changes from this changeset:

http://wcf.codeplex.com/SourceControl/changeset/changes/c4e21202c82d

Jan 24, 2011 at 9:54 AM

Thanks !great news.

Jan 25, 2011 at 1:27 AM

Hey Joe,

Can you tell me how you would feel about a slightly different approach to doing the query thing?  For example:

 

        // Get all people
        SimpleHttpClient client = new SimpleHttpClient();
        client.Accept = MediaType.QueryFactory; 
        var queryFactory = client.Get("http://localhost:1182/peopleQueries").Content.ReadAsQueryFactory();
       client.Accept = MediaType.Xml; 
        var query = queryFactory.CreateQuery<Person>();

        HandleQuery(query);

The difference here is that all the magic for creating queries is encapsulated in the parser for a new media type "application/query-factory+xml" or something like that.  That representation could contain all the necessary metadata to build what ever queries are valid.  For me, the extensibility point in HTTP is with the media types.  The idea of loading more functionality in the HttpClient class seems like going against the grain.

 

 

 

 

Jan 25, 2011 at 5:01 PM
Edited Jan 25, 2011 at 5:24 PM

Hey Darrel, I like the idea of having a QueryFactory. Right now, the all of the hard work is being done internally in the HttpQuery class, (using a QueryBuilder class) which is actually not dependant on the SimpleHttpClient.  The HttpQuery also uses a IHttpQueryProvider, which I have a HttpQueryProvider class which wraps the SimpleHttpClient.  I added a CreateQuery method to the SimpleHttpClient for simplicty sake.  The HttpQueryProvider class takes a HttpQuery, converts it to a HttpRequestMessage, and uses the SimpleHttpClient to send the message.  Right now the CreateQuery method just creates a HttpQuery directly, though by introducing a factory it could create any number of HttpQueries.

Here's the definition for an IHttpQueryProvider

 

public interface IHttpQueryProvider
{
	Task<TResult> ExecuteSingleAsync<T, TResult>(HttpQuery<T> query);
	Task<IEnumerable<T>> ExecuteAsync<T>(HttpQuery<T> query);
}

 

And here's some alternate syntax that is currently valid for using the HttpQuery class:

 

SimpleHttpClient client = new SimpleHttpClient("http://localhost:1182/people");

IHttpQueryProvider queryProvider = new HttpQueryProvider(client);

var query = new HttpQuery<Person>(queryProvider);
// query.ToString() == http://localhost:1182/people

var query = new HttpQuery<Person>(new HttpQueryProvider(new SimpleHttpClient("http://localhost:1182")), /* resource name*/ "people");
// query.ToString() == http://localhost:1182/people

var query = new HttpQuery<Person>(queryProvider).Skip(5).Take(10);
// query.ToString() == http://localhost:1182/people?$skip=5$top=10

int id = 1;
var query = new HttpQuery<Person>(queryProvider).Where(c => c.ID, id);
// query.ToString() == http://localhost:1182/people?$filter=ID eq 1

var query = new HttpQuery<Person>(null, /* resource name*/ "people");
// query.ToString() == people

var query = new HttpQuery<Person>(null, /* resource name*/ "people").Take(10);
// query.ToString() == people?$top=10

HandleQuery(query);

 

What I'm thinking is something like this:

 

SimpleHttpClient client = new SimpleHttpClient("http://localhost:1182/people");

IHttpQueryProvider queryProvider = new HttpQueryProvider(client);

IHttpQueryFactory queryFactory = new MyHttpQueryFactory(queryProvider);

var query = queryFactory.CreateQuery<Person>();

HandleQuery(query);

 

Which should allow you do add extension / helper methods on the SimpleHttpClient to achieve something like what you proposed.  That way the SimpleHttpClient is still just working with HttpRequest/HttpResponse messages.

 

SimpleHttpClient client = new SimpleHttpClient("http://localhost:1182/peopleQueries");
IQueryFactory queryFactory = client.GetQueryFactory("application/query-factory+xml");
// create queries

 

I guess in my sample I'm trying to abstract everything to do with specific media types into the a specialized HttpQuery + HttpQueryFactory + HttpQueryProvider.  Would that work?  Can you give a more concrete sample of a specific media type scenario you'd like to see covered?

Edit: changed syntax to what I really meant for the GetQueryFactory syntax.  ;]

Jan 29, 2011 at 6:56 AM
Edited Jan 29, 2011 at 7:01 AM

Code updated to support PUT and DELETE - Update and Delete methods on the HttpQuery class.  I added a Create method to the HttpQuery class which encapsulates a POST for a new object.  I also exposed the ETag on HttpResponseMessage, and added IfMatch and IfMatchNone to HttpRequestMessage.  So I think the next task is to refactor the use of Task's so we can make this WP7 compatible.  The Silverlight portion is currently written in Silverlight 3, so that's a start.

Here's a few more code snippets from the sample project:

 

private void CreatePerson(object sender, RoutedEventArgs e)
{
    Uri uri = new Uri("http://localhost:1182/people");

    SimpleHttpClient client = new SimpleHttpClient(uri.ToString());

    var person = new Person { ID = 0, Name = personName.Text };

    var query = client.CreateQuery<Person>();
    query.Create(person);

    var task = query.ExecuteSingleAsync();

    task.ContinueWith(t =>
    {
        Execute.OnUIThread(() =>
        {
            if (!t.IsFaulted && t.IsCompleted && t.Result != null)
            {
                Debug.WriteLine("Person: {0}", t.Result);

                _people.Add(t.Result);
            }
        });
    });
}
private void DeletePerson(object sender, RoutedEventArgs e)
{
    Person person = this.uxPeople.SelectedItem as Person;

    if (person != null)
    {
        SimpleHttpClient client = new SimpleHttpClient("http://localhost:1182/people");

        var query = client.CreateQuery<Person>().Delete(person.ID);

        uxQueryText.Text = query.GetFullyQualifiedQuery(client).ToString();

        var task = query.ExecuteSingleAsync();
        task.ContinueWith(t =>
        {
            Execute.OnUIThread(() =>
            {
                if (!t.IsFaulted && t.IsCompleted && t.Result != null)
                {
                    Debug.WriteLine("Person: {0}", t.Result);

                    _people.Remove(_people.First(p => p.ID == t.Result.ID));
                }
            });
        });
    }
}

private void UpdatePerson(object sender, RoutedEventArgs e)
{
    Person person = this.uxPeople.SelectedItem as Person;

    if (person != null)
    {
        person.Name = DateTime.Now.ToString();

        SimpleHttpClient client = new SimpleHttpClient("http://localhost:1182/people");

        var query = client.CreateQuery<Person>().Update(person.ID, person);

        uxQueryText.Text = query.GetFullyQualifiedQuery(client).ToString();

        var task = query.ExecuteSingleAsync();
        task.ContinueWith(t =>
        {
            Execute.OnUIThread(() =>
            {
                if (!t.IsFaulted && t.IsCompleted && t.Result != null)
                {
                    Debug.WriteLine("Person: {0}", t.Result);
                }
            });
        });
    }
}