| Comments

We were all jumping for joy when Silverlight 2 beta 1 was released and the ability to connect to services was more readily/easily available to us.  For discoverable services that provided a WSDL we were quickly able to implement them using the Add Service Reference capability in Visual Studio 2008.  Beta 2 brings a few changes to the world of services that you should know about.  I’ll do my best to recap some of them here.

Generating a WCF Service

In beta 1 when we created a WCF service for use in Silverlight, we used the “WCF Service” template in Visual Studio (assuming you used Visual Studio).  This was fine and created a standard WCF service for us.  There were a few changes that we had to make to ensure that our Silverlight application could consume it in an acceptable manner.  First, we had to change the binding configuration in ASP.NET web.config from wsHttpBinding to basicHttpBinding as Silverlight only supports that binding type right now.  Secondly, we might have had to add capabilities to enable ASP.NET compatibility support depending on what we were doing with the service.  In beta 2, this process gets a bit simpler for services specifically built for Silverlight.  After you install the tools for Visual Studio, you now get a new item type:

It is important to note that the WCF Service template is still a perfectly acceptable one to choose, you just have to ensure to make those changes accordingly.  The new Silverlight-enabled WCF Service basically does those for us as well as add the ASP.NET compatibility attributes for us in the code.  Additionally, the traditional interface/implementation is simplified into a single class.  Again, the other ways are still valid, but for services specifically built for Silverlight, this might be an easier route to get them done.

Cross-domain policy file updates

