f.NET Serviced Components

Summary

Introduction

COM provides one way of writing components, and it is well known that the underlying plumbing is significant and repetitive. The .NET Framework provides another way of writing components and has many advantages over COM such as better tool support, the CLR, and much easier coding syntax. COM+ on other hand, is not a new version of COM, but rather it provides service infrastructure for components . These components are built using COM and/or .NET Framework and are then deployed into a COM+ application in order to build scalable server applications that can achieve high throughout with ease of deployment. Note that the previous statement implies that COM+ services can be used with managed and unmanaged components. In fact, component services in unmanaged code are known as COM+ Services, and component services in managed code are known as COM+ Enterprise Services.

The basic idea behind Enterprise Services is to design for scalability and throughput from the outset and then use Enterprise Services to easily implement those design patterns where appropriate.

In general, scalability and throughput are achieved by designing server applications from the outset to use COM+ services such as pooling, JITA and transactions. If a component does not need COM+ services, then it should not be deployed in a COM+ application. Deriving a .NET class from ServicedComponent base class indicates that this class wants to use COM+ Enterprise Services.

It can also be argued that Enterprise Services has little to do with COM or even components: Enterprise services offers infrastructure services that can be applied not only by COM and .NET components, but also by non-component entities such as ASP.NET pages and arbitrary blocks of code.

.NET is essentially a component technology, just like COM, that provides you with the means to quickly build binary components. Like COM, .NET does not provide its component services, but relies on COM+ to provide services such as object pooling, transactions, activity-based synchronization, role-based security, disconnected asynchronous queued components and loosely-coupled events. The .NET namespace that contains COM+ services is named EnterpriseServices to reflect the role that these services play in building distributed applications. A .NET component that uses COM+ services is called a serviced component, to distinguish it from other managed components.

Quick Review of COM+

The following provides a quick review of COM+ concepts:

Like COM, the CLR also depends on COM+ to provide runtime services to components that need them. It is very important to understand that integrating COM+ with the CLR is not achieved through COM interoperability alone. The CLR uses the services of COM+ through the System.EnterpriseServices namespace. 

Developing Serviced Components

Classes that require enterprise services must derive from ServicedComponent base class and use various custom attributes to specify actual required services. In general, coding and deploying a serviced component can be summarized below:

Creating .NET Serviced Components

As mentioned previously, any .NET Component that takes advantage of COM+ services must derive from ServicedComponent class found in System.EnterpriseServices namespace. The following code shows how to write a serviced component that implements a interface called IMessage:

/* This code shows how to create a serviced component. Note that constructors for serviced components are not allowed to take parameters. If you must have such constructor, either use the Factory method or use Component Serviced to pass a Constructor string */

using System;
using System.EnterpriseServices;

namespace MyServicedComponents
{
    public interface IMessage
    {
        void ShowMessage( string strMsg );
    }

    public class MyComponent : System.EnterpriseServices, IMessage
    {
        // Constructo. See note above on constructor limitations
        public MyComponent() {}

