Building a Remote Service

Let’s take the plunge into .NET Remoting and start building a service that can be exploited and consumed from remote clients. In Chapter 13, we’ll extend the service to make it openly available to Internet clients too. In this way, you can really grab the essence of .NET Framework distributed programming and understand the key differences that keep .NET Remoting and Web services separate even though they’re both children of a common model for remotable objects.

A .NET Remoting server and a Web service are both .NET Framework classes. As such, they can inherit from a parent class and can be left open to further inheritance. As you’ll see in more detail in Chapter 13, a Web service class can optionally inherit from the WebService class, but there is no syntax obligation. A .NET Remoting server class must inherit from MarshalByRefObject.

The object-oriented nature of the .NET Framework makes sharing classes between a .NET Remoting server and a Web service straightforward. However, because of the inheritance difference just mentioned, you can’t have the Web service and the .NET Remoting server descend from the same base class of functionality. The .NET Framework, in fact, does not permit inheritance from multiple classes.

We’ll start by writing a helper class that constitutes the programming interface for both the .NET Remoting server in this chapter and the Web service we’ll create in Chapter 13. The remote service is actually a class built around the Northwind database that lets you obtain gross sales information on a per-year basis. A nice feature of this service is that it lets you obtain information in two ways: as raw tabular data to format and analyze or as a ready-to-print, snazzy bar chart.

Writing the Data Provider Class

Because our final goal is exposing a common set of functionalities through both the .NET Remoting server and the Web service interfaces, let’s group all the needed core code into a separate middle-tier class that both higher-level layers can easily call. We’ll call this helper class SalesDataProvider and bury into its code all the details about connection strings, SQL commands, and bar chart creation. The class outline is shown here:

namespace XmlNet.CS 
{
    public class SalesDataProvider
    {
        // Constructor(s)
        public SalesDataProvider() {...}

        // Internal properties
        private string m_conn = "DATABASE=northwind;SERVER=...;UID=sa;";
        private int m_Year = 0;
        
        // Returns sales details for the specified year
        public DataTable GetSalesReport(int theYear) {...}

        // Create a bar chart with the sales data for the specified year
        public string GetSalesReportBarChart(int theYear) {...}

        // INTERNAL METHODS

        // Fetch the data
        private DataTable ExecuteQuery(int theYear) {...}

        // Draw the bar chart based on the data in the specified table
        private string CreateBarChart(DataTable dt) {...}

        // Encode the specified bitmap object as BinHex XML
        private string SaveBitmapAsEncodedXml(Bitmap bmp)
    }
} 

The class contains only a couple of public methods—GetSalesReport and GetSalesReportBarChart. These methods will also form the public interface of the .NET Remoting server we’ll build in this chapter and the Web service slated for Chapter 13.

Implementation Details

GetSalesReport takes an integer that indicates the year to consider and returns a DataTable object with two columns—one containing employee last names and one showing total sales for the year for each employee. The method runs the following SQL query against the Northwind database:

SELECT e.lastname AS Employee, SUM(price) AS Sales FROM
  (SELECT o.employeeid, od.orderid, SUM(od.quantity*od.unitprice) 
    AS price
    FROM Orders o, [Order Details] od
    WHERE Year(o.orderdate)=@TheYear AND od.orderid=o.orderid
    GROUP BY o.employeeid, od.orderid 
  ) AS t1 
  INNER JOIN Employees e ON t1.employeeid=e.employeeid
  GROUP BY t1.employeeid, e.lastname

The query involves three tables—Employees, Orders, and Order Details—and basically calculates the total amount of each order issued in the specified year by a particular employee. Finally, the amounts of all orders are summed and returned together with the employee’s last name.

GetSalesReportBarChart works in two steps: first it gets the sales data by calling GetSalesReport, and then it uses this information to create the bar chart. The bar chart is generated as an in-memory bitmap object and is drawn using the GDI+ classes in the System.Drawing namespace. To make the image easily transportable over the wire for .NET Remoting clients as well as for Web service clients, the GetSalesReportBarChart method converts the bitmap to JPEG, encodes the bits as BinHex, and puts the results in an XML string.

Using GDI+ to Create Charts