Cross-domain restrictions still apply within beta 2 and the same rules apply.  There is one subtle change that is required to your clientaccesspolicy.xml file that is required.  In the allow-from node of the policy file, the attribute http-request-headers is now required.  If your service is an open/public one then specifying “*” is probably acceptable (you’ll have to be the judge of that.  If you only wanted to allow specific headers (besides the blacklisted ones) you can provide those in a comma-separated list and can use wildcards as well.  For example you could use X-MyApp-* if you wanted.

Another thing to note about the support for Adobe’s crossdomain.xml policy file is one thing we found in interpretation of the policy template.  Previously Flash was a Macromedia product and as such that file is adorned with a DOCTYPE that represents a schema with macromedia in it.  Well, Adobe has changed the schema a little bit and also updated the DOCTYPE to reflect Adobe as the authority.  Right now, Silverlight still expects to validate the macromedia declaration.

ServiceReferences.ClientConfig

In beta 1, when you performed the Add Service Reference operation a file named ServiceReferences.ClientConfig was created and had some configuration information in it.  This file, however, wasn’t really used.  In beta 2, this configuration file can be shipped with your XAP and used as configuration.  It provides a subset of WCF configuration.  Refined details of those settings are in the SDK, but I thought it might be helpful to know.

Change to WebClient

In beta 1, WebClient was the easiest library to use in accessing non-discoverable services.  One challenge was that it only supported GET verb requests.  In beta 2 WebClient has changed to enable POST verb actions as well.  the UploadStringAsync function will send the request but the endpoint URI must be a URI that accepts the POST verb.

In addition, WebClient is now callable on a background thread in addition to the UI thread.  This may come in handy for some situations.

I see these as small but helpful changes.  Most are based on feedback we received from beta 1 customers and community, so thank you for that feedback.  I hope this helps!

| Comments

Taking another cue from some great stuff Joel is doing, I liked his implementation of the ‘Leopard Screen Saver’ but wanted to make it more ‘real’ for me.  So I wired it up to my Flickr account.  Result here (using Silverlight Streaming):

I only had to change a few things.

First, in the Page_Loaded event, I removed the timer start function.  This was because with interacting with Flickr it was going to be async.  I didn’t want the timer to start until I knew the image collection was built.

My BuildCollection function now looks like this:

private void BuildCollection()
{
    // get Flickr NSID
    WebClient fu = new WebClient();
    fu.DownloadStringCompleted += new DownloadStringCompletedEventHandler(fu_DownloadStringCompleted);
    fu.DownloadStringAsync(new Uri(string.Format(FLICKR_USER_SEARCH, App.FlickrUser)));
}

This is a first call to get the NSID (internal Flickr user_id parameter) based on an initParam the user sends in.  The FLICKR_USER_SEARCH is just a const string parameter to the Flickr API rest call (with my API key in it) which looks like this:

const string FLICKR_USER_SEARCH = "http://api.flickr.com/services/rest/?method=flickr.urls.lookupuser
    &api_key=[yours]
    &url=http://flickr.com/photos/{0}";

I also use a FLICKR_USER_PHOTOS const which is like this:

const string FLICKR_USER_PHOTOS = "http://api.flickr.com/services/rest/?method=flickr.photos.search
    &user_id={0}
    &api_key=[yours]";

The completed event handler for the user search call looks like:

void fu_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
{
    XDocument resp = XDocument.Parse(e.Result);

    string nsid = resp.Element("rsp").Element("user").Attribute("id").Value;

    WebClient p = new WebClient();
    p.DownloadStringCompleted += new DownloadStringCompletedEventHandler(p_DownloadStringCompleted);
    p.DownloadStringAsync(new Uri(string.Format(FLICKR_USER_PHOTOS, nsid)));
}

to retrieve the NSID.  With that I then start the search photos call to retrieve 42 photos.  The sample is a little hard-coded with count values, but this is just a quick change to see if it would work.  When the async operation to search the photos returns, I use some LINQ to mash them into FlickrImage object types using a custom class I put in my Silverlight application.

public class FlickrImage
{
    public string id { get; set; }
    public string farm { get; set; }
    public string server { get; set; }
    public string secret { get; set; }
    public string title { get; set; }
    public int tagid { get; set; }
    public string url
    {
        get
        {
            return string.Format("http://farm{0}.static.flickr.com/{1}/{2}_{3}_m.jpg",
               farm, server, id, secret);
        }
    }
}

and then the LINQ query:

XDocument xml = XDocument.Parse(e.Result);

var photos = from results in xml.Descendants("photo")
             select new FlickrImage
             {
                 id = results.Attribute("id").Value.ToString(),
                 farm = results.Attribute("farm").Value.ToString(),
                 server = results.Attribute("server").Value.ToString(),
                 secret = results.Attribute("secret").Value.ToString(),
                 title = results.Attribute("title").Value.ToString()
             };

Once I have these, I essentially moved the iteration code Joel had into a foreach loop for my images and then started the timer.

foreach (FlickrImage photo in photos)
{
    Uri imageUri = new Uri(photo.url, UriKind.Absolute);

    if (i <= 16)
    {
        Tile tile = new Tile();
        Media media = new Media(imageUri, true);
        tile.Media = media;

        if (col % 4 == 0)
        {
            row++;
            col = 0;
        }
        tile.SetValue(Grid.ColumnProperty, col.ToString());
        tile.SetValue(Grid.RowProperty, row.ToString());
        this.LayoutRoot.Children.Add(tile);

        col++;
        _tiles.Add(tile);
        _images.Add(media);

    }
    else
        _images.Add(new Media(imageUri, false));

    i++;
}

_timer.Start();

The result is the same visual sample Joel had, but using my live Flickr photos from my gallery.  This is made possible because Flickr has a) a REST api and b) a valid cross-domain policy file.  Both of these enable Silverlight to be a great client for consuming this data.  The visualization could be enhanced to provide mouse-over effects to zoom the picture I suppose, but I’ll get to that later.  Pretty fun that just a few changes (and none to XAML) enabled me to make this real for me.

The code for my changes is here but note you must have your own Flickr API key from their site.  I also implemented supporting an initParams value so you can pass in the Flickr user name dynamically. 

UPDATE: To use the initParams capability and display a badge for your Flickr account, you can use an <iframe> tag and point to http://silverlight.services.live.com/invoke/217/FlickrBadge/iframe.html?u=[yourflickrname] where the [yourflickrname] is the last part of your Flickr photos url.  For example if your account is at http://flickr.com/photos/uhoh_over than you would use http://silverlight.services.live.com/invoke/217/FlickrBadge/iframe.html?u=uhoh_over.

 

| Comments

Wait! Don’t throw out your JSON services!

The Situation

You’ve made an investment in exposing some services for client script consumption.  Most likely if you did it in the past 2 years, that involved exposing your data as JSON formatted objects.

What is JSON?
It is a text-based, human-readable format for representing simple data structures and associative arrays (called objects)

Perhaps a search service returns a list of people formatted using your custom “Person” object and you’ve been using this in your AJAX applications for a while now.  Maybe your JSON data looks something like this:

