Wednesday, March 28, 2007

C# StreamReader and StreamWriter

You can have a StreamWriter object as the return value from calling the method File.CreateText(). You can also create a StreamWriter object using one of its constructor overloads. The following example illustrates using a StreamWriter object with a FileStream object to write characters to the file aFile.txt

using System;
using System.IO;

namespace MyStreams
{
class Class1
{
public static void Main()
{
try
{
FileStream fs = new FileStream
("aFile.txt",FileMode.Create,
FileAccess.ReadWrite,FileShare.None);
string[] strings = {"C#", "ASP.NET", "XML"};
using(StreamWriter sw = new StreamWriter(fs))
{
Console.WriteLine("This StreamWriter instance uses {0}
to write to the file", sw.BaseStream);
Console.WriteLine("The Property sw.Encoding returns:
{0}",sw.Encoding);
sw.WriteLine("www.aspfree.com");
sw.WriteLine("contains many useful articles");
sw.WriteLine("on many different technologies like {0},
{1} and {2}", strings);
}
Console.WriteLine("Data has been written to the file");
Console.ReadLine();
}
catch(IOException ex)
{
Console.WriteLine(ex.Message);
}
}
}
}

C# Delegates Explained

This may be the first time you've read about this new .NET type. A delegate is an object that refers to a static method or an instance method. In this article I discuss what delegates are, how you can create and use them, and how the C# compiler saves us time by generating the delegate's class. We will also look at the MSIL code, talk about multicast delegates and provide callback methods through the use of delegates.
A delegate is an object that is created to refer to a static method or an instance method, and then used to call this method. To start off, you create a new delegate type in a different way than you create any other class. You use the delegate keyword as in the following statement.

public delegate int DelegateToMethod(int x, int y);

It seems unusual I know, but I will explain how it's done. Let's take a look at the very first example that explains how to use a delegate.

The First Delegate Example

Copy the following code into your VS.NET class file and run the project.

using System;

namespace Delegates
{
public delegate int DelegateToMethod(int x, int y);

public class Math
{
public static int Add(int first, int second)
{
return first + second;
}

public static int Multiply(int first, int second)
{
return first * second;
}

public static int Divide(int first, int second)
{
return first / second;
}
}

public class DelegateApp
{
public static void Main()
{
DelegateToMethod aDelegate = new DelegateToMethod(Math.Add);
DelegateToMethod mDelegate = new DelegateToMethod(Math.Multiply);
DelegateToMethod dDelegate = new DelegateToMethod(Math.Divide);
Console.WriteLine("Calling the method Math.Add() through the aDelegate object");
Console.WriteLine(aDelegate(5,5));
Console.WriteLine("Calling the method Math.Multiply() through the mDelegate object");
Console.WriteLine(mDelegate(5,5));
Console.WriteLine("Calling the method Math.Divide() through the dDelegate object");
Console.WriteLine(dDelegate(5,5));
Console.ReadLine();
}

}
}

When you run the above code you will get the following:



Let's explain what's going on in this example step-by-step. We have defined a new delegate type using the statement

public delegate int DelegateToMethod(int x, int y);

You are used to defining a new class using the class keyword, then an identifier followed by {, then implementation in the form of methods, properties and fields followed by }. The case is different with delegates. When we define a new delegate type (like DelegateToMethod) the C# compiler generates a class called DelegateToMethod that derives the System.MultipcastDelegate as follows:

public sealed class DelegateToMethod : System.MulticastDelegate
{
public DelegateToMethod(object target, int method);
public virtual void Invoke(int x, int y);
public virtual IAsyncResult BeginInvoke(int x, int y,
AsyncCallback callback, object obj);
public virtual void EndInvoke(IAsyncResult result);
}

The Constructor method of this class takes two arguments. The first is an object reference of the type that defined the instance method that the delegate refers to, and the second is an int value of the function pointer to the method that the delegate encapsulates.

The Invoke() method has the same signature as our delegate declaration. This method is used to call the delegate's encapsulated method. Note that when we defined the delegate we provided a signature for the method that can be encapsulated. In other words, the delegate can't refer to a method with a different signature than the one that it is created with. The BeginInvoke() and EndInvoke() provide asynchronous calls, which are beyond the scope of this article.

The C# compiler generates the sealed class with the four virtual methods, but it doesn't generate any implementation for those methods because they have to be implemented by the Common Language Runtime. So up to the point we have discussed, the folks at Microsoft saved us a lot of time by providing the delegate keyword which we can use to generate a class based on the System.MulticastDelegate. Let's continue our example.

The Math class contains three simple methods (Add, Multiply and Divide) that accept two int values and return an int value. Note that those are static methods. The DelegateApp class creates three DelegateToMethod objects as shown next:

DelegateToMethod aDelegate = new DelegateToMethod(Math.Add);
DelegateToMethod mDelegate = new DelegateToMethod(Math.Multiply);
DelegateToMethod dDelegate = new DelegateToMethod(Math.Divide);

Those three statements create three delegate objects. I think that the issue that would confuse someone is illustrated in the following screen shot:



As you can see, the signature of the Constructor method is not shown; instead, the signature of the method that can be encapsulated by the delegate is shown, or we can say the signature of the delegate. Any method that accepts two int values can return an int. We can look at a delegate as a type-safe function pointer which means that the parameter list and the return type are known.

We create a delegate object using the new operator and pass it the method to be encapsulated. Note that we have said that the generated class' constructor is passed two parameters, and because the passed methods are static, the first parameter will be null value (if it was an instance method it would be the object reference that defined the method instead of the null value).

We have said that in order to call the delegate's encapsulated method we need to call the delegate's Invoke() method. Actually, we can't call this method directly; instead we use the object reference and pass it the arguments as we did in the above code. Take a look again:

Console.WriteLine(aDelegate(5,5));

The statement calls the encapsulated method (which is Math.Add) and passes the parameters as arguments to the method, which returns the value 10.

Put simply, to use a delegate:

Define a new type that inherits from System.MulticastDelegate class and provide the signature of the methods that can be encapsulated by the new type.

public delegate int DelegateToMethod(int x, int y);

Create an instance or static method that has the same signature as defined by the new delegate type.

public static int Add(int first, int second)
{
return first + second;
}

Create a new delegate object using the new operator and pass the method as a parameter to it.

DelegateToMethod aDelegate = new DelegateToMethod(Math.Add);

Invoke the delegate object and pass the arguments to it which in turn calls the referenced method.

Console.WriteLine(aDelegate(5,5));

Building a complete COM+ Server component using C# and .NET

To develop .NET managed components that can be configured to function under the COM+ Runtime, you need to provide these components with numerous attributes defined in the System.EnterpriseServices namespace. To start off, each .NET class that's supposed to run under COM+ needs to derive from the System.ServicedComponent class. This base class provides default implementations of the classic MTS/COM+ interface IObjectControl - Activate(), Deactivate(), and CanBePooled(). You can override the default implementations if you wish to do so, just as will be seen shortly.

Once any number of COM+ centric attributes are added to the .NET component, the assembly will have to be compiled. However, to place this assembly under the control of COM+, a new utility (regsvcs.exe) will have to be used as we will see soon. In addition to installing the component into the COM+ catalog, this utility also provides a lot of other services that we shall soon see.

Finally, for the COM+ Surrogate (dllhost.exe) to locate your assembly and to host it in a given activity, it must be able to locate your binary. Therefore, you should install your assembly into the system's Global Assembly Cache (GAC).

The various steps that are involved in creating a COM+ Server Component using C# and the .NET Framework are as follows (I'm going to assume you're using the VS.NET IDE):

Create a Visual C# - Class Library project

Generate a Key-Value pair to use when deploying your Shared Assembly

Configure your Project Property Pages with the right information

Develop the AccountManager.cs library

Modify the generated AssemblyInfo.cs to add the right assembly information

Build the Project Files

Deploy the component as a Shared Assembly, and Configure the Assembly in the COM+ Catalog

The BookKeeper Module

My goal is to simplify illustration of a typical COM+ serviced component development process. I am therefore, in this article, going to reuse the BookKeeper example for all database operations. As a result, all our data is going to be maintained in an XML datastore!!! I had used the BookKeeper example in an earlier article to illustrate ADO.NET's disconnected operation facility - the DataSet. To refresh, the DataSet facilitates the client to manipulate and update a local copy of any number of related tables while still disconnected from the data source and submit the modified data back for processing using a related data adapter at a later point in time.

The AccountManager Module

Our hypothetical AccountManager Module (that we will build in this article), is actually a COM+ Server component that performs just a couple of functions. It is the module that manages creation and deletion of accounts (Checking accounts or Savings accounts) for a Large Commercial Bank Project. It offers no other services except "Create Account", and "Delete Account".

1. Create a Visual C# - Class Library project

Create a new Visual C# Class Library project. Remember that the COM+ Runtime can only host types contained in a DLL.





2. Generate a Key-Value pair to use when deploying your Shared Assembly

Shared Assemblies are those that can be used by any client application, such as a system DLL that every process in the system can use. Unlike private-assemblies, shared assemblies must be published or registered in the system's Global Assembly Cache (GAC). As soon as they are registered in the GAC, they act as system components. An essential requirement for GAC registration is that the component must possess originator and version information. In addition to other metadata information, these two items allow multiple versions of the same component to be registered and executed on the same machine. Unlike Classic COM, we don't have to store any information in the system registry for clients to use these shared assemblies.

There are three general steps to registering shared assemblies in the GAC:

The Shared Name (sb.exe) utility should be used to obtain the public/private key pair. This utility generates a random key pair value, and stores it in an output file - for example, AccountManager.key.

Build the assembly with an assembly version number and the key information in the AccountManager.key

Using the .NET Global Assembly Cache (gacutil.exe) utility, register the assembly in the GAC.

The assembly now becomes a shared assembly and can be used by any client in the system.

Therefore, as a first step, use the Shared Name Utility to obtain a public/private key pair and store it in a file (AccountManager.key, in this case) as shown below.

Command Prompt
C:\MyProjects\Cornucopia\COMplus\BankServer\AccountManager>sn -k AccountManager.key

Microsoft (R) .NET Framework Strong Name Utility Version 1.0.2914.16
Copyright (C) Microsoft Corp. 1998-2001. All rights reserved.

Key pair written to AccountManager.key

C:\MyProjects\Cornucopia\COMplus\BankServer\AccountManager>

The -k option generates the random key pair and saves the key information in the AccountManager.key file. We use this file as input when we build our Shared Assemblies.



3. Configure your Project Property Pages with the right information

Configure the Project Properties with the right information. Make sure you specify the Assembly Name that you want for the Assembly. Specifically, move to the General tab, and in the Wrapper Assembly Key File area, enter the key file to use. In this case, it is AccountManager.key.



Move to the Reference Path Properties area, and select the directory that contains the BookKeeper executable.



Go to "Project Dependancy" and select the BookKeeper as a dependancy for this project. This means the BookKeeper project has to be compiled before compiling this project.



To the AccountManager project files, also add the BookKeeper.cs, and the AccountKey.cs files from the BookKeeper project.



4. Develop the AccountManager.cs library

Transactions

To develop a .NET class that supports transactions, here's what you have to do:

The class must derive from the System.ServicedComponent class to exploit COM+ Services as shown in Line 94.
The class must be created with the correct Transaction attribute such as Transaction (TransactionOption.Required) as shown in Line 78.
Besides this, you can use the System.EnterpriseServices.ContextUtil class to obtain information about the COM+ object context as shown in Line 138. This class exposes important methods of COM+ like SetComplete() and SetAbort(), and IsCallerInRole(), and important COM+ properties like IsInTransaction, and MyTransactionVote. Additionally, while it's not necessary to specify COM+ Application installation options, you can always specify what you want. Notice that we use attributes to specify a number of things.

In the AccountManager.create() method, we simply call ContextUtil.SetComplete() - Line 138 -when we've successfully created a new account into our database. If something has gone wrong during the process, we will vote to abort the transaction by calling ContextUtil.SetAbort() as shown on Line 142.

Instead of calling ContextUtil.SetComplete() and ContextUtil.SetAbort() explicitly, we can also use the AutoComplete( true ) attribute, as shown on line 165 which is conceptually equivalent to the previously shown AccountManager.create() method.

using System;
using System.Runtime.InteropServices;
using System.EnterpriseServices;
// Include the following for the Trace class
using System.Diagnostics;
// Include the following for Windows Message Box
using System.Windows.Forms;
// Include the BookKeeper namespace
using BookKeeper;

namespace Bank {

///////////////////////////////////////////////////////////////
///
/// The Account Manager interface
///

///
/// This interface defines create and delete methods to
/// add or delete Bank Accounts
///

///////////////////////////////////////////////////////////////

/// Indicate whether a managed interface is dual, IDispatch or
/// IUnknown based when exposed to COM
[ InterfaceTypeAttribute( ComInterfaceType.InterfaceIsDual ) ]

public interface IAccountManager {

///
/// The create method
///

///
/// Method used to create a new Bank account
///

/// Either Checking or Savings
/// Customers who own this account
/// Initial Deposit
///
int create (AccountType type, string[] customerNames, float startingBalance);

///
/// The delete method
///

///
/// Method used to delete and existing Bank account
///

/// the Account Number
/// true if Account deleted, false if not
bool delete (int accountKey);
}

///////////////////////////////////////////////////////////////
///
/// AccountManager used to create new Accounts or delete accounts.
///

///////////////////////////////////////////////////////////////

/// Specify a name for your serviced component
[ ProgId( "COM+ Bank Server Account Manager" ) ]
/// Add content to hosting COM+ App's description field
[ Description( "COM+ Bank Server Account Manager" ) ]
/// Configure component's Transaction Option
[ Transaction( TransactionOption.Required ) ]
/// Configure component's object pooling
[ ObjectPooling( MinPoolSize = 5, MaxPoolSize = 10, CreationTimeout = 20 ) ]
/// Specify COM+ Context Attributes
[ MustRunInClientContext( false ) ]
/// Enable event tracking
[ EventTrackingEnabled( true ) ]
/// Enable JITA for the component
[ JustInTimeActivation( true ) ]
/// Enable Construction String Support for the component
[ ConstructionEnabled( Enabled=true, Default="Gopalan's Bank Server" ) ]
/// Configure activity-based Synchronization for the component
[ Synchronization( SynchronizationOption.Required ) ]
/// Indicate the type of class interface that will be generated for this class
[ ClassInterface( ClassInterfaceType.AutoDual ) ]

public class AccountManager : ServicedComponent, IAccountManager {

///
/// Public No-argument Default Constructor
///

public AccountManager() {
MessageBox.Show ("Bank::AccountManager() invoked...");
}

/////////////////////////////////////////////////////////////////////////
/// The Following methods support core functionality required of the
/// AccountManager component and implement the IAccountManager interface
/////////////////////////////////////////////////////////////////////////

///
/// The create method
///

///
/// Method used to create a new Bank account
///

/// Either Checking or Savings
/// Customers who own this account
/// Initial Deposit
///

/// Add content to hosting COM+ App's description field
[ Description( "Creates a new account for the Bank Server" ) ]

public int create (AccountType type, string[] customerNames, float startingBalance) {
MessageBox.Show ("Bank::create() invoked...");
AccountKey key = null;
try {
// Create the BookKeeper class
BookKeeper.BookKeeper keeper = new BookKeeper.BookKeeper();
if (null != keeper) {
// Call the BookKeeper to create a new Bank Account
key = keeper.createAccount (type, customerNames, startingBalance);
// Clean-up the BookKeeper object
keeper.Dispose();
}
else {
throw new Exception ( "BookKeeper Object could not be created." );
}
// Since everything went well, commit the changes
ContextUtil.SetComplete();
}
catch (Exception exception) {
// An Error occured, so rollback the changes
ContextUtil.SetAbort();

// Trace the current COM+ context ID (its GUID) to the output window
// use the ContextId static property of ContextUtil
Guid contextID = ContextUtil.ContextId;
String traceMessage = "Context ID is " + contextID.ToString();
Trace.WriteLine (traceMessage.ToString ());

MessageBox.Show (exception.ToString (), "Bank::create()");
}
return key.Key;
}

///
/// The delete method
///

///
/// Method used to delete and existing Bank account
///

/// the Account Number
/// true if Account deleted, false if not

/// Take advantage of COM+'s method auto-deactivation
[ AutoComplete( true ) ]
/// Add content to hosting COM+ App's description field
[ Description( "Deletes an existing account from the Bank Server" ) ]

public bool delete (int accountKey) {
bool result = false;
MessageBox.Show ("Bank::delete() invoked...", "Key Value = "+accountKey);
/*
try {
// Programming Role based security
SecurityCallContext callContext;
callContext = SecurityCallContext.CurrentCall;
string caller = callContext.DirectCaller.AccountName;
bool isInRole = callContext.IsCallerInRole ( "Manager" );
if (false == isInRole) {
throw new Exception ( "Only Managers can delete Customers" );
}
*/
BookKeeper.BookKeeper keeper = new BookKeeper.BookKeeper();
if (null != keeper) {
AccountKey key = new AccountKey();
key.Key = accountKey;
result = keeper.deleteAccount (key);
keeper.Dispose();
}
else {
throw new Exception ( "BookKeeper Object could not be created." );
}
/*}
catch (Exception exception) {
Guid contextID = ContextUtil.ContextId;
String traceMessage = "Context ID is " + contextID.ToString();
Trace.WriteLine (traceMessage.ToString ());

MessageBox.Show (exception.ToString (), "Bank::delete()");
}*/
return result;
}

/////////////////////////////////////////////////////////////////////////
/// All the Following methods support overriding functionality required for
/// implementing some of the COM+ support interfaces like IObjectConstruct.
/////////////////////////////////////////////////////////////////////////

///
/// The method is called after the component's constructor
/// and is passed in the user specific constructionString
///

///
override public void Construct (string constructionString) {
MessageBox.Show ("Bank::Construct() invoked...");
MessageBox.Show (constructionString, "Construction String");
}

///
/// Do context specific initialization in this method
///

override public void Activate () {
MessageBox.Show ("Bank::Activate() invoked...");
}

///
/// Do context specific cleanup in this method
///

override public void Deactivate () {
MessageBox.Show ("Bank::Deactivate() invoked...");
}

///
/// Object Pooling support method
///

/// true if pooling is supported, false if not
override public bool CanBePooled () {
MessageBox.Show ("Bank::CanBePooled() invoked...");
return true;
}
}
}



Object Pooling

Object Pooling is a feature that was introduced in COM+, but was missing in MTS. Object Pooling allows you to minimize the use of system resources, by pooling objects that support transactions but are expensive to create. This improves performance and helps system scalability. If you want to support object pooling in you components, you need to derive from the System.ServicedComponent class, and override any of the Activate(), Deactivate(), and CanBePooled() methods, and specify object pooling requirements in an ObjectPooling attribute as shown on Line 80. You can take advantage of the Activate() and Deactivate() methods to perform the appropriate initialization and cleanup. The CanBePooled() method is used to tell COM+ whether this object can be pooled or not. This way, you can provide any expensive object-creation functionality in the constructor of the component.

Since our COM+ components support Object Pooling, The COM+ runtime activates and deactivates them as required. After each Client call has been serviced, it puts the component object back into the object pool. As soon as a new Client call arrives, it picks the same component object back from the pool to service the new request.



5. Modify the generated AssemblyInfo.cs to add the right assembly information

You provide the compiler with your assembly information in an assembly file called AssemblyInfo.cs. The assembly information file is compiled with the rest of the project's source files. The information is in the form of assembly attributes - directives to the compiler on the information to embed in the assembly.

AssemblyInfo.cs
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
62:
using System.Reflection;
using System.Runtime.CompilerServices;
using System.EnterpriseServices;

//
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
//
[assembly: AssemblyTitle("AccountManager for Bank")]
[assembly: AssemblyDescription("Creates and Deletes Accounts for the Bank")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("eCommWare Corporation")]
[assembly: AssemblyProduct("COM+ Bank Server")]
[assembly: AssemblyCopyright("(c) 2001, Gopalan Suresh Raj. All Rights Reserved.")]
[assembly: AssemblyTrademark("Web Cornucopia")]
[assembly: AssemblyCulture("en-US")]

//
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Revision and Build Numbers
// by using the '*' as shown below:

[assembly: AssemblyVersion("1.0.0.0")]

//
// In order to sign your assembly you must specify a key to use. Refer to the
// Microsoft .NET Framework documentation for more information on assembly signing.
//
// Use the attributes below to control which key is used for signing.
//
// Notes:
// (*) If no key is specified, the assembly is not signed.
// (*) KeyName refers to a key that has been installed in the Crypto Service
// Provider (CSP) on your machine. KeyFile refers to a file which contains
// a key.
// (*) If the KeyFile and the KeyName values are both specified, the
// following processing occurs:
// (1) If the KeyName can be found in the CSP, that key is used.
// (2) If the KeyName does not exist and the KeyFile does exist, the key
// in the KeyFile is installed into the CSP and used.
// (*) In order to create a KeyFile, you can use the sn.exe (Strong Name) utility.
// When specifying the KeyFile, the location of the KeyFile should be
// relative to the project output directory which is
// %Project Directory%\obj\. For example, if your KeyFile is
// located in the project directory, you would specify the AssemblyKeyFile
// attribute as [assembly: AssemblyKeyFile("..\\..\\mykey.snk")]
// (*) Delay Signing is an advanced option - see the Microsoft .NET Framework
// documentation for more information on this.
//
[assembly: AssemblyDelaySign(false)]
[assembly: AssemblyKeyFile("AccountManager.key")]
[assembly: AssemblyKeyName("")]

[assembly: ApplicationName( "COM+ Bank Server Account Manager" )]
[assembly: ApplicationActivation( ActivationOption.Server )]



In particular, pay attention to the fact that we specify a version number for this library using the AssemblyVersion attribute and also specify the assembly key file using the AssemblyKeyFile attribute. The ApplicationName attribute is self-explanatory. However, the attribute of special interest is the ApplicationActivation attribute. As you may know, MTS and COM+ applications may either be hosted as a Library (e.g., Activated in the Caller's process) or Server (e.g., Activated in a new instance of dllhost.exe). The default attribute is to configure your COM+ application as a Library. Here we want to explicitly set the activation option to be specified as ActivationOption.Server.

6. Build the Project Files

Build the files that make up the project.

------ Rebuild All started: Project: BookKeeper, Configuration: Debug .NET ------

Preparing resources...
Updating references...
Performing main compilation...

Build complete -- 0 errors, 0 warnings
Building satellite assemblies...



------ Rebuild All started: Project: AccountManager, Configuration: Debug .NET ------

Preparing resources...
Updating references...
Performing main compilation...

Build complete -- 0 errors, 0 warnings
Building satellite assemblies...



---------------------- Done ----------------------

Rebuild All: 2 succeeded, 0 failed, 0 skipped

7. Deploy the component as a Shared Assembly and Configure it in the COM+ Catalog

After you've built the assembly, you can use the .NET Global Assembly Cache (GAC) utility to register this assembly into the GAC as shown below.

Command Prompt
C:\MyProjects\Cornucopia\COMplus\BankServer\AccountManager\bin\Debug>gacutil /i Bank.dll

Microsoft (R) .NET Global Assembly Cache Utility. Version 1.0.2914.16
Copyright (C) Microsoft Corp. 1998-2001. All rights reserved.

Assembly successfully added to the cache

C:\MyProjects\Cornucopia\COMplus\BankServer\AccountManager\bin\Debug>regsvcs /fc Bank.dll
RegSvcs - .NET Services Installation Utility Version 1.0.2914.16
Copyright (C) Microsoft Corp. 2000-2001. All rights reserved.

Installed Assembly:
Assembly: C:\MyProjects\Cornucopia\COMplus\BankServer\AccountManager\bin\Debug\Bank.dll
Application: COM+ Bank Server Account Manager
TypeLib: c:\myprojects\cornucopia\complus\bankserver\accountmanager\bin\debug\Bank.tlb

C:\MyProjects\Cornucopia\COMplus\BankServer\AccountManager\bin\Debug>

Successful registration against the cache turns this component into a shared assembly. A version of this component is copied into the GAC so that even if you delete this file locally, you will still be able to run your client program.

Configuring our Assembly in the COM+ Catalog

Configuring a .NET assembly in the COM+ Catalog means, you need to generate a COM Type Library (tlbexp.exe), and register the type in the system registry (regasm.exe). You also have to make sure that you enter the right information into the COM+ Catalog (RegDB). Instead of using all these tools individually, the .NET SDK provides an additional tool called the Register Services utility (regsvcs.exe). This utility simplifies the process by making sure that all required details are taken care of in a single step. It performs the following functions:

Our Assembly is loaded into memory
Out Assembly is registered (e.g., just like using regasm.exe)
A COM Type Library (.tlb file) is generated and registered (e.g., just like using tlbexp.exe)
The generated COM Type Library is installed in the specified COM+ Application
Our Components are configured according to the attributes that are specified in the type definitions
If you notice carefully, when we use the regsvcs.exe utility, we specify the /fc option (find or create) to instruct the tool to build a new COM+ application if one does not currently exist.

The Component Services Explorer

Once you have done all this, you can open up the Windows 2000 Component Services Explorer and discover that your .NET Assembly is now recognized as valid COM+ Application.



While you explore the various property windows for this COM+ Application, you realize that the various attributes that you specified in the C# class have been used to configure our component in the COM+ Catalog. Right Click the Component and check out the Activation tab for example as shown in the screen shot below.



The above settings have been automatically configured based on the following class-level attributes that you set programmatically in your original C# class from lines 82-88 in the source code above.

/// Configure component's object pooling
[ ObjectPooling( MinPoolSize = 5, MaxPoolSize = 10, CreationTimeout = 20 ) ]
/// Specify COM+ Context Attributes
[ MustRunInClientContext( false ) ]
/// Enable event tracking
[ EventTrackingEnabled( true ) ]
/// Enable JITA for the component
[ JustInTimeActivation( true ) ]
/// Enable Construction String Support for the component
[ ConstructionEnabled( Enabled=true, Default="Gopalan's Bank Server" ) ]


Now you need to build a client application that can access this COM+ Server component.