Constructing BizTalk 2004 XML Messages (In an Orchestration) - Choices

Before using a BizTalk XML message inside an Orchestration , it must first be
constructed. In fact ,this is just populating the message with XML. If there is an
attempt to use the message before it is constructed, the following error will appear when 
building the BizTalk project -> use of unconstructed message 'MyMessageNameHere'.
Some messages come pre-constructed, such as messages that originate from a port, but there
will be times when a message needs to be constructed inside of an orchestration.

For example if there is a message inside an Orchestration called msgShippingInfo (that is unconstructed),
and is of type ShippingInfo.xsd and the xsd schema looks like the below:

<?xml version="1.0" encoding="utf-16"?>
<xs:schema xmlns="http://ConstructingXMLMessages.ShippingInfo" xmlns:b="" targetNamespace="http://ConsltructingXMLMessages.ShippingInfo" xmlns:xs="">
  <xs:element name="ShippingInfo">
      <xs:attribute name="ShipToAddress" type="xs:string" />
      <xs:attribute name="ShipToCity" type="xs:string" />
      <xs:attribute name="ShipToCountry" type="xs:string" />

Then at some point, the message must be populated with some xml as below, before that message can
be used:

<ns0:ShippingInfo ShipToAddress="Some Address" ShipToCity="Some City" ShipToCountry="Some Country"
xmlns:ns0="http://ConstructingXMLMessages.ShippingInfo" />

Below are some options for constructing XML messages, inside of an Orchestration.

1) Create new message(s) from a existing message(s) using a BizTalk Map.
This is one of the more common methods. For example,
I need to map my incoming Purchase Order message to an outgoing
Invoice message. Also keep in mind that you can have multiple input messages and output messages
when mapping. For example within an Orchestration, there is an existing constructed Purchase Order and Shipping messages.
I can then use these two messages to create an Order message.

A simple example is as below: 


If you have not done it before :
To create a map with multiple inputs and outputs, create the needed messages
in the Orchestration View. For example , in this case an Order message,
PurchaseOrder Message and the Shipping Message.
Drop a transform shape on your orchestration and then choose the source(input) message(s).
In this case there are two : PurchaseOrder message and Shipping message.
Choose the destination(output) message(s) : Order message.
In the Transform Configuration dialog box, choose -> new map. In this case
a new map is created that looks like the above (minus any of the links and functoids between source and
destination messages). These were put in later.

2) Assign one message to another.
At some point you may need a copy of another message. If msgOrderCopy is of the same type
as msgOrderOriginal, then the below assignment can be done.

Construct msgOrderCopy

  msgOrderCopy = msgOrderOriginal;
  //Once the new message is constructed, it can now be modified.
  // For example if the schema for the order has distinguished fields,
  // these can now be set. XPATH can also be used to set the data in (Some xpath examples are further down).
  msgOrderCopy.OrderedBy = "Bob";


3) Create a message with the help of a System.XML.XMLDocument variable.

In the below example, the XML to populate the message is hardcoded inside the
expression shape. The XMLDocument variable is initially assigned to the hardcoded XML. The BizTalk message is
then assigned to the XMLDocument variable. The BizTalk message is now good to go.
This is a handy technique especially when the schemas/xml instance messages are small. If the schema
has distinguished fields, then these can then be used to set the attributes in the message with real data, otherwise XPATH
can be used to populate the ID and Name attributes of the below simple XML message. It is also
possible to alter the hardcoded XML with the real data, and then do the message assignments.

construct msgShipping
  // Use the Variable varXMLDom to load in some XML
 varXMLDom.LoadXml(@"<ns0:ShippingInfo ShipToAddress=""Some Address"" ShipToCity=""Some City"" ShipToCountry=""Some Country"" xmlns:ns0=""http://ConstructingXMLMessages.ShippingInfo"" />");
  // Assign the BizTalk message to the XMLDom variable. Note this can be accomplished becuase a BizTalk message derives from an XMLDocument.
  msgShipping = varXMLDom;
  // If there are Distinguished Fields available, then
  msgShipping.ShipToAddress = "1 Main Street";
  msgShipping.ShipToCity = "Toronto";
  msgShipping.ShipToCountry = "Canada";
  // If there are no distinguished fields, then XPATH can be used to set the attributes in the message.
  xpath(msgShipping , "//@ShipToAddress") = "1 Main Street";
  xpath(msgShipping, "//@ShipToCity") = "Toronto";
  xpath(msgShipping, "//@ShipToCountry") = "Canada";

4) Creating a more complex message using : System.XML.Document, System.XML.XMLNode,System.XML. XMLAttribute, Interim BizTalk Message
The below example, uses the same method as in 3) , but the message is more complicated.
A Looping shape is used to add the order items to an order message. The part of the orchestration that does this
is as below:


In the the First Expression Shape, Construct the initial Order Message (With just the Order Header), with the help of a XMLDocument variable
construct msgOrderFromAppending
   // Initially create the Order Header part of the Order Message
   varOrderXMLDom.LoadXml(@"<ns0:Order OrderedBy=""SomeBody"" TotalOrderAmount=""0"" xmlns:ns0=""http://ConstructingXMLMessages.Orders""></ns0:Order>");
   msgOrderFromAppending = varOrderXMLDom;


In the next expression shape add the order items. Note that in the Orchestration this expression
shape is in a loop, so there are multiple order items being added.

// Construct the Temp Order Message to Append the Node.
construct msgOrderFromAppendingTemp
   // First set the XMLDOM variable to the msgOrderFromAppending in the first shape.
   varOrderXMLDom = msgOrderFromAppending;
   // use CloneNode to make a fresh copy of msgOrderFromAppending
   varOrderXMLDom = (System.Xml.XmlDocument) varOrderXMLDom.CloneNode(true);
   // Create a new Order Item Node
   varXMLNodeOrderItemNode = varOrderXMLDom.CreateElement("OrderItems");
   // Create Amount Attribute
   varXMLAttribute = varOrderXMLDom.CreateAttribute("Amount");
   varXMLAttribute.InnerText = "0";
   // Create Description Attribute
   varXMLAttribute = varOrderXMLDom.CreateAttribute("Description");
   varXMLAttribute.InnerText = "A Description";
   // Create Qty Attribute
   varXMLAttribute = varOrderXMLDom.CreateAttribute("Qty");
   varXMLAttribute.InnerText = "0";
   // Now append the actual Order Item Node to the XML Instance
   // Now actually construct the Temp message.
   msgOrderFromAppendingTemp = varOrderXMLDom;
   System.Diagnostics.Debug.WriteLine("The output is " + varOrderXMLDom.OuterXml);

// Now Set the Real Order Message From the Temp Message.
// This is so we can keep adding messages to the real message, without losing them.

construct msgOrderFromAppending
   msgOrderFromAppending = msgOrderFromAppendingTemp;
   // Set the Data in the newly added Order Item Node.
   // Now use XPAth to set the Data in the attributes,
   // NOTE: I could of also done this by setting the real data
   // on the XMLAttribute object -> varXMLAttribute.InnerText = "Description for Order Item : " +  System.Convert.ToString(varOrderItemCount) ;

   strvarOrderItemCount = System.Convert.ToString(varOrderItemCount); 
   xpath(msgOrderFromAppending,"//OrderItems[" + strvarOrderItemCount + "]//@Amount") = System  m.Convert.ToString(varOrderItemCount) ;
   xpath(msgOrderFromAppending,"//OrderItems[" + strvarOrderItemCount + "]//@Qty") = System.Convert.ToString(varOrderItemCount) ;
   xpath(msgOrderFromAppending,"//OrderItems[" + strvarOrderItemCount + "]//@Description") = "Description for Order Item : " +  System.Convert.ToString(varOrderItemCount) ; 

5) Use a .Net helper class to create the message.

As in the above example , instead of using XMLDOM type code in the orchestration, a helper
.NET class could of been called in the orchestration to create the message.
An example helper class would look like the below, just returning an XMLDocument that could be assigned to
the message inside the orchestration.

public static XmlDocument GetXmlDocumentOrderTemplate()
// Code to create the message.


Now inside the orchestration, the code to call the .Net helper class to construct the message.

Construct myOrderMessage

  myOrderMessage = HelperClass.GetXmlDocumentOrderTemplate 


Also look here

Download the above examples , Here

An Update on Whidbey/Yukon Release Dates

In case you missed it, in this article key microsoft executives are quoted as saying that Whidbey and Yukon are still slated to ship together but that won't be by end of June as earlier stated. They are now estimating that “summer” is a better statement of expected arrival. This may align itself nicely with the PDC 2005 September date. It sounds like both teams blame each other, but really, a lesson can be learned here for all of us: Cross product integration means longer delivery cycles. This is going to be a tough nut for our industry to crack moving forward, not just Microsoft. Is that what SOA promises?

Visual Studio Team System and XBox/Halo 2

What does project management, test management, defect tracking, build servers, methodology, automated testing, code coverage, and software diagramming have to do with Halo 2? I'm not sure really, but if you want both - then you need to come to the Toronto Visual Basic User Group meeting tomorrow night. I'll be doing a “powerpoint free” drive through of Visual Studio Team System AND raffling off an xbox console and a copy of Halo 2, worth about $270.  More details here:

Random ASP.NET AppDomain Restarts

Are you having a problem with apparently random application restarts in your ASP.NET application?  Does your session information mystically disappear with no discernable pattern?  Well, the following may help you out.

One of the things that ASP.NET does to help make it easier for developers to modify running web sites is to keep an eye on files that are part of the virtual directory.  If you drop a new version of a DLL into the bin directory, it takes effect from the next request on.  If you make a change to an ASPX file, it too is detected and becomes 'live' with any subsequent request.  No question that this is useful functionality.

A little known fact about this process is that ASP.NET is also aware of the amount of memory that maintaining two versions of the same assembly takes.  If changes were allowed to be made without a restart, eventually it could become detrimental to the overall performance ogf ASP.NET. To combat this, ASP.NET also tracks the number of files that are changed and after a certain number, performs an Application Restart.  As with any other restart, this causes the current session information to be lost.  By default, the number of changes before a restate is 15.  To modify this, you can change the numRecompilesBeforeApprestart attribute in the machine.config file.

While all of this is useful information, there is a small twist that can make our life more difficult.  The files that ASP.NET is watching while looking for a change is not limited to those with an .aspx extension. In fact, it is any file in the virtual directory.  Also, the name of the setting implies that the Recompiles are what is being counted.  It's not.  What is being counted is the number of files that change.  Or, more accurately, the number of changes to any file in the virtual directory. 

How can this cause problems?  What if you were to put a log file into the virtual directory of a web site.  What if that log file were opened and kept open while the information was written to it.  When that log file filled up, it is closed and a new one opened.  Of course, this close counts as a file change.  That would mean that after 15 log files are created (assuming no other changes to the web site), the virtual directory would automatically reset.  And since the amount of information written to the log file will be different depending on which pages/web methods are called, the frequency of the reset is dependent on factors that are not immediately apparent.  Imagine the joy of trying to discover why ASP.NET applications are being randomly reset without the benefit of this blog.

The moral of this story:  Don't put any non-static files into an ASP.NET virtual directory. My good deed for the day is done. 

BizTalk 2004 Map. Mapping two input messages into one output message , plus matching content from one message to another.

I have had the problem of having a message such as a Order message, being delivered 
as two separate messages. For example, the Order header Records delivered as one XML message,
and then all the Order Items that belong to Order header Records delivered as a separate XML message.

The two separate messages being delivered look something like below:

Order Headers:

<ns0:Orders Correlator="1" xmlns:ns0="http://IDREFTest.Orders">
  <Order OrderID="6" OrderedBy="BillyBobJim" />
  <Order OrderID="1" OrderedBy="Bob" />
  <Order OrderID="2" OrderedBy="Bobby" />
  <Order OrderID="3" OrderedBy="BobbyJohn" />
  <Order OrderID="4" OrderedBy="BobbyNoItems" />
  <Order OrderID="5" OrderedBy="BillyBob" />
  <Order OrderID="0022" OrderedBy="BillyBobJim" />
Order Items:

<ns0:Order Correlator="1" xmlns:ns0="http://IDREFTest.OrderItems">
  <OrderItems OrderID="1" Qty="1" />
  <OrderItems OrderID="1" Qty="11" />
  <OrderItems OrderID="1" Qty="111" />
  <OrderItems OrderID="2" Qty="2" />
  <OrderItems OrderID="2" Qty="22" />
  <OrderItems OrderID="2" Qty="222" />
  <OrderItems OrderID="3" Qty="3" />
  <OrderItems OrderID="3" Qty="33" />
  <OrderItems OrderID="3" Qty="333" />
  <OrderItems OrderID="3" Qty="3333" />
  <OrderItems OrderID="5" Qty="5" />
  <OrderItems OrderID="5" Qty="1" />
  <OrderItems OrderID="5" Qty="11" />
  <OrderItems OrderID="5" Qty="111" />
  <OrderItems OrderID="6" Qty="333" />
  <OrderItems OrderID="6" Qty="3333" />
  <OrderItems OrderID="0022" Qty="3" />
  <OrderItems OrderID="0022" Qty="33" />

My goal then was to create a final Order XML Message that looks like the below (combining
information from the two messages into one message, plus also matching the correct order items with
the correct order headers). I wanted to accomplish this by just using BTS maps and functoids
that are installed with BTS2004.

<ns0:Orders xmlns:ns0="http://IDREFTest.OrdersandOrderItems">
 <Order OrderedBy="BillyBobJim" OrderID="6">
    <OrderItems OrderID="6" Qty="333" />
    <OrderItems OrderID="6" Qty="3333" />
 <Order OrderedBy="Bob" OrderID="1">
    <OrderItems OrderID="1" Qty="1" />
    <OrderItems OrderID="1" Qty="11" />
    <OrderItems OrderID="1" Qty="111" />
    <Order OrderedBy="Bobby" OrderID="2">
    <OrderItems OrderID="2" Qty="2" />
    <OrderItems OrderID="2" Qty="22" />
    <OrderItems OrderID="2" Qty="222" />
  <Order OrderedBy="BobbyJohn" OrderID="3">
     <OrderItems OrderID="3" Qty="3" />
     <OrderItems OrderID="3" Qty="33" />
     <OrderItems OrderID="3" Qty="333" />
     <OrderItems OrderID="3" Qty="3333" />
 <Order OrderedBy="BobbyNoItems" OrderID="4" />
 <Order OrderedBy="BillyBob" OrderID="5">
     <OrderItems OrderID="5" Qty="5" />
     <OrderItems OrderID="5" Qty="1" />
     <OrderItems OrderID="5" Qty="11" />
     <OrderItems OrderID="5" Qty="111" /> 
 <Order OrderedBy="BillyBobJim" OrderID="0022">
     <OrderItems OrderID="0022" Qty="3" />
     <OrderItems OrderID="0022" Qty="33" />

Therefore to accomplish this:

1) Created an Orchestration that contains a parallel convoy, that correlates on
the Correlator attribute on both the Orders.xsd and OrderItems.xsd properties.

2) Created a Map with two inputs (Order Header and Order Items) and one output (OrdersAndOrderItemsAll). 
To create a map with multiple inputs and outputs, create the needed messages
in the Orchestration View. For example , in this case an OrdersOnly message,
OrderItemsOnly Message and the OrdersAndOrderItemsAll Message.
Drop a transform shape on your orchestration and then choose the source(input) message(s).
In this case there are two : OrdersOnly message and OrderItemsOnly message.
Choose the destination(output) message(s) : OrdersAndOrderItemsAll message.
In the Transform Configuration dialog box, choose -> new map. In this case
a new map is created that looks like the below (minus any of the links and functoids between source and
destination messages). These were put in later.


3) In the map as above, links and two looping functoids were used to create an interim message (OrdersAndOrderItemsAll)
All OrderHeader records will contain all the OrderItem records.

4) Create another map that looks like the below, using the OrdersAndOrderItemsAll message as the source and using a new
message OrdersAndOrderItems as the destination. The equal functoid is used to include only the correct orderitems with each
order header. This map creates the desired resultant xml instance.


Download the complete solution Here.

What I like about this solution, is that it works. What I don't like about this solution is the interim message, with each order header message
including all the order items. In my case the incoming OrderHeader message and OrderItem messages were not terribly large, therefore
the interim message was not huge. Of course there may be other ways to accomplish this, possibly using custom xslt.

Moving Data Across Service Boundaries

Yet another question that has come across my inbox more than once in the past week.  Which is a good enough reason to become the topic of a blog.  The question:

"Can a SqlDataReader by return by a web service method?"

The short answer:  No

If you attempt to do so, you will get an InvalidOperationException indicating that the SqlDataReader could not be serialized because it doesn't have a public constructor.  And while that is the technical result, the true reason is slightly different.

