Harnessing the BackPack API - Part I

  This article discusses how to set up a free BackPack account, grab the XML Token needed to interact with the BackPack web service, and delve into connecting to the server to send commands and data back and forth.

Difficulty: Intermediate
Time Required: 1-3 hours
Cost: Free
Software: Visual Studio Express Editions, BackPack API
Hardware:
Download:

 

In the "old days" there was a debate that raged between Palm and Windows CE zealots. The focus of the debate was a question of whether Palm, with its simple OS and simple functionality, was better suited to meeting users needs, or whether Windows CE, with all of its complex functionality, provided a better option. In simplest terms, the argument was really a question of which was more. Was more more, or was less more? I was always of the impression that more was more—at least in that debate.

But sometimes less can definitely be more. Enter a small company made up of five individuals that understands this very well: 37 Signals. They've successfully launched three VERY SIMPLE online applications that have made them nearly legendary in the web development community. The secret of their success: carefully targeted applications that leverage cutting-edge technology to make collaboration intuitive through absurdly clean and simple user interfaces that anyone can quickly and easily use. Couple those strengths with a "first hit is free" marketing model to compel users to upgrade to paid services for improved productivity, and it's not hard to see why they've done so well.

Glad you like the 37 Signals Kool-Aid, but I thought this was an article about XML?

