.NET Framework Remoting

November 23, 2013

Introduction

.NET remoting enables you to build widely distributed applications easily, whether the application components are all on one computer or spread out across the entire world. You can build client applications that use objects in other processes on the same computer or on any other computer that is reachable over its network. You can also use .NET remoting to communicate with other application domains in the same process.

.NET remoting provides an abstract approach to interprocess communication that separates the remotable object from a specific client or server application domain and from a specific mechanism of communication. As a result, it is flexible and easily customizable. You can replace one communication protocol with another, or one serialization format with another without recompiling the client or the server. In addition, the remoting system assumes no particular application model. You can communicate from a Web application, a console application, a Windows Service – from almost anything you want to use. Remoting servers can also be any type of application domain. Any application can host remoting objects and provide its services to any client on its computer or network.

To use .NET remoting to build an application in which two components communicate directly across an application domain boundary, you need to build only the following:

  • A remotable object.
  • A host application domain to listen for requests for that object.
  • A client application domain that makes requests for that object.

The host and the client application must also be configured with the remoting infrastructure and you must understand the lifetime and activation issues that the remoting infrastructure introduces.

Building an application that uses .NET Framework remoting to communicate across application domain boundaries is very straightforward. You must have an implementation of a remotable type, a listening or host application domain, a client or calling application domain, and you must configure the remoting system in each application domain to use remote activation for the remotable type. This process applies no matter how complex your remoting scenario becomes.

Building  a remotable type

To enable objects in other application domains to use an instance of your class, your class must inherit from MarshalByRefObject.

To build a remotable type

Define a class that derives from the MarshalByRefObject class:

EX-VB

' RemotableType.vb
 Imports System
 Public Class RemotableType Inherits MarshalByRefObject 
   Public Function SayHello() As String
      Console.WriteLine("RemotableType.SayHello() was called!")
      Return "Hello, world"
   End Function 
End Class

EX-C#

// RemotableType.cs
using System;
public class RemotableType : MarshalByRefObject
{
    public string SayHello()
    {
        Console.WriteLine("RemotableType.SayHello() was called!");
        return "Hello, world";
    }
}

MarshalByRefObject Class

Namespace: System

Enables access to objects across application domain boundaries in applications that support remoting.

An application domain is a partition in an operating system process where one or more applications reside. Objects in the same application domain communicate directly. Objects in different application domains communicate either by transporting copies of objects across application domain boundaries, or by using a proxy to exchange messages.

MarshalByRefObject is the base class for objects that communicate across application domain boundaries by exchanging messages using a proxy. Objects that do not inherit from MarshalByRefObject are implicitly marshal by value. When a remote application references a marshal by value object, a copy of the object is passed across application domain boundaries.

MarshalByRefObject objects are accessed directly within the boundaries of the local application domain. The first time an application in a remote application domain accesses a MarshalByRefObject, a proxy is passed to the remote application. Subsequent calls on the proxy are marshaled back to the object residing in the local application domain.

Types must inherit from MarshalByRefObject when the type is used across application domain boundaries, and the state of the object must not be copied because the members of the object are not usable outside the application domain where they were created.

EX.

The following code example shows the simplest way to execute code in another application domain. The example defines a class named Worker that inherits MarshalByRefObject, with a method that displays the name of the application domain in which it is executing. The example creates instances of Worker in the default application domain and in a new application domain.

The assembly that contains Worker must be loaded into both application domains, but it could load other assemblies that would exist only in the new application domain.

EX-VB

Imports System
Imports System.Reflection
Public Class Worker
    Inherits MarshalByRefObject
    Public Sub PrintDomain() 
        Console.WriteLine("Object is executing in AppDomain ""{0}""", _
            AppDomain.CurrentDomain.FriendlyName)
    End Sub 
End Class 
Class Example
    Public Shared Sub Main() 
        ' Create an ordinary instance in the current AppDomain
        Dim localWorker As New Worker()
        localWorker.PrintDomain()

        ' Create a new application domain, create an instance
        ' of Worker in the application domain, and execute code
        ' there.
        Dim ad As AppDomain = AppDomain.CreateDomain("New domain")
        Dim remoteWorker As Worker = CType( _
            ad.CreateInstanceAndUnwrap( _
                [Assembly].GetExecutingAssembly().FullName, _
                "Worker"), _
            Worker)
        remoteWorker.PrintDomain()

    End Sub 
