Chapter 29. Remoting

Remoting is the .NET technology that enables code in one application domain (AppDomain) to call into the methods and properties of objects running in another application domain. A major use of remoting is in the classic n-tier desktop approach, where presentation code on the desktop needs to access objects running on a server somewhere on the network. Another primary use for remoting is when code in ASP.NET Web Forms or Web Services needs to call objects running on an application server somewhere else on the network. In short, remoting is the technology to use when your n-tier code needs to talk to the business or data tier that is running on an application server.

Remoting is conceptually somewhat similar to Web services. Both remoting and Web services are TCP/IP-based technologies that enable communication between different machines over an IP network. This means that they both pass through firewalls, and they both provide stateless and connectionless communication between machines. These two technologies share many of the same principles.

Note

It is important to recognize that Microsoft has merged the functionality of remoting, Web services, enterprise services, and MSMQ (Microsoft Message Queue) into the Windows Communication Foundation (WCF) — the next generation of the technologies. You can find more information on WCF in Chapter 32.

When working with XML Web Services, you will find that the biggest problem with SOAP — Simple Object Access Protocol — is that it is not lightweight. It is designed with maximum platform interoperability in mind, and this puts certain limits on how data can be transferred. For example, imagine that Platform A stores Integer variables as a 4-byte block of memory, with the lowest-value byte appearing first. Now imagine that Platform B also uses a 4-byte block of memory, but this time the highest-value byte appears first. The encoding of the value is different. Without some form of conversion, if you copy that block of bytes from Platform A to Platform B, the platforms will not be able to agree on what the number actually is. In this scenario, one platform thinks it has the number 4, whereas the other thinks that the number is actually 536870912.

SOAP gets around this problem by representing numbers (and everything else) as strings of ASCII characters — since ASCII is a text-encoding standard that most platforms can understand. However, this means that the native binary representations of the numbers have to be converted to text each time the SOAP document has to be constructed. In addition, the values themselves have to be packaged in something that you can read (with a little bit of effort). This leads to two problems: massive bloat (a 4-byte value starts taking hundreds of bytes to store) and wasted CPU cycles used in converting from native encoding to text encoding and back again.

You can live with all these problems if you only want to run your web service on, say, Windows 2000, and have it accessed through a client running on a cell phone. SOAP is designed to do this kind of thing. However, if you have a Windows XP desktop application that wants to use objects hosted on a Windows 2000 server (using the same platform), the bloated network traffic and wastage in terms of conversion is sub-optimal at best and ridiculous at worst.

Remoting enables you to enjoy the power of Web services but without the downside. If you want, you can connect directly to the server over TCP and send binary data without having to do any conversions. If one Windows computer has a 4-byte block of memory holding a 32-bit integer value, you can safely copy the bit pattern to another Windows computer and both will agree on what the number is. In effect, network traffic sanity is restored and processor time is not wasted doing conversions.

Now that you know what remoting is, you're ready to look at its architecture.

Remoting Overview

It is important to understand several fundamental aspects of remoting, including the basic terms and related objects, which are covered in the following sections.

Basic Terminology

A normal object is not accessible via remoting. By default, .NET objects are only accessible to other code running within the same .NET AppDomain.

A remote object is an object that has been made available over remoting by inheriting from System.MarshalByRefObject. These objects are often also called MBROs. Remote objects are the same kinds of objects that you build normally, except they inherit from MarshalByRefObject and you register them with the Remoting subsystem to make them available to clients. Remote objects are anchored to the machine and AppDomain where they were created, and you communicate with them over the network. The wonderful part of this scenario is that the client is working with a proxy object, instantiated by the client, that enables programmatic access to the remote object just as if it were in the same process as your client application, even though it actually resides in a completely separate process — even on a completely separate machine.

A serializable object is an object that's been made available over remoting by marking the class with the <Serializable()> attribute. These objects will move from machine to machine or AppDomain to AppDomain. They are not anchored to any particular location, so they are known as unanchored objects. A common example of a serializable object is the DataSet, which can be returned from a server to a client across the network. The DataSet physically moves from server to client via the serialization technology in the .NET Framework.

A remoting host is a server application that configures remoting to listen for client requests. Remoting runs within the host process, using the memory and threads of the host process to handle any client requests. The most common remoting host is IIS. You can create custom remoting hosts, which are typically created as a Windows service, so they can run even when no user is logged in to the server. It is also possible to have any .NET application be a remoting host, which enables you to emulate ActiveX EXE behaviors to some degree. This last technique is most commonly used when creating peer-to-peer-style applications.

A channel is a way of communicating between two machines. In order for two separate application processes to communicate with each other, a transport channel is opened between the processes. A transport channel is a combination of underlying technologies required to open a network connection and use a particular protocol to send the bytes to the receiving application. A channel works with a stream of data and creates an object based upon the transport protocol. Out of the box, .NET comes with two channels: TCP and HTTP.

The TCP channel is a lightweight channel designed for transporting binary data between two computers. (The TCP channel is different from the TCP protocol that HTTP also uses.) It works using sockets, something discussed in much more detail in Chapter 31. TCP is more suited for an intranet environment because an intranet sits behind a firewall, and TCP does not always cross firewalls easily. Using TCP also means that a binary formatter is used by default, which actually reduces the size of the object that is being transported. Having smaller packages to transport allows for better and faster network communications.

HTTP, as you already know, is the protocol that web servers use. The HTTP channel hosted in IIS is the recommended approach by Microsoft. HTTP is a firewall-friendly transport because HTTP traffic generally flows directly through firewalls over port 80 (which is generally open on most computers).

After a channel is established through a specific port, a formatter object is needed to initiate the serialization or deserialization of the object. A formatter object is used to serialize or marshal an object's data into a format in which it can be transferred down the channel. In order for an object to move through the channel from one process to another, the formatter must take the object that you are sending and serialize it into the network stream. Out of the box, you have two formatter objects: BinaryFormatter and SoapFormatter. The BinaryFormatter is more efficient and is recommended. The SoapFormatter is not recommended and may be discontinued in future versions of the .NET Framework.

A message is a communication between the client and server. It holds the information about the remote object and the method or property that is being invoked, as well as any parameters.