GDI+ is the latest incarnation of the classic Windows Graphical Device Interface (GDI), a graphics subsystem that enables you to write device-independent applications. The .NET Framework encapsulates the full spectrum of GDI+ functionalities in quite a few managed classes that wrap any GDI+ low-level functions, thus making them available to Web Forms and Windows Forms applications.

GDI+ services fall into three broad categories: 2-D vector graphics, imaging, and typography. The 2-D vector graphics category includes drawing primitives such as lines, curves, and any other figures that are specified by a set of points on a coordinate system. The imaging category includes functions for displaying, manipulating, and saving pictures as bitmaps and metafiles. The typography category concerns the display of text in a variety of fonts, sizes, and styles. Only the imaging functions are key to the GetSalesReportBarChart implementation.

In GDI+, the Graphics class represents the managed counterpart of the Win32 GDI device context. You can think of it as the central console from which you call all primitives. Everything you draw, or fill, through a Graphics object acts on a particular canvas. Typical drawing surfaces are the window background (including control backgrounds), the printer, and in-memory bitmaps.

The following code creates a new bitmap object and gets a Graphics object from it:

Bitmap bmp = new Bitmap(500, 400);
Graphics g = Graphics.FromImage(bmp);
g.Clear(Color.Ivory);

From this point on, any drawing methods called on the Graphics object will result in changes to the bitmap. For example, the Clear method clears the bitmap’s background using the specified color.

Creating a bar chart is as easy as creating and filling a certain number of rectangles, as shown in the following code. We need to create a bar for each employee in the DataTable object and give it a height that is both proportional to the maximum value to draw and based on the scale given by the bitmap’s size.

// Save the names of the fields to use to get data
string fieldLabel, fieldValue;
fieldLabel = dt.Columns[0].ColumnName;
fieldValue = dt.Columns[1].ColumnName;

// For each employee...
for(int i=0; i<dt.Rows.Count; i++)
{            
    //
    // Set up some internal variables to determine
    // size and position of the bar and the 
    // companion text
    //

    // Draw the value (top of the bar)    
    g.DrawString(dt.Rows[i][fieldValue].ToString(), 
        fnt, textBrush, x, yCaption);

    // Draw the bar
    Rectangle bar = new Rectangle(x, yBarTop, barWidth - 10, barHeight);
    LinearGradientBrush fill = new LinearGradientBrush(bar,
        Color.SpringGreen, Color.Yellow, 
        LinearGradientMode.BackwardDiagonal);
    g.FillRectangle(fill, bar);
    fill.Dispose();

    // Draw the employee name (bottom of the bar)
    g.DrawString(dt.Rows[i][fieldLabel].ToString(), 
        fnt, textBrush, x, barBottom + textHeight);
}                            

At the end of the loop, the bar chart is completely rendered in the Bitmap object. The bitmap is still held in memory in an intermediate, internal format, however. Two more steps are necessary: converting the bitmap to a public format such JPEG, BMP, or GIF, and figuring out a way to persist or transfer its content.

Encoding Images as BinHex

Converting a Bitmap object to one of the commonly used image formats is a nonissue. You call the Save method on the Bitmap object, pick up one of the supported formats, and you’re done. The real difficulty has to do with the planned use of this helper class.

Remember, we designed this class for later use within a .NET Remoting server and a Web service. When Web services in particular are involved, having the helper class save the image to persistent storage just doesn’t make sense. An alternative approach would be saving the bitmap locally on the server in a location accessible for download via FTP or HTTP. Creating files on the server might pose security problems, however, and normally forces the system administrator to change default settings to allow for local files being created.

The SalesDataProvider helper class was designed to return the dynamically created image as an encoded text string packed in an XML document. This approach is not optimal in a .NET Remoting scenario, but it probably represents the only option if you have to also publish the function through a Web service.

As we saw in Chapter 4, the XmlTextWriter class provides methods for encoding and writing arrays of bytes, and an image—no matter the format—is just an array of bytes. A further step is needed to transform the Bitmap object into an array of bytes that make up a JPEG image. To convert a Bitmap object to a real-world image format, you must use the Save method. The Save method can accept only a file name or a stream, however.

To solve this problem, you first save the bitmap as a JPEG image to a memory stream. Next you read back the contents of the stream as an array of bytes and write it to an XmlTextWriter object as BinHex or base64 code, as shown here:

// Save the bitmap to a memory stream
MemoryStream ms = new MemoryStream();
bmp.Save(ms, ImageFormat.Jpeg);
int size = (int) ms.Length;

// Read back the bytes of the image
byte[] img = new byte[size]; 
img = ms.GetBuffer();
ms.Close();

The preceding code snippet converts the instance of the Bitmap object that contains the bar chart to an array of bytes—the img variable—that represents the JPEG version of the bitmap.

As the final step, you encode the bytes as BinHex (or base64, if you prefer) and write them to an XML stream, as shown here:

// Prepare the writer
StringWriter buf = new StringWriter();
XmlTextWriter xmlw = new XmlTextWriter(buf);
xmlw.Formatting = Formatting.Indented;

// Write the XML document
xmlw.WriteStartDocument();
xmlw.WriteComment("Sales report for " + m_Year.ToString());
xmlw.WriteStartElement("jpeg"); 
xmlw.WriteAttributeString("Size", size.ToString());
xmlw.WriteBinHex(img, 0, size);
xmlw.WriteEndElement(); 
xmlw.WriteEndDocument();

// Extract the string and close the writer
string tmp = buf.ToString();
xmlw.Close();
buf.Close();

The XmlTextWriter object is still a stream-based component that needs a destination to write to. Unlike the Bitmap object, however, the XmlTextWriter object can be forced to write the output to a string. To do that, you initialize the XML text writer with an instance of the StringWriter object. The final string with the XML code can be obtained with a call to the StringWriter object’s ToString method.

The format of the XML text returned is shown here:

<?xml version="1.0" encoding="utf-16" ?> 
<!-- Sales report for 1997 --> 
<jpeg Size="20146">
  FFD8FF...E00010 
</jpeg>

Notice that the comment and the size of the file are strictly call-specific parameters. The Size attribute refers to the size of the BinHex-encoded text. As you’d expect, this value is significantly larger than JPEG size. Having that value available is not strictly necessary, but once it’s on the client, it can simplify the task of transforming the XML stream back into a JPEG image.

StringWriter and Unicode Encoding

The XML output generated by the GetSalesReportBarChart method uses the Unicode encoding scheme—UTF-16—instead of the default UTF-8. This would be fine if not for the fact that Microsoft Internet Explorer returns an error when you double-click the XML file. The error has nothing to do with the XML itself; it is more a bug (or perhaps even a feature) of Internet Explorer and the internal style sheet Internet Explorer uses to display XML documents.

In general, UTF-16 is used whenever you write XML text to a StringWriter object. When a TextWriter object (StringWriter inherits from TextWriter) is passed to the XmlTextWriter constructor, no explicit encoding argument is allowed. In this case, the XmlTextWriter object transparently inherits the encoding set contained in the writer object being passed. The StringWriter class hard-codes its Encoding property to UTF-16—there’s no way for you to change it, because the property is marked as read-only. If you want to generate XML strings with an encoding scheme other than UTF-16, drop StringWriter objects in favor of memory streams.


The helper class shared by the remotable object and the Web service is now ready to use. Let’s look more closely at the remote service component.

Writing the Remote Service Component

As mentioned, a remotable component has just one requirement: the class that represents the object must be inherited from MarshalByRefObject. Unless you need to exercise stricter control over the object lifetime, you don’t need to override any of the methods defined in the base class for MBR objects.

Apart from the parent class, a remotable class is not different from any other class in the .NET Framework. All of its public methods are callable by clients, the class can implement any number and any type of interfaces, and the class can reference any other external class.

Because we already put all the core code in the SalesDataProvider class, writing the remote service class—ServiceSalesProvider—is a snap. The class is a simple wrapper for SalesDataProvider, as shown here:

public class ServiceSalesProvider : MarshalByRefObject
{
    // Properties
    protected SalesDataProvider m_dataManager;

    // Constructor
    public ServiceSalesProvider()
    {
        m_dataManager = new SalesDataProvider();
    }
        
    // GetSalesReport
    public DataSet GetSalesReport(int theYear)
    {
        DataSet ds = new DataSet();
        ds.Tables.Add(m_dataManager.GetSalesReport(theYear));
        return ds;
    }
    
    // GetSalesReportBarChart
    public string GetSalesReportBarChart(int theYear)
    {
        return m_dataManager.GetSalesReportBarChart(theYear);
    }
}

