Building a Serviced Component

I l @ ve RuBoard

You've learned how to consume a serviced component from J# and how to subscribe to a loosely- coupled event. It's now time to look at how a serviced component can be implemented in J#.

To provide a little relief from cakes, the upcoming examples use data from the Northwind Traders database that ships with the .NET Framework SDK and that was described in Chapter 1. This database holds information about products supplied by Northwind Traders, registered customers that can place orders for those products, and the details of those orders. Information about the goods ordered is held in two tables called Orders and Order Details. When a customer places an order, a new entry is added to the Order table and the individual order lines are inserted into the Order Details table. Both tables are linked using the OrderID column. Customer information is recorded in the Customers table and is linked to the Orders table using the CustomerID column. Figure 14-12 shows the structure of the relevant tables.

Figure 14-12. The Orders, Order Details, and Customers tables from the Northwind Traders database

We'll look at how to create a serviced component that exposes methods for performing the following operations:

  • Placing an order

    The component will insert one row in the Orders table and one or more rows in the Order Details table.

  • Querying an order

    The component will return the details of an order given the OrderID.

  • Canceling an order

    The component will delete information in the Order Details and Order tables, given the OrderID.

To preserve the integrity of the database, all of these methods will require the use of COM+ transactions.

Serviced Component Basics

You saw earlier that a serviced component must inherit from the System.EnterpriseServices.ServicedComponent class. In fact, this is all that a serviced component needs to do if it is to operate with COM+. The ServicedComponent class provides a default implementation of the methods that comprise the COM+ IObjectControl interface. (Strictly speaking, it doesn't actually implement IObjectControl ” it just looks like it does, for reasons we don't have space to fully explain here.) You can apply a selection of attributes to a serviced component to modify the way in which it interacts with the COM+ environment.

When you design a serviced component, you should remember the basic rule of COM: Use interfaces. Yes, you can simply create a serviced component and deploy it in a COM+ application, and the .NET Framework will generate a default class interface for you, but the component will support only late binding for unmanaged clients. (Managed clients reference the assembly that holds the serviced component and will still be able to use early binding.) If you define interfaces, you have far more control over the methods exposed to clients , and the .NET Framework will generate a COM custom interface that supports early binding. The IOrderComponent interface, shown below, is the interface implemented by the OrderComponent serviced component. It defines the methods for manipulating orders, as described previously. (Complete code for this example is available in the file OrderComponent.jsl in the NorthwindOrders project.)

 /** @attribute ComVisibleAttribute(true) */ /** @attribute GuidAttribute("FA068D1B-D61C-4c54-A68E-16CA49EB7872") */ public interface IOrderComponent {    public int PlaceOrder(String CustomerId, ICollection details)       throws System.Exception;    public ICollection QueryOrder(int orderId) throws System.Exception;    public void CancelOrder(int orderId); } 

The PlaceOrder method expects a customer ID (which is held as a string in the database) and a collection of order lines that define the goods that comprise the order. If successful, PlaceOrder will return the integer ID of the newly created order; if not, it will throw an exception. The QueryOrder method takes an order ID and returns a collection containing the order lines that comprise the order. Again, if something nasty happens, the method will throw an exception. The CancelOrder method is used to remove an order from the database. For reasons that will be explained shortly, this method does not explicitly throw an exception if an error occurs.

The interface is tagged as being visible to COM (it will be visible anyway, but it is good practice to be explicit), and a GUID has also been assigned, which saves the .NET Framework from having to assign one itself. So far, there is nothing special to see.

The definition of the OrderComponent serviced component is shown here:

 /** @attribute TransactionAttribute(TransactionOption.Required) */ /** @attribute ClassInterfaceAttribute(ClassInterfaceType.None) */ public class OrderComponent extends ServicedComponent    implements IOrderComponent { } 

The class inherits from ServicedComponent and implements the IOrderComponent interface, as expected. However, the OrderComponent class is also tagged with a couple of attributes. The ClassInterfaceAttribute , which was described in Chapter 13, indicates which interfaces should be generated for the class (if any). In this case, all functionality is exposed through the IOrderComponent interface, so the option ClassInterfaceType.None is used.

Of more interest is TransactionAttribute . One fundamental feature of COM+ and serviced components is the way in which they operate with transactional resource managers such as database management systems (DBMSs). (Chapter 7 described the use of transactions with Microsoft SQL Server.) The TransactionAttribute is used to indicate how the component will use transactions when its methods are called. The possible values are summarized in Table 14-1. The OrderComponent class specifies TransactionOption.Required , which indicates that each method should be called in the context of a transaction. If no transaction is currently active, a new one will be created. TransactionAttribute can be applied only to a class and not to individual methods, and it is meaningful only when applied to serviced components.