A proxy is used on the client side to call into the remote object. To use remoting, you do not typically have to worry about creating the proxy — .NET can do it all for you. However, there is a slightly confusing split between something called a transparent proxy and a real proxy. A transparent proxy is so called because "you can't see it." When you request a remote object, a transparent proxy is what you get. It looks like the remote object (that is, it has the same properties and methods as the original). This means that your client code can use the remote object or a local copy of the would-be-remote object without you having to make any changes and without you knowing there is any difference. The transparent proxy defers the calls to the real proxy. The real proxy is what actually constructs the message, sends it to the server, and waits for the response. You can think of the transparent proxy as a "fake" object that contains the same methods and properties that the real object contains. The real proxy is effectively a set of helper functions that manages the communications. You don't use the real proxy directly; instead, the transparent proxy calls into the real proxy on your behalf.

A message sink is an "interceptor object." Before messages go into the channel, these are used to do some further processing on them, perhaps to attach more data, reformat data before it is sent, route debugging information, or perform security checking. On the client side, you have an envoy sink. On the server side, you have a server context sink and an object context sink. In typical use, you can ignore these.

Message sinks are a pretty advanced topic and allow for some powerful extensions to the remoting model. It is not recommended that you create custom sinks, channels, or formatters, so they are not covered in this book. Creating them is not recommended because they will not transfer directly to WCF, the next generation of the technology from Microsoft. If you do opt to create your own custom sink, formatter, or channel, you must expect to rewrite it from scratch when you upgrade to WCF. Figures 29-1 and 29-2 show how these concepts fit together.

Figure 29-1

Figure 29.1. Figure 29-1

Figure 29-1 shows how a client calls the Hello method of a transparent proxy object. The transparent proxy looks just like the real object, so the client doesn't even realize that remoting is involved. The transparent proxy then invokes the real proxy, which converts the method call into a generic remoting message.

Figure 29-2

Figure 29.2. Figure 29-2

This message is sent through any messaging sinks configured on the client. These messaging sinks may transform the message in various ways, including adding encryption or compressing the data.

The message is then serialized by the formatter object. The result is a byte stream that is sent to the server by using the channel configured for use on the client.

Figure 29-2 shows how the server handles the message. The message comes into the server via a channel. The message is then deserialized by the formatter object and run through any messaging sinks configured on the server. These messaging sinks typically mirror those on the client, unencrypting or decompressing the data as appropriate.

Finally, the message is decoded by the object context sink, which uses the information in the message to invoke the method on the actual object. The object itself has no idea that it was invoked via remoting, as the method call was merely relayed from the client.

SingleCall, Singleton, and Activated Objects

The next step is to look at the way that remoting treats objects. In remoting, objects are divided into three camps: well-known objects, client-activated objects, and serializable objects.

  • The well-known (wellknown) objects run on the server and perform a service for the remote application, such as "give me a list of all the customers" or "create an invoice." They can be configured to act similarly to a Web service or use what is called a singleton pattern (which is discussed shortly).

  • Client-activated (Activated) objects are created for each client and maintain state on the server over time. In many ways, these objects act similarly to COM objects you may have accessed via DCOM in the past.

  • Serializable objects can move from machine to machine as needed. For instance, a serializable object can be created on the server (by a wellknown or Activated object) and then returned to a client. When the object is returned to the client, it is physically copied to the client machine, where it can be used by client code.

The following table summarizes the types of object:

Type

Calling Semantics

Key Attributes

SingleCall (wellknown)

An object is created for each client method call made to the server.

Stateless, per-method lifetime, atomic methods, no threading issues, anchored to AppDomain where created

Singleton (wellknown)

One object exists on the server and is used to handle all method calls from all clients.

Stateful, long-lived, shared instance, thread synchronization required, anchored to AppDomain where created

Activated

The client creates Activated objects on the server. The client can create many such objects. Activated objects are available only to the client that created the object.

Stateful, long-lived, per-client instances, threading issues only if client is multithreaded, anchored to AppDomain where created

Serializable

The object is automatically copied from machine to machine when it is passed as a parameter or returned as the result of a function.

Stateful, long-lived, no threading issues, non-anchored (moves across network automatically)

The following sections discuss each object in a bit more detail.

SingleCall Objects

SingleCall objects act much like typical Web service objects. Each time a client calls a method on a SingleCall object, an object is created specifically to handle that method call. Once the method call is complete, the object is not reused and is garbage collected by the .NET runtime.

SingleCall objects also work the way a JIT (just-in-time) Activated object does in COM+, and matches the way most people use MTS or COM+ objects. In those environments, good developers typically create a server-side object, make a method call, and then release the object.

These objects must inherit from System.MarshalByRefObject, so they are MBROs. This means that they always run in the AppDomain and Windows process where they are created. If they are created on a server in a host process, then that is where they live and run. Clients interact with them across the network.

The most commonly used type of service object in remoting is the SingleCall object. Not only do these objects provide semantics similar to Web services, MTS, and COM+, they also provide the simplest programming model.

Because an object is created for each method call, these objects are inherently stateless. Even if an object tried to keep state between calls, it would fail because the object is destroyed after each method is complete. This helps ensure that no method call can be affected by previous method calls or contaminate subsequent method calls.

Each method call runs on its own thread (from the .NET thread pool, as discussed in Chapter 26). However, because each method call also gets its own object, there's typically no contention between threads. This means you don't need to worry about writing synchronization or locking code in your SingleCall code.

Technically, it is possible to encounter synchronization issues if there are shared stateful objects on the server. Creating and accessing such shared objects requires substantial work and is a topic outside the scope of this book. Typically, this type of model is not used, so threading is a non-issue with SingleCall objects.

Because of their automatic isolation, statelessness, and threading simplicity, SingleCall objects are the preferred technology for creating server code in remoting.

Singleton Objects

Singleton objects are quite different from SingleCall objects. Only one Singleton object exists at a time, and it may exist for a long time and maintain state. All client method calls from all users are routed to this one Singleton object. This means that all clients have equal, shared access to any state maintained by the Singleton object.

These objects must inherit from System.MarshalByRefObject, so they are MBROs. This means that they always run in the AppDomain and Windows process where they are created. If they are created on a server in a host process, then that is where they live and run. Clients interact with them across the network.

As with the SingleCall scenario, all method calls are run on threads from the .NET thread pool. This means that multiple simultaneous method calls can be running on different threads at the same time. As discussed in Chapter 26, this can be complex, as you have to write multithreaded synchronization code to ensure that these threads do not collide as they interact with your Singleton object.