One of the great things about XML is that it is close to becoming ubiquitous. It really is becoming the lingua franca of data transfer. (There's still an inordinate amount of delimited, flat-file, legacy, evil out there in the corporate universe, though.) 37 Signals obviously appreciates the inherent benefits of XML as a data exchange mechanism, and has therefore provided an easy-to-use API that allows users to interact with one of their core products: BackPack.

BackPack is definitely a "less is more" application. It can be used solely by individuals, but it truly shines when used by small groups for effective collaboration. The best way to summarize it is to think of it in terms of being a terribly streamlined wiki—one that's so easy to use that even pointy-haired bosses would feel at home slicing and dicing with it. It's successful because it really doesn't need much in the way of explanation; the user interface nimbly guides users along to being able to quickly and deftly master the product—all without realizing that they've strayed into something as geeky as a wiki because the user interface does such a great job of shielding the user from all of the underlying complexity. And in many ways, that same simplicity is carried over very well into the XML API.

XML4Fun and the BackPack API

So, over the next three articles we're going to take a look at the BackPack API and put it to use for our own amusement. In the first article we'll quickly cover how to set up a free BackPack account, grab the XML Token needed to interact with the BackPack web service, and delve into connecting to the server to send commands and data back and forth. We'll do this by building a very rough and tumble Windows Forms application that models all of the available operations for just the BackPack page object. In the next two articles we'll quickly flesh out the rest of the API, improve the UI for our application, and then play around with XML serialization and other goodness to make our BackPack portable—in the sense that we'll pimp out the sample app for use when we're offline by making it capable of caching data and queuing changes/commands.

Creating an account

In order to play around with the API, you'll need to set up an account. Basic accounts are free and easy to set up. Once you register for your free account, you'll want to zip into your account details and grab your service token—a SHA1 security token that identifies you to the BackPack Service. Your service token is found at the bottom of your account page:

The API

The API is well documented with excellent examples on the BackPack site. The introduction to the API defines the interchange requirements (more on those in a second), and presents a bevy of examples showing both the posted data, and the corresponding response. It's an API that teaches through examples—and one that doesn't cut corners, which is usually quite rare.

The thing I just love about this API is the sheer simplicity of it. It reminds me of what I like to call the poor man's web service (web services too old or too poor to use SOAP). Lots of these "services" existed back in the day before SOAP became all the rage, and they were almost invariably just clients and servers shooting XML packages back and forth to each other. Only those "'services" were usually horribly documented, and hacked together with franken-logic that made them hard to understand. The BackPack API, on the other hand, is very cut-and-dried and logically constrained. Small XML packages are sent up to logically defined URLs on the server for each "method," and packets of XML "data" are sent back down for consumption.

Creating a Communication Mechanism

Once you've had a moment to scroll through the API documentation and get a good feel for it, it's time to address the creation of a communication mechanism between our client and the BackPack Service. Fortunately, .NET makes this kind of interaction brutally simple by providing the System.Net.HttpWebRequest, and corresponding System.Net.HttpWebResponse objects. All we need to do is add a bit of "decoration" to a standard POST screen-scrape, and we be flipping XML back and forth in no time.

The BackPack API specifies that we need to include a custom request header: X-POST_DATA_FORMAT (set to the value of "xml") in order to have our requests properly identified on the server. For good measure I also always like to specify the MIME Type wherever possible, which I've done as well:

Visual C#

HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
request.Method = "POST";
request.Headers.Add("X-POST_DATA_FORMAT", "xml");
request.ContentType = "application/xml";

// won't work: needs to be encoded:
request.ContentLength = xml.Length;
StreamWriter post = new StreamWriter(request.GetRequestStream());
post.Write(xml);
post.Close();

HttpWebResponse response = (HttpWebResponse)request.GetResponse();

StreamReader responseBody = new StreamReader(response.GetResponseStream());
string responseInfo = responseBody.ReadToEnd();
responseBody.Close();

Visual Basic

Dim request As HttpWebRequest = CType(WebRequest.Create(url), HttpWebRequest)

request.Method = "POST"
request.Headers.Add("X-POST_DATA_FORMAT", "xml")
request.ContentType = "application/xml"

' won't work: needs to be encoded:
request.ContentLength = xml.Length
Dim post As StreamWriter = New StreamWriter(request.GetRequestStream())
post.Write(xml)
post.Close()

Dim response As HttpWebResponse = CType(request.GetResponse(), HttpWebResponse)

Dim responseBody As StreamReader = New 
    StreamReader(response.GetResponseStream())
Dim responseInfo As String = responseBody.ReadToEnd()
responseBody.Close()

And that's about it. An HttpWebRequest abstracts away all the gory socket details, and the response, if everything works out correctly, is returned to a simple string that can easily be consumed as needed.

Building an Application to Interact with the API

Once we've got a suitable chunk of communication code, we can build an application around it. I'm using Visual C# Express, which makes creating and laying out form controls very simple. Since stubbing out code for an API usually involves lots of semi-repetitive copy/paste/tweak activities, typos are probably going to abound. That, and just to make this sample application cooler, I'm going to provide a simple way to view the XML sent up as well as the XML sent back down. Real estate for that will cover a large section of the sample app. Not only will this will help make debugging easier, it will provide a window on what is taking place from an XML standpoint as the gears churn inside our app.

In this first article we'll only be covering Page operations (we'll cover Notes, Tags, Reminders, etc. in the next article), but I'll still need a way to display a running list of pages from which we can pick targets to update, delete, display, etc. So we'll need a list box to house a list of available pages, and another section of the Winform dedicated to implementing the Page operations outlined by the API. Finally, we'll need a way to specify which account to connect to, along with a way to provide our API Key for inclusion during requests.

Click here for larger image

(click image to zoom)

As you can see, the choice of which API method to invoke is controlled through a simple radio button—with the exception of the List All method—which is bound to the Load Pages button (which makes more sense). A couple of text boxes and a check box are then laid off to the side of the radio buttons for use in collecting user input, and fields are toggled on and off as needed, depending upon which radio button is selected. (Something tells me that 37 Signals' UI is a tad better than mine.)

Constructing the XML

Of course, pushing buttons, no matter how elegantly (or horribly) laid out on a Winform, doesn't do much for an application that needs to communicate with a server in XML. Again, because of how simple the API is, and because the XML is also very simple, using the XML Document Object Model (DOM) is the perfect choice to generate suitable XML to translate user input where needed.

Working with the DOM is quite fun when it comes to generating new XML. The basic idea is that with an XML Document loaded (even a blank one) you can "float" new XML Elements (or other nodes, e.g. comments, attributes, etc.) off of the root of the document, meddle with the node's internal data, and then append that node to a selected location somewhere specific within the document. If you've done this before, it's second nature; but learning how it all takes place can be a bit of a mind-bender for many people. Within the .NET Framework, the DOM is abstracted into the System.Xml.XmlDocument object, which provides all of the needed functionality to create new elements, populate them with content, and then to arrange them as desired.

For example, to create a new page, the API indicates that we need to have two child elements (title and description), nested into the page element. The following code shows how a blank XmlDocument instance is used to create three new elements programmatically. When these elements are created, they're out in limbo—just sticking off of the XmlDocument like hideous extraneous appendages. It's not until they are ordered and arranged that they take the shape that we want:

Visual C#

XmlDocument args = new XmlDocument();
XmlElement page = args.CreateElement("page");
XmlElement title = args.CreateElement("title");
title.InnerText = this.txtPageTitle.Text.Trim();
XmlElement description = args.CreateElement("description");
description.InnerText = this.txtPageBody.Text.Trim();

page.AppendChild(title);
page.AppendChild(description);

Visual Basic

Dim args As XmlDocument = New XmlDocument()
Dim page As XmlElement = args.CreateElement("page")
Dim title As XmlElement = args.CreateElement("title")
title.InnerText = Me.txtPageTitle.Text.Trim()
Dim description As XmlElement = args.CreateElement("description")
description.InnerText = Me.txtPageBody.Text.Trim()

page.AppendChild(title)
page.AppendChild(description)

Pretty simple, isn't it? With that little bit of code we've created the XML necessary for interaction with the API.

Herding Ducks—or How to Wrap an API

With connectivity and XML manipulation logic in place, as well as a Winform to handle user input and interaction, it's time to figure out the best way to wrap the BackPack API. Any time you wrap an API, you are invariably embedding oodles of logic into your code, and one of the key goals is to do so without repetition—which makes code harder to maintain. The other goal is to make sure that you don't end up making your logic too aware of itself to the point where code in region x KNOWS how code in section y should behave, and comes to rely upon that anticipated behavior. Making that mistake is the sin of poor coupling, and can result in shame, ruin, and discredit to you and your children's children's children.

One of the key things I wanted to accomplish in my effort to wrap the API was to keep as much of the core logic as close together as possible without adding lots of "logic-bloat" around one of the core pieces of the API—manipulation of the URLs that effectively act as the method names. These "method names" needed to be dynamically selected based on the user action, but from there, similarity between the methods effectively breaks down. This is where Delegates come in. Using Delegates allowed me to keep all of the URL-building-goodness localized to one single routine: an event handler for the Submit button. Here all of the URLs could be easily mapped and dynamically created (if needed), and comparatively complex logic such as building XML-grams of various types of user data could be handled in subsequent "starting point" methods as required.

But Delegates also bring something else to the table. Winforms that spend time communicating with various off-box server endpoints are subject to "freezing up" while communication is underway unless a background thread is directed to handle the communication—leaving the main Winform thread free to handle UI tasks. Using Delegates allows for a clean and easy way to spawn new background threads that can handle implementation details such as gathering user input, bundling it up, and then invoking the proper web method—all while leaving the Winform's main thread open to handle the message pump and UI tasks. Here's a code snippet of the entire event handler for the Submit button, where the URL is constructed as needed, a Delegate is wired up with the correct handling routine, and a thread is started to handle the communication:

Visual C#

private void button2_Click(object sender, EventArgs e)
{
    // everything except for creating a new page requires a pageID:
    if (this._currentOperation != PageOperation.Create)
    {
        if (this._currentPageId == 0)
        {
            MessageBox.Show("Please Select a Page First.");
            return;
        }
    }

    // specify the path to connect to on the backpack server, and call appropriate methods
    //
        to load xml 'args' as needed:
    OperationDelegate operation = null;
    switch (this._currentOperation)
    {
        //case PageOperation.ListAll:
        //    break;
        case PageOperation.Create:
            this._webMethodPath = "/ws/pages/new";
            operation = new OperationDelegate(this.InvokeCreatePage);
            break;
        case PageOperation.Show:
            this._webMethodPath = string.Format("/ws/page/{0}/show", 
                this._currentPageId.ToString());
            operation = new OperationDelegate(this.InvokeSimpleMethod);
            break;
        case PageOperation.Destroy:
            this._webMethodPath = string.Format("/ws/page/{0}/destroy", 
                this._currentPageId.ToString());
            operation = new OperationDelegate(this.InvokeSimpleMethod);
            break;

    // elided for brevity... 
    
        case PageOperation.Email:
            this._webMethodPath = string.Format("/ws/page/{0}/email", 
                this._currentPageId.ToString());
            operation = new OperationDelegate(this.InvokeSimpleMethod);
            break;
        default:
            MessageBox.Show("Woops.");
            break;
    }

    if (operation != null)
    {
        Thread operationHandler = new Thread(new ThreadStart(operation));
        operationHandler.IsBackground = true;
        operationHandler.Start();
    }
}

Visual Basic

Private Sub Button2_Click(ByVal sender As System.Object, 
    ByVal e As System.EventArgs) Handles Button2.Click
    If Me._request Is Nothing Then
        MessageBox.Show("Please enter your credentials first.")
        Return
    End If

    If Me._currentOperation <> PageOperation.Create Then
        If Me._currentPageId = 0 Then
            MessageBox.Show("Please Select a Page First.")
            Return
        End If
    End If


    ' specify the path to connect to on the backpack server, and call 
    'appropriate methods
    'to load xml 'args' as needed:

    ' vb.net doesn't play nicely with delegates... so the vb.net version 
    'will create a different threadstart in each
    '   case:
    Dim ts As ThreadStart = Nothing
    Select Case Me._currentOperation
        'case PageOperation.ListAll:
        '    break;
        Case PageOperation.Create
            Me._webMethodPath = "/ws/pages/new"
            ts = New ThreadStart(AddressOf Me.InvokeCreatePage)
        Case PageOperation.Show
            Me._webMethodPath = String.Format("/ws/page/{0}/show", 
                Me._currentPageId.ToString())
            ts = New ThreadStart(AddressOf Me.InvokeSimpleMethod)
        Case PageOperation.Destroy
                Me._webMethodPath = String.Format("/ws/page/{0}/destroy",
                    Me._currentPageId.ToString())
                ts = New ThreadStart(AddressOf Me.InvokeSimpleMethod)
        Case PageOperation.UpdateTitle
                Me._webMethodPath = 
                    String.Format("/ws/page/{0}/update_title", 
                    Me._currentPageId.ToString())
                ts = New ThreadStart(AddressOf Me.InvokeUpdateTitle)
        Case PageOperation.UpdateDescription
                Me._webMethodPath = 
                    String.Format("/ws/page/{0}/update_body", 
                    Me._currentPageId.ToString())
                ts = New ThreadStart(AddressOf Me.InvokeUpdateDescription)
        Case PageOperation.Duplicate
            Me._webMethodPath = String.Format("/ws/page/{0}/duplicate", 
                Me._currentPageId.ToString())
            ts = New ThreadStart(AddressOf Me.InvokeSimpleMethod)
        Case PageOperation.Link
            Me._webMethodPath = String.Format("/ws/page/{0}/link", 
                Me._currentPageId.ToString())
            ts = New ThreadStart(AddressOf Me.InvokePageLinkManagment)
        Case PageOperation.Unlink
            Me._webMethodPath = String.Format("/ws/page/{0}/unlink", 
                Me._currentPageId.ToString())
            ts = New ThreadStart(AddressOf Me.InvokePageLinkManagment)
        Case PageOperation.Share
            Me._webMethodPath = String.Format("/ws/page/{0}/share", 
                Me._currentPageId.ToString())
            ts = New ThreadStart(AddressOf Me.InvokeSharePage)
        Case PageOperation.Unshare
            ' NOTE: Though this doesn't return an error, it doesn't seem 
            'to do ANYTHING in terms of sharing. 
            ' So the API/docs are either in error, or I don't understand 
            'what we're trying to do here (most likely).
            Me._webMethodPath = 
                String.Format("/ws/page/{0}/unshare_friend_page",
                    Me._currentPageId.ToString())
            ts = New ThreadStart(AddressOf Me.InvokeSimpleMethod)
        Case PageOperation.Email
            Me._webMethodPath = String.Format("/ws/page/{0}/email", 
                Me._currentPageId.ToString())
            ts = New ThreadStart(AddressOf Me.InvokeSimpleMethod)
        Case Else
            MessageBox.Show("Woops.")
    End Select

    If (ts <> Nothing) Then
        ' vb doesn't do delegates the same way as the c# code:
        Dim operationHandler As Thread = New Thread(ts)
        operationHandler.IsBackground = True
        operationHandler.Start()
    End If
End Sub

Now, I've managed to keep all of my URLs in one tidy location, without a ton of code crammed into each case, to help improve readability. Instead, Delegates allow the complex logic to be handled in a suitable method that will be started on another thread. For example, the creation of a page, which requires additional XML arguments to be assembled, is handled like so:

Visual C#

private void InvokeCreatePage()
{
    XmlDocument args = new XmlDocument();
    XmlElement page = args.CreateElement("page");
    XmlElement title = args.CreateElement("title");
    title.InnerText = this.txtPageTitle.Text.Trim();
    XmlElement description = args.CreateElement("description");
    description.InnerText = this.txtPageBody.Text.Trim();

    page.AppendChild(title);
    page.AppendChild(description);

    this._request.ExecuteWebMethod(this._webMethodPath, page);
}

Visual Basic

Private Sub InvokeCreatePage()
    If Me.txtPageTitle.Text.Length < 1 Or Me.txtPageBody.Text.Length < 1 
    Then
        MessageBox.Show("Please enter a title and description.")
        Return
    End If

    Dim args As XmlDocument = New XmlDocument()
    Dim page As XmlElement = args.CreateElement("page")
    Dim title As XmlElement = args.CreateElement("title")
    title.InnerText = Me.txtPageTitle.Text.Trim()
    Dim description As XmlElement = args.CreateElement("description")
    description.InnerText = Me.txtPageBody.Text.Trim()

    page.AppendChild(title)
    page.AppendChild(description)

    Me._request.ExecuteWebMethod(Me._webMethodPath, page)
End Sub

API operations that don't require any XML other than the service token are all handled by a catch-all method that just fires off the URL against a helper method:

Visual C#

private void InvokeSimpleMethod()
{
    this._request.ExecuteWebMethod(this._webMethodPath);
}

Visual Basic

Private Sub InvokeSimpleMethod()
    Me._request.ExecuteWebMethod(Me._webMethodPath)
End Sub

Also note: I don't pass the URL and other goodies into these delegated methods, as I need them to be void methods (Subs in VB.NET parlance) that contain no arguments so that their signature matches that of the System.Threading.ThreadStart delegate.

Connection Goodness, Revisited

To avoid scattering connection logic and the API key all throughout the application, I then created a class called a BackPackRequest class to abstract out all of the connection details. Its purpose is to abstract connection details away from the callers and provide a uniform way to ensure that calls to the BackPack service are properly formatted, and all provided with the requisite service token.

Each time user credentials (username and API Key) are entered, or changed in the Winform, a new instance of the BackPackRequest object is created, which requires the account name, API Key, and a logging delegate (more on that in a second) in the constructor. The constructor wires up the base BackPack URL, squirrels away the API key for each subsequent connection request, and offers a handful of overloads to one single point of contact to callers—a method that takes, effectively, a method location URL along with an optional array of XmlElements serving as arguments where needed.

In order to include the service token in each request, a private helper method was created to join the always-required token, wrapped in a request element, with any optional XmlElements representing user input. However, building that chunk of XML required floating elements off of a new, blank XmlDocument owned by the BackPackRequest instance. The optional elements to append would actually be coming from another document—causing a bit of a problem: while you can float new elements on a document and append them as desired, you can't "float" them on to another document. It wouldn't make any sense to have two classes (the Winform and the BackPackRequest instance) share a single XmlDocument in order to create nodes from a common trunk (that would be an unbearable coupling faux pas)—so I had to import the nodes from one document to the other. Again, the Framework makes this quite easy, and as you can see below, the logic to add zero to multiple elements was pretty easy to implement with the use of the XmlDocument's ImportNode() method:

Visual C#

private XmlNode BuildCommand(XmlElement[] args)
{
    XmlDocument command = new XmlDocument();
    XmlElement request = command.CreateElement("request");
    XmlElement token = command.CreateElement("token");

    token.InnerText = this._apiKey;
    request.AppendChild(token);
    command.AppendChild(request);

    if (args != null)
    {
        for (int i = 0; i < args.Length; i++)
        {
            XmlNode arg = command.ImportNode(args[i], true);
            request.AppendChild(arg);
        }
    }

    return command.SelectSingleNode("/"); // root
}

Visual Basic

Private Function BuildCommand(ByVal args() As XmlElement) As XmlNode

    Dim command As XmlDocument = New XmlDocument()
    Dim request As XmlElement = command.CreateElement("request")
    Dim token As XmlElement = command.CreateElement("token")

    token.InnerText = Me._apiKey
    request.AppendChild(token)
    command.AppendChild(request)

    If Not args Is Nothing Then
        Dim i As Integer
        For i = 0 To args.Length - 1 Step i + 1
            Dim arg As XmlNode = command.ImportNode(args(i), True)
            request.AppendChild(arg)
        Next
    End If

    Return command.SelectSingleNode("/") ' root
End Function

Note that in the above code, I'm making a deep copy of the nodes that are being imported (the second argument of ImportNode() is set to true). A shallow copy would only have served to import the element in name only—without the inner data.

Logging XML Interaction Details

The only big thing left at this point was to wire up logging functionality in the Winform. Each invocation of the delegate by the BackPackRequest instance sends information back to the Winform without the request really even knowing or caring what it is doing—which is why Delegates are such powerful tools, and such great allies in the war on coupling.

To handle the logging needs, and to account for the connectivity functionality mentioned above, the initial connection logic introduced early ended up being augmented into a full-blown method, which gets executed asynchronously as follows:

Visual C#

internal string ExecuteWebMethod(string methodLocation, XmlElement[] args)
{
    try
    {
        string url = this._url + methodLocation;
        XmlNode xmlCommandNode = this.BuildCommand(args);

        string xml = xmlCommandNode.InnerXml;
        this._screenWriter(" ");
        this._screenWriter("Send: (" + url + ")" + System.Environment.NewLine + xml);
        HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);

        request.Method = "POST";
        // ... 
        // elided - (this is the exact same connection code as listed initially)
        // ...    
        responseBody.Close();

        this._screenWriter("Response:" + System.Environment.NewLine + responseInfo);
        return responseInfo;
    }
    catch (Exception ex)
    {
        this._screenWriter("EXCEPTION: " + ex.ToString());
        return null;
    }
}

Visual Basic

Friend Function ExecuteWebMethod(ByVal methodLocation As String, 
    ByVal args() As XmlElement) As String
    Try
        Dim url As String = Me._url + methodLocation
        Dim xmlCommandNode As XmlNode = Me.BuildCommand(args)

        ' if (isOnline)
        ' otherwise, append the two args into a single element, and 
        ' append it to another thingy.

        Dim xml As String = xmlCommandNode.InnerXml
        Me._screenWriter(" ")
        Me._screenWriter("Send: (" + url + ")" + 
            System.Environment.NewLine + xml)
        Dim request As HttpWebRequest = CType(WebRequest.Create(url), 
            HttpWebRequest)

        request.Method = "POST"
        request.Headers.Add("X-POST_DATA_FORMAT", "xml")
        request.ContentType = "application/xml"

        ' won't work: needs to be encoded:
        request.ContentLength = xml.Length
        Dim post As StreamWriter = 
            New StreamWriter(request.GetRequestStream())
        post.Write(xml)
        post.Close()

        Dim response As HttpWebResponse = CType(request.GetResponse(), 
            HttpWebResponse)

        Dim responseBody As StreamReader = 
            New StreamReader(response.GetResponseStream())
        Dim responseInfo As String = responseBody.ReadToEnd()
        responseBody.Close()

        Me._screenWriter("Response:" + System.Environment.NewLine + 
            responseInfo)
        Return responseInfo
        Catch ex As Exception
            Me._screenWriter("EXCEPTION: " + ex.ToString())
            Return Nothing
    End Try
End Function

Only, there's an issue with that code: a background thread will be executing the code above, and the point of logging is to write the info written to the txtLog TextBox on our Winform. Winforms, however, get exceptionally cranky (literally) about a background thread touching their user controls (only the UI thread or owner of the controls can touch them). To account for this, we need to direct the delegate at a simple method that will store the log information in a local variable and then inform the UI thread that there's some new information to log to the screen:

Visual C#

private void OnLogged(string info)
{
    this._logMessage = info;
    this.BeginInvoke(new MethodInvoker(this.BindLogMessage));
}

Visual Basic

Private Sub OnLogged(ByVal info As String)
    Me._logMessage = info
    Me.BeginInvoke(New MethodInvoker(AddressOf Me.BindLogMessage))
End Sub

And when it comes time to paint the logged information to the screen, we need a way of "scrolling" the contents in the textbox so that the latest results are output as they are received. I accomplished this by capturing the control that currently has focus, stealing the focus and directing it to the txtLog TextBox, outputting the text, scrolling the text box to the end of its contents, and then returning control back to the control that should truly have it:

Visual C#

private void BindLogMessage()
{
    // probably should pay attention to race conditions/locking/etc.
    this.txtLog.Text += System.Environment.NewLine + this._logMessage;

    // handle jiggling, focus stealing, output scrolling, voodo:
    Control current = this.ActiveControl;
    this.txtLog.Focus();

    this.txtLog.SelectionStart = this.txtLog.Text.Length;
    this.txtLog.SelectionLength = 0;
    this.txtLog.ScrollToCaret();

    current.Focus();
    Application.DoEvents();
    
}