The SalesDataProvider protected member is initialized only once, when the ServiceSalesProvider class instance is constructed. After that, any call to the various methods is resolved using the same instance of the helper class.

The ServiceSalesProvider class has two public methods with the same names as the methods in SalesDataProvider. The implementation of these methods is straightforward and fairly self-explanatory. The only aspect worth noting is that the remotable GetSalesReport method adds the DataTable object returned by the corresponding method on the SalesDataProvider class to a newly created DataSet object. The DataSet object is then returned to the caller.

Note

When writing remotable classes, be sure that all the methods use and return serializable classes. No extra steps are required if you decide to write your own, user-defined classes as long as they include SerializableAttribute or implement the ISerializable interface.


Publishing the Remote Service Component

To be usable in a distributed environment, a remotable class must be configured and exposed so that interested callers can reach it. A remotable object needs a running host application to handle any incoming calls. In addition, the object must specify what protocol, port, and name a potential client must use to issue its calls. All requirements that callers must fulfill are stored in the remote object’s configuration file.

The Host Application

The host application can be IIS or a custom program (for example, a console application or a Microsoft Windows NT service) written by the same team that authored the class. Unlike DCOM, the .NET Remoting system does not automatically start up the host application whenever a client call is issued. To minimize network traffic, .NET Remoting assumes that the host application on the server is always up, running, and listening to the specified port. This is not an issue if you choose IIS as the host, as IIS is generally up all the time.

If you use a custom host, you must make sure it is running when a call is issued. A simple, yet effective, host program is shown here:

// MyHost.cs -- compiled to MyHost.exe
using System;
using System.Runtime.Remoting;

public class MyHost
{
    public static void Main()
    {
        RemotingConfiguration.Configure("MyHost.exe.config");
        Console.WriteLine("Press Enter to terminate...");
        Console.ReadLine();
    }
}

The key statement in the preceding code is this:

RemotingConfiguration.Configure("MyHost.exe.config");

The host program reads the given configuration file and organizes itself to listen on the specified channels and ports for calls directed to the remote object. The configuration file contains information about the remote class name, the assembly that contains the class, the required activation mode (Client, Singleton, or SingleCall), and, if needed, the object URI. Here is the configuration file that fully describes the ServiceSalesProvider class:

<configuration>
   <system.runtime.remoting>
      <application>
         <service>
            <wellknown mode="SingleCall" 
              type="XmlNet.CS.ServiceSalesProvider, ServiceSalesProvider" 
              objectUri="ServiceSalesProvider.rem" />
         </service>
         <channels>
           <channel ref="http" />
         </channels>
      </application>
   </system.runtime.remoting>
</configuration>

We’ll look more closely at channels and activation modes in a moment. For now, keep in mind that the contents of this configuration file tell the host application (whatever it is) which channels and ports to listen to and the name and the location of the class. In this example, the host application listens to the HTTP channel, and therefore the port must be 80.

Predefined Channels

A channel is the element in the .NET Remoting architecture that physically moves bytes from one endpoint to the other. A channel takes a stream of bytes, creates a package according to a particular protocol, and routes the package to the final destination across remoting boundaries. A channel object listens for incoming messages and sends outbound messages. The messages it handles consist of packets written in accordance with a variety of network protocols.

The .NET Framework provides two predefined channels, tcp and http, both of which are bidirectional and work as senders and receivers. The tcp channel uses a binary formatter to serialize data to a binary stream and transport it to the target object using TCP through the specified port. The http channel transports messages to and from remote objects using SOAP and always through port 80. A channel can connect two AppDomains in the same process as well as two machines over a network.

An object can legitimately decide to listen on both channels. In this case, the <channels> subtree in the configuration file changes as follows:

<channels>
  <channel ref="http" />
  <channel ref="tcp" port="3412" />
</channels>

A client can select any of the channels registered on the server to communicate with the remote object. At least one channel must be registered with the remoting system on the server.

Using IIS as the Remoting Host

If you write your own host application, you can make it as flexible as you need. If you decide to use IIS as the host, some constraints apply. To use IIS instead of a handcrafted host as the activation agent, you must first create a virtual directory (say, SalesReport) and copy the object’s assembly in the BIN subdirectory. The configuration file must have a fixed name—web.config—and must reside in the virtual directory’s root, as shown in Figure 12-4.

