Inside MSDN

Consuming MSDN Web Services

Craig Andera

Code download available at:InsideMSDN2006_10.exe(161 KB)

Contents

Anatomy of the MTPS Content Service
Documents
Basic Viewer Design
GetContent
Populating the TreeView
GetNavigationPaths
Handling Errors
Now Get Creative

About three years ago, Microsoft rewrote the MSDN site from the ground up, developing the MSDN/TechNet Publishing System (MTPS). The new site, called MSDN2 (msdn2.microsoft.com), boasts some important enhancements made possible by MTPS, including improved performance, scalability, and usability. You can read more about this in the February 2005 edition of this column (msdn.microsoft.com/msdnmag/issues/05/02/InsideMSDN).

But from the very beginning, we on the MSDN® team had more in mind for MTPS than simply powering the MSDN2 front-end. We designed MTPS to be flexible, allowing the hundreds of thousands of articles, documents, whitepapers, images, and other resources on the site to be used in new and creative ways. The MTPS Content Service is an important step towards this goal.

The MTPS Content Service is a set of XML Web services that expose the full breadth of the MTPS content to your custom applications. If you are writing development tools you can, for example, create a class viewer application that automatically displays documentation for any common language runtime (CLR) types. Or you can use the services to populate a cache for a lightning-fast offline help viewer containing the documentation for the classes you use most often. Even if you're not writing development tools, you might find the Web services useful for creating a different view of the MSDN content, using a format that you like better than the MSDN2 style. Anything within the MSDN terms of service (www.microsoft.com/en-us/legal/Copyright/Default.aspx) is fair game.

In this column, I'll demonstrate the MTPS Content Service by writing a simple Windows® Forms viewer. It will use a TreeView control for navigation and a WebBrowser control for display to replicate the basics provided by the MSDN2 site. I'll even show you how to add local caching to improve performance. Without much code, I'll create an app that does a fair job of providing the same functionality as the MSDN2 Web site!

You can find the full documentation and Web Services Description Language (WSDL) for the MTPS Content Service at services.msdn.microsoft.com/ContentServices/ContentService.asmx.

Anatomy of the MTPS Content Service

The MTPS Content Service currently consists of exactly two operations: GetContent and GetNavigationPaths. These give callers the ability to discover and retrieve any of the content in MTPS, as well as to discover relationships between different pieces of content. Currently, no search functionality is exposed, but I'll briefly discuss a workaround at the end of this column.

Both GetContent and GetNavigationPaths are built upon the concept of a content item. A content item is a collection of documents identified by a content key. Before I get into documents, let me describe content keys.

A content key consists of three parts: an identifier, a version, and a locale. Identifiers are case-insensitive and can take on any of several forms, described in Figure 1.

Figure 1 Forms of Content Identifiers

Identifier Type Example Description
Short ID b8a5e1s5 Short, easy-to-type identifier for a content item. Sometimes referred to as a “content ID.” Assigned by MTPS and cannot be inferred. Content items always have a Short ID.
GUID d0ab826d-93d4-4b49-85d1-87fdc21f2b94 Unique GUID for a content item. A content item always has a GUID.
Alias system.xml.xmlreader Easy-to-read, easy-to-remember identifier for a content item. Not every content item has one.
URL https://msdn2.microsoft.com/library/system.xml
.xmlreader
Primarily intended to provide an easy transition between various Microsoft Web sites and the MTPS Content Service.
Asset ID AssetID:t%3asystem.xml.xmlnodetype Semi-opaque MTPS-internal identifier for a content item. Always starts with “AssetID:”. A content item always has this type of identifier.

The second part of a content key is a version. We intend for MTPS to be around for a while, so we allow for more than one version of a particular content item. Versions are case-insensitive and consist of a dotted pair indicating a product family and version. For example, the documentation for the Microsoft® .NET Framework 2.0 version of the System.Xml.XmlReader class has a version of VS.80. Right now, most items in the system have only a single version, typically the one that corresponds to the .NET Framework 2.0. When the next version of .NET is released, a new content item will be entered into the system with the same content identifier, but with a different version. It will presumably be something like VS.90, but applications should treat versions as opaque strings for compatibility. Generally, if a version is not specified, the latest version is assumed.