Serviced Components and Context

You encountered the concept of context in Chapter 8, when we discussed multithreading, and in Chapter 11, when we looked at the .NET Remoting architecture. As you'll recall, a context is a set of properties that provide an environment for objects. For example, a context can determine the scope of a transaction (all objects executing in the same context can belong to the same transaction) as well as provide for synchronization between objects. An application domain can contain multiple contexts.

In Chapter 11, we also discussed context-agile and context-bound objects. A context-agile object can be accessed directly from any context in an application domain. Access to a context-bound object from outside the object's context is achieved through a proxy. All COM+ objects are context-bound, and the System.EnterpriseServices.ServicedComponent class inherits from System.ContextBoundObject .

Serviced components can be tagged with attributes (such as TransactionAttribute , which we just described) that define the context for that component. When you instantiate a serviced component, either it will join the current context or a new context will be created if the current context is incompatible with the attributes specified for the new component. If a serviced component demands that it be executed in a new transaction (see the options described in Table 14-1) or a new synchronization unit (see later), a new context will be created to house the component. Access to the component from another context will involve marshaling data through a proxy. In an ideal world, you should minimize the number of cross-context transitions when you build serviced components. ( Cross-context marshaling is a little more expensive for serviced components than it is for regular .NET components.) Therefore, to maximize performance you should carefully consider the transaction and synchronization options for a component when you design it.

One final word of warning: Contexts used by serviced components are specialized versions of contexts used by nonserviced components because they must be propagated into the COM+ runtime environment. The concepts are similar, but the implementation is different. For example, when you attach the Synchronization attribute to a regular .NET context-bound class, you use the System.Runtime.Remoting.Contexts.SynchronizationAttribute class. When you apply synchronization to a serviced component, you must use the System.EnterpriseServices.SynchronizationAttribute class instead.

Table 14-1. Transaction Options for Serviced Components

Option

Description

TransactionOption.Disabled

No transaction is used. If a transaction already exists, the component will not use it. The component executes in the current context.

TransactionOption.NotSupported

No transaction is used. If a transaction already exists, a new context is created without a transaction and the component is executed in the new context.

TransactionOption.Required

If a transaction already exists, it will be used. If not, a new transaction in a new context will be created.

TransactionOption.RequiresNew

A new transaction and context will always be created.

TransactionOption.Supported

If a transaction already exists, it will be used. If not, execution will proceed without a transaction.

The System.EnterpriseServices namespace defines a selection of assembly-level attributes that you can use to configure the COM+ application that hosts the serviced components. The ApplicationNameAttribute specifies the name of the COM+ application to be created, the ApplicationIDAttribute is the GUID that identifies the application, and ApplicationActivationAttribute indicates whether the COM+ application is a library or a server application. The Description attribute provides a text description of the application and can also be attached to individual classes as well as the assembly. These attributes are applied when a serviced component uses automatic installation; if the component is manually installed, an administrator can override these values (although it is considered bad etiquette to do so, and in some cases this can cause the component to malfunction ”the component author usually has a good reason for specifying particular settings).

If you examine the PlaceOrder and QueryOrder methods in the OrderComponent class, you'll see that they both adopt a similar strategy:

  1. Connect to the database.

  2. Perform some updates.

  3. Save the changes.

  4. Disconnect from the database.

In the event of an error, the changes will be rolled back. Data access is performed using ADO.NET. (See Chapter 7 for details.) The key point to notice is how changes are saved or rolled back. Remember that these methods execute in the context of a transaction. The ContextUtil class supplies static methods that you can use to query and manipulate the COM+ context. The two methods used by this example are SetComplete (which commits the transaction) and SetAbort (which causes the transaction to be rolled back). Both methods also indicate that the component instance can be discarded when the method completes. (More about activation shortly.) If you're familiar with COM+ development, note that the ContextUtil class is an implementation of the COM+ IObjectContext interface.

The CancelOrder method is slightly different because it does not explicitly call ContextUtil.SetComplete or ContextUtil.SetAbort . Instead, the method is tagged with AutoCompleteAttribute , as shown here:

 /** @attribute AutoCompleteAttribute() */ public void CancelOrder(int orderId) {     } 