        // Implement IMessage interface
        void ShowMessage( string strMsg )
        {
            /* ... * /
        }
    }
}

The presence of ServicedComponent in a class's inheritance hierarchy tell the CLR's object creation plumbing that the class is configured and that the class needs to live in a COM+ context. Note that a client assembly that creates a serviced component or uses any of its base class ServicedComponenet methods must add a reference to System.EnterpriseServices namespace to its project. Other client that use only the interfaces provided by the serviced component need not add a reference to this namespace.

Configuring .NET Serviced Components

There are two ways to configure serviced components to take advantage of COM+ services

  1. Create a .NET component that derives from ServicedComponent base class, deploy in Component Services MMC, and configure it there.
  2. Create a .NET component that derives from ServicedComponent base class and apply special attributes to the component, configuring at the source-code level. When the component is added to a COM+ application, the component will be configured according to the applied attributes.

If you do not apply your own attributes, a serviced component will be configured using the default COM+ settings when it is added to a COM+ application. In general, everything you can do with Component Services can be done with attributes. The recommendation is to use attributes where possible and use Component Services to configure for deployment-specific details.

.NET Assemblies & COM+ Applications

To use COM+ component services, you must map the assembly containing your serviced components into a COM+ application. COM+ does not care whether the component it is providing services to is a managed .NET component or a classic unmanaged COM component.  A COM+ application can contains components from different assemblies, and an assembly can contribute components to many COM+ applications. However, a component can only reside in one COM+ application.

Registering Serviced Components

To serviced components to a COM+ application, you must register the assembly containing those components.  The registration process can be performed in three ways:

  1. Manual registration using RegSvcs.exe command-line utility.
  2. Dynamic registration by having a client application register the assembly automatically.
  3. Programmatic registration using a .NET class.

Regardless of the technique, the registration process will add the serviced components to a COM+ application and configure those components according to their attributes (if present in the code.) Note that incompatible attributes are not detected at compilation time, only at registration time. Custom COM+ attributes are one of the key concepts of accessing COM+ services from managed code. Custom attributes such JustInTimeActivation and Transaction are used to specify the services that are required. The custom attributes are used by having some code load containing the attributes, and then use reflection to create instances of these attributes (attributes are classes!), and then call methods on those attribute instances to retrieve configuration information. This information can then be written to the COM+ catalog. The code that performs these and other steps is called RegistrationHelper. This process is illustrated below:

Conceptually, RegistrationHelper performs the following steps:

RegistrationHelper will attempt to perform these operations under a transaction, so if the registration process fails, the COM+ catalog will be restored to its original state. And because RegistrationHelper accesses the COM+ catalog, it requires unmanaged code permissions and admin rights on the machine. Therefore, all forms or registration (manual, dynamic and programmatic - shown later) require the same previliges.

A key requirement for registering an assembly with COM+ is that the assembly should have a strong name, i.e., it must be signed with sn.exe utility. Further, the assembly DLL must be in a known location. If the serviced components in that assembly are configured for server activation (DllHost.exe activation), that known location is the GAC and the assembly should be installed there using the gacutil.exe tool. If the serviced components in that assembly are configured for library activation, that known location is the directory of the host process (which may be DllHost.exe, or potentially a Windows Service used to service .NET Remoting calls). To maintain flexibility, it is advised that you always install your components in the GAC irrespective of the activation mode.

You can provide .NET with an the ApplicationName assembly attribute to specify the name of the COM+ application that you wish your components to be part of, 

[assembly:ApplicationName("MyApplication")];

If no name was specified, .NET will use the name of the assembly as the name of the COM+ application.

To remove a .NET application that uses serviced components, remove the assembly from the GAC (if it has been registered there), de-register the assembly from COM+ using regsvcs.exe, then delete the assembly and its associated type libraries

Whatever method (see below) is used to register the component, it should be noted that the COM+ catalog should be viewed as a copy of the class's declarative attributes and not the real source.  The only things that you should change in the COM+ catalog are deployment-specific settings like constructor strings and security settings.

Also not that a class's public methods do not appear in the COM+ catalog. This is because they do not appear in the type library that regsvcs.exe generates and used for registration. This is the default behavior. You can change this behavior either by defining and implementing interfaces on the class or by marking your class with the System.ClassInterfaceAttribute.

Understanding Serviced Component Versions

To fully understand the registration process, one needs to understand the relationship between an assembly's version and COM+ components. First, note that every managed client is built against a particular version of the assembly that contains the components - whether these components are serviced or just regular components. Recall that an assembly's version W.X.Y.Z is a combination of its version number (major and minor, W.X) and build and revision number (Y.Z). So a complete version number might be 1.2.3.44. The version number is often provided by the developer as an assembly attribute, while the build and revision numbers can be generated by the compiler (or the developer can provide them him/herself).

The second important fact to note is that the semantics of the version and build (or revision) number tell .NET whether two particular assembly versions are compatible with each other, and which one is the latest. Assemblies are compatible if the version number (W.X) is the same. The default is that a different build (Y) or revision (Z) number do not indicate compatibility, but a difference in either major (W) or minor (X) number does indicate incompatibility. 

Another fact to note is that the client's manifest contains the version of each assembly used by the client. It is the responsibility of the .NET runtime loader to examine the manifest, determine which assemblies are used by the client, and load the latest compatible assemblies, with compatible being defined as the major and minor numbers (W.X) and latest being defined as the build and revision numbers (Y.Z).

All the above is fine when everything is under the tight control of the .NET Runtime. But how does the .NET Runtime guarantee compatibility between an assembly's version and the configuration of the serviced components contained by that assembly? The answer is via the COM+ component's ID. The process is as follows: The first time a serviced component is registered in a COM+ application, the registration process generates a CLSID for it, based on the hash of the class definition, the assembly's version and the strong name. Subsequent registration of the same assembly with an incompatible version is considered a new registration for that serviced component. and the component is given a new CLSID. In this way, the component's CLSID acts as the component's configuration settings version number. Existing managed clients do not interfere with one another because each will use the assembly that it was compiled against. And when a managed client creates serviced component, the .NET Runtime will create a serviced component from an assembly with a compatible version and applies the COM+ configuration for the matching CLSID.

Manual Registration

Serviced components are registered manually using the RegSvcs.exe command line utility. RegSvcs.exe assumes that the COM+ application under which the component will be registered already exists. However, when registering the component, you can instruct RegSvcs.exe to create the application if it does not already exist using the /fc switch (this switch takes the application name if the application name was not supplied in the assembly). The following command will search for a COM+ application named MyApp, creates one if it does not exist, then registers the serviced components found in SomeAssembly.dll under MyApp COM+ application:

regsvcs.exe /fc MyApp SomeAssembly.dll

If the assembly does not programmatically supply an attribute for the COM+ application name that will host its components, RegSvcs.exe must be explicitly told what the COM+ application name will be using the /appname switch (/appname switch is ignored if the assembly already contains an application name). The following command assumes that MyApp COM+ application already exists:

regsvcs.exe /appname:MyApp SomeAssembly.dll

Note that if the COM+ application name was neither specified in the assembly nor in the RegSvcs.exe command line parameters, then .NET will use the assembly's name as the COM+ application name.

By default, when using RegSvcs.exe to register components under an existing COM+ application, RegSvcs.exe will not alter the application's settings. If that assembly's version is already registered with that COM+ application, then RegSvcs.exe will do nothing. If that version is not registered yet, RegSvcs.exe will add a new version and assign a new CLSID. 

Reconfiguring an existing version is done via the /reconfig switch:

regsvcs.exe /reconfig /fc MyApp MyAssembly.dll

The /reconfig switch causes RegSvcs.exe to reapply any application, component, interface and method attributes found in the assembly to the existing version and use the COM+ default settings for the rest. The effect of this is reversing any changes that you may have applied using Component Services MMC.

In addition to adding serviced components found in the supplied assembly to the COM+ catalog, RegSvcs.exe also creates a type library. This library contains the coclass and interface definitions to be use by non-managed (COM) clients. The default type library name will be <AssemblyName>.tlb.

Dynamic Registration

When a managed client attempts to create a serviced component, the .NET Runtime will attempt to resolve which assembly version to use for that client. Next, the runtime verifies that the requested and successfully loaded version is registered with COM+. If it is not registered, the runtime will automatically attempt to register it with the COM+ catalog. This process is called dynamic registration and requires that the client attempting to create serviced components have administrator privileges on the machine. Like RegSvcs.exe if the assembly contains the COM+ application name in an attribute, that name will be used. Else, the COM+ application name will be that of the assembly.

Note the following limitations:

In general, use RegSvcs.exe whenever you can. If you want to use dynamic registration, you will have to increment the version number of the assembly every time you make a change to one of the components' attributes to trigger dynamic registration.

Programmatic Registration

Both RegSvcs.exe and dynamic registration use a .NET helper class called RegistrationHelper to perform the registration process. This class implements the IRegistrationHelper interface, whose methods are used to register / unregister assemblies. RegistrationHelper class is often used as part of an installation program.

IDs 

This section discusses three attribute IDs that are often part of any registration process: ApplicationID, Component GUID, and ProgID. These IDs are implemented through the use of attributes as discussed below.

ApplicationID

Every COM+ application must have a GUID identifying it called the Application ID. You can provide an assembly attribute that specifies the ApplicationID GUID (this is in addition to the ApplicationName attribute that names the application.) Note that once an ApplicationID has been assigned. all future searches for this COM+ application will be performed using the ApplicationID. The Application Name is useful as a human readable form of the ApplicationID.

[assembly: ApplicationID( "22578A01-CCA8-11D2-A719-0060B0B41584" ) ];
[assembly: ApplicationName( "MyApp" ) ];

GUID

As discussed previously, instead of having the registration process generate a CLSID for your serviced component, you can specify one for it using the Guid attribute:

using System.Runtime.InteropServices;

[Guid("{12348A01-CCA8-11D2-A719-0060B0B41584}")]
public class MyComponent: ServicedComponent, IMyInterface
{
    ...
}

Note that there are important consequences to supplying a Guid attribute: Subsequent registrations of the assembly will generate a new CLSID for the component, regardless of the version of the assembly being registered. Registrations always configure the same component in the COM+ catalog. Specifying a Guid is useful during development and testing when you have multiple cycles of code-test-fix. Without it, every invocation by the client will trigger dynamic registration which can slow your testing effort as well as clutter your COM+ application with dozens of different versions of the same component.

ProgID

The registration process will generate a name for your serviced component using the component's namespace and class name. You can specify the serviced component's name using the ProgID attribute:

using System.Runtime.InteropServices;

[ProgId("Database Access Component")]
public class DAL: ServicedComponent, IDAL
{
    ...
}

Configuring Serviced Component

Declarative Attributes

The decision about whether a new instance of a ServicedCompnent-derived class needs to live in a new context depends on the class's declarative attributes. With a configured class based on the CLR, you can take advantages of the CLR's intrinsic support for metadata and embed the the declarative attributes directly in the class definition. During registration, the runtime extracts this data from the assembly using reflection classes and uses this data to correctly configure the class in the COM+ catalog.

.NET offers various attributes that can be used to configure serviced components to take advantage of COM+ component services. The following sections demonstrate these COM+ services

Application Activation Type

The COM+ application activation type can be specified using the ApplicationActivation assembly attribute. The application can be configured either as a library or a server application:

[assembly: ApplicationActivation( ActivationOption.Server )];    // Or: ActivationOption.Library

If this attribute was not specified, .NET will use the default library activation.

The Description Attribute

This attribute allows you to add test to the Description text field found on the General properties tab of an application, component, interface or method:

// Application description
[assembly: Description("Data Access Assembly")[];

// Interface description
[Description("Data Access Interface")]
public interface IDAL
{
   // Method description
    [Description("Opens a connection to a database")]
    void OpenConnection();

}

// Class description
[Description("Data Access Component")]
public class DAL : ServicedComponent, IDAL
{
    public void OpenConnection();
    ...
}

COM+ Context

Configured classes can easily interact with the runtime services they are using. Recall that COM+ implements its services via interception and objects interact with the services they are using via the context object. System.EnterpriseSerivces wraps CoGetObjectContext API, the COM+ API for retrieving the context object with the ContextUtil class. Therefore, to access interface methods and properties of the COM+ Context object you can use the ContextUtil helper class, where all context object interfaces are implemented as public static methods and public static properties. Static methods and properties mean that you do not have to instantiate an object of ContextUtil class.

ContextUtil class has methods for JITA, transaction voting, obtaining transactions and activity IDs, and obtaining the current transaction object. For example, to obtain the current COM+ context ID:

Guid guidCtx = ContextUtil.ContextId;

COM+ Context attributes

There are two context-related attributes that can be specified to your serviced component class: MustRunInClientContext and EventTrackingEnabled

MustRunInClientContext attribute tells COM+ that the class must be activated in the creator's context:

[MustRunInClientContext(true)]
public class MyComponent : ServicedComponent
{ ... }

If this attribute was not specified, then COM+ will not enforce same-context activation. No supplying the attribute is the same as supplying it and passing false for the constructor, i.e., [MustRunInClientContext(false)].

Note that using COM+ attributes with the COM+ default values is useful when you combine it with the /reconfig switch of RegSvcs.exe to undo any unknown changes made to your component's configuration and restore the component configuration to a known state.

This attribute informs COM+ that the said serviced component supports events and statistics collection during its execution.

[EventTrackingEnabled(true)]
public class MyComponent : ServicedComponent
{ ... }

Note that a component that supports statistics is usually placed in its own context.

COM+ Object Pooling

The ObjectPooling attribute can be used to configure every aspect of pooling for your serviced component. This attribute enables/disabled pooling, sets the min/max size of the object pool, and sets the object creation timeout. The following enables object pooling for a serviced component with min/max pool size of 3/10 and a creation timeout of 20 msec:

[ObjectPooling(Enabled=true, MinPoolSize=3, MaxPoolSize=10, CreationTimeout=20)]
public class MyServicedComponent : ServicedComponent
{ ... }.

The Enabled, MinPoolSize, MaxPoolSize and CreationTimeout are public properties of the ObjectPooling attribute class. If no values were specified, them COM+ defaults will be used (enabled is true, min pool size of 0, max pool size of 1,048,576, and creation timeout of 60 msec).

Note that if a pooled component is hosted in a library application, then each hosting Application Domain will have its own object pool. As a result, you may have multiple object pools in the same physical process if that process has multiple application domains.

Previously under COM, the object returns to the pool when the client releases its last reference to the object. Managed objects use garbage collection and do not use reference counting. A managed pooled object returns to the pool only when it has been garbage collected. Because .NET garbage collection is non-deterministic (i.e., we do not know when it will happen), there could be a significant delay between the time the object is no longer needed by the client and the time the object is returned to the pool. This could affect the application's scalability because a pooled object is one that is usually expensive to create. There are two ways to address this problem:

  1. Use JITA where the pooled object returns to the pool after every method call (discussed in the next section)
  2. Let the client explicitly request the  return of the object to the pool by calling the static ServicedComponent.DisposeObject().  Calling this method has the immediate effect of returning the object to the pool, as well as disposing the context object hosting the pooled object and of the proxy the client used.

However calling DisposeObject is ugly: you first have to figure out if the object is actually a serviced component before calling this method. This coupled the client to the type and renders many aspects of interface-based programming useless. The following example calls DisposeObject() directly:

// Component definition
public interface IMyInterface
{
    void foo();
}

[ObjectPooling]
public class MyClass : ServicedComponent, IMyInterface
{
    public void foo() { ... }
}

// Client calling the serviced component
IMyInterface ob = (IMyInterface) new MyClass();
ob.foo();

// Now return the object to the pool
ServicedComponent sc = ob as ServicedComponent;
if (sc != null )
    ServicedComponent.DisposeObject( sc );  

A much better solution is to have the serviced component implement the IDisposable interface:

[ObjectPooling]
public class MyClass : ServicedComponent, IMyInterface, IDisposable
{
    public void foo() { ... }
}

// Client calling the serviced component
IMyInterface ob = (IMyInterface) new MyClass();
ob.foo();

// Now return the object to the pool
IDisposable dis = ob as IDisposable;
if (dis != null)
    dis.Dispose();

The IDisposable technique is useful not only with serviced components, but with any component requiring a deterministic disposal of memory and resources.

Note that the basic assumption of object pooling is object reuse. Object pooling is often used with JITA. In general, object pooling can be used in one of two ways:

COM+ Just-In-Time Activation

JITA is enabled for a serviced component by using the JustInTimeActivation attribute. If this attribute is not added, COM+ by default will disable JITA support for the component.

[JustInTimeActivation(true)]
public class MyClass : ServicedComponent
{ ... }

To use JITA, not only do you need to enable it via the JustInTimeActivation attribute, but you also have to tell COM+ when to deactivate the object, and this can be done by setting the done bit in the context object. ContextUtil.DeactivateOnReturn is used to set the value of the done bit and hence determine whether the object will be deactivated or not. Recall that a JITA object should retrieve its state at the beginning of each call and, optionally, save it at the end of the method call.

// Component definition
public interface IMyInterface
{
    void foo();
}

[JustInTimeActivation(true)]
public class MyClass : ServicedComponent, IMyInterface
{
    public void foo()
    {
        // Get State ...

        // Do Work ...

        // Set state ...

        // Tell COM+ to deactivate the object on return
        ContextUtil.DeactivateOnReturn = true;
    }

    // Other methods ...
}

Assuming the DecativateOnReturn bit is set to true, the sequence of method calls would be: class constructor, Activate, actual method call, Deactivate, Dispose(true), and eventually the class finalizer, if one exists. The same sequence is repeated when another method is called.

A good design practice is to only override the Activate and Deactivate methods to know when the object is being taken out and back into the object pool. The remaining logic of Activate and Deactivate should be placed in the class constructor and Dispose(bool) methods

Enterprise Services infrastructure is based on the concept of contexts. A context is a runtime environment for objects having similar execution requirements. In general, enterprise services are enforced during activation and/or during method call interception. Serviced components can be activated and hosted in different ways:

The DecativateOnReturn bit is set usually with one of the following approaches:

All serviced components have an associated COM+ context. And in general it is better not to rely on garbage collection to clean up contexts. Contexts should be destroyed by calling Dispose(). And From a performance point of view, it is better not to implement a finalizer in a  serviced component-derived class, and instead place this log in the Dispose(bool) method. Therefore, a good design pattern for using JITA is:

IObjectControl

If your serviced component uses JITA and/or pooling, it may need to know when it is placed in a COM+ context so that it may perform context-specific initializations. This is the purpose of the IObjectControl interface. Note that the .NET ServicedComponent class already provides a virtual default implementation for this interface's members, so that you can override all or any of its virtual members. The default implementation of Activate returns success allowing the object to be activated, while the default implementation of Deactivate does nothing. Note that the default implementation of CanBePooled returns false meaning that objects cannot be use:

public class MyComponent : ServicedComponent
{
    ...

    // IObjectControl implementation
    public override void Activate() { /* TODO: Context-specific initialization */ }
    public override void Deactivate() { /* TODO: Context-specific initialization */ }
    public override bool CanBePooled() { /* TODO: Context-specific initialization */ }
}

To maintain JITA semantics, when the object de-activates itself, .NET explicitly calls DisposeObject() on to destroy it. Your object can do  specific clean in the Finalize() method which will be called as soon as the object deactivates itself without waiting for garbage collection. If the object is JITA as well as pooled, the object will be returned to the pool after deactivation without waiting for garbage collection. Or better, you can do perform clean-up operations in the IObjectControl.Deactivate() methods. In either case, you have a deterministic way of performing cleanup operations without having to invoke client participation.

Note that COM+ JITA gives your components the ability to have a deterministic finalization which nothing else in .NET can provide out-of-the-box.

COM+ Transactions

The Transaction attribute can be used to configure your serviced components to use any of the five available COM+ transaction options:

[Transaction(TransactionOption.Required)]
public class MyClass : ServicedComponent
{ ... }

Note that when you use the Transaction attribute, you implicitly set your serviced component to use JITA and require activity-based synchronization as well. 

To vote on a transaction outcome, you can use the ContextUtil.MyTransactionVote property. The following shows a transactional component that uses the ContextUtil.MyTransactionVote property to vote on a transaction. Note how the object retrieves its state at the beginning of the call, does some work, saves its state and then deactivate itself at the end of the method call to let the transaction vote take place. Also note that serviced components do not use HRESULT codes but rather use exceptions for error handling using the try-catch-finally blocks:

// Component definition
public interface IMyInterface
{
    void foo();
}

[Transaction(TransactionOption.Required)]
public class MyClass : ServicedComponent, IMyInterface
{
    public void foo()
    {
        try
        {
            // Get State ...

            // Do Work ...

            // Set state ...

            // Everything is OK. Tell COM+ to commit this transaction
            ContextUtil.MyTransactionVote = TransactionVote.Commit;
        }
        catch( Exception e )
        {
            ContextUtil.MyTransactionVote = TransactionVote.Abort;
        }
        finally
        {
             ContextUtil.DeactivateOnReturn = true;   
        }
    }