Figure 12-4. The SalesReport virtual directory created to make the remotable object accessible.


If you choose IIS as the activation agent, you must be aware of a few things. IIS can listen only to the http channel; any other channel you indicate is simply ignored. The way IIS applies the information read from the web.config file is hard-coded and can’t be programmatically controlled or changed. However, you can create a global.asax file in the virtual folder, hook the Application_Start event, and then execute some custom code. In addition, the inevitable use of SOAP as the underlying protocol increases the average size of network packets.

Note

As often happens, the use of IIS as the activation agent has pros and cons. You don’t need to write any extra code, but you lose a bit in flexibility. Regaining the lost flexibility is still possible, but at the price of writing nontrivial code. For example, you can write an Application_Start event handler and apply extra binary formatters at both ends of the http channel. In this way, the SOAP packets will contain binary data and you’ll save some bytes.


Using IIS as the activation agent is natural when you plan to expose the same remote service through .NET Remoting and Web services. So let’s assume in our example application that IIS is the activation agent and SalesReport is the virtual directory.

Activation Policies

In addition to the remotable object’s identity, channels, and ports, the server configuration file also contains another important piece of information—the object activation policy. An MBR remotable object can be either server-activated or client-activated. Server-activated objects are created by the server only when the client invokes the first method through the local proxy. Client-activated objects are created on the server as soon as the client instantiates the object using either the new operator or methods of the System.Activator class.

In addition, server-activated objects can be declared as Singleton or SingleCall objects. A Singleton object has exactly one instance to serve all possible clients. A SingleCall object, on the other hand, requires that each incoming call is served by a new instance of the remotable object. A remotable object declares its required activation policy in the configuration file through specific subtrees placed below the <application> node.

Server-Side Activation

Server-activated objects are remotable objects whose entire life cycle is directly controlled by the host application. Server-activated objects are instantiated on the server only when the client calls a method on the object. The object is not instantiated if the client simply calls the new operator or the methods of the System.Activator object. This policy is slightly more efficient than client-side activation because it saves a network round-trip for the sole purpose of creating an instance of the target object. In addition, this approach makes better use of server memory by delaying as much as possible the object instantiation.

What happens when the client code apparently instantiates the remote object? Consider the following client-side sample code:

ServiceSalesProvider ssp = new ServiceSalesProvider();
string img = ssp.GetSalesReportBarChart(theYear);

The remoting client treats the remote object as a local object and calls the new operator on it. The object has been previously registered as a well-known type, so the .NET Remoting system knows about it. In particular, the .NET Remoting system knows that any object of type ServiceSalesProvider is just a local proxy for a remote object. When the client calls new or System.Activator on the well-known type, only the remoting proxy is created in the client application domain.

The real instantiation of the object will take place on the server at a later time, when a non-null instance is needed to serve the first method call. Because the constructor is called implicitly and outside the control of the client, only the default constructor is supported. This means that if your class has a constructor that takes some arguments, that constructor is never taken into account by the host application and never used to create instances of the remotable class.

Note

As part of the .NET Framework reflection API, the System.Activator object provides a CreateInstance method that you can use to create instances of dynamically determined types. (Instantiating types this way is a kind of .NET Framework late binding.) Interestingly, this method supports a nice feature that would have fit well in the .NET Remoting system too (and hopefully will in a future version). The CreateInstance method has an overload that takes an array of object objects. It then uses the size of the array and the actual types boxed in the various objects to match one of the constructors declared on the target type. However, maybe for performance concerns or perhaps just to simplify the feature, the .NET Remoting infrastructure does not supply this facility.


If you need to publish a remotable type whose instances must be created using a specific, nondefault constructor, you should resort to client activation.

Well-Known Objects

From the perspective of a .NET Remoting client, server-activated objects are said to be well-known objects. Well-known objects have two possible working modes: Singleton and SingleCall. In the former case, one instance of the object services all calls from all clients. In the latter case, a new instance of the object is created to service each call.

A well-known object declares its working mode using the <wellknown> tag in the configuration file under the <service> tag, as shown here:

   <service>
     <wellknown mode="SingleCall" 
       type="XmlNet.CS.ServiceSalesProvider, ServiceSalesProvider" 
       objectUri="ServiceSalesProvider.rem" />
   </service>

