次の方法で共有


PictureServices and BizTalk Services

Previous posts have talked a bit about PictureServices. Now I'd like to run through what it took to bring PictureServices to the BizTalk Services. I've talked a bit in other posts about BizTalk Services, but it has some interesting and very useful messaging features. For starters, BizTalk Services has an endpoint that can do HTTP transforms on messages. It goes like this:

  1. MyApp connects to BizTalk Services
  2. BizTalk Services and MyApp use WS-* security, coupled with the WCF binary message encoding.
  3. Other clients (Java, PHP, Ruby, whatever) can hit an HTTP endpoint hosted in the BizTalk Services "mesh". We will call this endpoint the HTTP Endpoint.
  4. Upon receipt of a message, the HTTP Endpoint tries to dispatch that message (or request) to MyApp.

If we use this set of steps with PictureServices, then you have a way to serve local pictures over the internet. If you just look at this task, there are lots of other ways to do this. I'm not suggesting that BizTalk Services is the next cool way to share photos. Instead, I think there are lots of cool apps that can take advantage of this kind of feature set.

Down to nuts and bolts - the service contract. PictureServices defines a service contract with operations for RSS, ATOM, and Simple list extensions. For the sake of time, I opted to have a single feature: get an RSS feed. Here's what the service contract looks like:

 [ServiceContract]
interface IBTSPictureSyndication
{
  [OperationContract(Action="GET", ReplyAction="GETRESPONSE")]
  Message GetFeed();
}

The return type is System.ServiceModel.Channels.Message because the BizTalk Services SDK does not have full parity with the .NET Framework 3.5's capabilities. This will happen over time. To be honest, most of the head scratching I went through was due to the disparity between these two APIs.

Another interesting bit is the implementation of the GetFeed method. The syndication, the images, and all the thumbnail HTTP GETs (remember they are transformed) are going to flow through this one method. As a result, we have to test the requested address to determine which resource is being requested.

For that we turn to our new best friend: the UriTemplate. This is a new type introduced in .NET 3.5 - it makes URI parsing much easier (among other things).

There are 3 conditions I test for:

1) a request for the whole feed

2) a request for an image

3) a request for a thumbnail.

Here's the implementation:

 [ServiceBehavior(AddressFilterMode=AddressFilterMode.Prefix)]
sealed class BTSPictureSyndication : IBTSPictureSyndication
{
    String feedUri = "/Feed/";
    String pictureUri = "/Feed/Picture/{pictureId}";
    String thumbnailUri = "/Feed/Picture/t/{pictureId}";

    PictureService service = new PictureService();

    public Message GetFeed()
    {
        // get the To address and the base address
        Uri to = OperationContext.Current.RequestContext.RequestMessage.Headers.To;
        Uri baseAddress = new Uri(ConfigurationManager.AppSettings["baseAddress"]);

        // check to see if it's a request for the main feed
        UriTemplate feedTemplate = new UriTemplate(feedUri);
        UriTemplateMatch feedMatch = feedTemplate.Match(baseAddress, to);

        if (feedMatch != null)
        {
            return GetMainFeed();
        }

        // check to see if it's a request for a picture
        UriTemplate pictureTemplate = new UriTemplate(pictureUri);
        UriTemplateMatch pictureMatch = pictureTemplate.Match(baseAddress, to);

        if (pictureMatch != null)
        {
            String pictureId = pictureMatch.BoundVariables["pictureId"];
            return GetPicture(pictureId);
        }

        // check to see if it's a request for a thumbnail
        UriTemplate thumbnailTemplate = new UriTemplate(thumbnailUri);
        UriTemplateMatch thumbnailMatch = thumbnailTemplate.Match(baseAddress, to);

        if (thumbnailMatch != null)
        {
            String pictureId = thumbnailMatch.BoundVariables["pictureId"];
            return GetPictureThumbnail(pictureId);
        }

        // we don't know what it is, so throw
        throw new InvalidOperationException(String.Format("the address {0} was not matched", to.ToString()));

    }

    // use the service object to get the picture
    private Message GetPicture(String pictureId)
    {
        return StreamMessageHelper.CreateMessage(OperationContext.Current.IncomingMessageVersion, 
         "GETRESPONSE", service.GetPicture(pictureId));
    }

    // use the service object to get a thumbnail
    private Message GetPictureThumbnail(String pictureId)
    {
        return StreamMessageHelper.CreateMessage(OperationContext.Current.IncomingMessageVersion, 
         "GETRESPONSE", service.GetPictureThumbnail(pictureId));
    }

    // get the main feed (RSS)
    private Message GetMainFeed()
    {
        Rss20FeedFormatter formatter = service.GetPicturesAsRss();

        MemoryStream stream = new MemoryStream();
        XmlWriter writer = XmlWriter.Create(stream);

        formatter.WriteTo(writer);

        writer.Close();
        stream.Position = 0;

        return StreamMessageHelper.CreateMessage(OperationContext.Current.IncomingMessageVersion, "GETRESPONSE", stream);
    }
}

Another thing that's worth pointing out is the presence of the AddressFilterMode property on the ServiceBehavior attribute annotation. This setting tells the WCF dispatching infrastructure to allow prefix matches to filter through to the method. In practical terms, this means that a request to https://foo/bar/baz would get dispatched to the same method as https://foo/bar. That's how we are returning the full feed, the image, or a thumbnail.

Here's a zipped version of the project (note that you must also have PictureServices).

Comments

  • Anonymous
    April 13, 2008
    PingBack from http://www.travel-hilarity.com/airline_travel/?p=3667

  • Anonymous
    August 20, 2008
    Hello, Justin, I try to build your project sample and it's fails with follow error message: 'System.ServiceBus.AutomaticRenewalTokenProvider' is inaccessible due to its protection level. May be I have old version of BizTalk Services SDK? It's very strange, because I download and reinstall SDK today :) Can you help me?