- Introduction
- Creating a Sample COM+ Service
- Attaching to the Host Application
- Forcing Cleanup
- Summary
Creating a Sample COM+ Service
A hosted assembly is a .NET assembly that runs in some other application's space. For example, ASP.NET pages are hosted by aspnet_wp.exe, service components are hosted by COM+ (or dllhost.exe), and .NET assemblies loaded by NUnit test fixtures might be hosted by nunit-gui.exe.
If we want to test a hosted assembly, we'll need a hosted assembly to test. In this section, we'll create a basic COM+ serviced component in C#. This simple assembly will be our guinea pig.
NOTE
Unfortunately, we'll have to breeze over building serviced components, but there are many great books on COM+ for .NET. A quick search on Amazon.com or a referral by your friends will lead you to these sources if building serviced components is a subject you'd like to explore further.
Here's an overview of the steps to create a COM+ serviced component in .NET and to demonstrate debugging hosted services, using Visual Studio 2003 and the .NET Framework version 1.1. I'll elaborate modestly after the steps.
Run Visual Studio .NET and create a new Class Library project.
Modify the stubbed project by adding a reference to System.EnterpriseServices.
Add a using statement and add the System.EnterpriseServices namespace.
Add two assembly attributes to the class module: ApplicationActivationAttribute and ApplicationAccessControlAttribute.
Modify the template-generated class so that it inherits from System.EnterpriseServices.ServicedComponent.
Apply the TransactionAttribute to the template class.
Implement the GetLuckyNumber method.
Apply the AutoCompleteAttribute to the GetLuckyNumber method.
Create a key file (because our COM+ service will need to be strongly named).
Add the key file to the project, modifying the AssemblyKeyFileAttribute to refer to our newly created key file.
Build the service and register it.
I've included a paragraph or two to cover each of these steps. If you're very comfortable with creating COM+ servers, feel free to skip ahead to the section "Attaching to the Host Application."
Creating a Library Project
For this example, I used C#. To create the service project, follow these steps:
Start Visual Studio .NET.
Select File, New.
In the New Project dialog box, select Visual C# Project from the Project Types list and Class Library from the Templates list.
Make sure that the project is located in the desired folder; then click OK.
Using EnterpriseServices
COM+ services are supported by the System.EnterpriseServices namespace, so we'll need to add a reference to the assembly containing this namespace and a using statement to our class module. Follow these steps:
Select Project, Add Reference in the context of the project we just created.
On the .NET tab, find the System.EnterpriseServices assembly (see Figure 1), click Select, and then click OK to add the reference.
Figure 1 Adding a reference to the System.EnterpriseServices assembly.
After you've added the reference, add a using statement to the top of the class module created by the Class Library project template:
using statement for System.EnterpriseServices;
Adding Assembly Attributes
Working our way from the revisions at the top of the module to the bottom, next we need to add some assembly attributes. Often you'll find assembly attributes in the AssemblyInfo file, but we can place them in a class module as well. Because I want to show all the code we'll need to add (for the most part) in one module, the assembly attributes were added to the same class module as the using statement in the preceding section. Listing 1 shows the first few lines of code, including the assembly attributes:
Listing 1 Assembly Attributes for Our Serviced Component
using System; using System.EnterpriseServices; [assembly: ApplicationActivation(ActivationOption.Server)] [assembly: ApplicationAccessControl(false)] // ...
The ApplicationActivationAttribute indicates whether the serviced component runs in the creator's process or the server's process. By initializing the ApplicationActivationAttribute with ActivationOption.Server, we indicate that the service should run in the server's process (DLLHOST.EXE). This means that DLLHOST.EXE will be the host application for our service. The other enumeration for ActivationOption is Library, which is the default, and it means that the service would run in our application's process.
The second attribute is ApplicationAccessControlAttribute. Initializing this attribute with true would enable security configuration. We used false here because security is another topic; for now we want it to be easy to host, use, and debug the service.
Defining the ServicedComponent
Template projects and items are just what you might expect: text files with replaceable parameters. When you open a template, a wizard that ships with .NET, vswizard.dll, replaces the parameters in the template source file with options you select in the dialog boxes, such as the New Project dialog box. We'll need to modify the empty class template created by the wizard, as shown in Listing 2.
Listing 2 Completing the Serviced Component's Class
using System; using System.EnterpriseServices; [assembly: ApplicationActivation(ActivationOption.Server)] [assembly: ApplicationAccessControl(false)] namespace MyService { [Transaction(TransactionOption.Required)] public class LuckyNumberGenerator : ServicedComponent { public LuckyNumberGenerator(){} [AutoComplete()] public int GetLuckyNumber() { Random r = new Random(DateTime.Now.Second); return r.Next(); } } }
The first few lines in Listing 2 were shown in Listing 1 and described in the adjacent paragraphs. The class itself is simple: It uses the Random class to return a random number seeded with the system clock's current second count. We're interested in the modest revisions that make this a COM+ component.
The first bold statement in Listing 2 applies the TransactionAttribute. (Keep in mind that Attribute suffixes are left off by convention.) The TransactionAttribute indicates the type of COM+ transaction available to the component. TransactionOption.Required means that the component shares a transaction if one exists. (Other options are Disabled, NotSupported, RequiresNew, and Supported.) Our example is very trivial, so these options won't make a big impact on the behavior of the service.
TIP
To learn more about COM and .NET, pick up a copy of Juval Löwy's COM and .NET Component Services (O'Reilly, 2001).
The next bold clause in Listing 2 indicates that our class inherits from ServicedComponent. A ServicedComponent is derived from a ContextBoundObject and a MarshalByRefObject. These classes support marshalling and proxies, and ServicedComponent is the base class for all .NET COM+ components.
The final bold statement is the application of the AutoCompleteAttribute. AutoComplete makes the transaction call SetComplete automatically if the method called doesn't throw an exception. If you forget AutoComplete, you'll need to call ContextUtil.SetAbort or ContextUtil.SetComplete, respectively, based on the failure or success of the method call.
CAUTION
Forgetting to use the AutoCompleteAttribute or calling SetAbort or SetComplete may make your service component appear to hang interminably.
That's it. We have a basic serviced component. We need to make a few minor adjustments and register the component in the GAC to begin testing and debugging through our host application.
Strongly Naming the Serviced Component
COM+ services need to be strongly named and registered. We'll register the serviced component in the next section. To add a strong name to the assembly, perform these operations:
Open the Visual Studio .NET command prompt.
Run the sn.exe (strong name) utility; for example, sn k mykey.snk creates the public and private keys.
Add the mykey.snk file to the serviced component's project folder and incorporate it into the project.
Modify the AssemblyKeyFileAttribute, initializing it with the relative path and name of your key file. The AssemblyKeyFileAttribute is found stubbed in the AssemblyInfo.cs file and should look something like this (depending on where your key file is physically located):
[assembly: AssemblyKeyFile("..\\..\\..\\mykey.snk")]
TIP
The Visual Studio command prompt is located at Start, Program Files, Microsoft Visual Studio .NET 2003, Visual Studio .NET Tools, Visual Studio .NET 2003 Command Prompt.
Keep in mind that in C# the whack or backslash (\) character is an escape character, so you need to use two of them (\\) or a single whack and the @ character to unescape the string literal. With the @ symbol, the statement would be written as follows:
[assembly: AssemblyKeyFile(@"..\..\..\mykey.snk")]
Building and Registering the Serviced Component
The final two steps are building the project and registering the serviced component. Build the component from the Build menu in .NET and register the component using the regsvcs.exe utility.
To register the service, open the Visual Studio .NET command prompt, change to the bin folder containing your serviced components assembly, and type the following:
regsvcs myservice.dll
The sample service was actually called myservice.dll. If everything has been put together correctly, the Component Services console should contain your service (see Figure 2).
Figure 2 Our serviced component in the Component Services console.
NOTE
When you register the service, you get a warning about not having an interface for the serviced component. This error means that late bound instances of the service will not be able to AutoComplete. For a product service, you'll likely want to define an interface containing the method and property signatures for public members that comprise your service.
Creating a Client Sample Application
Next we need a client application that consumes the service component. To invoke the service, I added a button to a form. When you click the button, an instance of MyService.LuckyNumberGenerator is created, and LuckyNumberGenerator.GetLuckyNumber is called. The results of this method call are shown in a message box.
To complete the client application, add a reference to MyService and System.EnterpriseServices. Then add a button to the template-generated form, use the form designer to generate a button Click event, and add the code shown in Listing 3.
Listing 3 The Only Code We Need To Add To Invoke the LuckyNumberGenerator
private void button1_Click(object sender, System.EventArgs e) { const string message = "Your lucky number is {0}"; MyService.LuckyNumberGenerator generator = new MyService.LuckyNumberGenerator(); MessageBox.Show(string.Format(message, generator.GetLuckyNumber())); }