Kim Gräsman, January 2004
When I first learned about the integration between ASP and MTS and the way it used object context properties, my gut reaction was jealousy. Imagine how cool it would be to pass out-of-band data to COM objects, to escape obscure initialization interfaces and custom factories for providing contextual data to your objects.
For example, say you have a number of COM+ components called from an ASP application. You are running two IIS servers for load-balancing and redundancy, and your COM+ components are hosted on a separate application server. Now you want your components to be able to track from which web server they are called, for simple auditing. If the ASP application could write the machine name to its object context, the COM+ components could use the familiar GetObjectContext pattern for retrieving and logging it, without having to add the machine name as a parameter to every single method. There are countless similar cases, where contextual data and/or functionality could be beneficial to COM+ applications.
This article attempts to describe how to flow context properties from client to server, and why it works the way it does. Unfortunately, it also unveils a couple of serious limitations.
While looking into this technology, I've been confused a couple of times by COM+ terminology. Some concepts are fairly diffuse and I'd like to present my view of those here, and how they relate to object context properties.
Tim Ewald, in his Transactional COM+, talks about context flow. COM+ contexts are very concrete, i.e. they are identified by a GUID, so I found it hard to accept that the context, as a whole, would flow into another context. An MSDN article on contexts confirmed that this term was somewhat misleading, in that it implies that context is flowing rather than context properties. This section describes my view of context property flow, and the ramifications thereof.
When a configured object A creates another configured object B with not-exactly-compatible attributes, context flow denotes how the compatible 'parts' of A's context are replicated to B's context. These 'parts' are currently declarative transactions and activities, usually considered properties of the context.
The properties only flow to a downstream context if they are compatible with what's required of the object, so an object with matching synchronization settings will produce a context within the same activity, for example.
So, actually, context isn't flowing. If there is any difference in configuration between the two objects, with regard to transactions and synchronization, a new context is created, but it inherits some of the root context's properties. I would define it as compatible properties, rather than context, are flowing.
In fact, there is one part of the context which can never mismatch, and therefore always flows: the key-value dictionary exposed by IGetContextProperties. This dictionary is referred to as the context user properties.
Traditionally, the term root context has been reserved for COM+ transactions. Root context denotes the source of a transaction stream, i.e. the context created for an object which either requires or supports a transaction, when there is no transaction available.
In my opinion, this definition is too narrow — root contexts are the initial contexts that are created for an object. By 'initial', I mean the context closest to the client. As described above, not only transactions are defined by the root context, but also context user properties. Transaction roots can intervene further down the call-chain, if a non-transactional object calls a transactional, but the root context still owns the context user properties, which will flow into the transactional root context.
I believe the reason root contexts have been so intimately associated with transactions is because transactions were the number one service provided by MTS, whereas the COM+ of today provides a much richer set of services.
Activities in COM+, as described by Tim Ewald, are implicitly created by COM+ depending on synchronization attributes of components. A context can belong to either one activity, or none at all. The activity is really an abstraction for a collection of synchronized objects. That is, if all objects in a call-chain share the same synchronization properties, access to them, as a group, will be serialized.
In COM+ 1.0, the only mention of activities was the method IObjectContextInfo::GetActivityId. If you look at the MSDN page for that method, you will see the following remark:
If the object is not running within a synchronization domain, COM+ returns a GUID_NULL, which consists of all zeros.
This implies some confusion of concepts. I believe the author was supposed to have written
If the object is not running within an activity...
but synchronization domain is actually a much better name for a collection of contexts and objects that share a single lock.
COM+ 1.5 came with more bells and whistles. With the new concept of services without components, there is an API exposed to create contexts programmatically. One of the new functions is, curiously, called CoCreateActivity. This function does not necessarily create an activity in the sense of a synchronization domain. Based on a passed-in service configuration, it essentially just sets up a root context.
It does, however, return an IServiceActivity interface, which can be used to activate a call object, either synchronously, or asynchronously. Since this call object lives in a context specified by its service configuration, if it creates any configured objects, context properties will flow, provided that their configuration is compatible (and context user properties will always flow, unless the service configuration explicitly suppresses it.)
As a matter of fact, MTS and COM+ 1.0 had this functionality as well — it came in the form of a function called MTSCreateActivity, and was initially an undocumented export from comsvcs.dll. It provides more or less the same services as CoCreateActivity, but the created context isn't configurable, it's just spawned based on a hard-coded policy. This policy was designed to comply with ASP, since it's part of the integration between ASP and MTS/COM+, so it includes the necessary context attributes to properly flow context properties to configured components used from ASP pages.
So, I would argue that synchronization domain is a better term for the original activity concept, whereas activity is good for the call-batching objects. However, the contexts of these call-batching activities can join a synchronization domain if their service configuration says so (see the IServiceSynchronizationConfig interface.)
With this linguistic picnic out of the way, let's see how we can exploit these concepts to populate the object context with custom context user properties, which flow happily across contexts, processes and machines.
In popular belief, COM+ contexts are immutable. That is, once they are conjured up by the COM+ run-time to meet the requirements of their attributes, they cannot be modified. In my experience, this is almost true — rather, once context properties have flowed to another context, they can't be modified.
Of course, COM+ does not expose a way to modify the declarative transaction and synchronization attributes, even of a root context, but the IContextProperties interface has a couple of methods to modify context user properties. Attempting to use the RemoveProperty method to deprive a COM+ context of its ASP intrinsics will not work, however — it returns E_FAIL, presumably to tell you that the property you attempted to remove was not put there in the current context, and therefore can't be removed.
Using CoCreateActivity, we can actually configure the root context, prior to its creation, using the interfaces implemented by CServiceConfig. Besides, since it creates a root context, we have the opportunity of adding context user properties before calling any other configured components, which causes properties to flow and become read-only. MTSCreateActivity-generated contexts can also have context user properties modified at will.
There is also a third way of creating a root context. COM+ 1.5 didn't just bring CoCreateActivity, but also the functions CoEnterServiceDomain and CoLeaveServiceDomain, which can be used to create a temporary context on the current thread. You can read more in the MSDN Magazine article by Craig Andera and Tim Ewald, which concentrates on this feature-set.
ASP uses MTSCreateActivity to conjure up a root context. The context is populated with ASP's intrinsic objects, which then flow to configured components downstream in the call chain, so if we can run a non-configured component in this context, we should be able to modify the context properties before they flow.
It turns out we can. The provided sample code contains an ATL project that does just this.
// ATL method
STDMETHODIMP CContextWriter::SetContextProperty(BSTR Name, VARIANT Value)
{
HRESULT hr;
// Get current object context
CComPtr<IObjectContext> spObjCtx;
hr = GetObjectContext(&spObjCtx);
if(FAILED(hr)) return hr;
// Get context property writing interface
CComPtr<IContextProperties> spContextProps;
hr = spObjCtx->QueryInterface(&spContextProps);
if(FAILED(hr)) return hr;
// Add a context property
hr = spContextProps->SetProperty(Name, Value);
if(FAILED(hr)) return hr;
return S_OK;
}
// ASP code
<%
Dim objCtxWriter
Set objCtxWriter = Server.CreateObject("Hijack.ContextWriter")
objCtxWriter.SetContextProperty "Response", "Not an interface pointer"
' From here on in the request, all attempts to get Response
' from context will get a string rather than an interface pointer
Dim cfgObj
Set cfgObj = Server.CreateObject("ConfiguredComponent.Foo")
Response.Write "Response flowed: " & cfgObj.ExistsContextUserProperty("Response")
%>
Of course, this is not a particularly good idea. Any object written for ASP will break with a bang when the context properties are not in line with the default.
Not all clients are ASP-driven, and if you want to venture to inject context user properties from a bog-standard C++ app, here's a full example.
The requirements are simple: a root context is required to populate properties, and they will flow inevitably to configured components.
// External function which constructs a service config object
extern HRESULT SetupMyServiceConfig(IServiceConfig** ppConfig);
// Console application using COM+
int main(int argc, char* argv[])
{
HRESULT hr;
hr = CoInitialize(NULL);
if(FAILED(hr)) return hr;
{
// Create a service configuration
CComPtr<IServiceConfig> spServiceConfig;
hr = SetupMyServiceConfig(&spServiceConfig);
if(FAILED(hr)) return hr;
// Create a context on this thread
hr = CoEnterServiceDomain(spServiceConfig);
if(FAILED(hr)) return hr;
// Get the fresh context
CComPtr<IObjectContext> spObjectContext;
hr = GetObjectContext(&spObjectContext);
if(SUCCEEDED(hr))
{
// Get context property writing interface
CComPtr<IContextProperties> spContextProps;
hr = spObjCtx->QueryInterface(&spContextProps);
if(SUCCEEDED(hr))
{
// Add a context property
hr = spContextProps->SetProperty(CComBSTR(L"Response"), CComVariant(L"Thanks"));
if(SUCCEEDED(hr))
{
// Call some configured component, and ensure property has flowed
CComPtr<IConfiguredComponent> spConfigured;
spConfigured.CoCreateInstance(L"ConfiguredComponent.Foo");
VARIANT_BOOL vbExists = VARIANT_FALSE;
hr = spConfigured->ExistsContextUserProperty(CComBSTR(L"Response"), &vbExists);
ATLASSERT(SUCCEEDED(hr));
ATLASSERT(vbExists == VARIANT_TRUE);
}
}
}
// Leave context
CoLeaveServiceDomain(NULL);
}
CoUninitialize();
return 0;
}
This sample creates a root context, configured through a pointer to a pre-created IServiceConfig interface, and then writes a key-value pair to the context user properties. After that, it calls out to some configured component, which verifies that it can read the property we added.
See attached code for detailed samples of how to use CoCreateActivity, MTSCreateActivity or CoEnterServiceDomain to cook up contexts, and write context user properties.
Let's take a look at what rules user properties must abide by. First off, the VARIANT value provided must conform to either of the following types:
| VARTYPE | Value | C++ type |
| VT_I2 | 0x02 | short |
| VT_BSTR | 0x08 | BSTR |
| VT_DISPATCH | 0x09 | IDispatch* |
| VT_BOOL | 0x0B | VARIANT_BOOL |
| VT_UNKNOWN | 0x0D | IUnknown* |
| VT_UI1 | 0x11 | unsigned short |
This is not documented, but apparent from the implementation of CUserProps::IsValidProperty, called by IContextProperties::SetProperty.
Further to this, it turns out IContextProperties::GetProperty only accepts the following property names:
| Property name | Description |
| Application | ASP Application object |
| Request | ASP Request object |
| Response | ASP Response object |
| Server | ASP Server object |
| Session | ASP Session object |
| host-security-callback.cedar.microsoft.com | COMTI support object (used to resolve user credentials for mainframe transaction integration) |
This fact is actually documented, but somewhat disappointing. I would have expected these to be the properties most commonly available (given that ASP is the primary provider of context), but not the only ones allowed. The implementation just compares the requested property name to this list, when GetProperty is called, and blindly returns S_FALSE if there is no match. Note that value validity is checked in SetProperty, but name validity is checked in GetProperty.
In other words, you can populate context all you want, but you can only retrieve the keys specified by Microsoft.
So, why would Microsoft prevent people from putting arbitrary contextual data into their context user properties?
I can only speculate, but I suspect that they tailored the integration solely for their own needs, and didn't expect others to want to use the infrastructure. Initially, they probably wanted to keep it undocumented to allow redesign and re-implementation without breaking customer software.
In the end, I think it's about protecting people from themselves. Adding simple strings and numeric value types can't exactly hurt (beyond the fact that the COM+ infrastructure must marshal the data between contexts), but consider the requirements there would be on adding an interface pointer to the context. Essentially, it must live up to the same rules that the ASP objects do, i.e.
If any of these requirements are not met, the smelly context can ruin the sweet COM+ experience with regard to location transparency and scalability.
Also, the technique tightly couples the objects requiring a particular context to their call-sites, so that they cannot easily be re-used from other clients. The only sanctioned use should be if you're building all-purpose server software with a script host, that can call COM+ components and want to expose internal context to these transparently, and that kind of software is fairly unusual (actually, ASP is the one example I can think of off-hand.)
That said, I sincerely think the requirements for objects in context should be documented, and the infrastructure opened up for public use, but my hunch is that the CLR solves this elegantly for the managed world, and that it's not exactly a priority for the COM+ team.
And, frankly, you can use the sanctioned property names to transport user properties, it's just not very extensible.
This article has attempted to explain why it's not altogether intuitive to populate object context user properties, and presented some theoretical background for the related concepts.
I have shown that given the ability to create root contexts, you can also populate context user properties with arbitrary values. However, the valid key names are limited to the ones already in use — none other will be honored.
Of course, this largely makes the technique useless, unless you can stand re-using ASP's property names.
The sample code provided below gives you the opportunity to experiment with programmatic contexts and user property flow.
| PopCtx.zip | 15K | Various techniques for creating root contexts and populating context properties |
| Hijack.zip | 8K | Component for hijacking ASP's root context |
If you think the code doesn't work as advertised, could be improved, or you think the article makes a big fuss out of nothing, please contact me.
Besides the referential links in the text, I wanted to throw in a couple of extra sources. All of the referenced reading is invaluable for understanding MTS and COM+ concepts.