using System.Data.SqlClient; using System.EnterpriseServices;
[Transaction(TransactionOption.Required)] public class ExAirMain : ServicedComponent
{
public void Process()
{
/* call methods to add Food info and Ticket info */
AddFood process1 = new AddFood(); AddAirline process2 = new AddAirline(); process1.Add();
process2.Add();
}
}
[Transaction(TransactionOption.Supported)]
[AutoComplete]
public class AddFood : ServicedComponent
{
public void Add()
{
SQLConnection cnn = new SQLConnection("FoodSupplierConnection");
SQLCommand cmd = new SQLCommand(); cnn.Open();
cmd.ActiveConnection = cnn;
cmd.CommandText = ""; // Insert statement to DB cmd.ExecuteNonQuery();
cnn.Close();
}
}
[Transaction(TransactionOption.Supported)]
[AutoComplete]
public class AddAirline : ServicedComponent
{
public void Add()
{
SQLConnection cnn = new SQLConnection("AirlineConnection"); SQLCommand cmd = new SQLCommand(); cnn.Open();
cmd.ActiveConnection = cnn;
cmd.CommandText = "" // Insert statement to DB cmd.ExecuteNonQuery();
cnn.Close();
}
}
}
Accessing Object Context
The System.EnterpriseServices namespace includes a class called ContextUtil, which can be used by C# classes to access an object's COM+ runtime context. In Visual Basic 6, you
accessed the object context of the current component through the ObjectContext object, as the following code demonstrates:
Dim ctx as ObjectContext ctx = GetObjectContext
The ContextUtil class contains several properties and methods that give callers access to COM+ context state information. All of the methods and properties in the class are static, which means that you can access the members directly from the ContextUtil class without creating an object of the class. Table 35-3describes the properties of the ContextUtil class, and Table 35-4describes the methods of the ContextUtil class.
Table 35-3: ContextUtil Class Properties
Property
Description
ActivityId
Gets a GUID representing the activity containing the
component
ApplicationId
Gets a GUID for the current application
ApplicationInstanceId
Gets a GUID for the current application instance
ContextId
Gets a GUID for the current context
DeactivateOnReturn
Gets or sets the done bit in the COM+ context
IsInTransaction
Gets a value indicating whether the current context is
transactional
IsSecurityEnabled
Gets a value indicating whether role-based security is active in
the current context
MyTransactionVote
Gets or sets the consistent bit in the COM+ context
PartitionId
Gets a GUID for the current partition
Transaction
Gets an object describing the current COM+ DTC transaction
TransactionId
Gets the GUID of the current COM+ DTC transaction
Table 35-4: ContextUtil Class Properties
Property
Description
DisableCommit
Sets both the consistent bit and the done bit to False in the COM+
context
EnableCommit
Sets the consistent bit to True and the done bit to False in the
COM+ context
GetNamedProperty
Returns a named property from the COM+ context
IsCallerInRole
Determines whether the caller is in the specified role
SetAbort
Sets the consistent bit to False and the done bit to True in the
COM+ context
SetComplete
Sets the consistent bit and the done bit to True in the COM+
context
The code in Listing 35-4implements a transactional COM+ component that implements a public method called DoWork(). The DoWork() method checks the IsCallerInRole() property
to determine the caller's COM+ role. If the caller's role is the ClientRole role, then the object's transaction is committed with a call to SetComplete(). If the caller's role is a role other than the ClientRole role, then the object's transaction is rolled back with a call to SetAbort().
Listing 35-4: Accessing COM+ Context Through the ContextUtil Class
Exposing your C# class as a COM+ application takes very little effort and is certainly easier than implementing the same functionality using earlier versions of Visual Studio 6.0. COM+ applications written using Visual C++ 6.0 needed much more code to complete the same tasks, and some COM+ features (such as object pooling) were not even available to COM+ components written in Visual Basic 6.0.
Developing COM+ components using C# involves only four simple concepts:
•Derive your class from ServicedComponent
•Add attributes to describe your application's settings
•Use the regsvcs tool to build a COM+ application for your public classes
•Call methods and properties on the ContextUtil class to access the COM+ context at runtime.
Microsoft dropped hints about this COM+ programming model as far back as 1997. Back then, they described a model based on attributed programming, in which COM components could be described with attributes and the runtime would take over details such as class factories and IUnknown-style reference counting. It is now clear that the .NET model of COM+ component development is the fulfillment of that original vision.
Chapter 36: Working with .NET Remoting
In This Chapter
The .NET Framework provides several mechanisms that enable you to write applications that do not exist in the same application domain, server process, or machine. Based on the requirements of your application, such as the capability of non-.NET servers to access your data, you can choose any of the different types of object communication methods. In this chapter, you learn about .NET remoting. Using remoting, you can marshal objects and method calls across process boundaries and effectively pass data between applications.
In earlier chapters, you learned that ASP.NET and XML Web services were also excellent ways to pass objects and data between process boundaries, but depending on your application infrastructure, those services may not be the best option available. Remoting fills in any of the gaps that were left open by these services. In this chapter, you learn how to implement remoting, how to create the client and server objects in a remoting framework, and how to effectively pass data using remoting across process boundaries.
Introducing Remoting
.NET remoting enables applications to communicate between objects that reside on different servers, different processes, or different application domains. Before the .NET Framework was introduced, you could pass objects across process boundaries by using distributed COM, or DCOM. DCOM worked well, but it had limitations, such as the types of data that could be passed, and security context passed between the client caller and server activation. Moreover, it was based on COM, which meant that although you could communicate across machine boundaries, all machines had to be running a Microsoft operating system. This isn't a critical limitation, but it limited your options regarding what you could do with your existing infrastructure. In .NET, remoting takes care of these issues, and expands upon what DCOM offered as a viable method to remotely communicate between objects.
With remoting, you implement a server, or host application, and a client application. On the host or client, the application can be any of the .NET application templates that are available, including console applications, Windows services applications, ASP.NET applications,
WindowsForms applications, and IIS applications. On the host, you programmatically configure — or use a configuration file to specify — the type of activation that will be allowed by clients. Clients can use one of several types of activation methods, including Singleton and SingleCall, which is covered in the section "Activating the Remote Object," later in this chapter. It is at this point that you specify the channel and the port over which the object communicates, and the format the data will have when it is passed between host and client. You learn how channels and ports are implemented a little bit later. The format of the data is important, based on the system design; you can use binary data, SOAP, or a custom format to marshal the data. After you specify the channel, port, and format, based on the type of remoting host you are exposing, you need to determine how to expose the metadata to the clients. You can do this in several ways, such as by allowing the caller to download the assembly, or by making the source available to the caller. Either way, the client needs to know what object it is creating, so the metadata in some form needs to be made available to the caller. After the host is properly built and configured, you then write the client. On the client, all you need to do is create an instance of the object on the specified channel and port that is expecting requests on the server. You accomplish this programmatically or through a configuration file. At this point, the method calls are no different from any other object that you consume from a .NET application. After you create objects, you call methods, set and retrieve properties, and fire off events just as you would with an object that is not using the remoting framework.
This may seem like a lot of steps, but it is actually very simple after you have done it once. You can break down the overall process into the following broader tasks, which are illustrated in Figure 36-1.
Figure 36-1: .NET remoting overview
1.Specify the channels and ports that marshal objects between the host and the client.
2.Use formatters (which you will learn about later in the chapter) to specify the format in which the data is serialized and deserialized in between the host and the client.
3.Determine how the host objects are activated and how long the activation lasts.
In the following sections, you learn how to create the host application in a remoting scenario, including the specifics of formatters, channels, and ports, and how the host will be activated. After the host is built, you learn how to consume the remote object from a client application.
Creating a Remoting Host Assembly
To begin your remoting application, you need to create an assembly containing the actual method calls that the host application will use. Once you have created the assembly, you then
create the host application that accepts client requests for the methods in the assembly. In the following steps, you build the assembly that implements the methods to be called:
1.Create a new C# Class Library application and call it HostObject. For simplicity, I have created a directory on my C drive called cSharpRemoting, and have added three subfolders named Host, HostObject, and Client. You might see where we are going with this. The HostObject Class Library application should be created in the cSharpRemoting\HostObject directory. This makes is easier for you to run the console applications you create later.
2.After you create the HostObject class library application, you add a public method that accepts a parameter, called customerID, and returns the name of the customer from the Northwind database from SQL Server based on the customerID passed in. Your completed class for the HostObject application should look something like the one shown in Listing 36-1.
Listing 36-1: Creating the Host Object Application
using System; using System.Data;
using System.Data.SqlClient;
namespace HostObject
{
public class Class1: MarshalByRefObject
{
public string thisCustomer;
public Class1()
{
Console.WriteLine("HostObject has been activated");
Console.WriteLine(thisCustomer + " was returned to the client");
return thisCustomer;
}
}
}
The preceding code performs a simple query to SQL Server to grab the CompanyName field in the Customers database based on the parameter customerID, which is passed into the method. As you can see, this code is no different from any other class library that you would create in C#. The next step is to create the host application that services the client requests for this class library.
Creating the Remoting Server
To create the application that will host the HostObject assembly, which is where you actually start to use some of the remoting features created in Listing 36-1, you need to create a console application called Host in the C:\cSharpRemoting\Host directory. This host application is the actual remoting server that uses the features of the System.Runtime.Remoting namespace.
Before you start any coding, several key features of remoting need to be described. The namespace that contains the remoting functionality is the System.Runtime. Remoting namespace, whose classes are described in Table 36-1. Although you do not use all of these classes when writing remoting applications, several of the classes are extremely important to inplementing a remoting infrastrucure; namely, the ObjRef class, RemotingConfiguration class, the RemotingServices class, and the WellKnownObjectMode enumeration. You learn more about each of these in detail later in this section as you write the code for your host application.
Table 36-1: System.Runtime.Remoting Classes
Class
Description
ActivatedClientTypeEntry
Holds values for an object type registered on the
client end as a type that can be activated on the server
ActivatedServiceTypeEntry
Holds values for an object type registered on the
service end as one that can be activated on request
from a client
ObjectHandle
Wraps marshal by value object references, enabling
them to be returned through an indirection
ObjRef
Stores all relevant information required to generate a
proxy to communicate with a remote object
RemotingConfiguration
Provides various static methods for configuring the
remoting infrastructure
RemotingException
The exception that is thrown when something has
gone wrong during remoting
Table 36-1: System.Runtime.Remoting Classes
Class
Description
RemotingServices
Provides several methods for using and publishing remoted objects and proxies. This class cannot be inherited
RemotingTimeoutException
The exception that is thrown when the server or the client cannot be reached for a previously specified period of time
ServerException
The exception that is thrown to communicate errors to the client when the client connects to non-.NET Framework applications that cannot throw exceptions
SoapServices
TypeEntry
Provides several methods for using and publishing remoted objects in SOAP format
Implements a base class that holds the configuration information used to activate an instance of a remote type
WellKnownClientTypeEntry
WellKnownServiceTypeEntry
Holds values for an object type registered on the client as a well-known type object (single call or singleton)
Holds values for an object type registered on the service end as a well-known type object (single call or singleton)
To begin writing the host application, you need to understand what the remoting infrastructure needs to operate. To refresh your memory regarding the steps you need to take to create the host application, review the following three steps outlined earlier in the chapter:
1.Specify the channels and ports that marshal objects between the host and the client.
2.Use formatters to specify the format in which the data is serialized and deserialized in between the host and the client.
3.Determine how the host objects are activated, and how long the activation lasts.
The following sections examine each one of these steps.
Specifying channels and ports
In the remoting infrastructure, channels handle the transporting of messages, or data, between the client and the server objects. Recall what is actually happening when you are remoting objects: You are crossing a boundary, such as an application domain, a server process, or a physical machine. The specific channel that you provide handles all of the underlying details of getting the data to and from the remote objects; you simply specify a channel type and all of the dirty work is done for you. The System.Runtime.Remoting.Channels class provides the implementations for creating the channels that are used in your remoting host. When you register a channel in your application, you must make sure that it is registered before you attempt to access the remote objects. Failing to correctly register your channels causes an error. If another application is listening on a channel that you are attempting to listen on, an error occurs and your host application fails to load. You need to know which channels are
being used, and which channel your application needs to use, based on what the client is calling on. After you declare an instance of the channel type that you are going to use, you call the RegisterChannel() method of the ChannelServices class, which registers the channel for use. Table 36-2describes the methods of the ChannelServices class that are available to you.
Table 36-2: ChannelServices Class Methods
Method
Description
AsyncDispatchMessage
Asynchronously dispatches the given message to the
server-side chain(s) based on the URI embedded in
the message
CreateServerChannelSinkChain
Creates a channel sink chain for the specified
channel
DispatchMessage
Dispatches incoming remote calls
GetChannel
Returns a registered channel with the specified name
GetChannelSinkProperties
Returns an IDictionary of properties for a given
proxy
GetUrlsForObject
Returns an array of all the URLs that can be used to
reach the specified object
RegisterChannel
Registers a channel with the channel services
SyncDispatchMessage
Synchronously dispatches the incoming message to
the server-side chain(s) based on the URI embedded
in the message
UnregisterChannel
Unregisters a particular channel from the registered
channels list
Not listed in this table is a property in the ChannelServices class called RegisteredChannels, which sets or gets the registered channels in the current object instance.
The following code snippet creates and registers a TCP and HTTP channel on specific ports using the RegisterChannel method of the ChannelServices class:
TcpChannel chan1 = new TcpChannel(8085);
ChannelServices.RegisterChannel(chan1);
HttpChannel chan2 = new HttpChannel(8086);
ChannelServices.RegisterChannel(chan2);
When you create a channel, you also specify a type of formatter for the channel. The following sections describe the formatter types available.
Specifying a channel format
At the same time that you create a channel, you also specify a format for the type of channel that you have picked. Two default formatter types are available in the System.Runtime.Remoting.Channels namespace: the TCP channel and the HTTP channel.
System.Runtime.Remoting.Channels.Tcp Namespace
The System.Runtime.Remoting.Channels.Tcp namespace contains channels that use the TCP protocol to transport data between remote objects. The default encoding for the TCP is binary encoding, which makes this an efficient way to pass data between remote objects. Binary data always has a smaller footprint than the equivalent XML data passed through SOAP on an HTTP channel. The downside to using the TCP protocol is that it is a proprietary format, so it only works on systems that understand this formatting type. To make your remote object more accessible, you should use the HTTP channel for encoding, as it will be passing data down the wire in the SOAP protocol. Table 36-3summarizes the available classes in the System.Runtime.Remoting.Channels.Tcp namespace.
Provides an implementation for a sender-receiver channel that uses the TCP protocol to transmit messages. This class is a combination of the TcpClientChannel class and the TcpServerChannel class, which enables automatic two-way communication over TCP.
TcpClientChannel
Provides an implementation for a client channel that
uses the TCP protocol to transmit messages.
TcpServerChannel
Provides an implementation for a server channel that
uses the TCP protocol to transmit messages.
System.Runtime.Remoting.Channels.Http Namespace
The System.Runtime.Remoting.Channels.Http namespace contains channels that use the HTTP protocol to transport data between remote objects. The default encoding for the HTTP protocol is SOAP, which makes this a flexible way to pass data between remote objects. Table 36-4summarizes the available classes in the System.Runtime.Remoting.Channels.Http namespace.
Provides an implementation for a sender-receiver channel that uses the HTTP protocol to transmit messages. This class is a combination of the HttpClientChannel class and the HttpServerChannel class, which enables automatic two-way communication over HTTP.
HttpClientChannel
Provides an implementation for a client channel that
uses the HTTP protocol to transmit messages.
HttpRemotingHandler
Implements an ASP.NET handler that forwards
requests to the remoting HTTP channel.
HttpRemotingHandlerFactory
Initializes new instances of the HttpRemotingHandler