The use of AutoCompleteAttribute causes an implicit call to Context ­Util.SetComplete if the method returns normally, but it invokes ContextUtil.SetAbort if the method throws an unhandled exception. You'll notice that the method itself does not perform any exception trapping; this is intentional. If you catch an exception in the method, it will no longer be unhandled, and the method will complete successfully and call ContextUtil.SetComplete .

Warning

At first glance, it might seem that the PlaceOrder , QueryOrder , and CancelOrder methods perform a lot of unnecessary work in connecting to the database and disconnecting when they finish. Connecting and disconnecting from a database have a reputation for being time-consuming operations because user credentials are passed to the DBMS and checked, and then various data structures are set up to handle the connection internally by the DBMS software. You might therefore be tempted to create a constructor that connects to the database and exposes the database connection to the other methods in the class. Do not adopt this approach . True, each instance of the component will have its own private connection to the database, but this is not a scalable solution and will ultimately lead to resource contention in the DBMS and sluggish performance as more and more concurrent instances of the serviced component are created. You'll have little or no control over how many connections are established (which might also have some licensing implications), and eventually the DBMS will hit its limit and prevent any further connections from being established.


In Chapter 7, we looked at how ADO.NET supports connection pooling. This is the preferred approach. The OLE DB and SQL providers that Microsoft provides as part of the .NET Framework Class Library support connection pooling automatically, based on the connection string used to access the data source. All connections that use the same connection string will be pooled. Thus, once the pool has been populated with connections, the act of opening a connection will simply cause an existing connection to be retrieved from the pool, and closing a connection will cause it to be returned to the pool. (Do be sure to close connections rather than rely on the common language runtime to tidy them up for you.) Remember that you can configure the minimum and maximum size of the pool in the ADO.NET connection string and thus maintain control over the number of concurrent connections established.

The details for each order passed into PlaceOrder and returned by QueryOrder are held in an ICollection object that contains a set of OrderDetails objects. The OrderDetails class is also defined in OrderComponent.jsl, and it implements the IOrderDetails interface. The IOrderDetails interface exposes the contents of each order line as a series of properties, which are implemented as private variables in the OrderDetails class. The IOrderDetails interface is tagged as being visible to COM and has a GUID:

 /** @attribute ComVisibleAttribute(true) */ /** @attribute GuidAttribute("E65F6A55-C7F9-43af-B6CE-D693878A0596") */ public interface IOrderDetails {    /** @property */    public int get_OrderId();    /** @property */    public void set_OrderId(int orderId);    /** @property */    public int get_ProductId();    /** @property */    public void set_ProductId(int productId);    /** @property */    public Decimal get_UnitPrice();    /** @property */    public void set_UnitPrice(Decimal unitPrice);    /** @property */    public short get_Quantity();    /** @property */    public void set_Quantity(short quantity);    /** @property */    public double get_Discount();    /** @property */    public void set_Discount(double discount); } 

The OrderDetails class is a serviced component. However, it has no TransactionAttribute specified, so it will not participate in any COM+ transactions. The default value for TransactionAttribute is TransactionOption.Disabled , which permits a component to execute in the current context regardless of whether any transaction is active:

 /** @attribute ClassInterfaceAttribute(ClassInterfaceType.None) */ public class OrderDetails extends ServicedComponent      implements IOrderDetails {    private int orderId;    private int productId;    private Decimal unitPrice;    private short quantity;    private double discount;     } 

At this point, you can compile the OrderComponent file and place the resulting assembly, NorthwindOrders.dll, in the GAC.

Registering and Using the Serviced Component