Singleton objects have a potentially unpredictable life span. When the first client makes the first method call to the object, it is created. From that point forward, it remains in memory for an indeterminate period of time. As long as it remains in memory, all method calls from all the clients will be handled by this one object. However, if the object is idle for a long time, then remoting may release it to conserve resources. In addition, some remoting hosts may recycle their AppDomain objects, which automatically causes the destruction of all your objects.

Because of this, you can never be certain that the data stored in memory in the object will remain available over time. This means that any long-term state data must be written to a persistent store such as a database.

Due to the complexity of shared memory, thread synchronization, and dealing with object lifetime issues, Singleton objects are more complex to design and code than SingleCall objects. While they can be useful in specialized scenarios, they are not as widely used as SingleCall objects.

Activated Objects

Client-activated (or Activated) objects are different from both SingleCall and Singleton objects. Activated objects are created by a client application, and they remain in memory on the server over time. They are associated with just that one client, so they are not shared between clients. They are stateful objects, meaning that they can maintain data in memory during their lifetime.

These objects must inherit from System.MarshalByRefObject, so they are MBROs. This means that they always run in the AppDomain and Windows process where they are created. If they are created on a server in a host process, then that is where they live and run. Clients interact with them across the network.

A client can create multiple Activated objects on the server. The objects remain on the server until the client releases them or the server Appdomain is reset (which can happen with some types of remoting host). In addition, if the client does not contact the server for several minutes, then the server assumes the client abandoned the objects and it will release them.

Activated objects typically do not have any threading issues. The only way multiple threads will be running in the same Activated object is if the client is multithreaded, and multiple client threads simultaneously make method calls to the same server-side Activated object. If this is the case in your application, then you have to deal with shared data and synchronization issues as discussed in Chapter 26.

While long-lived, stateful, per-client objects can be useful in some specialized scenarios; they are not commonly used in most client/server or n-tier application environments. By storing per-client state in an object on the server, this type of design reduces the scalability and fault tolerance of a system.

Serializable Objects

While SingleCall, Singleton, and Activated objects are always anchored to the Appdomain, Windows process, and machine where they are created, this is not the case with serializable objects.

Serializable objects can move from machine to machine as needed. The classic example of this is the ADO.NET DataSet, which can be returned as a result of a function on a server. The DataSet physically moves to the client machine, where it can be used by client code. When the client wants to update the DataSet, it simply passes the object to the server as a parameter, causing the DataSet to physically move to the server machine.

These objects do not inherit from System.MarshalByRefObject. Instead, they are decorated with the < Serializable() > attribute and may optionally implement the ISerializable interface. The following is a very basic implementation of a < Serializable() > object:

<Serializable()> _
Public Class Customer
  Private mName As String = ""

  Public Property Name() As String
    Get
      Return mName
    End Get
    Set(ByVal value As String)
      mName = value
    End Set

    Public Sub Load()
      ' Load data here.
    End Sub
End Class

<Serializable()> objects are not anchored to the Appdomain or Windows process where they were created. The remoting subsystem automatically serializes the data of these objects and transfers it across the network to another machine. On that other machine, a new instance of the objects is created and loaded with the data, effectively cloning the objects across the network.

When working with serializable objects, it's typically a good idea to use a SingleCall object on the server to create the serializable object and call any server-side methods (such as ones to load the object with data from a database). The SingleCall object will then return the serializable object to the client as a function result, so the client can then interact with the object. The SingleCall object's method might look like the following:

Public Function GetCustomer(ByVal ID As Integer) As Customer

  Dim cust As New Customer()
  cust.Load(ID)
  Return cust

End Function

The client code might look as follows:

Dim cust As Customer

cust = myService.GetCustomer(123)
TextBox1.Text = cust.Name()

Note that both server and client code have direct, local access to the Customer object, because it is automatically copied from the server to the client as a result of the GetCustomer method call.

Serializable objects can be very useful in many client/server scenarios, especially if the application is created using object-oriented application design principles.

Implementing Remoting

When you implement an application using remoting, there are three key components to the application:

Client

The application calling the server

Server Library

The DLL containing the objects to be called by the client

Host

The application running on the server that hosts remoting and the Server Library

Basically, you create your server-side objects in a Visual Basic Class Library project. Then you expose the classes in that DLL from your server-side remoting host application. With the objects exposed on the server, you can then create client applications that call the objects in the Server Library DLL.

You might also have some other optional components to support various scenarios:

Interface

A DLL containing interfaces that are implemented by the objects in the Server Library

Proxy

A DLL containing generated proxy code based on the objects in the Server Library

Shared Library

A DLL containing serializable objects that must be available to both the Server Library and the client

Each of these is discussed in detail as it is used later in the chapter. Now it is time to get into some code and see how remoting works.

A Simple Example

This exercise has you create a simple remoting application consisting of a library DLL that contains the server-side code, a remoting host application, and a client to call the library DLL on the server.

Note that both the host and the client need access to the type information that describes the classes in the library DLL. The type information includes the name of the classes in the DLL and the methods exposed by those classes.

The host needs the information because it will be exposing the library DLL to clients via remoting. The client needs the information in order to know which objects to create and what methods are available on those objects.

You know that the library DLL will be on the server, so it is easy enough for the host application to just reference the DLL to get the type information. The client is a bit trickier because the library DLL will not necessarily be on the client machine. You have three options for getting the type information to the client:

Reference the library DLL

This is the simplest approach, as the client just references the DLL directly and therefore has all the type information. The drawback is that the DLL must be installed on the client along with the client application.

Use an interface DLL

This approach is more complex. The classes in the library DLL must implement formal interfaces as defined in this interface DLL. The client can then reference just the interface DLL, so the library DLL doesn't need to be installed on the client machine. The way the client invokes the server is different when using interfaces.

Generate a proxy DLL

This approach is of moderate complexity. The server must expose the objects via HTTP, so you can run the soapsuds.exe command-line utility. The utility creates an assembly containing the type information for the library DLL classes exposed by the server. The client then references this proxy assembly, rather than the library DLL.

You will implement all three options in this chapter, starting with the simplest — referencing the library DLL directly from the client application.

Library DLL

