Flickr, Silverlight og WCF

Jeg elsker billeder. Jeg elsker at se billeder på Flickr fordi de gør mig så glade og giver mig inspiration til nye ting. Billeder siger jo mere end 1000 ord; med mindre de ord er en del af et programmeringsprog :)

Jeg har været igang med at lave en random-image-generator udfra nogle af de brugere på Flickr jeg godt kan lide at se billeder fra.

Ideén kom sig egentlig af FogBugz, hvor der er optræder et billede som en del af en menu bjælke i applikationen. Det billede i FogBugz giver et indtryk af tryghed og tilfredsstillelse ved brug af softwaren, men desværre skifter det for sjældent.

Jeg gik selv igang med at lave lidt sjov...

Ind på flickr.com, sign up, søg om et application token og kopier det ind i ens egen web applikation. Flickr tilbyder dig at hooke ind på flere forskellige services (SOAP, REST etc) og der er lavet rigtig mange forskellige API kits til at tilgå disse services med. Jeg fandt et API kit på codeplex.com som jeg kunne bruge sammen med .NET. Jeg bruger kun FlickrNet dll filen.

Jeg tænkte jeg ville lave en Silverlight applikation som en slags host for min billede generator. Men jeg ville også gerne have noget WCF indover for at se på hvordan WCF og Silverlight virker i sammenspil.

Så jeg lavede en WCF service med en basicHttpBinding og funktionalitet til at hente billeder fra Flickr.com. Man kan vel kalde servicen for en slags proxy imellem Flickr og min Silverlight applikation.

Lad os se på den funktionalitet jeg fik smidt ind i min WCF service.

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

// NOTE: If you change the class name "Service" here, you must also update the reference to "Service" in Web.config and in the associated .svc file.
public class Service : IService
{
    private Flickr flickr;
    private FlickrImage flickrImage;
    private string flickrUserId;

    private readonly string favoriteUserIds = "86474756@N00,81249677@N00,52733933@N00";
    private readonly string applicationToken = "XXX";

    private void GetImage( Photos photoSearch, int retriesCounter )
    {
        try {
            //generate a random photo index
            Random randomPhoto = new Random();
            int photoIndex = randomPhoto.Next( int.Parse( photoSearch.TotalPhotos.ToString() ) );

            //get the collection of photos from our search
            PhotoCollection photoCollection = photoSearch.PhotoCollection;

            //set the image attributes
            flickrImage = new FlickrImage();
            flickrImage.ImageUrl = photoCollection[ photoIndex ].SmallUrl;
            flickrImage.ImageAlt = photoCollection[ photoIndex ].Title;
        } catch {
            if( retriesCounter <= 2 ) {
                //if failing, try again.
                GetImage( photoSearch, retriesCounter++ );
            }

            throw new FaultException( new FaultReason( "Fejl" ), new FaultCode( "GetImage" ) );
        }
    }

    private string GetUserId()
    {
        if( favoriteUserIds.Length > 0 ) {
            //split the user ids up and put them in array
            string[] idsArray = favoriteUserIds.Split( ',' );

            //randomly picking user
            Random randomUserId = new Random();
            return flickrUserId = idsArray[ randomUserId.Next( idsArray.Count() ) ];
        } else {
            throw new FaultException( new FaultReason( "favoriteUserIds er af ugyldig længde (0)" ), new FaultCode( "GetUserId" ) );
        }
    }

    private string GetUserTag()
    {
        if( flickrUserId.Length > 0 ) {
            //get the tag list from our user
            Tag[] tags = flickr.TagsGetListUser( flickrUserId );

            //randomly picking a tag
            Random randomTag = new Random();
            return tags[ randomTag.Next( tags.Count() ) ].TagName;
        } else {
            throw new FaultException( new FaultReason( "flickrUserId er af ugyldig længde (0)" ), new FaultCode( "GetUserTag" ) );
        }
    }

    public FlickrImage GetFlickrImage()
    {
        if( applicationToken.Length > 0 ) {
            //new instance of Flickr
            flickr = new Flickr( applicationToken );

            //set the search options
            PhotoSearchOptions searchOptions = new PhotoSearchOptions();
            searchOptions.UserId = GetUserId();
            searchOptions.Tags = GetUserTag();

            //apply the search options and search
            GetImage( flickr.PhotosSearch( searchOptions ), 0 );
        }

        return flickrImage;
    }
}

Læg mærke til at jeg bruger FaultExceptions i min WCF service, men husk at dette kan Silverlight ikke forstå. Det skyldes at Silverlight kun kommer med et subset af WCF eller det kun kommer med et subset af WPF.

Jeg smed servicen ind i IISen og testet om der var hul igennem. Jeg lavede 2 nye filer som lagde i roden af mit site hvor min WCF service lå; clientaccesspolicy.xml og crossdomain.xml.

Disse to filer er sikkerheds konfigurations filer og ser således ud:

clientaccesspolicy.xml:

<?xml version="1.0" encoding="utf-8"?>
<access-policy>
    <cross-domain-access>
        <policy>
            <allow-from http-request-headers="*">
                <domain uri="*"/>
            </allow-from>
            <grant-to>
                <resource path="/" include-subpaths="true"/>
            </grant-to>
        </policy>
    </cross-domain-access>
</access-policy>

crossdomain.xml

<?xml version="1.0"?>
<!DOCTYPE cross-domain-policy SYSTEM "http://www.macromedia.com/xml/dtds/cross-domain-policy.dtd">
<cross-domain-policy>
    <allow-http-request-headers-from domain="*" headers="*"/>
</cross-domain-policy>

crossdomain filen er faktisk den samme som Flash bruger. Den fortæller at vi kan connecte fra et vilkårligt site igennem den port og protocol sitet nu engang bruger.

Så lavede jeg en ny silverlight applikation (Husk at SL 2.0 RC0 er ude) og tilføjde et ganske almindeligt web site til silverlight løsningen. Jeg tilføjede en service reference til min WCF service på localhost og skrev et par liniers kode til for at komme i kontakt med min WCF service.

private void GetNewImage()
{
    ServiceClient client = new ServiceClient();
    try {
        client.GetFlickrImageCompleted += new EventHandler<GetFlickrImageCompletedEventArgs>( client_GetFlickrImageCompleted );
        client.GetFlickrImageAsync();
    } catch( FaultException faultException ) {
        error.Text = faultException.ToString();
    }
}

Godt nok tjekker jeg for en FaultException i min klient, men min OperationContract i WCF servicen er ikke market med en FaultException attribut. Markere du den med attributen vil din klient fejle næste gang du opdatere service referencen.

Jeg lavede en "Set as start page" på min test side i mit silverlight hosting website og kørte debuggeren. Op kom et billede.

Jeg har lavet nogle ekstra features i applikationen som jeg har vist her pga. længden af posten, men hvis du downloader koden kan du se hvad jeg mener :)