The final part of a content key, locale, indicates which culture- or language-specific translation of a content item is desired. Including the locale as part of the content key allows MTPS to retain content simultaneously in several languages. Locales are case-insensitive, but otherwise follow the form outlined by System.Globalization.CultureInfo (and therefore outlined by RFC 1766 from the Internet Engineering Task Force). Thus, if you want to retrieve the U.S. English translation of a content item, you'd specify a locale of "en-us."

Documents

The content key identifies a content item that contains zero or more documents. A document always has a type and a format, and may also have a name and some content. The content contained by a document can be either XML or binary, depending on the type and format of the document. Figure 2 lists some of the currently defined document types.

Figure 2 Document Types

Type Example Formats Description
Primary Mtps.Xhtml, Mtps.Toc, Mtps.Failsafe Holds the main content of a content item.
Common Mtps.Links, Mtps.Search Contains information applicable to all content within the system.
Feature None currently defined Contains auxiliary information that complements the content item.
Image .gif, .jpg, .png Images referenced by a primary document.

Arguably the most important document in most content items is the Mtps.Xhtml primary document. This format is generally compliant with the W3C's Modular XHTML 1.1 standard. Because of the large amount of legacy content in the system, complete compliance was not possible. But content is always well-formed XML, and in most cases it is also standards-compliant.

It's worth pointing out that the XHTML in MTPS is presentation-free. That is, the XHTML contains no script, styles, or other artifacts that control the look of the rendered content. This allows the XHTML to be used in different contexts by having styling applied at run time, after the document has been retrieved. The Mtps.Xhtml document does, however, contain some extension elements to enable certain behaviors on the MSDN2 Web site. (These are easy to detect, as they are not in the XHTML namespace.) If processing these extra elements poses problems for your application, you can use the Mtps.Failsafe primary document instead. This document is an equivalent to the Mtps.Xhtml document, but contains only XHTML elements.

Basic Viewer Design

Now that you understand what a content item looks like, let's start building a documentation viewer. Figure 3 shows the layout for the app, which I'll call MtpsClient. It has a TreeView for navigation on the left and a WebBrowser that renders the content for the currently selected node on the right. To keep this example focused on MTPS concepts, I won't do much with the XHTML beyond simply displaying it. (I'll leave styling and behaviors as an exercise for you.) For the same reason, I'm not going to deal with images or locales other than U.S. English.

Figure 3 MtpsClient Design

Figure 3** MtpsClient Design **(Click the image for a larger view)

To begin, assume that when the user has selected a node in the TreeView, I want to retrieve and render the content for that node. This is done by hooking the AfterSelect event of the TreeView and making a call to GetContent with the appropriate content key that is stored in the selected TreeNode's Tag property for a Toc object. (I'll talk later about how to set up the Tag property properly.)

MtpsClient defines a class called ServiceHelper, where I've encapsulated all the Web service calls. After extracting the content key, AfterSelect ultimately calls ServiceHelper.GetContent (shown in Figure 4). ServiceHelper.GetContent returns the requested document from the cache if present (I use a simple directory with files named the ID of the document), and otherwise calls the Web service. In this example, the Web service call is far more interesting, so let's look at what's required.

Figure 4 ServiceHelper.GetContent

public static string GetContent(string id, string locale, string version) { // If the data is in the cache, retrieve it from there. if (IsInCache(id)) return GetCacheFileName(id); // Otherwise, go to the Web service to get it. getContentRequest request = new getContentRequest(); request.contentIdentifier = id; request.locale = locale; request.version = version; // Make sure to request that the Web service send back the body of // the Mtps.Xhtml primary document. Without this you just get // summary information. requestedDocument[] requestedDocuments = new requestedDocument[1]; requestedDocuments[0] = new requestedDocument(); requestedDocuments[0].type = documentTypes.primary; requestedDocuments[0].selector = "Mtps.Xhtml"; request.requestedDocuments = requestedDocuments; ContentService proxy = new ContentService(); getContentResponse response = null; try { response = proxy.GetContent(request); } catch (SoapException e) { // If the call contains any MTPS-specific error messages, format // them for display and rethrow a new exception. throw MapSoapException(e); } // Look through the returned documents and pull out the body of the // Mtps.Xhtml document. XmlElement xhtml = GetXhtml(response); if (xhtml == null) throw new MtpsClientException( "No XHTML was returned from the web service."); // Cache the XHTML document. string cachefile = WriteXhtmlToCache(id, xhtml); return cachefile; }

GetContent

GetContent's job is to return a content item for a content key. So the first thing I do after creating the request object is to set up the content key:

getContentRequest request = new getContentRequest(); request.contentIdentifier = id; request.locale = locale; request.version = version;

At this point, I could invoke the GetContent operation. The response, however, wouldn't actually contain any XHTML. By default, GetContent doesn't return the body of the documents; it just returns summary information, such as document format. This is because some documents can be quite large and returning all of the documents every time would often waste resources on both the client and the server.

To retrieve the contents of a document, I must use the requestedDocuments property on the request object. This is an array of requestedDocument objects. Each requestedDocument object has two properties: selector and type. The type property is a documentTypes enum that indicates the document type I am requesting (see Figure 2). The selector property allows me to indicate which particular document I want.

For primary, common, and feature documents, the selector indicates the format of the document—as only one document of a particular type and format will exist. For images, the selector indicates both the format and name of the desired image document, since more than one image in a particular format may exist. Name and format are specified as name.format.

I'm interested in the XHTML content, so I set the selector to Mtps.Xhtml and the type to primary, like so:

requestedDocument[] requestedDocuments = new requestedDocument[1]; requestedDocuments[0] = new requestedDocument(); requestedDocuments[0].type = documentTypes.primary; requestedDocuments[0].selector = "Mtps.Xhtml"; request.requestedDocuments = requestedDocuments;

And that's all that's required. From here, I can make the GetContent Web service call. The response is represented by an object of type getContentResponse. The properties of getContentResponse are shown in Figure 5, and can be broadly grouped into three categories: identity information, alternates, and documents. Identity information includes the contentId, contentGuid, contentAlias (if one exists), version, and locale. So one thing that GetContent can be used to do is to resolve the unknown parts of a content key. As long as you specify one valid identifier and a locale, you'll receive these other bits of the content item's identity in the response.

Figure 5 getContentResponse Properties

Property

Description

contentId

The short ID of the content item. Always returned.

contentGuid

The GUID of the content item. Always returned.

contentAlias

The alias of the content item if it has one.

locale

The locale of the returned content item.

version

The version of the returned content item.

primaryDocuments

All the primary documents for this content item.

availableVersionsAndLocales

An array of availableVersionAndLocale objects listing all the alternative versions and locales available for the specified content identifier.

imageDocuments

All the image documents for this content item.

commonDocuments

All the common documents for this content item.

featureDocuments

All the feature documents for this content item.

The availableVersionsAndLocales property is an array of avail-able VersionAndLocale objects. Each availableVersionAndLocale lists an alternate version and locale for the specified content identifier. If, for example, you request the content item for System.Xml.XmlReader in U.S. English for the .NET Framework 2.0, you might receive a list of availableVersionAndLocale objects that tell you that System.Xml.XmlReader is also available in a .NET Framework 2.0/Italian flavor, .NET Framework 3.0/Italian flavor, and a .NET Framework 2.0/German flavor. You'll always receive information about alternates, even when you specify a version/locale combination that doesn't exist in the system.

For the purposes of MtpsClient, I'm only interested in the documents—specifically, the Mtps.Xhtml primary document. I retrieve this via a call to the method ServiceHelper.GetXhtml, which is shown in Figure 6. Note that the method loops through all the primary documents looking for a match on primaryFormat. Order is not guaranteed in the returned documents, and summary information is returned for all documents including ones not specified in requestedDocuments.

Figure 6 ServiceHelper.getXhtml Method

private static XmlElement GetXhtml(getContentResponse response) { return GetPrimaryDocument(response, "Mtps.Xhtml"); } private static XmlElement GetPrimaryDocument( getContentResponse response, string format) { foreach (primary document in response.primaryDocuments) { if (string.Compare(document.primaryFormat, format, true) == 0) { return document.Any; } } return null; }

If a document of format Mtps.Xhtml is found, its Any property (which is simply a System.Xml.XmlElement object containing the XHTML document) is returned. Feature and common documents are similar, although they may contain more than one root XML element. As such, they are modeled as an array of XmlElement objects. Image documents are binary data and therefore have a Value property that is a byte array.

One important thing to consider is that links in both the Mtps.Xhtml and Mtps.Failsafe primary documents may need special handling from your application. Links between content items in these documents are by asset ID. While you can use subsequent calls to GetContent to resolve these AssetIDs into something more useful, this is inefficient. Instead, you can simply use requestedDocuments to retrieve the Mtps.Links common document at the same time you retrieve the primary documents. The Mtps.Links document, which is available for every content item that has primary documents that contain links, uses a simple XML format (shown in Figure 7) to associate every asset ID in the document with a short ID, a GUID, and possibly an alias.

Figure 7 Mtps.Links Document

<links xmlns:mtps="urn:msdn-com:public-content-syndication"> <link> <assetId>assetid:t%3asystem.xml.xmlurlresolver</assetId> <k:contentId xmlns:k= "urn:mtpg-com:mtps/2004/1/key">008et5fc</k:contentId> <k:contentGuid xmlns:k="urn:mtpg-com:mtps/2004/1/key"> 725ceabb-b81d-4b7e-8e5c-4b3179bdae5d</k:contentGuid> <k:contentAlias xmlns:k= "urn:mtpg-com:mtps/2004/1/key"> system.xml.xmlurlresolver</k:contentAlias> </link> <link> <assetId> assetid:e695047f-3c0f-4045-8708-5baea91cc380</assetId> <k:contentId xmlns:k= "urn:mtpg-com:mtps/2004/1/key">2bcctyt8</k:contentId> <k:contentGuid xmlns:k= "urn:mtpg-com:mtps/2004/1/key"> e695047f-3c0f-4045-8708-5baea91cc380</k:contentGuid> <k:contentAlias xmlns:k="urn:mtpg-com:mtps/2004/1/key" /> </link> </links>

To keep the code small and easy to understand, I won't use the Mtps.Links document in my MtpsClient application. Instead, I will call GetContent to resolve asset IDs to short IDs (even though this causes an unnecessary round-trip). But when you code your own applications, use Mtps.Links for the sake of efficiency.

Once the XHTML has been acquired, displaying it is easy. MtpsClient merely needs to apply a simple XSLT to render the desired page to a temporary directory, and it can then use the Web browser control's Navigate method to display the file. Refer to the source code download for this column at msdn.microsoft.com/msdnmag/code06.aspx for the complete details.

Populating the TreeView

When I first began designing the MTPS Content Service operations, I assumed there would be a document of type Mtps.Toc in every content item and that it would contain information about the content item's position within the system. I was wrong! Since items can exist in more than one location in the tree, content is extensively reused. There is a completely separate set of content items that track relationships, with a primary document of format Mtps.Toc. Figure 8 shows an example of an Mtps.Toc document. Notice that the Title attribute reflects the familiar hierarchy of a .NET class, with child nodes for members, the constructor, and so on.

Figure 8 Mtps.Toc Document

<toc:Node toc:Title="XmlReader Class" toc:SubTree="AssetId:VS%7ccpref19%7c%24%5ccpref.hxt%400%2c0%2c47" toc:SubTreeVersion="VS.80" toc:SubTreeLocale="en-US" toc:Description="" toc:Target="AssetId:T%3aSystem.Xml.XmlReader" toc:TargetLocale="en-US" toc:TargetVersion="VS.80" toc:IsPhantom="false" xmlns:toc="urn:mtpg-com:mtps/2004/1/toc"> <toc:Node toc:Title="XmlReader Members" toc:Target="AssetId:AllMembers.T%3aSystem.Xml.XmlReader" toc:TargetLocale="en-US" toc:TargetVersion="VS.80" toc:IsPhantom="false" /> <toc:Node toc:Title="XmlReader Constructor" toc:Target="AssetId:M%3aSystem.Xml.XmlReader.%23ctor" toc:TargetLocale="en-US" toc:TargetVersion="VS.80" toc:IsPhantom="false" /> <toc:Node toc:Title="XmlReader Methods" toc:SubTree="AssetId:VS%7ccpref19%7c%24%5ccpref.hxt %400%2c0%2c47%2c2" toc:SubTreeVersion="VS.80" toc:SubTreeLocale="en-US" toc:Target="AssetId:Methods.T%3aSystem.Xml.XmlReader" toc:TargetLocale="en-US" toc:TargetVersion="VS.80" toc:IsPhantom="false" /> <toc:Node toc:Title="XmlReader Properties" toc:SubTree="AssetId:VS%7ccpref19%7c%24%5ccpref.hxt %400%2c0%2c47%2c3" toc:SubTreeVersion="VS.80" toc:SubTreeLocale="en-US" toc:Target="AssetId:Properties.T%3aSystem.Xml.XmlReader" toc:TargetLocale="en-US" toc:TargetVersion="VS.80" toc:IsPhantom="false" /> </toc:Node>

Of the remaining attributes, six are of particular interest. SubTree, SubTreeVersion, and SubTreeLocale combine to identify a navigation key. This is a content key of another content item with an Mtps.Toc document. Target, TargetLocale, and TargetVersion combine to identify a content key. They identify the content item containing the Mtps.Xhtml primary document with the documentation, article, or whatever it is that this TOC node represents. Another way to think of this is that the navigation key models the left-hand pane of the MSDN2 window, and the content key models the right-hand pane.

MtpsClient makes extensive use of Mtps.Toc documents. When the program first starts, it uses my ServiceHelper class to retrieve the Mtps.Toc document for the root of the MSDN documentation tree (short ID ms310241, locale en-us, version MSDN.10). It then deserializes this document into an XmlSerializable class of type Toc, like so:

// Retrieve the Mtps.Toc document from the Web service. XmlElement tocElement = ServiceHelper.GetToc(id, locale, version); // Deserialize it into a Toc object. XmlSerializer ser = new XmlSerializer(typeof(Toc)); XmlNodeReader reader = new XmlNodeReader(tocElement); Toc toc = (Toc)ser.Deserialize(reader);

The Toc class simply provides a convenient object-oriented view over the Mtps.Toc document.

With the Mtps.Toc information in a convenient format, I can use it to populate the TreeView by looping over its children and creating a TreeNode for each. By setting the Text property to the Title of the corresponding Toc object, I get the familiar MSDN documentation hierarchy.

I can tell whether a particular child node has children by examining its SubTree property. If specified, it indicates the ID of a document that contains the Mtps.Toc document for that node. If it's present, a user will be able to drill further down into the navigation tree from that point. I use this information to add a dummy child node to any node that itself has children. It doesn't really matter what the node says—I retrieve the Mtps.Toc document and populate the children of that node when the node is expanded.

The only other thing I need to do is store the deserialized Toc object in the Tag property of the TreeNode it corresponds to. As mentioned previously, this lets me easily display the right content when a node is selected, by examining the Target, TargetLocale, and TargetVersion properties of the Toc object to get a content key. The complete code for this sequence is shown in Figure 9.

Figure 9 Populating the TOC Tree

private void PopulateNode( string id, string locale, string version, TreeNode parent) { // Let the TreeView control know that I’m going to be making a bunch // of changes. Calling this now prevents flickering. parent.TreeView.BeginUpdate(); try { // Clear out the children of this TreeNode and re-add them based // on the information in the Mtps.Toc document. parent.Nodes.Clear(); // If there’s no ID (which there may not be in cases where we’re // populating a leaf node) then we’ve done all we can. if (string.IsNullOrEmpty(id)) return; // Retrieve the Mtps.Toc document from the Web service. XmlElement tocElement = ServiceHelper.GetToc(id, locale, version); // If no TOC element is returned, that means this node is a leaf // node so there’s no more work to do. if (tocElement != null) { // Deserialize it into a Toc object. XmlSerializer ser = new XmlSerializer(typeof(Toc)); XmlNodeReader reader = new XmlNodeReader(tocElement); Toc toc = (Toc) ser.Deserialize(reader); foreach (Toc child in toc.Children) { TreeNode treeNode = new TreeNode(); treeNode.Text = child.Title ?? "[no title]"; // I store the Toc object right in the TreeNode so I can // later look up things like the content key for this // node. treeNode.Tag = child; // I can tell from looking at the Toc data whether a node // has children or not—the SubTree property will be // missing for nodes that don’t. So I can add a dummy // node for any nodes that have children, which will cause // the little plus sign to appear in the tree. if (!string.IsNullOrEmpty(child.SubTree)) { TreeNode placeholder = new TreeNode("Loading..."); treeNode.Nodes.Add(placeholder); treeNode.Collapse(); } // Red nodes indicate data that’s in our local cache. if (ServiceHelper.IsInCache(child.Target)) { treeNode.ForeColor = Color.Red; } parent.Nodes.Add(treeNode); } } } finally { // Let the TreeView know I’m done making updates, and that it // can redraw itself now. parent.TreeView.EndUpdate(); } }

GetNavigationPaths

Clicking through the TreeView is not the only way to navigate through content in MtpsClient. The Mtps.Xhtml document is full of links, and I'd like the user to be able to follow the links. Even better, I'd like the TreeView to synchronize to the clicked link, showing where in the documentation tree the displayed content item falls. But GetContent doesn't really provide a good way to do that, short of performing a brute-force search of Mtps.Toc documents. That would potentially require many round-trips to the Web service, making it prohibitively expensive and slow. Enter GetNavigationPaths.

GetNavigationPaths returns information about how the content items in MTPS relate to each other, even if they are not immediately adjacent in the navigation tree. We thought about calling it GetTableOfContentsData, but it is somewhat broader than that. For example, the information can also be used to generate the eyebrow (or breadcrumb) trail of topics you see at the top of an MSDN2 page. In MtpsClient, I use it to synchronize the navigation tree.

The parameters to GetNavigationPaths are conceptually pretty simple: you give it two content keys, one identifying the "from" content item and one identifying the "to" content item. The response is a list of paths between those two items.

Examining the parameters in more detail, you can see that GetNavigationPaths takes a getNavigationPathsRequest object. This object has root and target properties. Both of these are of type navigationKey and consist of an identifier, a version, and a locale. The identifier must be a short ID—aliases, GUIDs, asset IDs, and URLs are not legal identifiers. If necessary, you can resolve one of these other forms to a short ID with a call to GetContent first.

The other thing to note about the navigation keys is that root should identify a navigation node, and target should identify a content node. That is, the ID for root should be for a content item with a primary document of type Mtps.Toc, and the ID for target should identify a content item with a primary document of type Mtps.Xhtml. Put simply, GetNavigationPaths tells you possible routes between a particular point in the left-hand tree and a particular right-hand document. Most of the time you'll simply request paths from the documentation root (short ID ms310241, version MSDN.10) to some content node.

The response to GetNavigationPaths is a getNavigationPathsResponse object, which contains an array of navigationPath objects. Each item in this array represents one possible route between the root and target passed in the request. Remember, an item can appear more than once in the tree, so there may be more than one way to get from the root to the target. A navigationPath is itself an array of navigationPathNode objects—one for each step in the path between the root and target. And navigationPathNode is essentially the same as the TOC XML shown in Figure 8—it has a title, two keys (one for the navigation node itself and one for the content node it corresponds to), and information about whether the node is a phantom. (Phantoms are outside the scope of this column. Consult the online documentation for more details.)

Figure 10 shows how MtpsClient uses GetNavigationPaths to synchronize the TreeView with a content item. GetNavigationKeyFromId is a simple helper method that uses GetContent to resolve an asset ID to a short ID.

Figure 10 Synchronizing the TOC

private void SyncToc(string id, string locale, string version) { // Retrieve the navigation path between the root of the TOC and the // specified node. navigationPathNode[] path = ServiceHelper.GetNavigationPath( id, locale, version); // Now walk down the tree, matching each path node if possible. I // match by title because the IDs in the TOC are asset IDs, but the // IDs we get back from GetNavigationPaths are short IDs. TreeNode currentNode = navigationTree.Nodes[0]; for (int i = 1; i < path.Length; i++) { foreach (TreeNode node in currentNode.Nodes) { Toc nodeInfo = node.Tag as Toc; if (nodeInfo != null && nodeInfo.Title == path[i].title) { node.Expand(); currentNode = node; break; } } } navigationTree.SelectedNode = currentNode; } public static navigationPathNode[] GetNavigationPath( string id, string locale, string version) { // I start off with the root of the documentation tree, which has a // well-known content key. navigationKey root = new navigationKey(); root.contentId = "ms310241"; root.locale = "en-us"; root.version = "msdn.10"; // Since I don’t know whether the specified ID is an asset ID, a // short ID, or so on, I have to resolve it to a short ID first— // GetNavigationPaths only accepts short IDs. Also, I may not know // the version, so I grab that in the same round-trip. navigationKey target = GetNavigationKeyFromId(id); if (!string.IsNullOrEmpty(version)) { target.version = version; } getNavigationPathsRequest request = new getNavigationPathsRequest(); request.root = root; request.target = target; ContentService proxy = new ContentService(); getNavigationPathsResponse response = null; try { response = proxy.GetNavigationPaths(request); } catch (SoapException e) { // If the call contains any MTPS-specific error messages, format // them for display and rethrow a new exception. throw MapSoapException(e); } if (response == null || response.navigationPaths.Length == 0) { throw new MtpsClientException("No navigation information " + "was returned from the web service."); } return response.navigationPaths[0].navigationPathNodes; }

Handling Errors

What if you call GetContent or GetNavigationPaths with erroneous input? It is illegal to omit version or locale when calling GetNavigationPaths. Or say the content identifier you specified in a call to GetContent does not exist in MTPS. In these cases, the MTPS Content Service returns a SOAP Fault element that contains extra information about the problem. This information can be retrieved by catching the SoapException that is thrown on the client side of the Web service call and accessing its Detail property. This property is an XmlNode that contains three items: an eventId, a source, and a helpLink.

MtpsClient handles errors by catching any SoapExceptions and passing them to the MapSoapException helper method. This method checks to see if MTPS has provided any extra information and, if so, formats the information into a helpful error message and tucks the info into an MtpsClientException. MtpsClient attaches an unhandled exception handler to the Application.ThreadException event and watches for exceptions of type MtpsClientException, displaying a friendly error message when one is detected. The extra info provided by MTPS is described in Figure 11; the MapSoapException helper method is shown in Figure 12.

Figure 12 Extracting Error Details from an MTPS SOAP Fault

private static Exception MapSoapException(SoapException e) { string eventId = null; string helpLink = null; string source = null; XmlNode detailNode = e.Detail; // If the detail node contains the mtpsFaultDetail element, then I // try to extract the eventID, helpLink, and source elements from the // fault message. if (detailNode.NamespaceURI == "urn:msdn-com:public-content-syndication" && detailNode.LocalName == "mtpsFaultDetail") { XmlNamespaceManager nsMgr = new XmlNamespaceManager(detailNode.OwnerDocument.NameTable); nsMgr.AddNamespace( "mtps", "urn:msdn-com:public-content-syndication"); if (detailNode.LocalName == "mtpsFaultDetail") { XmlNode helpLinkNode = detailNode.SelectSingleNode("helpLink"); if (helpLinkNode != null) { helpLink = helpLinkNode.Value; } XmlNode eventIdNode = detailNode.SelectSingleNode("eventId"); if (eventIdNode != null) { eventId = eventIdNode.Value; } XmlNode sourceNode = detailNode.SelectSingleNode("source"); if (sourceNode != null) { source = sourceNode.Value; } } } string message = "ERROR: The MSDN web service reported the " + "following error: " + e.Message; // After extracting eventId, source, and helpLink information // from the fault, I append them to the message. if (eventId != null && eventId.Length > 0) { message += "\nThe event ID was " + eventId; } else { message += "\nNo event ID was reported."; } if (source != null && source.Length > 0) { message += "\nThe source was " + source; } if (helpLink != null && helpLink.Length > 0) { message += "\nFor more infomation, see " + helpLink; } return new MtpsClientException(message, e); }

Figure 11 MTPS Fault Detail Properties

Field Description Example
eventId Code that identifies what went wrong. mtpsContentIdentifierNotFound
Source Source of the error. Typically the name of the Web service operation. GetContent
helpLink URL that contains helpful information on how to resolve the error. services.msdn.microsoft.com/ContentServices/ContentService.asmx

Now Get Creative

Now that you know how to use the MTPS Content Service, you can start designing your own uses for the information. We look forward to seeing what the developer community will dream up. There's great satisfaction when someone comes up with a great idea for the system. So I challenge you to come up with the killer app for the MTPS Content Service!

Just so you don't think that I'm asking you to do something I wouldn't do myself, I've published msdnman, an open-source tool modeled after the UNIX man command. It uses the MTPS Content Service to provide command-line access to the documentation managed by MTPS. Aside from providing a real-world example of how to deal with some of the more subtle issues around using the MTPS Content Service, it also demonstrates how to use search functionality (which the MTPS Content Service currently lacks) via the MSN Search Web service.

Send your questions and comments to insdmsdn@microsoft.com.

s

Craig Andera is a consultant specializing in the design and implementation of large-scale .NET systems and was a member of the team that implemented both MTPS and the MTPS Content Service. He has his own consulting company, Wangdera Corporation, where he holds the title of Jedi Master. He can be contacted via his blog at craigandera.blogspot.com/.