To begin, create the library DLL. This is just a regular Class Library project, so open Visual Studio .NET (Visual Studio) and create a new class library named SimpleLibrary. Remove Class1.vb and add a new class named Calculator.vb. Because you are creating a well-known remoting object, it must inherit from MarshalByRefObject:

Public Class Calculator
  Inherits MarshalByRefObject
End Class

That's really all there is to it. At this point, the Calculator class is ready to be exposed from a server via remoting. Of course, you need to add some methods that clients can call.

Any and all Public methods written in the Calculator class will be available to clients. How you design the methods depends entirely on whether you plan to expose this class as SingleCall, Singleton, or Activated. For SingleCall you know that an instance of Calculator will be created for each method call, so there is absolutely no point in using any class-level variables. After all, they will be destroyed along with the object when each method call is complete.

It also means that you cannot have the client call a sequence of methods on your object. Each method call gets its own object, so each method call is entirely isolated from any previous or subsequent method calls. In short, each method must stand alone.

For illustration purposes, you need to prove that the server-side code is running in a different process from the client code. The easiest way to prove this is to return the thread ID where the code is running. You can compare this thread ID to the thread ID of the client process. If they are different, then you know that the server-side code is actually running on the server (or at least in another process on your machine).

Add the following method:

Public Function GetThreadID() As Integer

  Return Threading.Thread.CurrentThread.ManagedThreadId

End Function

You can add other Public methods as well if you would like, such as the following:

Public Function Add(ByVal a As Integer, ByVal b As Integer) As Integer

  Return a + b

End Function

As this is a Calculator class, it seems appropriate that it should do some calculations. At this point, you have a simple but functional Calculator class. Build the solution to create the DLL. Your remoting host application will use this DLL to provide the calculator functionality to clients.

Host Application

With the server-side library complete, you can create a remoting host. It is recommended that you use IIS as a remoting host, but it is possible to create a custom host as well. You will use IIS later in the chapter, but for now let's see how you can create a custom host in a Console Application for testing.

Most custom hosts are created as a Windows Service so the host can run on the server even when no user is logged into the machine. However, for testing purposes, a console application is easier to create and run.

The advantage to a custom host is that you can host a remoting server on any machine that supports the .NET Framework. This includes Windows 98 and later. If you use IIS as a host, then you can only host on Windows 2000 and later, which is a bit more restrictive (but not much).

The drawback to a custom host is that it is not as robust and capable as IIS, at least not without a lot of work. For this chapter's example, you are not going to attempt to make your host as powerful as IIS. You will just stick with the basic process of creating a custom host.

Setting Up the Project

Create a new solution in Visual Studio, with a console application named SimpleServer. Because the remoting host will be interacting with remoting, you need to reference the appropriate framework DLL. Use the Add Reference dialog box to add a reference to System.Runtime.Remoting, as shown in Figure 29-3.

Figure 29-3

Figure 29.3. Figure 29-3

Then, in Module1 you need to import the appropriate namespace:

Imports System.Runtime.Remoting

At this point, you can configure and use remoting. However, before you do that, you need to have access to the DLL containing the classes you plan to expose via remoting — in this case, SimpleLibrary.dll.

Referencing the Library DLL

There are two ways to configure remoting: via a configuration file or via code. If you opt for the configuration file approach, then the only requirement is that SimpleLibrary.dll be in the same directory as your host application. You don't even need to reference SimpleLibrary.dll from the host. However, if you opt to configure remoting via code, then your host must reference SimpleLibrary.dll.

Even if you go with the configuration file approach, referencing SimpleLibrary.dll from the host project enables Visual Studio to automatically keep the DLL updated in your project directory, and any setup project you might create will automatically include SimpleLibrary.dll. In general, it is a good idea to reference the library DLL from the host, and that is what you will do here.

Add a reference to SimpleLibrary.dll by clicking the Browse button in the Add References dialog box and navigating to the SimpleLibraryinRelease directory, as shown in Figure 29-4. Note that if you are running in Debug mode, then you will find the DLL in the Debug folder, rather than the Release folder.

Figure 29-4

Figure 29.4. Figure 29-4

All that remains now is to configure remoting.

Configuring Remoting

The typical way to do this is with a configuration file. Open the app.config file (add this file to your project if it isn't already present) in the SimpleServer project. In this config file, you'll add a section to configure remoting. Remember that XML is case sensitive, so the slightest typo here will prevent remoting from being properly configured:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.runtime.remoting>
    <application>
      <!-- The following section defines the classes you're
            exposing to clients from this host. -->
      <service>
        <wellknown mode="SingleCall"
             objectUri="Calculator.rem"
             type="SimpleLibrary.Calculator, SimpleLibrary" />
      </service>

      <channels>
        <channel ref="tcp" port="49341" />
      </channels>
</application>
  </system.runtime.remoting>
</configuration>

Notice that all configuration is within the < system.runtime.remoting > element, and then within an <application> element. The real work happens first inside the <service> element. The <service> element tells remoting that you're configuring server-side components. It is within this block that you define the classes you want to make available to clients. You can define both wellknown and Activated classes here. In this case you're defining a wellknown class:

<wellknown mode="SingleCall"
     objectUri="Calculator.rem"
     type="SimpleLibrary.Calculator, SimpleLibrary" />

The mode will be either SingleCall or Singleton as discussed earlier in the chapter.

The objectUri is the "end part" of the URL that clients will use to reach your server. You'll revisit this in a moment, but this is basically how it fits (depending on whether you're using the TCP or HTTP protocol):

tcp://localhost:49341/Calculator.rem

or

http://localhost:49341/Calculator.rem

The .rem extension on the objectUri is important. This extension indicates that remoting should handle the client request, and it is used by the networking infrastructure to route the request to the right location. You can optionally use the .soap extension to get the same result. The .rem and .soap extensions are totally equivalent.

Finally, the type defines the full type name and assembly where the actual class can be found. Remoting uses this information to dynamically load the assembly and create the object when requested by a client.

You can have many <wellknown> blocks here to expose all the server-side classes you want to make available to clients.

The other key configuration block is where you specify which remoting channel (protocol) you want to use. You can choose between the TCP and HTTP channels.

TCP

Slightly faster than HTTP, but less stable (not recommended)

HTTP

Slightly slower than TCP, but more stable (recommended)