End Class 
' This code produces output similar to the following:
'Object is executing in AppDomain "source.exe"
'Object is executing in AppDomain "New domain"

EX-C#

using System;
using System.Reflection;
public class Worker : MarshalByRefObject
{
    public void PrintDomain() 
    { 
        Console.WriteLine("Object is executing in AppDomain \"{0}\"",
            AppDomain.CurrentDomain.FriendlyName); 
    }
}
class Example
{
    public static void Main()
    {
        // Create an ordinary instance in the current AppDomain
        Worker localWorker = new Worker();
        localWorker.PrintDomain();
        // Create a new application domain, create an instance
        // of Worker in the application domain, and execute code
        // there.
        AppDomain ad = AppDomain.CreateDomain("New domain");
        Worker remoteWorker = (Worker) ad.CreateInstanceAndUnwrap(
            Assembly.GetExecutingAssembly().FullName,
            "Worker");
        remoteWorker.PrintDomain();
    }
}
/* This code produces output similar to the following:
Object is executing in AppDomain "source.exe"
Object is executing in AppDomain "New domain"
 */

EX.

The following example demonstrates a class derived from MarshalByRefObject that is used later in remoting.

EX-VB

Imports System
Imports System.Runtime.Remoting
Imports System.Security.Permissions
Public Class SetObjectUriForMarshalTest
    Class TestClass
        Inherits MarshalByRefObject
    End Class
    <SecurityPermission(SecurityAction.LinkDemand)> _
    Public Shared Sub Main()
        Dim obj As TestClass = New TestClass()
        RemotingServices.SetObjectUriForMarshal(obj, "testUri")
        RemotingServices.Marshal(obj)
        Console.WriteLine(RemotingServices.GetObjectUri(obj))
    End Sub
End Class

EX-C#

using System;
using System.Runtime.Remoting;
using System.Security.Permissions;
public class SetObjectUriForMarshalTest  {

    class TestClass : MarshalByRefObject {
    }
    [SecurityPermission(SecurityAction.LinkDemand)]
    public static void Main()  {
        TestClass obj = new TestClass();    
        RemotingServices.SetObjectUriForMarshal(obj, "testUri");
        RemotingServices.Marshal(obj);
        Console.WriteLine(RemotingServices.GetObjectUri(obj));
    }
}

Building a host application that listens for remote clients of a remotable type

To enable objects in other application domains to create instances of this object remotely, you must build a host or listener application to do the following things:

  • Choose and register a channel, which is an object that handles the networking protocols and serialization formats on your behalf.
  • Register your type with the .NET remoting system so that it can use your channel to listen for requests for your type.

The .NET Framework includes three default channels:

  •  HttpChannel (which uses SOAP formatting by default) – HttpChannel can be used through firewalls without opening a port and it supports standard security and authentication protocols.
  • TcpChannel (which uses binary formatting by default), and
  • IpcChannel (which uses binary formatting by default)

You can build listener applications using any type of application domain — a Windows Forms application, an ASP.NET Web application, a console application, a Windows Service (also known as a Windows NT Service), or any other managed application domain. Because remote configuration is done for each application domain, the application domain must be running to listen for requests. Configuration can be done programmatically or by using an application or machine configuration file.

The remoting system uses the information in this file to listen for and route remote requests to an instance of a remotable type. The file specifies the server-activation mode, the type name and assembly of the type on behalf of which it is to listen, and the object Uniform Resource Identifier (URI) or external name of the object.

Using a configuration file enables you to change the remoting configuration without recompiling your executable, among other things.

To implement a simple host application domain that uses a configuration file

1-      Continuing on from the How to: Build a Remotable Type, create another directory under remoting and call it host. Create a configuration file for the remote class. The host application must be able to load the configuration for the remote class, and therefore, the configuration file should be saved in the same directory as the host application’s assembly, or it is not found and an exception is thrown. The following code shows a configuration file that specifies the remote object is a Singleton, its implementation is a class called RemotableType located in an assembly called RemotableType. Next, an HttpChannel is registered listening on port 8989. Save this file in the remoting\listener directory. The filename should follow the pattern of app-name.exe.config. In this case it is called listener.exe.config.

