The .NET Common Language Runtime (CLR) provides many of the same services provided by classic COM, such as interoperability for components implemented in different languages. However, the CLR doesn’t replace services provided by COM+, such as automatic transactions, just-in-time (JIT) object activation, and object pooling. Instead, the .NET framework and the CLR provide mechanisms to allow .NET types to use these services from COM+. In this article, I'll cover some COM+ basics and explain how .NET types can use COM+ services. Then, I’ll demonstrate by showing you how to implement a .NET type that uses COM+ automatic transactions.
A COM+ review
COM+ organizes components into groups called applications. There are two types of applications, which differ by where instances of the components that they contain run when activated. Instances of library application components run in the same process as the client that created the instance. Instances of server application components run in a separate process provided by COM+.
A component within a COM+ application is known as a configured component. The COM+ runtime intercepts every client call made to a configured component and performs pre- and post-processing to provide the services configured for the component. The component can affect the post-processing by manipulating the context that COM+ maintains for each instance of a configured component.
In a simple scenario for a component that requires automatic transaction services, COM+ creates a transaction when it intercepts a call to an instance of the component and then forwards the call to the component. Resource managers automatically enlist in this transaction when the component accesses a data resource. Before returning, the component tells COM+, through its context, whether the transaction is ready to be committed or whether it should be aborted. When control returns to COM+, COM+ examines the status set by the component in its context and either commits or aborts the transaction before returning to the client.
A component that is configured for auto transactions is also implicitly configured for JIT activation, which ensures that a component instance is alive only while it is being used. When a client creates an instance of a configured component, COM+ intervenes and transparently supplies a stub to the client. When the client calls a method through this stub, COM+ creates an instance of the component and forwards the call. When the method returns, COM+ destroys the instance before returning to the client. Only the COM+ context remains between client calls. By limiting the number of component instances to only those needed at the moment, JIT activation allows a server to support more concurrent clients. If creating an instance of a component is expensive, the component can be configured for object pooling to allow COM+ to deactivate instances and store them in a pool, from which they can later be reactivated, instead of destroying them.
Information about configured components is stored in two places. The standard COM information for the component is stored in the registry under HKEY_CLASSES_ROOT. The COM+ configuration for the component is stored in a separate database known as RegDB. The registry information andRegDB are collectively known as the COM+ catalog.
.NET enterprise services
A .NET type that uses COM+ services is known as a serviced component. The .NET framework provides support for serviced components under the System.EnterpriseServices namespace in the System.EnterpriseServices.dll assembly.
The following steps allow a .NET type to make use of COM+ services:
- · Derive from the ServicedComponent base class.
- · Apply attributes to the assembly, class, and methods to configure the COM+ services.
- · Create a strongly named assembly containing the component.
- · Register the assembly in the COM+ catalog.
Additionally, an assembly that is part of a COM+ server application must be installed in the Global Assembly Cache, or GAC, to enable COM+ to locate the assembly at runtime.
Now that we've covered some of the concepts, let's look at a component that takes advantage of this approach. I use a router with network address translation (NAT) as a firewall with my home DSL connection. The router logs incoming access attempts, and I’m surprised at how often I’m scanned for a Web server (port 80) and Windows file sharing access (ports 137, 138, and 139). So I’ve written a component called FirewallLog that enables me to log these access attempts to a database and then query by source and destination address.
The FirewallLog component
Listing A shows the C# version of theFirewallLog serviced component. If you are a Visual Basic aficionado, you'll probably find Listing B more to your liking. The attributes that specify the assembly: target apply to the assembly. The AssemblyKeyFile attribute specifies the file that contains a public/private key pair to strongly name the assembly. The .NET Framework SDK sn utility generates this key file. Attributes ApplicationName and ApplicationID specify the name and GUID for the COM+ application. The ApplicationActivation attribute specifies that the components in the assembly are to run in a COM+ server application.
The FirewallLog class derives from ServicedComponent, which provides the necessary plumbing to ensure that FirewallLog instances are hosted inside COM+. The Transaction attribute specifies that FirewallLog methods require a transaction. The ObjectPooling attribute enables pooling of FirewallLog instances and specifies a minimum pool size of two objects and a maximum of 10 objects. FirewallLog overrides ServicedComponent.CanBePooled(), which returns false, and returns true to indicate that instances can be pooled. The ClassInterface attribute automatically generates an IDispatch interface to expose the FirewallLog’s public, nonstatic methods to COM clients.
The FirewallLog methods LogAccess and QueryAccess manage the single database table shown in Figure A.
|Database for logging activity from FirewallLog|
LogAccess and QueryAccess use the OLE DB .NET data provider to connect to the database and execute a SQL command. Both methods use a finally clause to release the underlying OLE DB connection resources as soon as processing is complete. LogAccess inserts a new record and then tells COM+ that the transaction is complete and ready for a commit by calling ContextUtil.SetComplete(). ContextUtil.SetAbort() is called to abort the transaction if an error occurs. ContextUtil provides access to the current object’s COM+ context through a set of static methods and properties.
QueryAccess executes a SQL SELECT statement to search for accesses that match specified source and destination addresses, including the beginning fragment of an IP address. The data adapter from the OLE DB .NET data provider fills an ADO.NET DataSet with the query results. DataSet replaces the RecordSet from classic ADO. A DataSet is able to easily render its contents as XML and generate an XSD schema for the data. QueryAccess returns the query results as XML and returns the corresponding schema through the schema reference argument. The AutoComplete attribute is applied to QueryAccess, making it unnecessary to call SetComplete or SetAbort. If QueryAccess returns normally, SetComplete is called automatically. SetAbort is called if an exception is thrown.
The commands in Listing C compile and deploy the C# implementation of FirewallLog. FirewallLog.cs is compiled with references to the Enterprise Service and Data assemblies. The Visual Basic version requires additional references to System.dll and System.Xml.dll. The .NET Framework SDK utility regsvcs generates a COM type library for the assembly and registers it in the COM+ catalog, creating a new COM+ application. The name, ID, and activation type for the COM+ application are obtained from DLL via reflection, as are the application components and their configuration. Finally, the .NET Framework SDK utility gacutil installs FirewallLog.dll in the GAC.
Note that you need Administrator privileges to both create COM+ applications and to add assemblies to the GAC. After deployment, the FirewallLog application should be visible in the Component Services explorer, as shown in Figure B.
|Component Services window showing newly created service|
Using the FirewallLog component from a client application is straightforward. You simply instantiate the component and call methods. Listing D shows a simple FirewallLog client application, together with its output when run against an empty database table.
Getting more out of .NET enterprise services
Now you don’t have to give up that COM+ functionality you've gotten to know and love. By following the examples here, you can see that these services can still be a part of your .NET application.