Sunday, April 17, 2011

Adventures with BizTalk: HTTP "GET" Part 6: Custom .NET Code

[Note: This post is based upon an old blog post that I'm migrating for reference purposes, so some of the content might be a bit out of date. Still, hopefully it might help someone sometime...]

So, we've seen that we can retrieve files from a dynamically-obtained URL inside BizTalk using the WCF-Custom Adapter, but it's not exactly straight-forward.

In the absence of any other bright ideas, next step is resorting to custom .NET code, so let's look at how we might achieve the desired result using this approach.

So, the basic design of the utility we'll build is that we'll pass in the URL of the file we want retrieved, and the utility will take care of retrieving the file and populating a BizTalk message instance with the results. That way, similar to the previous WCF-Custom Adapter approach, we can then do whatever we want with the message back in our orchestration.

.NET Component

Without further ado, here's the code for the .NET component that will do our job for us:

public class WebUtility
{
  public static void Get(string sourceUrl, XLANGMessage targetMessage)
  {
    if (targetMessage.Count == 0)
    {
      throw new ArgumentException("Parameter 'targetMessage' must have at least one message part.", "targetMessage");
    }

    long bytesProcessed = 0;

    // Assign values to these objects here so that they can be referenced in the finally block.
    Stream retrievedStream = null;
    WebResponse response = null;
    MemoryStream memStream = null;

    // Use a try/catch/finally block as both the WebRequest and Stream classes throw exceptions upon error.
    try
    {
      // Create a request for the specified web-based resource.
      WebRequest request = WebRequest.Create(sourceUrl);
      if (request != null)
      {
        // Send the request to the server and retrieve the WebResponse object.
        response = request.GetResponse();
        if (response == null)
        {
          throw new System.Exception("GetResponse returned null.");
        }

        // Once the WebResponse object has been retrieved,
        // get the stream object associated with the response's data.
        retrievedStream = response.GetResponseStream();

        memStream = new MemoryStream();

        // Allocate a 1k buffer.
        byte[] buffer = new byte[1024];
        int bytesRead;

        // Simple do/while loop to read from stream until no bytes are returned.
        do
        {
          // Read data (up to 1k) from the stream
          bytesRead = retrievedStream.Read(buffer, 0, buffer.Length);

          // Write the data to the local file
          memStream.Write(buffer, 0, bytesRead);

          // Increment total bytes processed
          bytesProcessed += bytesRead;
        } while (bytesRead > 0);

        memStream.Seek(0, SeekOrigin.Begin);

        //Load the Binary representation into the first message part:
        targetMessage[0].LoadFrom(memStream);

        //Set properties of the message being returned.
        targetMessage.SetPropertyValue(typeof(FILE.ReceivedFileName), response.ResponseUri.Segments[response.ResponseUri.Segments.GetUpperBound(0)]);
        targetMessage.SetPropertyValue(typeof(HTTP.ContentType), response.ContentType);
      }
    }
    finally
    {
      // Close the response and stream objects here to make sure they're closed even if an exception
      // is thrown at some point.
      if (response != null) response.Close();
      if (retrievedStream != null) retrievedStream.Close();
      if (memStream != null) memStream.Close();
    }
  }
}

The key parts here are:
  • The Get method accepts two parameters, the url to retrieve the file from, and the target XLANGMessage instance that we want to populate with the retrieved file. It's important to note that we force this to be passed in from the calling orchestration rather than being constructed in our component. This forces the orchestration to control the lifetime of the message instance, as opposed to the custom .NET component, and is (apparently) a recommended practice.
  • We use the .NET WebRequest and WebResponse to retrieve the file from the specified URL.
  • We then force the entire response to be loaded into a MemoryStream that can be better consumed by BizTalk.
  • We use the LoadFrom method of the XLANGMessage Part to load the first Part of the provided target XLANGMessage instance from the MemoryStream.
  • We set a couple of Message Context properties for good measure that might be of use to the calling orchestration, FILE.ReceivedFileName and HTTP.ContentType.
Usage

To use this component from an orchestration, we need to:
  • Declare a message variable of type System.Xml.XmlDocument, eg "myMessage".
  • Use a Construct Message and Message Assigment shape with the following code:
myMessage = new System.Xml.XmlDocument();
WebUtility.Get("http://localhost/vdir/some-file.txt", myMessage);
  • Replacing the hard-coded URL with our dynamically-obtained URL.
  • After this Construct Message shape, we'll then have the dynamically-retrieved file in our "myMessage" message variable, ready to do what we'd like with it.
Introspection...

Pros:
  • Much, much simpler to implement than the WCF-Custom approach.
Cons:
  • We lose the "power" of using a BizTalk Adapter. If we wanted to extend this to allow for a proxy server, we'd need to extend our code accordingly, etc...
Overall though, the simplicity of this approach probably wins out over the WCF-Custom solution. I still don't particularly like moving away from using BizTalk's Adapters (after all, isn't that what BizTalk's meant to be good at?), but the simplicity of this approach makes it much more straight-forward to get up and running with.

No comments:

Post a Comment