Visual Basic

Private Sub BindLogMessage()
    ' probably should pay attention to race conditions/locking/etc.
    Me.txtLog.Text += System.Environment.NewLine + Me._logMessage

    ' handle jiggling, focus stealing, output scrolling, voodo:
    Dim current As Control = Me.ActiveControl
    Me.txtLog.Focus()

    Me.txtLog.SelectionStart = Me.txtLog.Text.Length
    Me.txtLog.SelectionLength = 0
    Me.txtLog.ScrollToCaret()

    current.Focus()
    Application.DoEvents()

End Sub

It feels a bit like a hack, but I couldn't think of a better way to do it given the fact that that scrolling requires that the control to be "scrolled" have focus.

Test Driving your BackPack

At this point, we've got a user interface that routes operation choices into a single event handler that determines the correct URL to invoke, assembles any required user input into XML, and then fires it off against the server. It has been a bit of a long road, but Page functionality from the BackPack API is now fully implemented and can be taken for a test drive:

Click here for larger image

(click image to zoom)

Once you enter your Account Name and API Key details (and leave the Connection Information Group Box), the Winform detects that both fields have been filled out and creates an instance of the BackPackRequest class in the background, so you can then press the Load Pages button to load your pages (if you have any—if not, you can create a page, and then Load Pages). Once your pages are loaded, you can destroy pages, rename them, or link and share them, etc. You can even direct BackPack to email you a copy of your pages—which, for some reason, is very satisfying to my inner geek.

Conclusion—Part I

So, we've taken a look at an XML API, and played around with dynamically generating XML documents using the DOM to build "data grams" from our own crude user interface. We've largely ignored using or consuming the returned XML, with the exception of the Pages List box, which simply iterates through the returned pages nodes and binds them to a Hashtable and to the List Box. Otherwise, the only use we make of the XML returned from the service is to output it to the Winform via the logging functionality.

However, in the next article we'll take a better look at how to usefully and effectively consume the returned XML. In addition to fleshing out the rest of the API, we'll also make our application functional—by making it so that we can grab BackPack data from the web, go offline, make updates and changes in our application, and then marshal those changes back up to the server when we get back online. It should make for a very cool tool once we're done.

Follow the Discussion

Comments Closed

Comments have been closed since this content was published more than 30 days ago, but if you'd like to continue the conversation, please create a new thread in our Forums,
or Contact Us and let us know.