The mode attribute specifies the working mode of the well-known object. Allowed values are Singleton and SingleCall, defined in the WellKnownObjectMode enumeration. The type attribute contains two pieces of information. It is a comma-separated string in which the first token represents the fully qualified name of the remotable type and the second part of the string points to the assembly in which the remotable type is defined. You must use the display name of the assembly without the DLL extension. The assembly must be located either in the global assembly cache (GAC) or on the server in a location that the host application can reach.

If the host application is a normal console application or a Windows NT service, the directory of the application’s executable is a safe place to store the remotable type’s assembly. Similarly, you can store the assembly in any other path for which the host application is configured to probe when searching for assemblies. If you use IIS as the activation agent, all the assemblies needed for the remotable type must be located in the BIN directory of the host application.

Giving Well-Known Types a URI

A well-known type also needs to be identified by a unique URI. The URI must be unique for the type and not for the object. This name represents remote objects of a certain type and is the means by which the client gets a proxy pointing to the specified object. The server-side remoting infrastructure maintains a list of all published well-known objects, and the object URI is the key to access this internal table. Well-known objects must explicitly indicate the URI. For client-activated objects, a unique URI is transparently generated (and used) for a particular instance of the class.

When an object is hosted in IIS, the objectUri name must have a .soap or .rem extension, as shown in Figure 12-5. This naming convention enables IIS to recognize the incoming call as a remoting request that must be routed to a particular handler.

Figure 12-5. The IIS application mapping table for .rem and .soap URIs.


When IIS detects a remoting call, it passes the call to the ad hoc HTTP handler registered to handle .soap and .rem resources. Although the object URI gives the impression of being a URL—that is, a true server-side resource—it is only a name and should in no way correspond to a physical file. Whether the URI should be a string or the name of a physical resource depends on the expectations of the handler. The remoting handler uses .soap and .rem URIs as strings to retrieve the proxy for the type.

Singleton Objects

When an object declares itself as a Singleton type, the host application uses only a single instance of the object to service all incoming calls. So when a call arrives, the host attempts to locate the running instance of the object. If such an instance exists, the request for execution is processed. Otherwise, the host creates the unique instance of the remote class (using the default constructor) and forwards the request to it.

What happens if two requests arrive at the same time? The .NET Remoting subsystem arranges for them to be automatically serviced by distinct threads. This requires that Singleton objects be thread-safe. Note that this is not a mandatory programming rule but is more of a practical guideline for real-world scenarios.

State management for Singleton objects is certainly possible in theory, but it must be coded in the body of the object in much the same way as you do with Active Server Pages (ASP) and Microsoft ASP.NET pages and even Web services. The idea is that you use a shared cache that all clients can access (a sort of ASP.NET Application object), unless you apply a filter on a per-client basis (a sort of ASP.NET Session object).

The lifetime of a Singleton well-known object is managed by the .NET Remoting system through a special module called the lease manager (LM). (See the section “Memory Management,” on page 551, for more information.)

SingleCall Objects

A well-known type declared as a SingleCall object has a new instance of it created whenever a request arrives. The host application creates a new instance of the SingleCall object, executes the requested method, and then routes any return values back to the client. After that, the object goes out of scope and is left to the garbage collector.

Although it’s not completely impossible, preserving state from one call to the next is realistically a bit impractical for SingleCall objects. In this case, the lifetime of the object instance is extremely short and barely covers the duration of the method call. You can try either storing information in a database (or any sort of persistent storage medium) or parking data in other objects with a different lifetime scheme.

Client-Side Activation

Client-activated objects are instantiated on the client as the result of a call to the new operator or to the System.Activator object. Each remoting client runs its own copy of the object and can control it at will. For example, the client can use any of the available constructors. In addition, persisting the state during the session is straightforward and does not require any special coding. On the down side, sharing state between clients is difficult, and to do so, you must resort to a database, a disk file, or any other global object in the current AppDomain.

The following code snippet shows how to change the contents of the <service> tag to reflect a client-activated object. Instead of the <wellknown> tag, you use the <activated> tag. This tag supports only the type attribute. No object URI is necessary with client-activated objects. More precisely, the URI is still necessary, but because the activation occurs on the client and at a very specific moment in time, the URI can be silently generated by the .NET Remoting infrastructure and attached to each call.

<service>
  <activated 
    type="XmlNet.CS.ServiceSalesProvider, ServiceSalesProvider" />
</service>

As with Singleton objects, the lifetime of a client-activated object is controlled by the LM. The instance of the object remains active until the proxy is destroyed.

Choosing the Activation Mode That Fits

Theoretically, all the working modes examined up to now don’t affect in any shape or fashion the way in which you code your remotable classes. For example, a client-activated object is in no way different from a Singleton object. All options can be set declaratively and, again speaking theoretically, each object can be configured to work in different ways simply by changing a few entries in the server’s configuration file.

Intriguing as this possibility is, such flexibility is not realistic in practice because a real-world object might want to exploit in depth the specific features of a working mode. In other words, you should thoughtfully and carefully choose the configuration options for your remote object and then stick to that configuration as long as the user’s requirements are stable. For example, if you determine that the Singleton mode is appropriate for your component, you will probably want to implement an internal state management engine to share some variables. When at a later time you decide to set the object to work—say, in SingleCall mode—the state management engine is somewhat useless.

Let’s analyze our ServiceSalesProvider class to determine the most appropriate options. To begin, the object needs to query a back-end database (Northwind). Even this little requirement is enough to lead us to discard the option of making the object available by value. As an MBR object, the remotable class can be client-activated or server-activated. What’s better to us?

The ServiceSalesProvider class doesn’t need a nondefault constructor, so both client-activated and server-activated modes are fine. The object is expected to work as a one-off service and has no need to maintain per-client state, so you can discard the client-activated option and go for the server-driven activation. OK, but should you opt for Singleton or SingleCall?

SingleCall—that is, a short-lived instance that serves the request and dies—is certainly an option. If you use the object as a Singleton, however, you can architect slightly more efficient code and avoid having to query SQL Server each and every time a request comes in. The remoting code included in this book’s sample files makes use of the ServiceSalesProvider class configured to run as a SingleCall object.

Memory Management

SingleCall objects present no problems in terms of memory management. They require a new object instance that is extremely volatile and does not survive the end of the method’s code. Singleton and client-activated objects, on the other hand, need a mechanism to determine when they can be safely destroyed. In COM, this issue was resolved by implementing reference counting. In the .NET Remoting system, the same tasks are accomplished using a new module: the LM.

Unlike reference counting, the LM works on a per-AppDomain basis and allows objects to be released even though clients still hold a reference. Let’s quickly review the differences between these two approaches.

Old-Fashioned Reference Counting

Reference counting requires clients—including, of course, distributed and remote clients—to communicate with the server each time they connect or disconnect. The object maintains the number of currently active client instances, and when the count goes to 0, the object destroys itself.

In the presence of an unreliable network, however, chances are good that some objects might remain with a reference count that never goes to 0. If this weren’t bad enough, the continual sequence of AddRef/Release calls would generate significant network traffic.

The Lease Manager (LM)

The idea behind leasing is that each object instance is leased to the client for a given amount of time fixed by the LM. The lease starts when the object is created. By default, each Singleton or client-activated object is given 5 minutes to process incoming calls. When the interval ends, the object is marked for deletion. During the object’s lifetime, however, any processed client call resets the lease time to a fixed value (by default, 2 minutes), thus increasing or decreasing the overall lease time.

Note that leasing is managed exclusively on the server and doesn’t require additional network traffic, apart from the traffic needed for normal method execution. The initial lease time and the renewal period can be set both programmatically and declaratively in the configuration file.

Getting a Sponsor

Another mechanism for controlling an object’s lifetime is sponsorship. Both clients and server objects can register with the AppDomain’s LM to act as sponsors of a particular object. Prior to marking an object for deletion when its lease expires, the .NET Remoting run time gives sponsors a chance to renew the lease. By implementing sponsors, you can control the lifetime of objects based on logical criteria rather than strict time intervals.

In summary, nothing can guarantee that clients will always find their server objects up and running. When a remoting client attempts to access an object that is no longer available, a RemotingException exception is thrown. One way to resolve the exception is by creating a new instance of the remote object and repeating the operation that failed.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.143.144.82