The OrderTest class (in the file OrderTest.jsl in the OrderTest project, which has been added to the NorthwindOrders solution for your convenience) exercises the OrderComponent and OrderDetails classes. The project contains a reference to the NorthwindOrders assembly held in the GAC. (You cannot reference the NorthwindOrders component through COM because it was written in managed code ”you're not allowed to create an RCW that directly references a CCW.) If you have rebuilt the NorthwindOrders project, you should update the reference to point to the correct version of the assembly. (Drop and re-create the reference.)

The main method of the OrderTest class creates an OrderComponent object and an array of OrderDetails . The method then populates the order details array and places an order. The number of the new order is returned from PlaceOrder and displayed by the main method. The QueryDetails method is then called to check that the order has been placed, and the details of the order are displayed in the console. The order is then canceled by calling the Cancel ­Order method. Finally, QueryOrder is executed again, with the same order number as before. This time, the details collection returned should be empty because the order no longer exists.

If you run the OrderTest application, two things will happen. The second thing (we'll come to the first in a moment) is that a console window will display the number of the newly created order (returned by PlaceOrder ), the details of the order (returned by QueryOrder ), and the details again (returned by QueryOrder after CancelOrder is called). The second set of details should be empty (the order was canceled), as shown in Figure 14-13.

Figure 14-13. The output of the OrderTest program

The first thing that happens is that the Northwind Orders COM+ application is created in the COM+ catalog, and the OrderComponent and OrderDetails components are added. The configuration settings used for the Northwind Orders application are taken from the various attributes attached to the classes (along with some default values for settings whose values were not specified). This is known as dynamic registration .

When a managed client invokes a serviced component, the runtime will check to make sure that the serviced component is available in the COM+ catalog. If not, the runtime will create a System.EnterpriseServices.RegistrationHelper object and execute its InstallAssembly method, which will examine the metadata of the serviced component, create the COM+ application, and register the components. You can also use the RegistrationHelper class from your own code. The InstallAssembly method performs the following tasks :

  • It registers the assembly in the Windows Registry by calling RegisterAssembly method of a System.Runtime.InteropServices.RegistrationServices object. Managed classes registered in this way have their InprocServer32 key set to MSCOREE.DLL (the entry point to the common language runtime). Other keys will be added to indicate the name of the managed class implementing the serviced component, the assembly and version, and the codebase . Figure 14-14 shows the keys added to the Registry for the OrderDetails component.

    Figure 14-14. The Windows Registry showing the keys for the OrderDetails component

    Note

    Although serviced components must be signed, they do not have to be deployed as shared assemblies in the GAC ”they can be private and use XCOPY deployment. This approach is not recommended, however. Once a serviced component has been registered, it is potentially available to every application running on that computer (and elsewhere, if the application is exported). Each client would have to have its own local copy of the same version of the assembly implementing the serviced component; otherwise , the common language runtime would not be able to locate it and the client application would fail.


  • It generates a type library, using the ConvertTypeLibToAssembly method of a System.Runtime.InteropServices.TypeLibConverter object, and registers it in the Windows Registry.

  • It creates a new COM+ application, using the values specified in attributes of the assembly that holds the serviced component. This is achieved using the unmanaged COM+ administrative APIs (details of which you can find in the Platform SDK).

  • It uses the type library to install and configure the serviced components in the COM+ application, again using unmanaged COM+ administrative API calls.

  • Finally, it examines any custom attributes attached to the components and applies them to the components in the COM+ catalog.

Note that this process happens only when a managed client attempts to access an unregistered serviced component. If the component is called by unmanaged code, it must already be registered. You can either write the registration code yourself (using the RegistrationHelper class ”an example is available in the OvenInstaller project at the end of this chapter) or an administrator can manually create a COM+ application and install the components. If you adopt the latter approach, be sure that you have documented the settings required by the COM+ application and each component so that the administrator will configure them correctly.

The RegistrationHelper class also contains the method UninstallAssembly , which you can call to remove a COM+ application from the catalog.

GUIDs and Versioning

The OrderComponent and OrderDetails components shown in the previous example use fixed GUIDs, which are generated by the developer. This is fine for the production version of a component, but if you're in the development phase of a project, you might find it more convenient to omit the GUID and instead let the runtime generate one for you automatically.

Each time you invoke a serviced component from a managed client, the common language runtime will load information about the serviced component from the assembly used when the client was compiled or follow any versioning policy specified by the client's configuration file (if available). If a GUID is supplied explicitly by the serviced component, the COM+ runtime will use the GUID to activate the appropriate serviced component as indicated by the Registry. If the version of the serviced component referenced by the client is different from that indicated in the Registry, you might get a mismatch between the two versions of the serviced component (the client-referenced serviced component might be more recent and implement an interface that has been modified from that recorded in the Registry), possibly resulting in some rather obscure errors.

If a serviced component does not specify a GUID, a new GUID will be generated for each version of the serviced component. The new version of the serviced component will be installed in the COM+ application, and the client will access the correct version through COM+. However, the serviced component will be registered multiple times ”once for each version. This means that existing clients, built using earlier versions of the serviced component, will still function. (The version that a client references is specified in the manifest in the client assembly that's generated when the client is compiled.)

I l @ ve RuBoard


Microsoft Visual J# .NET (Core Reference)
Microsoft Visual J# .NET (Core Reference) (Pro-Developer)
ISBN: 0735615500
EAN: 2147483647
Year: 2002
Pages: 128

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