Creating a Serviced Component

Team-Fly    

 
.NET and COM Interoperability Handbook, The
By Alan Gordon
Table of Contents
Chapter Nine.  Using COM+(Enterprise Services)

Creating a Serviced Component

A managed class that uses the .NET Enterprise Services is called a serviced component. To create a serviced component, you need to perform the following steps:

  1. Derive your class either directly or indirectly from the System.EnterpriseServices.ServicedComponent base class.

  2. Add attributes to your classes, methods , and interfaces that specify the services that you want to use.

  3. Add assembly-level attributes that specify the type of COM+ application you would like to create: Library (in-process) or Server (out-of-process). You can also use assembly-level attributes to specify other things about your COM+ application, such as its name or GUID.

  4. Use the ContextUtil and SecurityCallContext classes to interact with your classes' context if necessary.

  5. Compile your classes into an assembly with a strong name.

  6. Use the .NET Services Installation Tool (regsvcs.exe) to register and build a COM+ application for your assembly. You do not have to do this step explicitly because the .NET Framework supports Lazy Registration, but, in most cases, it is a good idea.

  7. Insert your serviced component assembly into the GAC. This last step is optional for a serviced component hosted in a Library (in-process) COM+ application, but it is required for a serviced component that is hosted in a Server (out-of-process) COM+ application.

After you have performed all of these steps, you can call your serviced component from either a managed or an unmanaged client in the same way that you would call any other managed component. Let's examine each of these steps in greater detail.

The Serviced Component Base Class