    // Other methods ...
}

.AutoComplete

The AutoComplete attribute can be used on serviced components so that the object is automatically deactivated on return from a method call without having to explicitly set the done bit using DeactivateOnReturn method. 

Transactional serviced components that use AutoComplete do not need to vote explicitly on their transaction outcome. This is because the object's interceptor sets the done and consistency bits of the context object to true if the method did not throw an exception, and the consistency bit to false if it did. As a result, the transaction is committed if no exception is thrown and aborted otherwise. Either way, the object will be deactivated (the done bit is set either way).

Non-transactional serviced components that use AutoComplete do not need to call ContextUtil.DeactivateOnReturn as the object will be deactivated automatically on method return.

Note that the AutoComplete attribute can be applied to methods as part of an interface definition:

public interface MyInterface
{
    // Avoid this
    [AutoComplete]
    void foo();    
}

The above should be avoided because an interface is a contract between the client and an object and the above obliges the client to use auto-completion. AutoComplete is an implementation decision as one implementation may choose not to use it, while another implementation may choose to use.

TransactionContext object

The TransactionContext object solves the issue of a client wanting to scope all its interactions with objects under one transaction. The TransactionContext object acts as the middleman between the client and its objects. It is especially useful with .NET managed components that already derive from a class. Recall that in .NET,  a class can only derive from one concrete class and since the class already derives from one concrete class other than ServicedComponent, it cannot use the Transaction attribute. TransactionContext class comes to the rescue by giving this class the ability to initiate and manage a transaction. 

The following shows how a non-transactional managed object can use the TransactionContext object to initiate and manage a transaction:

using COMSVCSLib;

void CoordinateWork()
{
    // Create a transactional context object
    ITransactionContext txCtx = (ITransactionContext) new TransactionContext();

    // Use the transactional context object to create object whose methods will be bound within the
    // the transaction represented by txCtx

    ob1 = (ISomeInterface1)txCtx.CreateInstance("...");
    ob2 = (ISomeInterface2)txCtx.CreateInstance("...");
    ob3 = (ISomeInterface3)txCtx.CreateInstance("...");

    try
    {
        // Execute methods within a transaction
        ob1.foo1();
        ob2.foo2();
        ob3.foo3();

        // No errors. Now commit
        txCtx.Commit()
    }
    catch( Exception e )
    {
        // Some error. Abort all operations performed on ob1, ob2, and ob3
        txCtx.Abort();
    }
}

Note how the client decides whether to commit or abort the transaction based on whether an exception was thrown or not.

COM+ Transactions and Non-Serviced Components
Web Services & Transactions

Web Service classes derive from WebService class (defined in System.Web.Services) . Because Web Service classes do not derive from ServicedComponent class and .NET does not allow multiple inheritance of concrete classes.  The solution is to use the TransactionOption public property on the WebMethod attribute:

[WebMethod(TransactionOption=TransactionOption.Supported)]
void foo()
{ ... }

Note that in web methods, a transaction is committed automatically if no exceptions are thrown. Conversely, a transaction is aborted automatically if exceptions are thrown. Another valid approach is to explicitly vote on the transaction outcome using ContextUtil class.

ASP.NET & Transactions

An ASP.NET page may be required to coordinate the work of a few components into a transaction. The problem again relates to .NET inheritance restrictions: a class can derive only from one concrete class, and a Web Form already derives from System.Web.UI.Page base class and not from ServicedComponent class, and therefore, cannot use the Transaction attribute.

However, to provide transaction functionally, the Page base class has a public property called TransactionMode that can be used to control transactions. An ASP.NET page can also use transactions with a declarative statement:

<@% Page Transaction="RequiresNew" %>

As with Web Services, an ASP.NET page votes on the outcome of the transaction by using the ContextUtil methods. Note that ASP.NET pages can subscribe to events notifying it when a transaction is initiated and when it is aborted.

COM+ Synchronization

Multithreaded managed components can use .NET-provided synchronization locks such as the classic events and mutexes. However, serviced components should COM+ activity-based synchronization by adding the Synchronization attribute to the class definition:

[Synchronication(SynchronizationOption.Required)]
public class MyComponent : ServicedComponent
{ ... }

Development Complexities

There are a couple of issues that make writing COM+ code with the CLR a little more complicated. Most of these issues arise because the CLR is not COM and the CLR can do things that COM cannot. These issues are not complicated - but you do need to understand certain features of the CLR such as static methods and garbage collection that impact COM+ programming.

Before addressing these complexities, you need to be aware of two details about the relationship between the CLR and COM+ contexts:

Static Methods & Properties

If a CLR-based configured class has static methods and properties, what context should they execute in? The caller's. This makes sense when you consider that static methods are not object-specific and do not need to be access from within the same context where the object lives. In other words, instance methods will always execute in the object's context, whereas static methods will always execute in the caller's context

Now what happens if you stored a reference to an object in a static property while executing in context A, and then try to access this object reference from context B? The CLR always wraps each instance of a configured class with a very thin proxy so that even other objects in the same context do not have a direct reference to the class instance. So when code executing in context A stores a reference to a configured class in a static property, it is really storing a reference to a proxy. When code executing in another context retrieves this object reference and starts using it, the proxy will automatically detect that contexts have changed and marshals the reference it holds so that the right interception occurs.

This solution implies that you can store references to CLR-based configured classes anywhere and use them from anywhere without worrying about mashalling and interception because it will automatically be taken care of.

Managing Resources

Recall that one of the major features of the CLR is its reliance on garbage collection to reclaim unused memory. Because managing resources is key to building scalable solutions, you need to understand how to properly clean up previous CLR-based resources.

Consider the following code:

public void GetData()
{
    // Open a database connection
    OdbcConnection conn = new OdbcConnection();
    conn.open( strConnectionString);

    // Use the connection to execute SQL statement
    ODbcCommand cmd = new OdbcCommand();
    cmd.CommandText = "select * from T1";
    cmd.ExecuteReader();
}

At the end of the call, the conn object goes out of scope. The question is: when will the connection held open by conn object be closed? Not until the garbage collector kicks in, realizes that conn object is no longer being used, then calls its finalizer. The finalizer returns the connection to the pool and then the object's memory is reclaimed. There is a major problem with the above code: database connections are expensive resources and you cannot and should not leaving them floating in memory until the GC determines it is time to cleanup.

There must be a way to reclaim expensive resources immediately and on-demand. One way to do this is to force the garbage collector to kick in:

public void GetData()
{
    // Open a database connection
    OdbcConnection conn = new OdbcConnection();
    conn.open( strConnectionString);

    // Use the connection to execute SQL statement
    ODbcCommand cmd = new OdbcCommand();
    cmd.CommandText = "select * from T1";
    cmd.ExecuteReader();

    // Option 1: Clean up resources immediately
    System.GC.Collect();
}

However, another more appropriate approach is to call the object's implementation of IDisposable.Dispose(). In our example, the OdbcConnection implementation of Dispose() returns the underlying database connection to the pool immediately. The improved code for the above would then be:

public void GetData()
{
    OdbcConnection conn;~
    try
    {
         // Open a database connection
        OdbcConnection conn = new OdbcConnection();
        conn.open( strConnectionString);

        // Use the connection to execute SQL statement
        ODbcCommand cmd = new OdbcCommand();
        cmd.CommandText = "select * from T1";
        cmd.ExecuteReader();
    }
    catch( SqlException )
    {
        /* Handle exceptions */
    }
    finally
    {
        conn.Dispose();
    }
}

The above raises two questions: