From the discussion in the previous sections, you know that it is easy to create a serviced componentall you need to do is to inherit from the ServicedComponent class and apply attributes to use various COM+ component services. However, to use the COM+ component services, the component must be registered into the COM+ catalog. The following steps are typically involved in creating and registering a serviced component.
Create a class that inherits from the ServicedComponent class.
Compile the class and assign a strong name to create a strongly named assembly.
Install the strongly named assembly in the Global Assembly Cache (GAC).
Run the Services Installation Tool ( regsvcs.exe ) to install the assembly into the COM+ catalog.
After you have installed the serviced component in the COM+ catalog, the component can be used by
Application Programs To create instances of the serviced components and execute methods on it
System Administrators To configure various attributes of the serviced component using the Component Services administrative tool
In the following subsections, you will walk through the complete process of creating and consuming serviced components.
In this section, you will learn how to create a simple serviced component. You will also learn how to set various assembly-level attributes to specify the COM+ application name, description, and the activation type.
STEP BY STEP
7.1 Creating a Serviced Component
Step By Step 7.1 defines a class NorthwindSC that is a serviced component because the class derives from ServicedComponent . Using an assembly level attribute, I have also specified that NorthwindSC should be activated in Library mode. The Library activation mode is the default; I have used it anyway to be explicit.
Recall from the previous sections that a serviced component must reside in a class library (DLL file). Therefore, I chose to create a class library project in Step By Step 7.1. A class library project can contain multiple class files. Therefore, Visual Studio .NET uses a separate file named AssemblyInfo.vb to store assembly-level attributes. For a single-class library, you can write the assembly-level attributes directly in the class file if you want.
Every serviced component must be signed with a strong name before it can be registered into the COM+ catalog. A strong name guarantees a unique identity for an assembly by relying on a pair of public and private keys.
Strong names can be easily created using the Strong Name tool (sn.exe) that comes as a part of the .NET Framework SDK and stored in a file. In Step By Step 7.2, you first create a strong name and then modify the program from Step By Step 7.1 to associate the strong name with the assembly StepByStep7-1.dll . StepByStep7-1.dll needs to be regenerated because an already created assembly cannot be signed with a strong name. Signing with a strong name must be a part of the assembly creation process.
STEP BY STEP
7.2 Creating a Strongly Named Assembly
This key file created in Step By Step 7.2 is also called a strong name key file. You will be reusing the key generated in Step By Step 7.2 to sign assemblies in other examples in this book.
Protecting Your Identity When you start using a strong name key file to sign your components, the key pair stored in the strong name key files uniquely identifies your components from the components written by other vendors . Anyone who has access to the key pair can potentially sign malicious code on your behalf . To protect their identity, most software development teams use a slightly different process for signing assemblies. This process is called delay signing, a technique that I'll discuss in detail along with strong names in Chapter 10.
A serviced component must be registered in the COM+ catalog to use any of the COM+ component services. A serviced component must be signed with a strong name before it can be registered. The registration process involves retrieving all the necessary runtime information from the class library and copying it to the COM+ catalog. The runtime information is mostly specified in the form of attributes that are applied at the assembly level, class level, and method level in the class library.
Remember that the COM+ catalog came before the managed code; therefore, any managed component must appear as a COM component before it can be registered in the COM+ catalog. The .NET Framework Class Libraries (FCL) provides the EnterpriseServices.RegistrationHelper class to simplify the registration process. The RegistrationHelper class performs the following steps to register a serviced component:
Use the RegistrationServices.RegisterAssembly() method to register the assembly in the registry. For this reason, all classes in the assembly appear as COM components in the registry.
Generate a COM type library (a TLB file) from the assembly using the TypeLibConverter.ConvertAssemblyToTypeLib() method.
Register the type library by using the LoadTypeLibrary() method.
Use the COM+ admin API to configure COM+ based on the information stored in the type library.
Using the Registration helper class requires that you write a program for registering the serviced components. Alternatively, the .NET Framework provides two other methods for registering a component that do not require writing any registration code. Both of these methods however, internally use the RegistrationHelper class:
Administrative Access Is Required for Registration Installing a component to the COM+ catalog requires that a user or an application has administrative privileges.
Dynamic or Lazy Registration In dynamic registration, the registration of a serviced component is delayed until the component is first used. When a client application attempts to create an instance of a serviced component for the first time, the CLR registers the assembly and the type library and configures the COM+ catalog. Dynamic registration occurs only once for a particular version of an assembly.
Manual Registration The .NET Framework provides the .NET Services Installation Tool ( regsvcs.exe ) to manually install a serviced component from the command line.
Table 7.6 compares the advantages and disadvantages of the dynamic and manual registration process.
| || |
| || |
Use Manual Registration to Get Feedback on Registration Errors Unlike dynamic registration, the manual registration process that uses the regsvcs.exe tool provides feedback about the errors that were encountered during the registration process. In most cases, you might want to know the errors and correct them before any client actually accesses the serviced component.
Step By Step 7.3 shows how to register a serviced component manually using the .NET Framework Services Installation Tool ( regsvcs.exe ).
STEP BY STEP
7.3 Installing a Serviced Component in the COM+ Catalog
At this stage, the serviced component created in Step By Step 7.1 is installed in the COM+ catalog and can be instantiated by client programs or can be administered through the Component Services administrative tool.
When an application is registered into the COM+ catalog, the application can be easily configured by the system administrators using the Component Services administrative tool. In Step By Step 7.4, you will learn how to use this tool to configure COM+ applications.
STEP BY STEP
7.4 Managing the Serviced Component by Using the Component Services Administrative Tool
In Step By Step 7.4, you see various options to configure a COM+ application and the serviced components. At this time, you might not have a good idea about what each of these options means and how to configure them. Later in this chapter, when I discuss individual COM+ services, I will return to the relevant property pages and explain them in detail.
Configuration can also be performed at the level of methods. However, the methods of a serviced component are not directly accessible to the Component Services administrative tool. I discuss in the next section what you need to do in order to make the methods of a serviced component visible to the Component Services administrative tool as well as to other COM applications.
COM clients and COM+ services communicate with the .NET components using the interfaces provided by the .NET component. The CCW automatically generates these interfaces for a .NET class based on the setting of the ClassInterface and the InterfaceType attributes. Additionally, you can also write an interface explicitly and inherit the .NET class from that interface. These interfaces do not affect how a managed client calls another managed component (or a serviced component), but they surely affect how COM programs (or COM+) interact with the managed components. I discuss both of these attributes in this section.
The ClassInterface attribute specifies how the interfaces will be generated for a class (if they will be generated at all). This attribute can be applied either to a class or to an assembly. If you apply this attribute to an assembly, the attribute applies to all classes in that assembly.
The ClassInterface attribute can be set with any of the three values specified by the ClassInterfaceType enumeration, as shown in Table 7.7.
This is the default setting for the ClassInterface attribute. This setting automatically generates a dispatch-only interface for the class, which means that the class only supports late binding for COM clients. When using this setting, no type information is published to the COM type libraries.
This setting is called AutoDual because it does two things. First, it automatically creates an interface that exposes all the public members of a class (such as methods, properties, fields, and events) and the public methods, properties, and fields of the base class. Second, this setting generates dual interfaces. That means COM clients can use these interfaces for both late and early binding. When using this setting, all the type information is produced for the class interface and published in the type library. This is not a recommended setting and creates versioning problems.
This setting does not generate any automatic interfaces. In this case, you need to explicitly write interfaces for your class. This is the recommended setting for ClassInterface attribute.
Step By Step 7.5 shows how to use these attributes to generate different types of interfaces for a serviced component.
STEP BY STEP
7.5 Creating Interfaces That Are Visible to COM
In Step By Step 7.5, I demonstrated how to use the ClassInterface attribute with ClassInterfaceType set to AutoDual or None. You already know how the AutoDispatch option works because that's the default. If you don't apply the ClassInterface attribute at all, the ClassInterface attribute is, in fact, implicitly applied with the ClassInterfaceType set to AutoDispatch.
From Step By Step 7.4 and Step By Step 7.5, you can also see that with both the AutoDispatch and AutoDual settings, an interface is automatically generated. The name of the automatic interface is the name of the class preceded by an underscore. For example, the class NorthwindSC has the interface _NorthwindSC. However, when you set the ClassInterfaceType to None, no automatic interfaces are generated. In that case, you only get information about interfaces and methods written to the COM+ catalog if you explicitly define and then implement an interface.
You can see from Step By Step 7.5 that the AutoDual setting of the ClassInterface exports the type information and DispId of the class and base class members to the COM type library. This allows COM clients to bind their programs with the DispId of the members at compile time.
The DispId s are generated based on the position of the member in the class. This can cause versioning problems because if in the next version of your component, you change the order of the members and export the class to a COM type library, the DispId of the members will be changed. This will cause already compiled COM programs to fail. Because of this versioning issue, even if the AutoDual setting of the class interface looks flexible, it is not a recommended option.
The AutoDispatch setting, on the other hand, does not export the type information and DispId . Therefore, clients cannot bind to a particular DispId at compile time, and no problems occur when the order of methods are changed in the next version. However, because of its support for late binding only, AutoDispatch is normally limited to scripted or interpreted execution environments.
The last option, the None setting, prevents any automatic interface generation in the COM type library. If you want any interfaces to be written to the COM type library, you must explicitly define them. In this case, you get the benefit of both early and late binding.
However, writing your own interface separates the "view" of a class from its implementation. Even in the future, if you decide to change the order of the methods in your implementation, you are fine unless you modify the interface definition.
To summarize, the best practice is to mark the class with the ClassInterface attribute set to the ClassInterfaceType.None value and create your own interface explicitly. I will use this technique in the rest of the examples in this chapter.
The InterfaceType attribute can be applied to the interfaces that you declare. You can use this attribute to configure how these interfaces are exposed to the COM clients. The InterfaceType attribute can be set with any of the three values specified by the ComInterfaceType enumeration, as shown in Table 7.8.
This is the default setting for an interface. This value specifies that the interface should be exposed to COM as a dual interface. This allows COM clients to use either early or late binding.
This value specifies that the interface should be exposed to COM as a dispatch-only interface; that is, one supporting only late binding.
This value specifies that the interface should be exposed to COM as an IUnknown derived interface.
In most cases, you should leave the default value of the InterfaceType attribute unchanged.
Configuring COM Visibility By default, all public elements in your programs are visible to COM. You might want to hide certain elements from COM. You can mark those elements with the ComVisible attribute with its Value property set to False. These hidden members are not included in the COM type library, and the CCW rejects all requests to hidden elements from a COM client.
The ComVisible attribute can be set up in a hierarchy. That is, if you apply this attribute to an assembly and set its value to False, all the classes and members in the assembly are hidden from COM. However, if any class in the assembly overrides the ComVisible attribute by setting its value to True, that class will be visible to COM clients even though its parent is not.
COM+ uniquely identifies each COM+ application and its component using GUIDs. If you don't use GUIDs to identify the COM+ application and components, the assembly registration process will assign them automatically.
Each assembly is registered as a separate COM+ application; you can use the assembly level ApplicationID attribute to specify a GUID for the COM+ application. However, in most cases you might not want to specify a fixed ApplicationID because doing so prevents COM+ partitioning.
COM+ Partitions A COM+ partition is a logical container that allows multiple versions of COM+ applications to exist on a single computer. By default, all applications are installed in a partition named the Global Partition. However, administrators can define additional partitions to install the same or different versions of the application. Applications installed in different partitions can be configured and managed independently of each other. For more information on COM+ partitions, refer to the COM+ Platform SDK documentation in the MSDN Library (msdn.microsoft.com/library).
Each component in an assembly can be uniquely identified by applying the Guid attribute as in the following code:
<Guid("0F1FD944-ADDC-4d34-A4D0-90F9AA707930")> Public Class NorthwindSC Inherits ServicedComponent Implements INorthwind ...
You should pass a GUID to the constructor of the Guid attribute. A GUID can be generated using the command line GUID generation tool ( guidgen .exe ) or through the Tools, Create GUID menu option in Visual Studio .NET.
In Step By Step 7.5, you did not hard-code any GUID for the components; as a result, a GUID was automatically generated for the component by the registration process each time a different version of the component was registered into the COM+ catalog.
When you are developing, you might need to register the component several times in the COM+ catalog. In that case unless you hard-code a GUID for the component, you end up registering multiple versions of the same componenteach with a different GUIDin the COM+ catalog.
Now when you use the Component Services administrative tool, you see multiple instances of a component in the COM+ application. It's hard to determine which component you need to configure. In this case, a good idea is to hard-code each component with a GUID instead of depending on the automatically generated GUID.
Before client programs can invoke methods on a serviced component, the serviced component must be installed at a location where the client programs can reliably locate it. Serviced components are usually shared by several applications on a computer; therefore, a common practice is to install a serviced component in the Global Assembly Cache (GAC). The GAC is a machine-wide central repository for storing shared components.
Benefits of Installing a Component in the GAC Apart from acting as a central repository for storing shared components, the GAC provides several other benefits such as side-by-side versioning and file security. You will learn more about the GAC and its features in Chapter 10.
Installing the assembly in the GAC is not a requirement for using a serviced component. In fact, an application that uses the serviced component will work correctly as long as the CLR can load the corresponding assembly.
Installing the serviced component assembly in the GAC is the recommended option when you deploy an application because it ensures that the CLR is always able to locate an assembly. To learn more about the GAC and to learn how the runtime locates assemblies, refer to Chapter 10.
Strong Name Required for Installing a Component to the GAC An assembly must be signed with strong name before it can be installed in the GAC.
In Step By Step 7.6, you'll learn how to use the Global Assembly Cache tool ( gacutil.exe ) to install an assembly to the GAC and how to view the contents of the GAC.
Installing a Serviced Components for Use by Web Forms ASP.NET uses a technique called shadow copy to eliminate the need to stop the running Web application whenever any components of that application need to be updated or replaced. To participate in the shadow copy process, a component must be deployed in the bin directory of the virtual root of a Web application instead of in the GAC.
STEP BY STEP
7.6 Installing a Serviced Component in the GAC by Using the Global Assembly Cache Tool ( gacutil.exe )
Step By Step 7.6 uses the Global Assembly Cache tool to deploy an assembly to the GAC. This tool comes as part of the .NET Framework SDK.
Internally, the GAC is maintained using a bunch of directories nested within each other. To be able to view the contents of the GAC in a much more readable way, the .NET Framework installs the Assembly Cache Viewer ( shfusion.dll ) shell extension that integrates with the Windows shell and allows you to view contents of the GAC just like viewing files in a folder.
All components in an assembly share the same version. The version of an assembly can be specified using the assembly level AssemblyVersion attribute. When working with Visual Studio .NET, each time you modify and recompile a program, the version of the assembly is changed. This happens because the AssemblyVersion attribute is set to 1.0.* , which means that the major and minor version of the assembly are fixed to 1 and 0, respectively, but * in the third partthe build numberspecifies a number that changes automatically when the assembly is rebuilt.
A common destination for installing the serviced component is the GAC. The GAC is capable of installing different versions of an assembly side by side. While in development, if you install several builds of a component in the GAC, you can easily clutter up the GAC with several versions of the component when you are only interested in the most recent one.
For development, it might be good idea to fix the version number specified in the AssemblyVersion attribute so that the GAC always contains only the most recent version of the components. However, in a production environment, there might be cases in which you need to maintain different versions of a serviced component in the GAC.
Instantiating and using a serviced component is no different from any other managed component. In Step By Step 7.7, I create a Windows form application that instantiates the serviced component and calls methods on it. This application allows users to retrieve and update data from the SQL Server sample Northwind database.
STEP BY STEP
7.7 Using a Serviced Component
Although Step By Step 7.7 uses a Windows application to invoke a serviced component, you have other options too. For example, a distributed application can use a remoting server or a Web service to consume a serviced component.
GUIDED PRACTICE EXERCISE 7.1
You have just learned the basics of developing serviced components. You want to create a sample application that you will use to experiment with various COM+ services. You want to start with a serviced component similar to the one created in Step By Step 7.5.
You want to develop the application in a gradual manner. Each time you add a new feature, you want to test whether the feature is working and then proceed with adding a new feature. You are only interested in using the most recent version of a component. You do not want multiple copies of a component registered with the COM+ catalog and installed in the GAC.
How would you create such a serviced component?
This exercise helps you practice creating serviced components and controlling their versioning and identification.
You should try working through this problem on your own first. If you are stuck, or if you'd like to see one possible solution, follow these steps:
The serviced component with a fixed version and component identification information is now ready. When you rebuild and reregister this assembly in the COM+ catalog, you will still have just one entry for the component. Similarly, when you install multiple builds of this assembly to the GAC, only the most recent copy is present in the GAC at all times.
If you have difficulty following this exercise, review the sections "Creating a Serviced Component," "Component Identification," and "Component Versioning" earlier in this chapter. After doing that review, try this exercise again.