The first step to creating a serviced component is to derive your class from the ServicedComponent class in the System.EnterpriseServices namespace as shown here:

 using System; using System.EnterpriseServices; namespace BusObjectLibrary {         public class Book :  ServicedComponent  {         // Implementation omitted...         } } 

In order to inherit from the ServicedComponent class, you will first need to reference the System.EnterpriseServices assembly. In Visual Studio .NET, you can do this by selecting Project Add Reference and then selecting System.EnterpriseServices from the .NET tab as shown in Figure 9-1.

Figure 9-1. Referencing the Enterprise Services assembly in Visual Studio .NET.

graphics/09fig01.jpg

You then need to add a using statement for the System.EnterpriseServices namespace as shown in the previous code listing.

The ServicedComponent class contains most of the logic needed to interact with the COM+ infrastructure. Table 9-1 lists the methods in the ServicedComponent class.

Table 9-1. The methods in the ServicedComponent class

Method Name

Accessibility

Description

Activate

Protected

This method is called by the runtime when an object is created or activated from the object pool. Override this method to provide custom initialization logic. This method is equivalent to the Activate method in the IObjectControl interface from MTS/COM+.

Deactivate

Protected

This method is called by the runtime when an object is destroyed or returned to the object pool. Override this method to provide custom finalization logic. This method is equivalent to the Deactivate method in the IObjectControl interface from MTS/COM+.

CanBePooled

Protected

Override this method to indicate whether an object can be used with the COM+ object pooling service. Return true if the object can be pooled and false if it cannot be pooled.

Construct

Protected

This method is called by the runtime to give an object an opportunity to cache a construction string that is passed to it. Override this method if your class uses construction strings.

Dispose

Public

Call this method to free the managed and unmanaged resources associated with a serviced component.

Dispose(Boolean)

Protected

Call this method with the parameter set to true to release both managed and unmanaged resources. Passing in false for the parameter will release only unmanaged resources. You do not need to call this method directly. Calling the Dispose method with no parameters will call this Dispose method with the parameter set to true. The finalize destructor for the ServicedComponent class will call this method with the parameter set to false because the CLR will already have reclaimed the managed memory.

Perhaps the most important thing that the ServicedComponent base class does is to endow a derived class with the correct context and marshaling behavior. In order for an object to reside in a COM+ application and use the COM+ services, it must be a marshal by reference object, and it must be context bound.

MARSHAL BY REFERENCE

A marshal by reference object communicates across application domain (and process) boundaries using a proxy to send messages. This is in contrast to marshal by value objects (which the .NET Framework also supports) where the entire object's state is passed across application domain and process boundaries. A serviced component must be marshal by reference because it must execute within a COM+ application. It would not make sense to marshal the object's state over to a client and have it execute there. The System.EnterpriseServices.ServicedComponent class derives (indirectly) from the System.MarshalByRefObject class, which enforces the correct marshal by reference behavior.

CONTEXT-BOUND OBJECTS

Understanding what a ContextBound object is a little more difficult. A context-bound object is an object that resides in a context. A context is a runtime environment in which one or more compatible COM+ objects in a particular process execute. Contexts are created during object activation. When you instantiate a context-bound object, the new object is placed into an existing context or into a new context that is created for it. Whether the object goes into a new context or an existing one depends on whether the runtime requirements of the object (as identified by the attributes included in the metadata of the type) are compatible with the context of its creator. Context-bound classes are marked with context attributes that identify the services the object requires from its runtime environment. These services include thread synchronization, thread affinity, transactions, and so forth. If they are compatible, the object shares the context of its creator, and the creator can make direct calls on the object, assuming it is in the same AppDomain as its creator (see Figure 9-2).

Figure 9-2. Two objects that share the same context.

graphics/09fig02.gif

If the object and its creator are incompatible, the runtime will place the object in a new context and set up a proxy between the object and its creator. The runtime also sets up the necessary interception logic to provide the services that the object needs (see Figure 9-3). The runtime determines what services the object needs by examining its metadata.

Figure 9-3. Two objects in different contexts.

graphics/09fig03.gif

Any code that is executing outside of the object's context must access the object through a proxy, which will call an interception layer on creation of the object, before and after each method call on the object, and when the object is destroyed. This interception layer is actually just a chain of objects that are called in succession whenever a piece of code in another context activates, makes a method call on, or destroys a context-bound object. These service-providing objects are sometimes called policy objects. Any managed class that derives from ContextBoundObject (as the ServicedComponent class does) will be bound to a context, and the CLR will ensure that the requirements of the classes' context (as identified by its context- related attributes) are enforced through interception. The CLR automatically places the object within a context, ensuring that only compatible objects share the same context, and sets up the proxy and interception layer.

Eventually, the .NET Framework will use this infrastructure to implement Enterprise Services entirely in managed code, but, for now, serviced components use the COM+ services, which are implemented in unmanaged code. At activation, instances of the ServicedComponent class insert a special object called a Serviced Component Proxy (SCP) into the chain of policy objects in the managed Interception layer. The serviced component class also sets up a COM+ context that reads the COM+ configuration of the object from the COM+ catalog. The SCP takes care of communicating with the COM+ context to provide the COM+ services that the serviced component requested . Figure 9-4 shows how this works for an in-process object, that is, an object that is configured to run in a Library COM+ Application.

Figure 9-4. Setting up the unmanaged context for an in-process object.

graphics/09fig04.gif

In this case, the client object and the context-bound object reside in different contexts within the same AppDomain. The client object will access the context-bound object using a context-aware proxy that will call into the managed interception layer. The policy objects for the serviced components in the Interception layer will call the SCP, which in turn will call the COM+ context to provide the COM+ services that the serviced component requested.

Figure 9-5 shows how this works for an out-of-process object, that is, an object that is configured to run in a Server COM+ application.

Figure 9-5. Setting up the unmanaged context for an out-of-process object.

graphics/09fig05.gif

The situation here is obviously far more complicated. When you activate an object that resides in a server COM+ application, the CLR will setup a Remote Serviced Component Proxy (RSCP) in the client process. When you make a method call, the RSCP will use a RCW to call a DCOM proxy for the context-bound object. This proxy will talk to a stub in the process that hosts the serviced component ( dllhost.exe ). The server process will use a CCW to invoke a managed proxy, which will call into the managed Interception layer. At the managed Interception layer, there will be an SCP for the serviced component that will call a COM+ context to provide COM+ services for the object.

Add Class, Interface, and Method Level Attributes

The next step to create a serviced component is to add class, interface, and method level attributes that specify the services that your component requires. You specify which services you want to use using the attributes in the System.EnterpriseServices namespace. Table 9-2 summarizes the available services and the attributes that relate to those services.

Table 9-2. The .NET Enterprise Services attributes

Attribute Name

Purpose

Applies to

Default Value When Not Applied

ApplicationName

Specifies the name of the COM+ application.

Assembly

The assembly name

ApplicationId

Specifies the COM+ application's GUID. The COM+ runtime generates a GUID for you if you don't specify a value for this attribute.

Assembly

A system-generated value

ApplicationActivation

Specifies whether the COM+ application will be a library application, which runs in its creator's process (ActivationOption.Library), or a server application, which runs in dllhost.exe (ActivationOption.Server).

Assembly

Library

(in-process)

ApplicationAccessControl

Specifies the security configuration for your COM+ application. You can configure whether access control is turned on. You can also specify the authentication and impersonation levels.

Assembly

Enabled=false

Description

Allows you to specify a description for your COM+ application that will appear in the Component Services explorer.

Assembly, Class, Interface, Method

 

Transaction

Specifies COM+ (distributed) transaction support. Possible values reside in the TransactionOption enumeration: Ignored, None, Required, RequiresNew, Supported.

Class

Disabled

Synchronization

Specifies COM+ concurrency support. Possible values reside in the SynchronizationOption enumeration: None, Required, RequiresNew, Supported.

Class

Disabled

JustInTimeActivation

Specifies JIT Activation support.

Class

False

ObjectPooling

Specifies support for object pooling. Properties on this attribute allow you to configure the minimum and maximum size of the pool and the creation timeout.

Class

Enabled=false

ConstructionEnabled

Specifies that a class supports construction strings. Use the default property of this attribute to specify an initial value for this construction string.

Class

Enabled=false

AutoComplete

Specifies that the transaction automatically calls SetComplete if the method call returns successfully. If the method call throws an exception, the transaction is aborted.

Method

False

MustRunInClientContext

Specifies that a class must run in the same context as its client.

Class

False

EventTrackingEnabled

Specifies that a class supports events and statistics.

Class

False

EventClass

Specifies that a class is a COM+ event class. Calls on the class are delivered to the classes event subscribers.

Class

False

LoadBalancingSupported

Specifies whether a component supports the Component Load Balancing service available in Microsoft Application Center Server.

Class

False

ComponentAccessControl

Specifies whether role-based security checks are performed at the component level.

Class

False

ApplicationQueuing

Enables queuing support for a COM+ application.

Assembly

Enabled=false

InterfaceQueuing

Specifies that calls on this interface will be queued using message queuing.

Interface, Class

Enabled = false

ExceptionClass

For queued components, allows you to specify a class that will receive exceptions.

Class

None

PrivateComponent

Specifies that a class can only be used by components in the same COM+ application.

Class

N/A If this attribute is not used, the class is public.

SecurityRole

Adds a role to a COM+ application and specifies that a class requires users to be in the role.

Assembly, Class, Interface, Method

No default roles are added.

SecureMethod

Specifies that role-based security will be used on an assembly, class or method and that you will configure role-based security administratively.

Assembly, Class, Method

 

All of the attributes listed in Table 9-2 relate to a preexisting COM+ feature. These attributes are just a way to make these services available in a convenient way to allow managed components to use these services. Column 3 of Table 9-2 lists which entity (assembly, class, interface, or method) the attribute can apply to. In most cases, you will not apply all of these attributes to your components, so column 4 of Table 9-2 lists the value that your assembly, class, or method will assume for that attribute if you do not use the attribute.

The following code shows an example of a class that uses some of the most commonly used of these attributes:

 1.  using System; 2.  using System.Messaging; 3.  using System.Data; 4.  using System.Data.SqlClient; 5.  using System.EnterpriseServices;  6.  namespace BusObjectLibrary 7.  {  8.      [ConstructionEnabled(true,Default=   9.        "user id=sa;password=;initial   10.       catalog=DevDotNet;data source=localhost")]   11.     [Transaction(TransactionOption.Required,   12.       Isolation=   13.        TransactionIsolationLevel.RepeatableRead,   14.        Timeout=45)]   15.      [ObjectPooling(Enabled=true,MinPoolSize=2,   16.        MaxPoolSize=100,   17.        CreationTimeout=3000)]  18.      public class Book :  ServicedComponent  19.      { 20.        private string mConnString; 21.        private const string 22.           m_strQueuePath=".\private$\devdotnet"; 23.        public Book() 24.        { 25.        } 26.        protected override void Construct(string s) 27.        { 28.          mConnString=s; 29.        } 30.  [AutoComplete]  31         public DataSet GetBooksByTitle(32.          string title) 33.        { 34.        // Implementation omitted... 35.        } 36.  [AutoComplete]  37.        public DataSet GetBooksByISBN(string isbn) 38.        { 39.        // Implementation omitted... 40.        } 41.  [AutoComplete]  42.        public int InsertOrder(DataSet dsOrderInfo) 43.        { 44.        // Implementation omitted... 45.        } 46.      } 47.  } 

Lines 8 through 17 use the attributes in the System.EnterpriseServices namespace to specify which of the COM+ services you would like the class to use. In this case, I am using construction strings, transactions, and object pooling. The construction string is configured on lines 8 through 10 and is used to specify the database connection string for the object. Lines 26 through 29 override the Construct method in the System.EnterpriseServices class to receive the construction string. You then cache the string that you receive in a private variable called mConnString that I declared on line 20. Lines 11 through 14 use the Transaction attribute to specify that all method calls on instances of this class will execute within a distributed transaction. I use the TransactionIsolation and Timeout properties of the Transaction attribute to specify that the transaction for the class will have the Repeatable Read isolation level and a timeout of 45 seconds. These two Transaction properties are new features of COM+ 1.5 and are only supported on the Windows XP family of operating systems. If you use these properties on Windows 2000, it will cause an error when you attempt to register the component. On Windows 2000, you should replace lines 11 through 14 with the following:

 [Transaction(TransactionOption.Required)] 

The Transaction attribute has an implicit dependency on the Synchronization and JustInTimeActivation attributes. In order to use automatic DTC transactions, an object must also be configured with the Synchronization attribute set to Required (if the Transaction attribute is set to Required or Supported) or Requires New (if the Transaction attribute is set to Requires New). The JustInTimeActivation attribute must also be set to True whenever you use transactions. If you set the Transaction attribute to Supported, Requires, or Requires New and you do not set the Synchronization and JustInTimeActivation attributes appropriately, the system will just ignore your Synchronization and JustInTimeActivation settings and set them to the appropriate value required by the Transaction attribute. In other words, the following attribute settings

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

are equivalent to

 [Transaction(TransactionOption.Required)] [Synchronization(SynchronizationOption.Required)] [JustInTimeActivation(true)] public class Book : ServicedComponent { } 

If you tried to configure the following settings

 [Transaction(TransactionOption.Required)] [Synchronization(SynchronizationOption.Disabled)] [JustInTimeActivation(false)] public class Book : ServicedComponent { } 

the .NET Services Installation Tool (regsvcs.exe) will simply ignore your settings for the Synchronization and JustInTimeActivation attributes and configure these attributes as shown here:

 [Transaction(TransactionOption.Required)] [Synchronization(SynchronizationOption.Required)] [JustInTimeActivation(true)] public class Book : ServicedComponent { } 

I did find one exception to this rule. If you attempt to set both the Transaction and Synchronization attributes to Supported, regsvcs.exe will fail and report the following error:

[View full width]
 
[View full width]
The following installation error occurred: 1: Errors occurred while configuring components. BusObjectLibrary.Book.Synchronization: One or more property settings are either graphics/ccc.gif invalid or in conflict with each other

Lines 15 through 17 in the previous code listing use the ObjectPooling attribute to specify that the class will use the object pooling service provided by COM+. I use the MinPoolSize property to specify the number of objects that the system should create initially and the MaxPoolSize property to specify the maximum number of objects that are allowed to exist before the system starts queuing further activation requests . Lines 30, 36, and 41 use the AutoComplete attribute on each method in the book class. This specifies that the object should call SetComplete automatically to commit the object's transaction if the method returns without error and SetAbort to roll back the transaction if the methods raises an error. If you register the class using the NET Services Installation Tool (I talk about this shortly), you will receive the following warning:

[View full width]
 
[View full width]
WARNING: The class 'BusObjectLibrary.Book' has no class interface, which means that graphics/ccc.gif unmanaged late bound calls cannot take advantage of AutoComplete methods.

The problem is that the AutoComplete attribute is a method-level attribute, but the Book class does not currently expose any early-bound methods. If you remember from Chapters 6 and 8, a managed object exposes a COM class interface to unmanaged consumers. By default, this interface is a late-bound interface. You can probably understand this better by looking at the Book class in the COM+ Component Services explorer after you have registered our assembly. Figure 9-6 shows the interfaces that the class exposes to COM consumers, after it is registered.

Figure 9-6. The interfaces that the Book class exposes.

graphics/09fig06.jpg

Notice that, in addition to the class interface for the managed Book class (_Book), there is a _Object interface, which explicitly exposes all the methods found in the System.Object class. The serviced component also exposes IDisposable, IManagedObject, and IServicedComponentInfo interfaces, but the key point is that the _Book interface contains no methods.

The end result of all of this is that the Book class, as written, is unsuitable for use with an unmanaged client. The AutoComplete attribute will not work when the Book class is called from an unmanaged client, and I have not called SetComplete or SetAbort explicitly in the Book class' methods. A transactional managed object will not commit (or roll back) its transaction or free its resources until the object calls SetComplete or SetAbort either explicitly or by using the COM+ AutoComplete feature.

Remember that this is only a problem if you attempt to call the serviced component from an unmanaged client. The AutoComplete attribute does work correctly with a managed client.

You can make the Book class work with an unmanaged client in one of two ways. You can use the System.Runtime.InteropServices.ClassInterface attribute to create a dual interface for the Book , or you can declare and implement a managed interface that contains the class' methods.

Note

You could also call the SetComplete/SetAbort methods on the System.EnterpriseServices. ContextUtil class explicitly instead of using the AutoComplete attribute, but I would be jumping ahead if I discussed this here. I will discuss this in the next section.


To use the first approach, change the definition of the Book class to look as follows :

 1.  using System; 2.  using System.Messaging; 3.  using System.Data; 4.  using System.Data.SqlClient; 5.  using System.EnterpriseServices; 6.  using System.Runtime.InteropServices; 7.  namespace BusObjectLibrary 8.  { 9.      [ConstructionEnabled(true,Default= 10.      "user id=sa;password=;initial catalog=DevDotNet; 11.      data source=localhost")] 12.     [Transaction(TransactionOption.Required, 13.        Isolation= 14.        TransactionIsolationLevel.RepeatableRead, 15.        Timeout=45)] 16.      [ObjectPooling(Enabled=true,MinPoolSize=2, 17.       MaxPoolSize=100, 18.       CreationTimeout=3000)] 19.     [ClassInterface(ClassInterfaceType.AutoDual)] 20.     public class Book : ServicedComponent 21.     { 22.       private string mConnString; 23.       private const string m_strQueuePath= 24.          ".\private$\devdotnet"; 25.       public Book() 26.       { 27. 28.       } 29.       protected override void Construct(string s) 30.       { 31.          mConnString=s; 32.       } 33.       [AutoComplete] 34.            public DataSet GetBooksByTitle(35.                 string title) 36.       { 37.         // Implementation omitted... 38.       } 39.       [AutoComplete] 40.       public DataSet GetBooksByISBN(string isbn) 41.       { 42.         // Implementation omitted... 43.       } 44.       [AutoComplete] 45.       public int InsertOrder(DataSet dsOrderInfo) 46.       { 47.         // Implementation omitted... 48.    } 49.   } 50. } 

As I mentioned in Chapter 8, using the AutoDual class interface setting is problematic because managed class signatures are not subject to the same immutability rules as COM interfaces. If you expose an AutoDual class interface from a managed object, and a COM client early binds to it, and then you change the interface, the COM client may break. A better approach is to define a managed interface explicitly as follows. This will allow the AutoComplete attribute to work, as well as provide the most stable and easy-to-use interface for unmanaged clients .

 1.       public interface IBook 2.       { 3.         DataSet GetBooksByTitle(string title); 4.         DataSet GetBooksByISBN(string isbn); 5.         int InsertOrder(DataSet dsOrderInfo); 6.       } 7.       [ConstructionEnabled(true,Default= 8.         "user id=sa;password=; 9.           initial catalog=DevDotNet; 10.        data source=localhost")] 11.      [Transaction(TransactionOption.Required, 12.        Isolation= 13.        TransactionIsolationLevel.RepeatableRead, 14.        Timeout=45)] 15.       [ObjectPooling(Enabled=true,MinPoolSize=2, 16.         MaxPoolSize=100, 17.         CreationTimeout=3000)] 18.      public class Book : ServicedComponent,  IBook  19.      { 20.        private string mConnString; 21.        private const string m_strQueuePath= 22.                 ".\private$\devdotnet"; 23.        public Book() 24.        { 25.        } 26.        protected override void Construct(string s) 27.        { 28.          mConnString=s; 29.        } 30.        [AutoComplete] 31.        public DataSet GetBooksByTitle(32.                string title) 33.        { 34.          // Implementation omitted... 35.        } 36.        [AutoComplete] 37.        public DataSet GetBooksByISBN(string isbn) 38.        { 39.          // Implementation omitted... 40.        } 41.        [AutoComplete] 42.        public int InsertOrder(DataSet dsOrderInfo) 43.        { 44.          // Implementation omitted... 45.        } 46.      } 

Lines 1 through 6 define an interface that contains the GetBooksByTitle, GetBooksByISBN, and InsertOrder methods from the Book class. I then implement the IBook interface on the Book class on line 18. You can then use the AutoComplete attribute on the three methods that you are implementing from the IBook interface. As long as an unmanaged client uses the IBook interface, the AutoComplete attribute will work as advertised.

Add Assembly-Level Attributes

You can compile the Book class into an assembly without applying any of the assembly-level attributes listed in Table 9-2. You can then register the assembly using the .NET Services Installation Tool (regsvcs.exe). Regsvcs.exe will create a COM+ Library application and install the class into the application. By default, it will give the COM+ application the same name as the assembly. It will generate a GUID for the application and configure all of the assembly-level attributes with their defaults as outlined in Table 9-2. In most cases, though, you will want to exercise more control over the properties of the COM+ application that regsvcs.exe will create. The assembly-level attributes in Table 9-2 provide this control. For instance, the ApplicationName attribute allows you to control the name that regsvcs.exe will assign to the COM+ application. The ApplicationActivation attribute allows you to control whether your serviced components will be hosted in a Library (in-process) or Server (out-of-process) COM+ application. The following code demonstrates how to use the ApplicationName and ApplicationActivation attributes.

 [assembly: ApplicationName("DemoServicedComponent")] [assembly: ApplicationActivation(ActivationOption.Server)] 

If you add this code to the AssemblyInfo.cs file that Visual Studio .NET creates for your project, your COM+ application will have the name DemoServicedComponent, and it will be hosted in a COM+ Server application In order to use these attributes in your assembly info file, you will need to add a using statement for System.EnterpriseServices to the top of the assembly info file as shown here:

 using System.EnterpriseServices; 

The following code shows another example of assembly-level enterprise services attributes. Here I am using the ApplicationAccessControl and SecurityRole attributes to configure COM+ role-based security.

 [assembly: ApplicationActivation(ActivationOption.Server)] [assembly: ApplicationAccessControl(true, AccessChecksLevel= AccessChecksLevelOption.ApplicationComponent,          Authentication=AuthenticationOption.Privacy,          ImpersonationLevel= ImpersonationLevelOption.Impersonate)] [assembly: SecurityRole("Manager")] [assembly: SecurityRole("Customer",true)] 

Once again, you should add these attributes to your assembly info file. Setting these security settings will configure security for the component, as shown in Figure 9-7.

Figure 9-7. The COM+ application configured using the settings shown in the previous code.

graphics/09fig07.jpg

In this case, I am specifying that the COM+ application should be an out-of-process (Server) application, and I also turn on Access Control at the component level and use the Privacy authentication level, which will encrypt communications between the client and the server. I also have added two rolesManager and Customerto the application. In the case of the Customer role, the second parameter (true) indicates that regsvcs.exe should add the Identity called Everyone to the customer role. In order to use these two roles, you have to attach them to a class, interface, or method. This indicates that membership in the role is required to use that class, interface, or method. For instance, if we attach the Customer role to the GetBooksByTitle and GetBooksByISBN methods and the Manager role to the InsertOrder method as follows

 public interface IBook {   DataSet GetBooksByTitle(string title);   DataSet GetBooksByISBN(string isbn);   int InsertOrder(DataSet dsOrderInfo); } // Other attributes omitted... [ComponentAccessControl(true)] public class Book : ServicedComponent, IBook {   [SecurityRole("Customer")]   [AutoComplete]   public DataSet GetBooksByTitle(string title)   {   //...   }   [SecurityRole("Customer")]   [AutoComplete]   public DataSet GetBooksByISBN(string isbn)   {   //...   }   [SecurityRole("Manager")]   [AutoComplete]   public int InsertOrder(DataSet dsOrderInfo)   {   //...   } } 

a user must be in the Customer role in order to call the GetBooksByTitle or GetBooksByISBN methods, and the user must be in the Manager role in order to call the InsertOrder method. If you assign a role to a method, regsvcs.exe will add an additional role called Marshaler to your COM+ application. Users must be added administratively to this role in order to call the methods.

Use the ContextUtil and SecurityCallContext Classes

In many cases, a serviced component will need to interact directly with its context. An object's context is divided into two objects that are accessed separately: a method-invariant context called an Object Context and a per-method context called a security call context. An object may need to call its object context directly to commit or abort a transaction (or at least to indicate that it is voting to commit or abort the transaction), to check if the current method is executing within a transaction, or to check if the caller is in a particular role. An object may need to access its call context to get the identity of the method's caller, to determine if security is enabled, or to determine the minimum authentication level in the current call chain. A serviced component can interact with its object context using the System.EnterpriseServices.ContextUtil class, and it can interact with its security call context using the System.EnterpriseServices.SecurityCallContext class. Table 9-3 lists the methods of the ContextUtil class.

Table 9-3. The methods of the ContextUtil class

Method Name

Description

SetComplete

Sets the transaction vote bit (also called the consistent bit) to commit and the done bit to true. An object calls this method to indicate that is has completed its work and is voting to commit the transaction. The DTC will commit the transaction as long as all of the other participants in the transaction also vote to commit.

SetAbort

Sets the transaction vote flag to abort and the done bit to true. An object calls this method to indicate that is has completed its work and is voting to roll back the transaction. The DTC will roll back this transaction regardless of how the other participants vote.

EnableCommit

Sets the transaction vote bit (also called the consistent bit) to commit and the done bit to false. An object calls this method to indicate that it is not done with its work, but, if the object is deactivated, the DTC can commit the transaction.

EnableCommit

Sets the transaction vote bit (also called the consistent bit) to abort and the done bit to false. An object calls this method to indicate that it is not done with its work, and, if the object is deactivated, the DTC should roll back the transaction.

IsCallerInRole

Is called to test if the caller is in a specified role.

GetNamedProperty

Returns a named property of the context.

Table 9-4 lists the properties of the ContextUtil class.

Table 9-4. The properties of the ContextUtil class

Property Name

Description

ActivityId

Returns the GUID of the activity of the current context or GUID_NULL if the context is not part of an activity.

ApplicationId

Returns the GUID of the object's COM+ application.

ApplicationInstanceId

Returns the GUID associated with the current instance of the object's COM+ application.

ContextId

Returns the GUID of the current context.

DeactivateOnReturn

Gets or sets the done bit of the current context.

IsInTransaction

Returns true if the current context is transaction and false otherwise .

IsSecurityEnabled

Returns true if role-based security is enabled.

MyTransactionVote

Gets or sets the transaction vote (consistent) bit in the COM+ context.

PartitionId

Returns the GUID of the current partition. Partitions are a COM+ 1.5 feature that is only supported on Windows .NET Server and the Windows XP family of operating systems.

Transaction

Returns an ITransaction interface that represents the current DTC transaction.

TransactionId

Returns the GUID of the current DTC transaction.

All of the methods and properties on the ContextUtil class are static. You do not need to create an instance of this class. The following code shows how you might use the ContextUtil class in a method of a serviced component:

 1.  public DataSet GetBooksByTitle(string title) 2.  { 3.      DataSet ds; 4.      try 5.      { 6.        if (!  ContextUtil.IsCallerInRole("Customer")  ) 7.          throw new UnauthorizedAccessException(8.            "Caller must be in the Customer role"); 9.        if (  ContextUtil.IsInTransaction  ) 10.       { 11.       // Log the transaction ID 12.           System.Diagnostics.Trace.WriteLine(13.              "Transaction ID=" + 14.  ContextUtil.TransactionId.ToString  ()); 15.       } 16.        StoredProcParam[] spParameters=new 17.          StoredProcParam[1]; 18.        spParameters[0]=new StoredProcParam(19.           "@Title",SqlDbType.NVarChar,255,title); 20.        ds=GetDataSetFromStoredProc(21.          "GetBooksByTitle",spParameters,"Titles"); 22.  ContextUtil.SetComplete()  ; 23.      } 24.      catch (Exception) 25.      { 26.  ContextUtil.SetAbort();  27.        throw; 28.      } 29.      return ds; 30.  } 

Line 6 calls the IsCallerInRole method to programmatically check if the caller is the customer role. Line 9 checks if the method is executing within a transaction. If it is, you log the transaction ID on lines 12 through 14. Lines 16 through 21 execute the business logic of this method. Don't worry if you don't understand this code; I will explain it shortly. Line 22 calls the SetComplete method on the ContextUtil class to indicate that you are voting to commit the transaction. If any of the logic in this method throws an exception, it will jump to the exception handler on lines 24 through 28, which will abort the transaction and rethrow the error.

You use the SecurityCallContext to access security information related to a particular call on an object. Just like the ContextUtil class, all the methods and properties of the SecurityCallContext class are static. Table 9-5 lists the methods of the SecurityCallContext class.

Table 9-5. The methods of the SecurityCallContext class

Method Name

Description

IsCallerInRole

Tests if the direct caller is a member of the specified role.

IsUserInRole

Tests if the specified user is a member of the specified role.

Table 9-6 lists the properties of the SecurityCallContext class.

Table 9-6. The properties of the SecurityCallContext class

Property Name

Description

Callers

Gets a SecurityCallers object that describes the callers of a method.

CurrentCall

Gets a SecurityCallContext object that describes the current method call.

DirectCaller

Gets a SecurityIdentity object that describes the direct caller of the current method.

IsSecurityEnabled

Returns true if role-based security is enabled in the current context.

MinAuthenticationLevel

Returns the lowest authentication level of all callers in the current call chain.

NumCallers

Returns the number of callers in the current chain of calls.

OriginalCaller

Gets a SecurityIdentity object that describes the caller that initiated the current call chain.

The following code demonstrates how you to use the SecurityCallContext class.

 1.  [AutoComplete] 2.  public DataSet GetBooksByISBN(string isbn) 3.  { 4.      DataSet ds; 5.  string callerName=  6.        SecurityCallContext.CurrentCall.  7.        DirectCaller.AccountName  ; 8.      System.Diagnostics.Trace.WriteLine(9.        "Caller=" + callerName); 10.     if (  SecurityCallContext.CurrentCall.IsSecurityEnabled  ) 11.     System.Diagnostics.Trace.WriteLine(12.       "Authentication level=" + 13.  SecurityCallContext.CurrentCall.   14.     MinAuthenticationLevel.ToString()  ); 15.     StoredProcParam[] spParameters=new StoredProcParam[1]; 16.     spParameters[0]=new StoredProcParam(17.       "@ISBN",SqlDbType.NVarChar,13,isbn); 18.     ds=GetDataSetFromStoredProc(19.       "GetBooksByISBN",spParameters,"Titles"); 20.     return ds; 21.  } 

Lines 5 through 7 get the AccountName of the current caller. Line 10 checks if security is enabled, and, if it is, you write the minimum authentication level of the current call chain to the trace log on lines 13 and 14.

Compile Your Classes into an Assembly with a Strong Name

An assembly that contains serviced components must have a strong name. There are 3 reasons for this. (1) If an assembly does not have a strong name, the GUIDs that the .NET Services Installation Tool (regsvcs.exe) generates to expose the types in the assembly through COM/COM+ are not guaranteed to be unique. Because of this, regsvcs.exe will throw an error if you attempt to register a component that is not signed. (2) Another more subtle reason for requiring a strong name for serviced components is that even if you made a serviced component's assembly private, that is, each client had its own copy of the assembly beneath its directory tree, the COM+ configuration for each of these assemblies is shared. Thus, one application's version of the assembly can affect another application's version. It is best then to go ahead and make the assembly shared and place it in the GAC. If we want to put an assembly in the GAC it must have a strong name. (3) If the serviced component is hosted in a server (out-of-process) COM+ application, you cannot put the assembly underneath the directory tree of its client application. With a server application, the assembly will actually run within the dllhost process, which resides in your System32 directory. In order for dllhost to be able to load the assembly, you have to either place the assembly in the GAC or put it in the same directory as the dllhost executable. Putting application code in the System32 directory is obviously a bad idea. It makes more sense to just put the assembly in the GAC and hence you need a strong name.

Register Your Assembly Using Regsvcs.exe

You have already learned that, in order to use a .NET assembly from a COM client, you must create a type library and add the necessary entries to the registry to support the COM activation functions like CoCreateInstance. You need to perform these steps for a serviced component even if it will only be called by managed clients because the COM+ runtime only knows how to work with COM objects.

Note

One positive side benefit of the need to create a type library and add registry entries for all serviced components is that serviced components are immediately callable from a COM client with no additional work.


In addition to registering the serviced component as a COM component, you must also create a COM+ application for the serviced component if one does not exist already. You must also add the serviced components to the COM+ applications and configure the components according to their attributes.

The .NET Framework SDK contains a tool called the .NET Services Installation Tool (regsvcs.exe) that performs all of these tasks . Execute the following command at a Visual Studio .NET command prompt to use regsvcs.exe:

 regsvcs assemblyname.dll 

Because the .NET Services Installation Tool accesses the COM+ catalog, you will need to have admin rights in order to run this command. Regsvcs.exe will perform the following steps:

  1. Register the assembly in the registry, generating GUIDs as appropriate and adding an InprocServer32 entry that points to mscoree.dll .

  2. Generate a COM type library for the assembly.

  3. Register the type library.

  4. Create a COM+ application for the component if one does not exist already using the assembly-level attributes in the serviced component's assembly.

  5. Add each ServicedComponent derived class to the COM+ application and configure the class according to the class, interface, and method level attributes associated with each class.

Regsvcs will perform all of these updates within a transaction using the RegistrationHelperTx class. If any of the aforementioned steps fail, it will roll back the state of the registry and the COM+ catalog. You need to remove the type library manually, however. You can unregister a serviced component's assembly using the /u parameter to regsvcs.exe as shown here:

 regsvcs /u assemblyname.dll 

This command removes the COM+ application and the registry entries that regsvcs created for the assembly. You need to remove the assembly from the GAC manually if you added it. The full list of possible command-line arguments for regsvcs.exe is shown in Table 9-7.

Table 9-7. Command-line arguments for regsvcs.exe

Argument

Description

/ appname :ApplicationName

Specifies the name of the COM+ application to either find or create.

/c

Creates the target COM+ application. This command will fail if the application already exists.

/componly

Configures classes only. This argument will cause regsvcs.exe to ignore methods and interfaces.

/exapp

Modifies an existing COM+ application. This command will fail if the application does not exist.

/extlb

Uses an existing type library.

/fc

Uses an existing COM+ application if one exists already. Creates a new application if one does not exist.

/help

Displays help information for regsvcs.exe.

/noreconfig

If the COM+ application exists already, it will not alter the current settings even if the settings in the assembly are different than those currently configured in the assembly. (I could not get this setting to work for me as advertised. Regsvcs.exe would always behaved as though I was using the reconfig setting.)

/nologo

Suppresses the Microsoft logo when the tool runs.

/parname:Name

Specifies the name or ID of the COM+ application to either find or create.

/reconfig

If the COM+ application exists already and its settings are different than those in the assembly, it will alter the configuration of the COM+ application to match the attributes specified in the assembly (default).

/tlb:TypeLibraryFile

Specifies the type library to install.

/u

Uninstalls the target COM+ application.

/quiet

Suppresses both the Microsoft logo and the success message.

/?

Displays help information for regsvcs.exe

You can also install serviced components programmatically using the System.EnterpriseServices.RegistrationHelper class. This class has two methods: InstallAssembly and UninstallAssembly. The InstallAssembly method has the following prototype:

 public void InstallAssembly(string assembly,    ref string application,    ref string tlb,    InstallationFlags installFlags); 

The assembly argument is the name of the assembly that you want to register, application is the name of the COM+ application that you want to create, tlb is the name of the type library that the tool will output, and installFlags allows you to control the installation process by passing a bitwise combination of values from the System.EnterpriseServices.InstallationFlags enumeration. This enumeration contains the values shown in Table 9-8.

Table 9-8. Values for the InstallationFlags enumeration

Enumeration Value

Description

ConfigureComponentsOnly

Configures classes only. This argument will cause regsvcs.exe to ignore methods and interfaces.

CreateTargetApplication

Creates the target COM+ application. This command will fail if the application already exists.

Default

Does the default installation, which does the configure, install, and register steps, and assumes that the application already exists.

ExpectExistingTypeLib

Modifies an existing COM+ application. This command will fail if the application does not exist.

FindOrCreateTargetApplication

Uses an existing COM+ application if one exists already. Creates a new application if one does not exist.

ReconfigureExistingApplication

If using an existing application, ensures that the properties on this application match those in the assembly.

ReportWarningsToConsole

Writes warnings and alerts to the console.

The values in this enumeration correspond almost exactly to the command-line arguments for the regsvcs.exe tool.

There is also a second version of this method InstallAssembly method that has the following prototype:

 public void InstallAssembly(string assembly,    ref string application,    string partition,    ref string tlb,    InstallationFlags installFlags); 

This method adds an additional string parameter called partition, which allows you to specify the partition of your COM+ application.

Note

Partitions are a COM+ 1.5 feature. See my article in the September 2000 edition of Visual C++ Developers Journal .


The UninstallAssembly method in the RegistrationHelper class has the following two overloads:

 public void UninstallAssembly(string assembly,    string application); public void UninstallAssembly(string assembly,    string application,    string partition); 

The first version of the method has two arguments, assembly and application, that allow you to specify the assembly name and the COM+ application name for the serviced component assembly that you want to remove. The second version also allows you to specify a partition.

In most cases, you will want to use the regsvcs.exe tool or the RegistrationHelper class to register your serviced component explicitly, but the .NET Framework also supports lazy registration. With lazy registration, the CLR (actually it's the Interception layer doing the work at Activation) will create the COM+ application, register the assembly, generate and register the type library, and configure the classes in the assembly (in other words, perform all the steps that regsvcs does) automatically the first time a client attempts to use a serviced component. Lazy registration has two major problems, however: (1) It does not work for server (out-of-process) COM+ applications, and (2) you must have admin privileges to edit the COM+ catalog. Therefore, lazy registration will fail if the user who first activates a serviced component is not an administrator. All things considered , you're better off running a script or batch file to configure your serviced components either using regsvcs.exe or the RegistrationHelper class.

Insert Your Assembly into the GAC

The final step to create a serviced component is to install the component's assembly into the GAC. Strictly speaking, this is not a requirement if you are using a Library (in-process) COM+ application. If you are using a library application, you can deploy the assembly that contains your serviced component into the same directory as the client, and everything will work. Remember, however, if you have two clients that use the same assembly, they will share the same COM+ configuration settings even if the each client has its own private copy of the assembly. Moreover, an assembly that contains serviced components in a server COM+ application must be installed into the GAC so that the dllhost process can load the assembly. You can install a serviced component into the GAC the same way that you would install any managed component into the GAC using gacutil.exe.


Team-Fly    
Top
 


. Net and COM Interoperability Handbook
The .NET and COM Interoperability Handbook (Integrated .Net)
ISBN: 013046130X
EAN: 2147483647
Year: 2002
Pages: 119
Authors: Alan Gordon

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net