[{"City":"Queen Creek","FirstName":"Tim","LastName":"Heuer",
"Website":"http:\/\/timheuer.com\/blog\/"},
{"City":"Portland","FirstName":"Scott","LastName":"Hanselman",
"Website":"http:\/\/hanselman.com\/blog\/"},
{"City":"Redmond","FirstName":"Scott","LastName":"Guthrie",
"Website":"http:\/\/weblogs.asp.net\/scottgu"},
{"City":"New Hampshire","FirstName":"Joe","LastName":"Stagner",
"Website":"http:\/\/joestagner.net"},
{"City":"Boston","FirstName":"Jesse","LastName":"Liberty",
"Website":"http:\/\/silverlight.net\/blogs\/jesseliberty"}]

If you squint long enough you can see that this represents what looks like an object that would have this structure:

FirstName, LastName, City, Website

You could consume this in a Javascript function or something in the client script of your web application and nicely iterate through the array of ‘Person’ types, using some human-readable code.

But now you want Silverlight!  And you think to yourself that you need to completely re-write everything to return .NET objects, etc.  Well, not so fast.

JSON Serialization

Because Silverlight 2 supports managed code development, you have some tricks in your bag to leverage existing services that you might not want (or need) to re-write entirely or just right away.  Let’s use the simple example above and assume I now want to use that same service and the same data result in my Silverlight application.  For now let’s assume the endpoint to that service is something like http://foo/mypeople/js.  Most JSON services were a result of using some type of RESTful query model, so essentially your request would likely be a simple GET or POST.

Using Silverlight 2 and the simple WebClient, we can easily get that information from that REST endpoint.

WebClient proxy = new WebClient();
proxy.OpenReadCompleted += new OpenReadCompletedEventHandler(proxy_OpenReadCompleted); 
proxy.OpenReadAsync(new Uri("http://foo/mypeople/js"));

Using WebClient, we essentially open a Stream (which is our endpoint) and get the data back.  You can learn more about WebClient and other services with Silverlight by watching my videos about web services and other HTTP-based communication.  Remembering that service calls in Silverlight 2 are asynchronous, we look at our ‘proxy_OpenReadCompleted’ event and could first get our result (with proper error checking of course) which is of type Stream:

Stream strm = e.Result;

Now with that stream (which is essentially the JSON data now), what do we do?  Enter DataContractJsonSerializer.  Remember, we have this available to us thanks to the CLR being in Silverlight 2.  Before we start to use this, however, our client application must be aware of the Type we plan to de-serialize it back into.  So in our Silverlight application we need to have that type defined as such:

public class Person
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string City { get; set; }
    public string Website { get; set; }
}

Simple enough.  Now we can complete our asynch handler like this and our Stream is now converted into an enumerable type that we can bind, iterate or do whatever we need to with our data.

void proxy_OpenReadCompleted(object sender, OpenReadCompletedEventArgs e)
{
    Stream strm = e.Result;
    DataContractJsonSerializer ser = new DataContractJsonSerializer(typeof(Person[]));
    Person[] ppl = (Person[])ser.ReadObject(strm);

    if (ppl.Length > 0)
    {
        // do something with the data
        // bind, interate, whatever
    }
}

That’s it!  With a few lines of code we’ve been able to re-use our JSON service and data from Silverlight.  This might not be the best idea in all of your scenarios but it is possible if you want to transition to other services or make re-use out of your investments already.

A Word About WCF Script-enabled Services

For some of you, if you were already a part of the WCF wave when you created your services, you may have already exposed them as scriptable services using an endpoint behavior that was enableWebScript.  This may have been working fine for you but if you look at the output of that, it might be adding some things that you may not need.  This is because it was intended for ASP.NET AJAX consumption (i.e., it adds “_type” and “_d” stuff).  This is easily rectified to make it a cleaner JSON result as well as make the messages smaller.

By implementing the webHttpBehavior in conjunction with the webHttpBinding type for WCF, you will get a much cleaner/smaller JSON payload for your service.  When implementing this, you’ll want to decorate your service methods accordingly using the WebGet attributes:

[OperationContract]
[WebGet(UriTemplate="people", ResponseFormat=WebMessageFormat.Json)]
Person[] GetListOfPeople();

While I’d argue this isn’t completely necessary to use your existing services, it might make your type parsing a little cleaner and as noted, the message size smaller.  So using this (in fact my endpoint this whole time actually is a WCF webHttpBinding endpoint) the entire code looks something like this:

public partial class Page : UserControl
{
    public Page()
    {
        InitializeComponent();
        Loaded += new RoutedEventHandler(Page_Loaded);
    }

    void Page_Loaded(object sender, RoutedEventArgs e)
    {
        WebClient proxy = new WebClient();
        proxy.OpenReadCompleted += new OpenReadCompletedEventHandler(proxy_OpenReadCompleted); 
        proxy.OpenReadAsync(new Uri("http://localhost:34907/JsonData_Web/People.svc/people"));
    }

    void proxy_OpenReadCompleted(object sender, OpenReadCompletedEventArgs e)
    {
        Stream strm = e.Result;
        DataContractJsonSerializer ser = new DataContractJsonSerializer(typeof(Person[]));
        Person[] ppl = (Person[])ser.ReadObject(strm);

        if (ppl.Length > 0)
        {
            
        }
    }
}

Easy enough.  If you want to know more about the WCF binding types and implementing full REST services in WCF, check out Rob Bagby’s blog…he has a lot of good material there.

Summary

If you have existing services that you’ve enabled JSON responses for already for use in AJAX applications, consider making re-use of them where appropriate.  This may bridge a transition to other WCF endpoints or other service-types while you are writing your Silverlight applications.

I’ve included my sample project used here so you can tinker and you can download the code here.

Hope this helps!

| Comments

In a previous post, I wrote about some samples of calling various types of services from Silverlight 2.  In the code, I was using constructors in my ASMX and WCF services with specifying a binding type and endpoint address.

It was called out to me that in other demonstrations, people did not use this construct.  While the method I demonstrated works (explicitly specifying the binding and endpoint), in some cases it may not be necessary.  One such case would be if you only have one endpoint and it is basicHttpBinding.

The error in my code/instructions was about changing the binding information in web.config.  The information is correct, however I wasn't clear on when/what you needed to do.  For example, the default information in web.config for the Silverlight project created for you uses wsHttpBinding.  If you add a service reference in your Silverlight project PRIOR to changing that binding information, your generated proxy will require you to specify a binding and endpoint as Silverlight doesn't support wsHttpBinding and it would be trying to use that as a default method.

So the appropriate way is to change the binding type in your web.config FIRST.  Then generate the service reference in your Silverlight application and your proxy code generated will then allow you to new up the service using:

WcfServiceClient wcf = new WcfServiceClient();

for both WCF and ASMX services...which is probably more familiar to most web developers implementing services in their applications.

Again, EITHER way is fine.  Providing no information in the constructor will use the default binding/endpoint information for that service, and if it isn't supported, you'll get a nasty exception.  Whether or not it is best practice to always explicitly call it out in your code is up to you.  I'd argue it is.  In looking at the code above do you know what binding/endpoint is being used at the time of the service call?  No.  You could make some reasonable assumptions (hey, I'm in Silverlight and I know I must use basicHttpBinding), but for maintainability, maybe someone else coming to the code doesn't have the same understanding.

To each his own.  Either way, I hope this clarifies and I've updated my post with the note about this as well.

| Comments

UPDATE: Source code posted here.

Now that Silverlight 2 is out to the masses (even in beta form), there are likely a lot of developers looking to wire-up web services with their applications in .NET rather than the Silverlight 1.0 method of Javascript.  I thought I'd give you some quick examples of how to do this using some different methods: ASP.NET Web Services (ASMX), Windows Communication Foundation (WCF), REST service, and talk about cross-domain calls.  These are meant to be examples using very much 'hello world' style services, but demonstrating at least how to execute the call.

If you are an ASP.NET developer, you likely are familiar with ASMX web services and the fact that they generate WSDL for anyone looking at their endpoint.  Basically you write some code, host it some where and anyone can call it.  Most of the time, the caller will be using SOAP to connect unless you also enabled other methods on that service.  When consuming the ASMX service you probably used Add Web Reference in Visual Studio and then did something like this:

SimpleWebServiceSoapClient svc = new SimpleWebServiceSoapClient();
string returnValue = svc.HelloWorld();

Fine and simple.  A few lines of code and you are calling your service getting it back value data.  This is a synchronous call.  Of course there are ways to make async calls with ASMX services, but my point is that most typical implementations of ASMX services aren't like that from what I've seen in casual use.  This is where Silverlight may differ for these developers.  In Silverlight 2, all service calls are asynchronous.  Let's take a look at how this is accomplished.

I'm going to use the same application throughout this sample.  The user interface is quite lame, but that's not what this is about.  I'm using a TextBox, TextBlock, and three Buttons all in a StackPanel layout.  =Hhere's what it looks like:

That represents the Silverlight application.  In the web project hosting the Silverlight app, I have 2 services: "SimpleAsmx" and "WcfService" -- aptly named so that they clearly represent the implementing technology.  They are both simple services that expose a method that takes a single string param and basically outputs it back out.  Again, the service portion is not what I'm concentrating on here -- I'm looking at the calling of the service.

Now that we have our layout and our web services, let's start tying them together.  My project looks like this for reference:

ASMX Web Service

In our Silverlight application, I'm going to choose to 'Add Service Reference' from Visual Studio.  This is the same method of previous 'Add Web Reference' but renamed essentially.  When I do that I click Discover and it finds my ASMX service which I select (and rename to AsmxService):

Once I have done that, Visual Studio has wired up a proxy object for me to use in my code.  In my Silverlight application I wire up the Click event in my ASMX Button to an event handler and start writing code.  The first thing you will notice is that implementing the service isn't the same as previously like noted above.  Using ASMX services in Silverlight still uses SOAP, but it also uses the same model of calling a WCF service, which means you have to define a binding and endpoint.  For our ASMX service our binding will be a BasicHttpBinding and our endpoint is our URI to the .asmx file):

UPDATE 19-MAR: The code below will absolutely work (specifying the binding and endpoint information).  However, you can also choose not to specify the binding/endpoint and it should still work.  For the WCF service code below, if you don't change the wsHttpBinding to basicHttpBinding BEFORE you make the service reference in your Silverlight application, then you will have to update your service reference in your Silverlight app (simply right-click on the service and choose 'update service reference').  Doing this will generate the correct proxy code for basicHttpBinding and enable you to just call the code using proxy.YourService() as a constructor rather than using a binding and endpoint.

BasicHttpBinding bind = new BasicHttpBinding();
EndpointAddress endpoint = new EndpointAddress(http://localhost/SimpleAsmx.asmx);

Now that we have those lines, we can new up our service, noticing that the constructor accepts a binding/endpoint for us, so we pass those in:

SimpleAsmxSoapClient asmx = new SimpleAsmxSoapClient(bind, endpoint);

The next step is to call our service.  Remember, we are doing things asynchronously.  So first, we wire up the async handler for when the service is called:

asmx.HelloWorldWithAsmxCompleted += 
   new EventHandler<HelloWorldWithAsmxCompletedEventArgs>(asmx_HelloWorldWithAsmxCompleted);

After that we can now call the service.  The resulting full code looks something like this:

private void AsmxServiceButton_Click(object sender, RoutedEventArgs e)
{
    BasicHttpBinding bind = new BasicHttpBinding();
    EndpointAddress endpoint = new EndpointAddress("http://localhost/SimpleAsmx.asmx");

    SimpleAsmxSoapClient asmx = new SimpleAsmxSoapClient(bind, endpoint);
    asmx.HelloWorldWithAsmxCompleted += 
       new EventHandler<HelloWorldWithAsmxCompletedEventArgs>(asmx_HelloWorldWithAsmxCompleted);
    asmx.HelloWorldWithAsmxAsync(StringToEmit.Text);
}

The event handler for our Completed event looks like this:

void asmx_HelloWorldWithAsmxCompleted(object sender, HelloWorldWithAsmxCompletedEventArgs e)
{
    OutputString.Text = string.Format("Output from ASMX: {0}", e.Result.ToString());
}

Basically when the event completes, the arguments provide us a Result object that represents the return type, in this case a String.  I can then put that string in my TextBlock as output.  And there you have it...we've called a simple ASMX web service.

WCF Services

Calling a WCF service isn't much different (in fact any different).  There is a couple config differences that you have to be aware of which I'll point out here.  But since ASMX services in Silverlight are implemented using the WCF constructs.  Here's the full implemented service with event handler using the same concept:

private void WcfServiceButton_Click(object sender, RoutedEventArgs e)
{
    BasicHttpBinding bind = new BasicHttpBinding();
    EndpointAddress endpoint = new EndpointAddress("http://localhost/WcfService.svc");

    WcfServiceClient wcf = new WcfServiceClient(bind, endpoint);
    wcf.HelloWorldFromWcfCompleted += 
      new EventHandler<HelloWorldFromWcfCompletedEventArgs>(wcf_HelloWorldFromWcfCompleted);
    try
    {
        wcf.HelloWorldFromWcfAsync(StringToEmit.Text);
    }
    catch (Exception ex)
    {
        OutputString.Text = ex.Message;
    }
}

void wcf_HelloWorldFromWcfCompleted(object sender, HelloWorldFromWcfCompletedEventArgs e)
{
    try
    {
        OutputString.Text = string.Format("Output from WCF: {0}", e.Result.ToString());
    }
    catch (Exception ex)
    {
        OutputString.Text = ex.Message;
    }
}

I mentioned a config change that you have to do.  When you add a WCF service to an ASP.NET application, it alters the web.config to add some binding information.  By default it adds an endpoint configuration but adds it like this:

<endpoint address="" binding="wsHttpBinding" contract="IWcfService">

Silverlight communicates using the BasicHttpBinding for WCF, so you have to change it to this (or add another endpoint with this binding):

<endpoint address="" binding="basicHttpBinding" contract="IWcfService">

And then you are done and the code should work.

REST S

Now let's talk about REST.  What is REST?  Representational State Transformation...read about it here.  REST basically takes advantage of existing HTTP verbs (GET, PUT, POST, DELETE) and enables access to actions based on those.  Because of this there is no real "contract" as you may be expecting, or WSDL definitions.  You execute a verb and you'll get a response, usually in XML.  Because there is not contract essentially, the 'Add Service Reference' won't work well for you.  Instead in Silverlight you'll want to use WebClient or HttpWebRequest.  What's the difference?  Here's the timheuer version.  WebClient is a simpler implementation doing GET requests really easily and get a response stream.  HttpWebRequest is great for when you need a bit more granular control over the request, need to send headers or other customizations.  For my sample here, I'm using WebClient because that is all I need.

First a note on remote web services, aka cross-domain services.  In Silverlight 1.0 you couldn't directly access cross-domain services.  In Silverlight 2, we are enabling support for doing that.  The approach we've taken so far is one where we have put the control of the access to the service to the owner of the service.  What that means is that you can't call *any* service on the web, but rather ones that have enabled permission to sites (or everyone) to call their services via rich internet applications.  Flash has enabled the same procedure for a while.  They use a policy access file called crossdomain.xml.  You can read more about this format at crossdomainxml.org.  Silverlight 2 currently supports the exact same policy file.  In addition, Silverlight has a policy file format, but in the end, both are supported, which is cool.  So if you have a web service on a domain separate from your Silverlight application, you'll have to create the policy file at the endpoint root of your web service to enable rich internet platforms to support it.

Once that policy file is in place you are good to go.  For demonstrating REST I am choosing to show you one that is a public API and has a policy file...Flickr.  My sample basically calls Flickr's REST API to search for photos based on a tag and then the result is to add Image elements to my Silverlight DOM in a StackPanel.  Here's what it looks like (after the wire-up button is hooked up).  In my click event handler it looks like this:

WebClient rest = new WebClient();
rest.DownloadStringCompleted += new DownloadStringCompletedEventHandler(rest_DownloadStringCompleted);
 rest.DownloadStringAsync(new Uri(flickrApi));

The "flickrApi" variable represents the REST api call to search photos for Flickr.

The async callback basically gets the Flickr REST response (XML) and parses it using LINQ, then adding a new Image element to the Silverlight tree:

string data = e.Result;
string url = string.Empty;

FlickrImages.Children.Clear();

XDocument doc = XDocument.Parse(e.Result);
var photos = from results in doc.Descendants("photo")
            select new
            {
                id = results.Attribute("id").Value.ToString(),
                farm = results.Attribute("farm").Value.ToString(),
                server = results.Attribute("server").Value.ToString(),
                secret = results.Attribute("secret").Value.ToString()
            };

foreach (var photo in photos)
{
    url = string.Format("http://farm{0}.static.flickr.com/{1}/{2}_{3}_m.jpg", 
      photo.farm, photo.server, photo.id, photo.secret);
    Image img = new Image();
    img.Stretch = Stretch.Fill;
    img.Margin = new Thickness(10);
    img.Source = new BitmapImage(new Uri(url));
    FlickrImages.Children.Add(img);
}

The result of which is 5 pictures added to my Silverlight application, and looks horrible like this:

So that's it, web services (hopefully) made simple.  I hope this helps.  What did I miss?