You'll look at the HTTP channel later, so you'll use the TCP channel now. Either way, you need to specify the IP port number on which you'll be listening for client requests. When choosing a port for a server, keep the following port ranges in mind:

  • 0–1023— Well-known ports reserved for specific applications such as web servers, mail servers, and so on

  • 1024–49151 — Registered ports that are reserved for various widely used protocols such as DirectPlay

  • 49152–65535 — Intended for dynamic or private use, such as for applications that might be performing remoting with .NET

You're setting remoting to use a TCP channel, listening on port 49341:

<channels>
  <channel ref="tcp" port="49341" />
</channels>

With the .config file created, the only thing remaining is to tell remoting to configure itself based on this information. To do this you need to add the following code to Sub Main:

Sub Main()
  RemotingConfiguration.Configure( _
    AppDomain.CurrentDomain.SetupInformation.ConfigurationFile, False)
  Console.Write("Press <enter> to exit")
  Console.Read()
 End Sub

The Console.Write and Console.Read statements are there to ensure that the application stays running until you are ready for it to terminate. The line that actually configures remoting is as follows:

RemotingConfiguration.Configure( _
  AppDomain.CurrentDomain.SetupInformation.ConfigurationFile)

You are calling the Configure method, which tells remoting to read a .config file and to process the <system.runtime.remoting> element in that file. You want it to use your application configuration file, so you pass that path as a parameter. Fortunately, you can get the path from your AppDomain object so you don't have to worry about hard-coding the filename.

Configuring Remoting via Code

Your other option is to configure the remoting host via code. To do this you'd write different code in Sub Main:

Sub Main()

  RemotingConfiguration.RegisterWellKnownServiceType( _
    GetType(SimpleLibrary.Calculator), _
    "Calculator.rem", _
    WellKnownObjectMode.SingleCall)

  System.Runtime.Remoting.Channels.ChannelServices.RegisterChannel( _
    New System.Runtime.Remoting.Channels.Tcp.TcpServerChannel(49341), False)
  Console.Write("Press <enter> to exit")
  Console.Read()
End Sub

As shown in the preceding snippet, you're providing the exact same information here as you did in the .config file, only via code. You call RegisterWellKnownServiceType, passing the mode, objectUri, and type data just as you did in the .config file. Then you call RegisterChannel, passing a new instance of the TcpServerChannel configured to use the port you chose earlier.

The result is the same as using the .config file. Most server applications use a .config file to configure remoting because it enables you to change things such as the channel and the port without having to recompile the host application.

Build the solution. At this point, your host is ready to run. Open a command prompt window, navigate to the bin directory, and run SimpleServer.exe.

The Client Application

The final piece of the puzzle is to create a client application that calls the server.

Setting Up the Project

Here is how to create a new Visual Studio solution with a Windows Application named SimpleClient. As discussed earlier, the client needs access to the type information for the classes it wants to call on the server. The easiest way to get this type information is to have it reference SimpleLibrary.dll. Because you will be configuring remoting, you also need to reference the remoting DLL. Then import the remoting namespace in Form1:

Imports System.Runtime.Remoting

Now you can write code to interact with the Calculator class. Add controls to the form as shown in Figure 29-5.

Figure 29-5

Figure 29.5. Figure 29-5

Name the controls (in order): ConfigureButton, CodeConfigureButton, LocalThreadButton, LocalThread, RemoteThreadButton, and RemoteThread. First, write the code to get the thread ID values for each object:

Private Sub LocalThreadButton_Click( _
  ByVal sender As System.Object, ByVal e As System.EventArgs) _
  Handles LocalThreadButton.Click
LocalThread.Text = CStr(Threading.Thread.CurrentThread.ManagedThreadId)

End Sub

Private Sub RemoteThreadButton_Click( _
    ByVal sender As System.Object, ByVal e As System.EventArgs) _
    Handles RemoteThreadButton.Click

 Dim calc As New SimpleLibrary.Calculator

 RemoteThread.Text = CStr(calc.GetThreadID)

End Sub

Displaying the thread ID of the local process is easily accomplished. More interesting, though, is that your code to interact with the Calculator class does not look special in any way. Where is the remoting code?

This example reflects the idea of location transparency, whereby it is possible to write "normal" code that interacts with an object whether it is running locally or remotely. This is an important and desirable trait for distributed technologies, and remoting supports the concept. Looking at the code you've written, you can't tell whether the Calculator object is local or remoting; its location is transparent.

All that remains is to configure remoting so that it knows that the Calculator object should, in fact, be created remotely. As with the server, you can configure clients either via a config file or through code.

Before you configure remoting, note something important: If remoting is not configured before the first usage of SimpleLibrary.Calculator, then the Calculator object will be created locally. If that happens, then configuring remoting will not help, and you'll never create remote Calculator objects.

To prevent this from happening, you need to ensure that you cannot interact with the class until after remoting is configured. Typically, this is done by configuring remoting as the application starts up, either in Sub Main or in the first form's Load event. In this case, however, you are going to configure remoting behind some buttons, so a different approach is required.

In Form_Load, add the following code:

Private Sub Form1_Load( _
  ByVal sender As System.Object, ByVal e As System.EventArgs) _
  Handles MyBase.Load

  RemoteThreadButton.Enabled = False

End Sub

This prevents you from requesting the remote thread. You won't enable this button until after remoting has been configured through either the config file or code.

Configuring Remoting

To configure remoting via a config file, you first need to add a config file to the project. Use the Project

Configuring Remoting
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.runtime.remoting>
    <application>
      <!-- The following section defines the classes you're
            getting from the remote host. -->
      <client>
        <wellknown mode="SingleCall"
            type="SimpleLibrary.Calculator, SimpleLibrary"
            url="tcp://localhost:49341/Calculator.rem" />
       </client>
    </application>
  </system.runtime.remoting>
</configuration>

In this case, you are using the <client> element, telling remoting that you are configuring a client. Within the <client> block, you define the classes that should be run on a remote server, both wellknown and Activated. In your case, you have a wellknown class:

<wellknown
     type="SimpleLibrary.Calculator, SimpleLibrary"
     url="tcp://localhost:49341/Calculator.rem" />

On the client, you only need to provide two bits of information. You need to tell remoting the class and assembly that should be run remotely. This is done with the type attribute, which specifies the full type name and assembly name for the class, just as you did on the server. You also need to provide the full URL for the class on the server.

You defined this URL when you created the server, though it might not have been clear that you did so. When you defined the class for remoting on the server, you specified an objectUri value (Calculator.rem). In addition, on the server you specified the channel (TCP) and port (49341) on which the server will listen for client requests. Combined with the server name itself, you have a URL:

tcp://localhost:49341/Calculator.rem

The channel is tcp://, the server name is localhost (or whatever the server name might be), the port is 49341, and the object's URI is Calculator.rem. This is the unique address of your SimpleLibrary.Calculator class on the remote server.

As with the server configuration, you might have multiple elements in the .config file, one for each server-side object you wish to use. These can be a mix of <wellknown> and <activated> elements.

With the configuration set up, you just need to tell remoting to read the file. You'll do this behind the ConfigureButton control:

Private Sub ConfigureButton_Click( _
  ByVal sender As System.Object, ByVal e As System.EventArgs) _
Handles ConfigureButton.Click

  RemotingConfiguration.Configure( _
    AppDomain.CurrentDomain.SetupInformation.ConfigurationFile, True)

  ConfigureButton.Enabled = False
  CodeConfigureButton.Enabled = False
  RemoteThreadButton.Enabled = True

End Sub

Once remoting is configured in an application, you cannot configure it again, so you're disabling the two configuration buttons. In addition, you're enabling the button to retrieve the remote thread ID. Now that remoting has been configured, it is safe to interact with SimpleLibrary.Calculator.

The line of code that configures remoting is the same as it was in the server:

RemotingConfiguration.Configure( _
  AppDomain.CurrentDomain.SetupInformation.ConfigurationFile)

Again, you are telling remoting to read your application configuration file to find the < system.runtime.remoting > element and process it.

Configuring Remoting via Code

Another option for configuring remoting is to do it via code. You must provide the same information in your code as you did in the .config file. Put this behind the CodeConfigureButton control:

Private Sub CodeConfigureButton_Click( _
  ByVal sender As System.Object, ByVal e As System.EventArgs) _
  Handles CodeConfigureButton.Click
  RemotingConfiguration.RegisterWellKnownClientType( _
    GetType(SimpleLibrary.Calculator), "tcp://localhost:49341/Calculator.rem")

  ConfigureButton.Enabled = False
  CodeConfigureButton.Enabled = False
  RemoteThreadButton.Enabled = True

End Sub

The RegisterWellKnownClientType method requires that you specify the type of the class to be run remotely, in this case SimpleLibrary.Calculator. It also requires that you provide the URL for the class on the remote server, just as you did in the .config file.

Regardless of whether you do the configuration via code or the .config file, the result is that the .NET runtime now knows that any attempt to create a SimpleLibrary.Calculator object should be routed through remoting, so the object will be created on the server.

Compile and run the application. Try configuring remoting both ways. In either case, you should discover that the local thread ID and the remote thread ID are different, proving that the Calculator code is running on the server, not locally in the Windows application, as shown in Figure 29-6.

Figure 29-6

Figure 29.6. Figure 29-6

Of course, your specific thread ID values will be different from those shown here. The important point is that they are different from each other, establishing that the local code and remote code are running in different places.

Using IIS as a Remoting Host

You have learned how to create a very basic custom host. In most production environments, however, such a basic host is not directly useful. You'd need to create a Windows Service, add management and logging facilities, implement security, and so forth.

Alternatively, you could just use IIS as the host and get all those things automatically, so it is often better to use IIS as a remoting host than to try to create your own.

Creating the Host

Using IIS as a host is a straightforward exercise. First, create a web project. To do this, create a new solution in Visual Studio with an Empty Web Site template, as shown in Figure 29-7. Name it SimpleHost. When you click OK, Visual Studio will properly create and configure the virtual root on your server.

Figure 29-7

Figure 29.7. Figure 29-7

The next task is to ensure that the SimpleLibrary.dll is in the bin directory under the virtual root. While you could copy the DLL there by hand, it is often easier to simply add a reference to the DLL from the website. This enables Visual Studio to automatically copy the DLL to the right location, and it has the added benefit that if you create a deployment project, then the DLL will be automatically included as part of the setup.

Add a reference to SimpleLibrary.dll using the Add References dialog box as you did previously in the SimpleServer and SimpleClient projects. This way, Visual Studio will ensure that the DLL is available as needed.

All that remains now is to configure remoting. Within an IIS host, add the < system.runtime.remoting > section to the web.config file. Remoting is automatically configured based on web.config by ASP.NET.

Use the Project

Figure 29-7

More important, however, add the remoting configuration to the file:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.runtime.remoting>
    <application>
      <!-- The following section defines the classes you're
            exposing to clients from this host. -->
      <service>
        <wellknown mode="SingleCall"
             objectUri="Calculator.rem"
             type="SimpleLibrary.Calculator, SimpleLibrary" />
      </service>

    </application>
  </system.runtime.remoting>
</configuration>

An IIS host can only support the HTTP channel. In addition, the port on which the host listens is defined by IIS, not by your configuration file. This means that all you need to do here is define the classes you want to expose to clients. This is done within the <service> element, just like with a custom host. Again, you use a <wellknown> element to define your class:

<wellknown mode="SingleCall"
     objectUri="Calculator.rem"
     type="SimpleLibrary.Calculator, SimpleLibrary" />

The <wellknown> element shown here is the exact same definition used with the custom host, and you'll get the same result.

The primary difference between your custom host and the IIS host is that IIS cannot use the TCP channel, but only uses the HTTP channel. This means that the URL for your server-side class is different:

http://localhost/SimpleHost/Calculator.rem

The channel defines the protocol, which is http://. The server name is localhost (or whatever your server name might be). The virtual root within IIS is SimpleHost, named just as it is with any Web project. Finally, the objectUri value for your class (Calculator.rem) rounds out the URL.

Again, the .rem extension is important. This extension (or the equivalent .soap extension) tells IIS to route the client request to ASP.NET, and it tells ASP.NET to route the request to remoting so it can be properly handled by invoking your Calculator class.

At this point, the remoting host is done and ready to go. Because it is using the HTTP protocol, you can test it with the browser by navigating to the following URL:

http://localhost/SimpleHost/Calculator.rem?wsdl

This should return an XML description of the host service and all the classes exposed from the host.

Updating the Client Application

With a new host set up, you can change the client application to use this IIS host instead of the custom host. To do this, all you need to do is change the URL for the object when you configure remoting.

If you were using the .config file to configure remoting, you would make the following change:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.runtime.remoting>
    <application>
      <!- the following section defines the classes you're
            getting from the remote host ->
      <client>
        <wellknown
             type="SimpleLibrary.Calculator, SimpleLibrary"
             url="http://localhost/SimpleHost/Calculator.rem" />
       </client>
    </application>
  </system.runtime.remoting>
</configuration>

After making this change to App.config, be sure to rebuild the project so Visual Studio copies the new .config file to the bin directory and renames it SimpleClient.exe.config.

When configuring remoting via code, change the code to the following:

Private Sub CodeConfigureButton_Click( _
  ByVal sender As System.Object, ByVal e As System.EventArgs) _
  Handles CodeConfigureButton.Click

  RemotingConfiguration.RegisterWellKnownClientType( _
    GetType(SimpleLibrary.Calculator), _
    "http://localhost/SimpleHost/Calculator.rem")
  ConfigureButton.Enabled = False
  CodeConfigureButton.Enabled = False
  RemoteThreadButton.Enabled = True

End Sub

In either case, you are simply changing the URL, so remoting now routes your calls to the IIS host instead of your custom host.

Using the Binary Formatter in IIS

One thing to note about using IIS as a host is that it always uses the HTTP channel. The HTTP channel defaults to using the SoapFormatter instead of the BinaryFormatter to encode the data sent across the network. While SOAP is a fine format, it is extremely verbose. The BinaryFormatter generates about one-third the number of bytes as the SoapFormatter to send the same data.

As stated, the SoapFormatter class is the default formatter when using an HTTP channel with .NET remoting, and this class serializes any objects that it receives into a SOAP 1.1-compliant text format. The HTTP channel uses the SoapFormatter to serialize the objects that it sends through the channel. After the object is received and serialized into XML, this formatter also adds any appropriate SOAP headers to the message before it is sent through the channel.

Besides the SoapFormatter, the BinaryFormatter class is typically used when sending an object through a TCP network protocol. When objects are sent using the BinaryFormatter, these objects are more compact and therefore require less network utilization.

For production code, it is good practice to use the BinaryFormatter to reduce the amount of data sent across the network and to improve performance. The formatter is controlled by the client, so you need to update the client configuration of remoting.

Change the .config file as follows:

<?xml version="1.0" encoding="utf-8" ?>
     <configuration>
       <system.runtime.remoting>
         <application>
           <!- the following section defines the classes you're
                 getting from the remote host ->
           <client>
             <wellknown
                 type="SimpleLibrary.Calculator, SimpleLibrary"
                 url="http://localhost/SimpleHost/Calculator.rem" />
            </client>
      <!-- use the binary formatter over the
             http channel ->
      <channels>
         <channel ref="http">
          <clientProviders>
            <formatter ref="binary" />
          </clientProviders>
        </channel>
      </channels>
    </application>
  </system.runtime.remoting>
</configuration>

The highlighted XML configures remoting, so when it initializes the HTTP channel, it does so with a BinaryFormatter instead of the default SoapFormatter.

To do the equivalent to the XML configuration in code, you'll want to import two namespaces into Form1:

Imports System.Runtime.Remoting.Channels
Imports System.Runtime.Remoting.Channels.Http

This also requires that the SimpleClient project reference the System.Runtime.Remoting.dll assembly. Do this using the Add References dialog as you did earlier in the SimpleLibrary project.

Then add the following when configuring remoting:

Private Sub CodeConfigureButton_Click( _
  ByVal sender As System.Object, ByVal e As System.EventArgs) _
  Handles CodeConfigureButton.Click

  RemotingConfiguration.RegisterWellKnownClientType( _
    GetType(SimpleLibrary.Calculator), _
    "http://localhost/SimpleHost/Calculator.rem")


  ' Use the binary formatter with the
  ' HTTP channel.
  Dim clientFormatter As New BinaryClientFormatterSinkProvider
  Dim channel As New HttpChannel(Nothing, clientFormatter, Nothing)
  ChannelServices.RegisterChannel(channel)
  ConfigureButton.Enabled = False
  CodeConfigureButton.Enabled = False
  RemoteThreadButton.Enabled = True

End Sub

As with the .config file approach, you're specifically creating the HttpChannel object, specifying that it should use a BinaryFormatter, rather than the default.

At this point, you have explored the basic use of remoting. You have created a library DLL, a client that uses the library DLL, and two different types of remoting hosts, so the library DLL can run on the server.

There are many other facets of remoting to explore, more than what can fit into this single chapter. The remainder of the chapter explores some of the more common features that you might encounter or use in your applications. You will have to take them pretty fast, but the complete code for each is available in the code download for the book, so you can get the complete picture there.

Using Activator.GetObject

In your simple client, you configured remoting so that all attempts to use SimpleLibrary.Calculator were automatically routed to a specific server. If you want more control and flexibility, you can take a different approach by using the System.Activator class. The full code for this example is in the ActivatorClient project.

Instead of configuring remoting to always know where to find the remote class, you can specify it as you create the remote object. As you will not be configuring remoting, you don't need a reference to System.Runtime.Remoting.dll, nor do you need any of the remoting configuration code you had in the client to this point.

All you do is replace the use of the New keyword with a call to Activator.GetObject. To use the custom host, you would use the following code to retrieve the remote thread ID:

Private Sub RemoteThreadButton_Click( _
  ByVal sender As System.Object, ByVal e As System.EventArgs) _
  Handles RemoteThreadButton.Click

  Dim calc As SimpleLibrary.Calculator

  calc = CType(Activator.GetObject( _
    GetType(SimpleLibrary.Calculator), _
    "tcp://localhost:49341/Calculator.rem"), _
    SimpleLibrary.Calculator)
  RemoteThread.Text = CStr(calc.GetThreadID)

End Sub

For this to work, the SimpleServer application must be running before the RemoteThread button is clicked.

The Activator.GetObject method accepts the type of object to create (SimpleLibrary.Calculator) and the URL where the object can be found. To use the IIS host, you would change the URL:

calc = CType(Activator.GetObject( _
  GetType(SimpleLibrary.Calculator), _

  "http://localhost/SimpleHost/Calculator.rem"), _
  SimpleLibrary.Calculator)

Using this approach, you lose location transparency because it is obvious looking at your code that you're using a remote object. However, you gain explicit control over where the remote object will be created. This can be useful in some cases, such as when you want to programmatically control the URL on a per-call basis.

Interface-Based Design

One drawback to the simple implementation you have used thus far is that the library DLL (SimpleLibrary.dll) must be installed on the client machine. Sometimes this is not desirable, because you don't want clients to have access to the server-side code. You have two options in this case: use an interface DLL or use a generated proxy.

Interface DLL

To use this approach, you need to create a new DLL containing interface definitions for your server-side classes and their methods. For instance, in the SimpleInterface project, you have the following interface defined:

Public Interface <code> ICalculator
  Function GetThreadID() As Integer
  Function Add(ByVal a As Integer, ByVal b As Integer) As Integer
End Interface

This interface defines the methods on your Calculator class. You need to update the Calculator class to implement this interface. The SimpleLibrary project must reference the SimpleInterface DLL; then you can do the following in your Calculator class:

Public Class Calculator
  Inherits MarshalByRefObject

 Implements SimpleInterface.ICalculator
 Public Function GetThreadID() As Integer _
   Implements SimpleInterface.ICalculator.GetThreadID

   Return AppDomain.GetCurrentThreadId

 End Function

 Public Function Add(ByVal a As Integer, ByVal b As Integer) As Integer _
   Implements SimpleInterface.ICalculator.Add

      Return a + b

  End Function

End Class

At this point, the SimpleLibrary.Calculator class can be invoked either directly or via the ICalculator interface.

Be sure to rebuild the custom and IIS host projects so that the new SimpleLibrary and the SimpleInterface DLLs are both copied to the host directories. Note that because SimpleLibrary.Calculator is still available natively, your existing client applications (SimpleClient and ActivatorClient) will continue to run just fine.

Updating the Client Application

The InterfaceClient project only references SimpleInterface.dll, not SimpleLibrary.dll. This means that the client machine doesn't need to install SimpleLibrary.dll for the client to run, which means the client has no access to the actual server-side code.

Because you don't have access to the types in SimpleLibrary, you can't use them in your code. The only types you can use come from SimpleInterface. This means that your code to retrieve the remote thread ID is a bit different. To use the custom host, you do the following:

Private Sub RemoteThreadButton_Click( _
  ByVal sender As System.Object, ByVal e As System.EventArgs) _
  Handles RemoteThreadButton.Click

  Dim calc As SimpleInterface.ICalculator

  calc = CType(Activator.GetObject( _
    GetType(SimpleInterface.ICalculator), _
    "tcp://localhost:49341/Calculator.rem"), _
    SimpleInterface.ICalculator)

  RemoteThread.Text = CStr(calc.GetThreadID)

End Sub

Note that the calc variable is now declared as type ICalculator, rather than Calculator. Notice too that you're using Activator.GetType. This is required when using interfaces, because you can't use the New keyword at all. That is, you can't do the following:

calc = New SimpleInterface.ICalculator()

The result is a compiler error because it isn't possible to create an instance of an interface. Therefore, you can't just configure remoting and use location transparency; you must use Activator.GetObject to have remoting create an instance of the object on the server.

Remoting knows how and where to create the object based on the URL you provide. It then converts the object to the right type (SimpleInterface.ICalculator) based on the type you provide in the GetObject call. If the remote object doesn't implement this interface, then you'll get a runtime exception.

Using Generated Proxies

Another way to create a client that does not reference the library DLL is to use the soapsuds.exe command-line utility to create a proxy assembly for the service and the classes it exposes. This proxy assembly is then referenced by the client application, giving the client access to the server type information so that it can interact with the server objects.

Proxy DLL

To create the proxy DLL, you just run the soapsuds.exe utility with the following command line:

> soapsuds -url:http://localhost/SimpleHost/Calculator.rem?wsdl -oa:SimpleProxy.dll

Note that you are going against the IIS host here because it uses the HTTP protocol. This won't work against your current custom host, as the soapsuds.exe utility doesn't understand the tcp:// prefix. To use this against a custom host, you would have to ensure that the custom host used the HTTP protocol.

Creating the Client Application

The code download includes a ProxyClient project, which is a Windows application that references only SimpleProxy.dll. There is no reference to SimpleLibrary.dll or SimpleInterface.dll — this client relies entirely on the generated proxy assembly to interact with the server.

The best part of this is that the generated proxy contains the same namespace and class names as the service on the server. In other words, it appears that you are working with SimpleLibrary.Calculator because the proxy is set up with that same namespace and class name. To get the remote thread ID, write the following code:

Private Sub RemoteThreadButton_Click( _
  ByVal sender As System.Object, ByVal e As System.EventArgs) _
  Handles RemoteThreadButton.Click

  Dim calc As New SimpleLibrary.Calculator()
  RemoteThread.Text = CStr(calc.GetThreadID)

End Sub

Note that this is the same code used in the original simple example. You've come full circle at this point, but now the client application doesn't directly reference your library DLL.

Summary

Remoting is a powerful technology that provides many of the capabilities of Web services and DCOM, plus some new capabilities of its own. Using remoting, you can create both Windows and Web applications that interact with objects on an application server across the network.

On the server you can create SingleCall, Singleton, and Activated objects. These three object types provide a great deal of flexibility in terms of n-tier application design and should be able to meet almost any need. SingleCall gives you behavior similar to Web services or typical COM+ objects. Activated gives you objects that act similar to COM objects exposed via DCOM. Singleton objects are unique to remoting and enable all your clients to share a single stateful object on the server.

You can also create serializable objects, which can move from machine to machine as needed. Using this type of object enables you to easily move data and business logic from server to client and back again. This technology is particularly exciting for object-oriented development in a distributed environment.

In this chapter, you created a library DLL and exposed it to clients from both a custom and IIS remoting host. You then created client applications to use your server-side code by referencing the library DLL directly, using an interface DLL and using the soapsuds.exe utility to create a proxy DLL. These techniques apply not only to SingleCall objects but also to Singleton and Activated objects, so you should have a good grounding in the techniques available for using remoting in your environment.

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

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