Implementing Broker with .NET Remoting Using Server-Activated Objects
Retired Content |
---|
This content is outdated and is no longer being maintained. It is provided as a courtesy for individuals who are still using these technologies. This page may contain URLs that were valid when originally published, but now link to sites or pages that no longer exist. Please see the patterns & practices guidance for the most current information. |
Version 1.0.1
GotDotNet community for collaboration on this pattern
Complete List of patterns & practices
Context
You are using the Microsoft .NET Framework to build an application that requires the use of distributed objects. Your requirements include the ability to pass objects by value or reference, whether those objects reside on the same computer, on different computers in the same local area network (LAN), or on different computers in a wide area network (WAN). Your application does not require you to control the lifetime of the remote objects explicitly.
Background on .NET Remoting
Remoting uses object references to communicate between the client and the server. In the server-activated scenario, your client retrieves a reference to an existing server object using the remoting infrastructure (Activator.GetObject). After you have a reference to the object, you can call its methods as though the object were in your process rather than running on a separate computer. The following underlying mechanism is used to implement this functionality:
The client retrieves an instance of the remote type.
The remoting infrastructure creates a proxy object that acts as a surrogate for the remote type.
The client calls a method on that proxy. The remoting system receives the call, routes it to the server process, invokes the server object, and then returns a result to the client proxy, which passes the result to the client object.
The calls themselves have to be sent in some fashion between the client and server. The remoting infrastructure refers to this mechanism as a transport channel. Channels transport messages between applications across remoting boundaries, whether between application domains, processes, or computers. A channel can listen on an endpoint for inbound messages; send outbound messages to another endpoint, or both. This enables you to plug in a wide range of protocols, even if the common language runtime is not at the other end of the channel.
Although the server process knows everything about each unique object, the client knows only that it wants a reference to an object in another application domain, perhaps on another computer. From the world outside the server application domain, the object is located by a URL.
Server Activation
As described in the introduction to the Distributed Systems cluster, the .NET Framework supports two activation models: server activation and client activation. Server-activated objects are objects whose lifetimes are directly controlled by the server. The server application domain creates these objects only when the client makes a method call on the object, not when the client calls new or Activator.GetObject(); this saves a network round-trip solely for the purpose of instance creation. Only a proxy is created in the client application domain when a client requests an instance of a server-activated type. This also means, however, that only default constructors are allowed for server-activated types. To publish a type whose instances will be created with specific constructors that take arguments, you can use client activation.
To create an instance of a server-activated type, clients typically use Activator.GetObject().
Choosing a Protocol and Serialization Mechanism
The type of protocol you choose to use has an impact on the how your application performs. For some criteria for choosing the correct type of channel for your application, see "Choosing Communication Options in .NET" from the .NET Framework Developer's Guide, available on the MSDN developer program Web site at: https://msdn.microsoft.com/library/.
In this pattern, you will see an example of HttpChannel/SOAP and TcpChannel/Binary.
Implementation Strategy
This pattern presents two examples of server-activated objects and the flexibility of the .NET remoting infrastructure. The first example uses HttpChannel with its default serialization mechanism, SOAP. The second example uses TcpChannel with its default serialization mechanism, which is binary. Before examining the applications themselves, let's first look at the class that must be distributed across the network.
Server Object
The RecordingsManager class provides a method named GetRecordings, which retrieves a list of recordings from a database and return the result in DataSet. Note that a series of considerations is involved in determining the best data types to be transmitted over a remote connection. This example uses DataSet because it keeps the sample code short and demonstrates the transfer of complex data types. For a thorough treatment of this topic, see the MSDN article "Designing Data Tier Components and Passing Data Through Tiers" at:
https://msdn.microsoft.com/library/en-us/dnbda/html/BOAGag.asp
RecordingsManager.cs
The following sample shows the RecordingsManager class:
using System;
using System.Data;
using System.Data.SqlClient;
public class RecordingsManager
{
public DataSet GetRecordings()
{
String selectCmd = "select * from Recording";
SqlConnection myConnection = new SqlConnection(
"server=(local);database=recordings;Trusted_Connection=yes");
SqlDataAdapter myCommand =
new SqlDataAdapter(selectCmd, myConnection);
DataSet ds = new DataSet();
myCommand.Fill(ds, "Recording");
return ds;
}
}
This class must be accessed remotely. First, the RecordingsManager class must inherit from a class in the remoting infrastructure called MarshalByRefObject. 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 marshaled by value. When a remote application references a marshal-by-value object, a copy of the object is passed across remoting boundaries. Because you want to use the proxy method instead of the copy method to communicate, you need to inherit from MarshalByRefObject. Second, you need to extract an interface from this class. The interface is necessary to reduce the dependencies between the client and server and also to better deploy the application. For more information, see "Deployment Considerations" later in this pattern.
IRecordingsManager.cs
The following is the code for the extracted IRecordingsManager interface:
using System;
using System.Data;
public interface IRecordingsManager
{
DataSet GetRecordings();
}
RecordingsManager.cs (Remote-Enabled)
Making the changes to RecordingsManager results in the following code:
public class RecordingsManager : MarshalByRefObject, IRecordingsManager
{ /* */ }
HttpChannel: SOAP Serialization
The primary motivations for choosing this channel and serialization mechanism include security and interoperability. HttpChannel hosted in Microsoft Internet Information Services (IIS) enables you to take advantage of security functions that are built into IIS and ASP.NET. If you choose any other channel or choose not to host HttpChannel in IIS, you have to provide your own security functions. Also, to interoperate between different operating systems, you must use the HttpChannel and SOAP serialization. However, HttpChannel is not the highest-performing option due to the use of XML serialization and the additional overhead of using the HTTP protocol inside IIS and ASP.NET. For more information, see "Operational Considerations" later in this pattern.
The following solution uses HttpChannel with SOAP serialization for the RecordingsManager class described earlier (See Figure 1).
Figure 1: HttpChannel implementation
HttpServer.cs
HttpServer is a console application that creates the HttpChannel object and assigns port 8100. The code then associates the name "RecordingsManager.soap" with an instance of RecordingsManager.
There are two activation modes for server-activated objects: Singletonand SingleCall.
Singleton types never have more than one instance at any one time. If an instance exists, all client requests are serviced by that instance. If one does not exist, the server creates an instance and all subsequent client requests are serviced by that instance.
SingleCall types always have one instance per client request. The next method invocation will be serviced by a different server instance, even if the previous instance has not yet been recycled by the system.
RecordingsManager uses the Singleton activation mode, so that you will have only one instance of RecordingsManager running on the server. This works fine, because the object has only a single method that retrieves a predefined set of data. The last line makes sure that the code will not exit until the user presses ENTER. You should note that this is probably not the best way to ensure that the program does not exit. If the program were to do so, clients would not be able to access the server objects.
using System;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Http;
public class HttpServer
{
static void Main(string[] args)
{
HttpChannel channel = new HttpChannel(8100);
ChannelServices.RegisterChannel(channel);
RemotingConfiguration.RegisterWellKnownServiceType(
typeof(RecordingsManager),
"RecordingsManager.soap",
WellKnownObjectMode.Singleton);
Console.ReadLine();
}
}
HttpClient.cs
The client program calls the remoting framework function Activator.GetObject(), specifying the URL where the object is located along with the type that should be returned. In this case, you should expect an IRecordingsManager object at https://localhost:8100/RecordingsManager.soap. After you have the instance, you can call methods on it as if it were in the same application domain.
using System;
using System.Data;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Http;
public class HttpClient
{
[STAThread]
static void Main(string[] args)
{
HttpChannel channel = new HttpChannel();
ChannelServices.RegisterChannel(channel);
IRecordingsManager mgr = (IRecordingsManager)
Activator.GetObject(typeof(IRecordingsManager),
"https://localhost:8100/RecordingsManager.soap");
Console.WriteLine("Client.main(): Reference acquired");
DataSet ds = mgr.GetRecordings();
Console.WriteLine("Recordings Count: {0}",
ds.Tables["recording"].Rows.Count);
}
}
TcpChannel: Binary Serialization
The primary motivation for choosing this channel and serialization mechanism is performance. In fact, using binary serialization alone increases performance dramatically. (See "Operational Considerations.") If you do not have any security issues (for example, you are building a small application that runs entirely inside a firewall), you should use TcpChannel with binary serialization, because it performs the best.
The following solution uses the TcpChannel with binary serialization for the RecordingsManager class described earlier (See Figure 2).
Figure 2: TcpChannel/binary serialization implementation
TcpServer.cs
TcpServer is a console application that creates the TcpChannel object and assigns port 8100. The code then associates the name "GetRecordingsManager" with an instance of RecordingsManager. The activation mode for RecordingsManager is a singleton, so you will have only one instance of RecordingsManager running on the server. The last line ensures that the code will not exit until the user presses ENTER. You should note that this is probably not best the way to ensure that the program does not exit. If the program were to do so, clients would not be able to access the server objects.
using System;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Tcp;
public class TcpServer
{
static void Main(string[] args)
{
TcpChannel channel = new TcpChannel(8100);
ChannelServices.RegisterChannel(channel);
RemotingConfiguration.RegisterWellKnownServiceType(
typeof(RecordingsManager),
"GetRecordingsManager",
WellKnownObjectMode.Singleton);
Console.ReadLine();
}
}
TcpClient.cs
The client program calls the remoting framework method Activator.GetObject() to retrieve a proxy for the RecordingsManager object on the server. The method specifies the URL where the object is located along with the type that should be returned. In this case, you should expect an IRecordingsManager object at the following location: https://localhost:8100/GetRecordingsManager. After you have the instance, you can call methods on it as if it were in the same application domain.
using System;
using System.Data;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Tcp;
class TcpClient
{
[STAThread]
static void Main(string[] args)
{
TcpChannel channel = new TcpChannel();
ChannelServices.RegisterChannel(channel);
IRecordingsManager mgr = (IRecordingsManager)
Activator.GetObject(typeof(IRecordingsManager),
"tcp://localhost:8100/GetRecordingsManager");
Console.WriteLine("Client.main(): Reference acquired");
DataSet ds = mgr.GetRecordings();
Console.WriteLine("Recordings Count: {0}",
ds.Tables["recording"].Rows.Count);
}
}
Deployment Considerations
When using .NET remoting, you must pay careful attention to the deployment of the application into different assemblies. The main goal is to ensure that the code on the server does not have to be shipped to the client. Figure 3 is a UML deployment diagram for the HttpChannel/SOAP example.
Figure 3: Structure for the HttpChannel/SOAP example
This example uses an assembly named IRecordingsManager, which is shared between the client and server. This assembly contains the IRecordingsManager interface, which defines the interface of the remote object that the client and server are sharing. In the example, the IRecordingsManager assembly is downloaded to the client.
Tests
It is relatively simple to write tests for the server in NUnit. You retrieve the object from the server and call methods as if they were local objects. The following class tests the HttpServer class:
HttpServerFixture.cs
using System;
using System.Data;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Http;
using NUnit.Framework;
[TestFixture]
public class HttpServerFixture
{
private IRecordingsManager mgr;
private HttpChannel channel;
private DataSet dataSet;
[SetUp]
public void LoadDataSet()
{
channel = new HttpChannel();
ChannelServices.RegisterChannel(channel);
mgr = (IRecordingsManager)
Activator.GetObject(typeof(IRecordingsManager),
"https://localhost:8100/RecordingsManager.soap");
dataSet = mgr.GetRecordings();
}
[Test]
public void RetrieveDataSet()
{
DataTable recording = dataSet.Tables["recording"];
Assertion.AssertEquals(4,recording.Rows.Count);
DataRow row = recording.Rows[0];
string title = (string)row["title"];
Assertion.AssertEquals("Up", title.Trim());
}
[TearDown]
public void Release()
{
ChannelServices.UnregisterChannel(channel);
}
}
Resulting Context
Using server-activated objects to implement Broker with .NET remoting results in a number of benefits and liabilities.
Benefits
.NET remoting provides a fully featured distributed object model with full common language runtime semantics running on the client and server. You do not lose any fidelity between client and server. The example demonstrated passing the complicated type, System.Data.DataSet between the client and server. This would not be possible without having the common language runtime on both sides of the connection.
Liabilities
Some of the Broker benefits are offset by the following potential liabilities:
Remote objects. You cannot forget that these are remote objects. Even though they look like local objects, there is still overhead involved in marshaling data back and forth from the server. Keep in mind that a remote call can be at least 1000 times slower than a local call in the common language runtime. Therefore, you do not want to make more calls than necessary. This desire to minimize round-trips may cause you not to use the finest granularity in regards to the interface.
Deployment complexity. When using server-activated objects as described in the examples, the objects must have been registered prior to the client asking for them. This makes deployment more complex.
Limited interoperability. You can use .NET remoting to build Web services. However, you must pare down your endpoints to the simplest data types. For example, if you want interoperability with other Web service toolkits, you must restrict parameters to built-in simple types and your own data types (don't use .NET Framework types like DataSet), and use server-activated objects.
More complicated. Compared to Web services, .NET remoting is more difficult to learn, implement, and debug.
Security Considerations
To use the security features available with Microsoft Internet Information Services (IIS) (for example, standard HTTP authentication schemes include Basic, Digest, digital certificates, and even Microsoft .NET Passport), you must use an HTTP-based application hosted in IIS with ASP.NET. Using any other transport protocol or using the HttpChannel outside of IIS requires you to provide a security mechanism.
Operational Considerations
The following is a summary of a performance comparison that appears in the MSDN article, "Performance Comparison: .NET Remoting vs. ASP.NET Web Services" [Dhawan02]. The article concludes that you can achieve the highest performance by using the TCP channel and binary serialization with a Windows Service host. This configuration transmits binary data over raw TCP sockets, which is more efficient than HTTP. Performance is 60 percent faster than with the slowest approach, which is HttpChannel using SOAP serialization hosted in IIS with ASP.NET.
Hosting in IIS is slower because it involves an extra process hop from IIS (Inetinfo.exe) to Aspnet_wp.exe. However, if you choose to host your channel without IIS and ASP.NET, you will need to provide your own mechanisms for authentication, authorization, and privacy.
Related Patterns
For more information, see the following related patterns:
Proxy [Gamma95]
Acknowledgments
[Dhawan02] Dhawan, Priya. "Performance Comparison: .NET Remoting vs. ASP.NET Web Services." MSDN Library, September 2002. Available at: https://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnbda/html/bdadotnetarch14.asp.
[Crocker02] Crocker, Olsen, and Jezierski. "Designing Data Tier Components and Passing Data Through Tiers." MSDN Library, August 2002. Available at: https://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnbda/html/boagag.asp.