Configuration File
Although there are only a few settings in the preceding configuration 
file, most of the problems using .NET remoting occur because some of 
these settings are either incorrect or do not match the configuration settings 
for client applications. It is easy to mistype a name, forget a port, 
or neglect an attribute. If you are having problems with your remoting application, 
check your configuration settings first.

<configuration>
   <system.runtime.remoting>
      <application>
         <service>
            <wellknown 
               mode="Singleton" 
               type="RemotableType, RemotableType" 
               objectUri="RemotableType.rem"
            />
         </service>
         <channels>
            <channel ref="http" port="8989"/>
         </channels>
      </application>
   </system.runtime.remoting>
</configuration>

2- Create a new source file for your language of choice. At the top of the source file import the System.Runtime.Remoting namespace:

Imports System
Imports System.Runtime.Remoting

3- In the Main method, load the configuration file that configures the remote class, display a message letting the user know the host is running, and then wait for a key press. Save this file in the remoting\listener directory.

Public Class Listener
    Public Shared Sub Main()
        RemotingConfiguration.Configure("Listener.exe.config", False)
        Console.WriteLine("Listening for requests. Press enter to exit...")
        Console.ReadLine()
    End Sub
End Class
public class Listener
{
    public static void Main()
    {
        RemotingConfiguration.Configure("Listener.exe.config", false);
        Console.WriteLine("Listening for requests. Press enter to exit...");
        Console.ReadLine();
    }

}

4- Copy the RemotableType.dll generated in How to: Build a Remotable Type into the remoting\listener directory. The host application must reference this assembly. Compile this class into an executable by typing the following command:

vbc /r:RemotableType.dll Listener.vb

5-You now have an assembly called Listener.exe. Try running it now to see if the configuration succeeds. A security dialog may be displayed if a firewall is currently blocking the 8989 port. If so click the “Unblock” button to temporarily open the firewall on that port.

' Listener.vb
Public Class Listener
    Public Shared Sub Main()
        RemotingConfiguration.Configure("Listener.exe.config", False)
        Console.WriteLine("Listening for requests. Press enter to exit...")
        Console.ReadLine()
    End Sub
End Class
// Listener.cs
using System;
using System.Runtime.Remoting;

public class Listener
{
    public static void Main(string[] args)
    {
        RemotingConfiguration.Configure("Listener.exe.config", false);
        Console.WriteLine("Listening for requests. Press enter to exit...");
        Console.ReadLine();
    }
}

Specify a Channel in a Configuration File

Because the Listener.exe.config and Client.exe.config files contain all the configuration information necessary to make a remote connection, you can change the channel without recompiling your application merely by changing the channel specified in the configuration file.

To change the channel specified in the configuration file

Change the channel to a TcpChannel object, which uses binary serialization by default, by changing the <wellknown> element in the Client.exe.config file to the following:

<wellknown 
   type="RemotableType, RemotableType"
   url="tcp://localhost:8989/RemotableType.rem"
/>
You can specify the machine name instead of localhost, if the remote type is available on another machine.
  1. Change the <channel> element in the Listener.exe.config file to the following:
  1. <channel ref=”tcp” port=”8989″/>
  1. Rerun the application according to the preceding execution instructions. You do not need to recompile the application.

Compile and Run a Basic Remoting Application

The following procedure shows how to use the command-line tools that ship with the .NET Framework SDK to compile the basic remoting application:

To compile and run a basic remoting application

  1. At the command prompt in the remoting\type directory, type the following command:

VB

