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:
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 ClassThe 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.
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
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 REFERENCEA 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 OBJECTSUnderstanding 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.
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.
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.
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.
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 AttributesThe 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
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]
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]
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.
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 AttributesYou 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.
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 ClassesIn 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
Table 9-4 lists the properties of the ContextUtil class. Table 9-4. The properties of the ContextUtil class
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
Table 9-6 lists the properties of the SecurityCallContext class. Table 9-6. The properties of the SecurityCallContext class
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 NameAn 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.exeYou 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:
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
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
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 GACThe 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 |