The SqlDataReader class is stream based.  That is to say that the connection to the database that feeds the SqlDataReader is kept open and busy for as long as the reader is working with data. If an instance of SqlDataReader were to be returned from a web service method, the client would attempt to do a Read.  Which would use the connection to retrieve data from a database that is no longer around.  And certainly not where the connection expects it to be.  Not a situation that's amenable to success.

Passing an XML instance to a Web Service Method via a BizTalk 2004 Orchestration Web Port -> Choices.

Need to call a web method from a BizTalk 2004 Orchestration Web Port and pass an instance of XML to the web method.
Below are some options:

1) Pass the XML as a string.

For example your Web Method that you need to call from your orchestration might look something like this:

ProcessOrder (string myOrder)

 System.XML.XMLDocument myOrderDom = new System.XML.XMLDocument()
   // Now have code to process the order
 System.Xml.XmlNodeList nodeList = myOrderDom.GetElementsByTagName("OrderItems");
 System.Diagnostics.Debug.WriteLine("The number of Order Items passed in with the XMLDocument is: " + nodeList.Count.ToString());
 foreach(System.Xml.XmlNode orderItemNode in nodeList)
  System.Diagnostics.Debug.WriteLine("The order item node is : " + orderItemNode.OuterXml);



In the Orchestration :
To create the XML string representation of the message in your orchestration:
a) In your orchestration create a variable of type System.XML.XMLDocument

System.XML.XMLDocument varXMLDOM

b) In an expression shape assign the XMLDocument type variable to the message that
you want to pass to the XMLDocument.

varXMLDOM = msgIncomingOrder;

c) Finally set the message that contains the string parameter that will be passed to the Web Method:

msgXMLStringRequest.XMLAsString = varXMLDOM.OuterXml;

2) Pass the XML as a System.XML.XMLDocument
For example your Web Method that you need to call from your Orchestration might look something like this:

ProcessOrder (system.XMl.XMLDocument myOrder)

 System.Xml.XmlNodeList nodeList = myOrder.GetElementsByTagName("OrderItems");
 System.Diagnostics.Debug.WriteLine("The number of Order Items passed in with the XMLDocument is: " + nodeList.Count.ToString());
 foreach(System.Xml.XmlNode orderItemNode in nodeList)
  System.Diagnostics.Debug.WriteLine("The order item node is : " + orderItemNode.OuterXml);



In the Orchestration :
To set the message that contains the XMLDocument parameter that will be passed to the Web Method , simply set the web message to the orchestration message :
msgXMLDomRequest.XMLAsXMLDocument = msgIncomingOrder;

3) Pass the XML as a Strongly Typed Class

Your order class (defined on the Web Service side) might look something like this :

[System.Xml.Serialization.XmlRootAttribute(Namespace="http://PassingXMLToWS.Orders", IsNullable=false)]
public class Orders
 [System.Xml.Serialization.XmlElementAttribute("OrderItems", Form=System.Xml.Schema.XmlSchemaForm.Unqualified)]
 public OrdersOrderItems[] OrderItems;

 public string OrderId;
 public string TotalAmount;

public class OrdersOrderItems
 public string OrderItemId;
 public string ItemAmount;

Note: I created the above class by using the xsd utility that is installed with VS.Net.
a) I created an xsd BizTalk Schema, and called it Orders.xsd
b) I then generated the above class, by opening up the Visual Studio Command Prompt and then
typing in the following :
c:\passxmltowebservice> xsd Orders.xsd /c /l:cs
c) The xsd utility then created a class for me called, Orders.cs, which I then copied over to
my web service project.

Your Web method that accepts an Order class might look like the below:

public void AcceptOrderClass(Orders myOrders)
 System.Diagnostics.Debug.WriteLine("In AcceptOrderClass");
 System.Diagnostics.Debug.WriteLine("The Id of the Order passed in is: " + myOrders.OrderId);
 foreach (OrdersOrderItems orderItem in myOrders.OrderItems)
  System.Diagnostics.Debug.WriteLine("The Id of the Order Item is " + orderItem.OrderItemId); 

In your BizTalk project, when the Web Reference is added,
an XSD schema will be created that represents the Orders class (Called Reference.xsd).
Inside your Orchestration you create a message of type Reference.xsd and then construct this message, just like any other message.
When your message reaches the web service side it will automatically be converted to an instance of -> public class Orders  

Here is a sample of each.




Assembly Reference Resolving in Visual Studio.

I got a question today about a problematic assembly references during a build of a large project.

Visual Studio projects have a user file (*.csproj,user, *.vbproj.user) that stores a “References Path”. You can edit this list of paths in your project properties dialog and it works like a PATH variable.

The twist comes in when you add a reference to a project. The IDE creates a relative formed path to the assembly you picked in the add reference dialog and places that as a “hint path” in the .csproj/.vbproj file.

So imagine you have some carefully crafted hint paths in your project file references, and no references paths in your user file. It's still possible, for your assembly to not be found where you expect it.

Did you know that VS.NET will automatically add References Paths for you as part of it's build process. Let's say you have a list of references in your project. As VS.NET iterates through the list of them one at a time, it will first check the references path. If the assembly is not found, it will use the hint path. If it finds it with the hint path - it will take the fully qualified path to that assembly - and put it in references path of your user file. When it gets to the next assembly it will check all those references paths - including the one it just automatically added.

What happens if you have multiple copies of your referenced dll hanging around? It could find one other than the one you referenced in your hint path.

This is all pretty rare to happen, but if you are like some folks who use Visual Studio itself as your build server (not NAnt) and as a matter of practice you delete your .user files as part of that build (with the predefined reference paths), you could find yourself in hot water. The only solution in this mixed up process is to make sure you don't have multiple copies/versions of your referenced assemblies lying around. Or better yet, use NAnt.

When is a cache not really a cache

I spent a large portion of the day inside of the Caching Application Block.  Specifically, a colleague and I were tracking down what appeared to be a nasty threading bug caused by having the creation of two different cached objects that were related to one another.  As it turned out, the bug that existed couldn't explain away all of the behaviors that we observed.

As it turns out, there was what appears to be a poorly documented aspect of the Caching Application Block that was causing us grief.  The problem is that the default number of objects that the cache can store before scavenging begins is set very low.  Specifically, it is set to 5.  As well, there is a UtilizationForScavenging setting (by default, it is 80) that lowers this number ever more.  Once the cache contains the (maximum * utilization / 100) items, the scavenger starts to remove the excess items from the cache, trying to keep the number of items below the calculated value.  With the default values, this means that no more than 3 elements will be saved in the cache at a time.  The scavenging class uses a least recently used algorithm, however if you're using an absolute time expiration, there is no 'last used' information saved.  So the scavenger appears to remove the last added item. 

That's right.  Unless you make some changes to the default config, only three elements are kept in the cache.  Probably not the performance enhancer that you were looking for from a cache.  Fortunately, the values can easily be changed.  The can be found in the ScavengingInfo tag in app.config. And there is no reason not to set the maximum value much higher, as there is no allocations performed until actual items are cached. It was just the initial surprise (and subsequent fallout) that caused me to, once again, question how closely related the parents of the designers were.  But only for a moment. ;)

As one further word of warning, if there is no ScavengingInfo tag in the config class, then the default class (the same LruScavenging class just described) is used. And instead of getting the maximum cache information from the config file, a file called CacheManagerText.resx is used.  In that file, the entries called RES_MaxCacheStorageSize and RES_CacheUtilizationToScavenge are used to determine how many items to keep in the cache.  Out of the box, these values are set to the same 5 and 80 that the config file contains.

Solving the "No such interface is supported" problem

I was asked a question today about a fairly common error that occurs when serviced components are used in conjunction with ASP.NET. Specifically, a COM+ component (one that is derived from ServicedComponent) was being used on an ASP.NET page.  When the page was loaded, an error of "No such interface is supported" was raised.

To understand the why of this error requires a little bit on knowledge about COM+.  When a serviced component is first instantiated, the CLR checks the COM+ catalog for information about the runtime requirements of the class. But if the class had not previously been registered, then no information will be available.  To correct this discrepancy, the CLR automatically creates a type library for the class and uses that information to populate the COM+ catalog. This mechanism is called lazy registration. 

But wait.  It requires a privileged account in order to register a component in the COM+ catalog.  In particular, you need to be a machine admin.  And, unless you have been silly enough to grant the ASP.NET user admin rights to your machine, the update of the COM+ catalog fails.  No catalog information, no instantiation.  An unsuccessful instantiation means an exception.  An exception that includes an error message of "No such interface is supported". Go figure.

So ultimately, the solution is not to depend upon lazy registration of COM+ components that are deployed for use in ASP.NET.  Instead, perform the registration using the regsvcs.exe command manually.