vbc /t:library RemotableType.vb
 [C#]
csc /noconfig /t:library RemotableType.cs
  1. Copy RemotableType.dll into the remoting\client and remoting\listener directories.
  2. Copy listener.exe.config into the remoting\listener directory.
  3. Copy client.exe.config into the remoting\client directory.
  4. At the command-prompt in the remoting\listener directory, type the following command:

VB

vbc /r:RemotableType.dll Listener.vb
csc /noconfig /r:RemotableType.dll Listener.cs
  1. At the command-prompt in the remoting\client directory, type the following command:

VB

vbc /r:RemotableType.dll client.vb
csc /noconfig /r:RemotableType.dll Listener.cs csc /noconfig /r:RemotableType.dll Client.cs
  1. At the command prompt in the remoting\listener directory, type Listener.
  2. 8.      When the Listener application is running, open a new command prompt in the remoting\client directory and type Client.

Build a Client Application

To build a client of the remote type your application must register itself as a client for that remote object and then invoke it as though it were within the client’s application domain. The .NET remoting system intercepts your client calls, forwards them to the remote object, and returns the results to your client. The following procedure describes how to build a basic remoting client:

  1. Create a new directory under remoting called client. Create a configuration file for the client application as shown in the following code and save the file in the remoting\client directory. The file name should follow the pattern of app-name.exe.config. In this case, it is called client.exe.config. The following configuration file tells the remoting system that the type information for the RemotableType remote object can be found in the RemotableType assembly and the object is located at http://localhost:8989/RemotableType.rem.
<configuration>
   <system.runtime.remoting>
      <application>
         <client>
            <wellknown 
               type="RemotableType, RemotableType"
               url="http://localhost:8989/RemotableType.rem"
            />
         </client>
      </application>
   </system.runtime.remoting>
</configuration>

NOTE

  • If you want to run this application over a network, you must replace localhost in the client configuration with the name of the remote computer.
  • Although there are only a few settings in the preceding configuration file, most of the problems using .NET remoting occur because some of these settings are either incorrect or do not match the configuration settings for client applications. It is easy to mistype a name, forget a port, or neglect an attribute. If you are having problems with your remoting application, check your configuration settings first.

2- Create a new source file in the language of your choice. In the main method, call RemotingConfiguration.Configure passing in the name of the client configuration file (client.exe.config). Next, instantiate an instance of RemotableType and call its SayHello method. Save the client application as Client.cs or Client.vb in the remoting\client directory.

Public Shared Sub Main()
      RemotingConfiguration.Configure("Client.exe.config")
      Dim remoteObject As New RemotableType()
      Console.WriteLine(remoteObject.SayHello())
   End Sub 'Main

   public static void Main(){
      RemotingConfiguration.Configure("Client.exe.config");
      RemotableType remoteObject = new RemotableType();
      Console.WriteLine(remoteObject.SayHello());
   }

3- Copy the RemotableType.dll assembly from remoting\Type into remoting\client.

NOTE

A common question at this point is “How do I know that the remote object gets called if I am copying the assembly to the client?” This is exactly why we added the call to Console.WriteLine in the RemotableType.SayHello() method. If the remote object is being called the WriteLine occurs in the Listener process, if not the WriteLine occurs in the client process.

4-      Compile client application by typing the following command in the remoting\client directory:

VB

vbc /r:RemotableType.dll Client.vb

csc /noconfig /r:RemotableType.dll Client.cs

5-      Open up two command prompts. In one, go to the remoting\listener directory and run listener.exe. In the other, go to the remoting\client directory and run client.exe. The client command-prompt should look like this:

C:\tmp\Remoting\client>client

Hello, world

6-      The listener command-prompt should look like this:

C:\tmp\Remoting\listener>listener

Listening for requests. Press Enter to exit…

RemotableType.SayHello() was called!

7-      You can see by the output of the listener that it received a call to RemotableType.SayHello().

VB

‘ Client.vb

Imports System

Imports System.Runtime.Remoting

Public Class Client

Public Shared Sub Main()

RemotingConfiguration.Configure(“Client.exe.config”)

Dim remoteObject As New RemotableType()

Console.WriteLine(remoteObject.SayHello())

End Sub ‘Main

End Class ‘Client

// Client.cs

using System;

using System.Runtime.Remoting;

public class Client{

public static void Main(){

RemotingConfiguration.Configure(“Client.exe.config”);

RemotableType remoteObject = new RemotableType();

Console.WriteLine(remoteObject.SayHello());

}

}

Additional Information

Remoting Settings Schema

Contains tags used to put custom settings in remoting application configuration files.

<configuration>

<system.runtime.remoting>

<application>

<lifetime>

<channels> (Instance)

<channel> (Instance)

<serverProviders> (Instance)

<provider> (Instance)

<formatter> (Instance)

<clientProviders> (Instance)

<provider> (Instance)

<formatter> (Instance)

<client>

<wellknown> (Client Instance)

<activated> (Client Instance)

<service>

<wellknown> (Service Instance)

<activated> (Service Instance)

<soapInterop>

<interopXmlType>

<interopXmlElement>

<preLoad>

<channels> (Template)

<channel> (Template)

<serverProviders> (Instance)

<provider> (Instance)

<formatter> (Instance)

<clientProviders> (Instance)

<provider> (Instance)

<formatter> (Instance)

<channelSinkProviders>

<serverProviders> (Template)

<provider> (Template)

<formatter> (Template)

<clientProviders> (Template)

<provider> (Template)

<formatter> (Template)

<customErrors>

<debug>

Element

Description

<system.runtime.remoting> Contains information about remote objects and channels.
<application> Contains information about remote objects the application consumes and exposes.
<lifetime> Contains information about the lifetime of all client-activated objects serviced by this application.
<channels> (Instance) Contains channels that the application uses to communicate with remote objects.
<channel> (Instance) Configures the channel that the application uses to communicate with remote objects.
<serverProviders> (Instance) Contains providers for channel sinks that are to become part of the default server-side channel sink call chain for this channel template when the template is referenced elsewhere in the configuration file.
<provider> (Instance) Contains the channel sink provider for a channel sink that is to be inserted into the channel sink chain.
<formatter> (Instance) Contains the channel sink provider for a formatter sink that is to be inserted into the channel sink chain.
<clientProviders> (Instance) Contains providers for channel sinks that are to become part of the default client-side channel sink call chain for this channel template when the template is referenced elsewhere in the configuration file.
<client> Contains objects the application consumes.
<wellknown> (Client Instance) Contains information about server-activated (well-known) objects the application wants to consume.
<activated> (Client Instance) Contains information about client-activated objects the application wants to consume.
<service> Contains objects the application exposes to other application domains or contexts.
<wellknown> (Service Instance) Contains information about server-activated (well-known) objects the application wants to publish.
<activated> (Service Instance) Contains information about client-activated objects the application wants to publish.
<soapInterop> Contains type mappings used with SOAP.
<interopXmlType> Creates a bidirectional map between a common language runtime type and an XML type and XML namespace.
<interopXmlElement> Creates a bidirectional map between a common language runtime type and an XML element and XML namespace.
<preLoad> Specifies the type to load the mappings from classes that extend SoapAttribute.
<channels> (Template) Contains channel templates that the application uses to communicate with remote objects.
<channel> (Template) Contains the channel template that the application can specify and configure to communicate with or listen to requests for remote objects.
<channelSinkProviders> Contains templates for client and server channel sink providers. Any channel sink providers specified underneath this element can be referenced anywhere a channel sink provider might be registered.
<serverProviders> (Template) Contains channel sink templates that can be inserted into a server channel call chain.
<provider> (Template) Contains the channel sink provider template for a channel sink that is to be inserted into the server or client channel sink chain.
<formatter> (Template) Contains the channel sink provider for a formatter sink that is to be inserted into the client or server channel sink chain.
<clientProviders> (Template) Contains channel sink templates that can be inserted into a client channel call chain.
<debug> Specifies whether to load types in the configuration file when the application starts.
<customErrors> Indicates whether the server channels in this application domain return filtered or complete exception information to local or remote callers.

HttpChannel Class

Namespace: System.Runtime.Remoting.Channels.Http

Channels transport messages across remoting boundaries (for example, between computers or application domains). The HttpChannel class transports messages using the HTTP protocol.

Channels transport messages across remoting boundaries (for example, between computers or application domains). The HttpChannel class transports messages using the HTTP protocol.

Channels are used by the .NET Framework remoting infrastructure to transport remote calls. When a client makes a call to a remote object, the call is serialized into a message that is sent by a client channel and received by a server channel. It is then deserialized and processed. Any returned values are transmitted by the server channel and received by the client channel.

A HttpChannel object has associated configuration properties that can be set at run time either in a configuration file (by invoking the static RemotingConfiguration.Configure method) or programmatically (by passing a IDictionary collection to the HttpChannel constructor).

EX.

The following code example shows how to use a HttpClientChannel to set up a remoting server and its client. The example contains three parts:

  • A server
  • A client
  • A remote object used by the server and the client

The following code example shows a server.

using System;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Http;

public class Server
{
    public static void Main(string[] args)
    {
        // Create the server channel.
        HttpServerChannel serverChannel = new HttpServerChannel(9090);

        // Register the server channel.
        ChannelServices.RegisterChannel(serverChannel);

        // Expose an object for remote calls.
        RemotingConfiguration.RegisterWellKnownServiceType(
            typeof(RemoteObject), "RemoteObject.rem", 
            WellKnownObjectMode.Singleton);

        // Wait for the user prompt.
        Console.WriteLine("Press ENTER to exit the server.");
        Console.ReadLine();
        Console.WriteLine("The server is exiting.");
    }
}

The following code example shows a client for this server.

using System;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Http;
using System.Security.Permissions;

public class Client
{
[SecurityPermission(SecurityAction.Demand)]
    public static void Main(string[] args)
    {
        // Create the channel.
        HttpClientChannel clientChannel = new HttpClientChannel();

        // Register the channel.
        ChannelServices.RegisterChannel(clientChannel);

        // Register as client for remote object.
        WellKnownClientTypeEntry remoteType = 
            new WellKnownClientTypeEntry(typeof(RemoteObject), 
            "http://localhost:9090/RemoteObject.rem");
        RemotingConfiguration.RegisterWellKnownClientType(remoteType);

        // Create a message sink.
        string objectUri;
        System.Runtime.Remoting.Messaging.IMessageSink messageSink = 
            clientChannel.CreateMessageSink(
            "http://localhost:9090/RemoteObject.rem", 
            null, out objectUri);
        Console.WriteLine(
            "The URI of the message sink is {0}.", 
            objectUri);
        if (messageSink != null)
        {
            Console.WriteLine("The type of the message sink is {0}.", 
                messageSink.GetType().ToString());
        }

        // Display the channel's properties using Keys and Item.
        foreach(string key in clientChannel.Keys)
        {
            Console.WriteLine(
                "clientChannel[{0}] = <{1}>", 
                key, clientChannel[key]);
        }

        // Parse the channel's URI.
        string objectUrl = "http://localhost:9090/RemoteObject.rem";
        string channelUri = clientChannel.Parse(objectUrl, out objectUri);
        Console.WriteLine("The object URL is {0}.", objectUrl);
        Console.WriteLine("The object URI is {0}.", objectUri);
        Console.WriteLine("The channel URI is {0}.", channelUri);

        // Create an instance of the remote object.
        RemoteObject service = new RemoteObject(); 

        // Invoke a method on the remote object.
        Console.WriteLine("The client is invoking the remote object.");
        Console.WriteLine("The remote object has been called {0} times.", 
            service.GetCount());
    }
}

The following code example shows the remote object used by the server and the client.

using System;
using System.Runtime.Remoting;

// Remote object.
public class RemoteObject : MarshalByRefObject
{
    private int callCount = 0;

    public int GetCount()
    {
        Console.WriteLine("GetCount was called.");
        callCount++;
        return(callCount);
    }

}

Conclusion

.NET Remoting is a mechanism for communicating between objects which are not in the same process. It is a generic system for different applications to communicate with one another. .NET objects are exposed to remote processes, thus allowing inter process communication. The applications can be located on the same computer, different computers on the same network, or on computers across separate networks.

Microsoft .NET Remoting provides a framework that allows objects to interact with each other across application domains. Remoting was designed in such a way that it hides the most difficult aspects like managing connections, marshaling data, and reading and writing XML and SOAP. The framework provides a number of services, including object activation and object lifetime support, as well as communication channels which are responsible for transporting messages to and from remote applications.

Advertisements