Printer Friendly Version      Send     
Click to Rate and Give Feedback
Related Articles
We introduce you to the benefits of building composite applications with the Composite Application Guidance for WPF from Microsoft patterns & practices.

By Glenn Block (September 2008)
ADO.NET Data Services provide Web-accessible endpoints that allow you to filter, sort, shape, and page data without having to build that functionality yourself.

By Shawn Wildermuth (September 2008)
See how routed events and routed commands in Windows Presentation Foundation form the basis for communication between the parts of your UI.

By Brian Noyes (September 2008)
Technology changes at a lightening-fast pace. This month Howard Dierking considers how the rapid changes affect developer priorities and magazine focus.

By Howard Dierking (September 2008)
More ...
Articles by this Author
The new version of COM+ that ships as part of Windows XP includes APIs for low-level context programming. These functions allow you to create contexts that use COM+ runtime services, independent of objects and without registering anything in the COM+ Catalog. Designed for advanced COM+ developers who understand the COM+ context model, these APIs make it easy to integrate runtime services with code in nonstandard ways. This article explains how these low-level context APIs work, discusses when you'd want to use them, and provides a .NET-based wrapper to make it simpler to use the APIs from C#.

By Craig Andera and Tim Ewald (April 2002)
More ...
Popular Articles
Microsoft Robotics Studio is not just for playing with robots. It also allows you to build service-based applications for a wide range of hardware devices.

By Sara Morgan (June 2008)
Efficient parallel applications aren’t born by merely running an old app on a parallel processor machine. Tuning needs to be done if you’re to gain maximum benefit.

By Rahul V. Patil and Boby George (June 2008)
Speech Server 2007 lets you create sophisticated voice-response applications with Microsoft .NET Framework and Visual Studio tool integration. Here’s how.

By Michael Dunn (April 2008)
In this excerpt from his upcoming book, Laurence Moroney explains the basics of Silverlight animation and the animation tools available in Expression Blend.

By Lawrence Moroney (August 2008)
More ...
Read the Blog
SQL Server 2008 supports a new data type, HierarchyID, that helps solve some of the problems in modeling and querying hier­archical information. In the September 2008 issue of MSDN Magazine, Kent Tegels introduces you to the ...
Read more!
Many people using SharePoint technologies don't realize that there is auditing support built directly into the Windows SharePoint Services (WSS) 3.0 platform. In the September 2008 issue of MSDN Magazine, Ted Pattison walks you through a ...
Read more!
The September 2008 issue of MSDN Magazine is now available online. Here's what's in the issue: Hierarchy ID: Model ...
Read more!
Silverlight 2 features a rich and robust control model that is the basis for the controls included in the platform and for third-party control packages. You can also use this control model to build controls of your own. In the August 2008 issue of MSDN Magazine, Jeff Prosise describes how to ...
Read more!
In the August 2008 issue of MSDN Magazine, Matt Milner covers several topics regarding development with Windows Workflow Foundation, some that are intended to address specific reader questions, such as how to safely share a persistence database ...
Read more!
LINQ is a powerful tool enabling quick filtering data based on a standard query language. It can tear through a structured set of data using a simple and straightforward syntax. In the August 2008 issue of MSDN Magazine, Jared Parsons demonstrates a ...
Read more!
More ...
Inside MSDN
Consuming MSDN Web Services
Craig Andera

Code download available at: InsideMSDN2006_10.exe (161 KB)
Browse the Code Online
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 (microsoft.com/info/cpyright.mspx) 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.
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.
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 (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.

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.
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.
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.
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.
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.

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.

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.

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 pluralsight.com/craig.

© 2008 Microsoft Corporation and CMP Media, LLC. All rights reserved; reproduction in part or in whole without permission is prohibited.
Page view tracker