3. Programmatic Configuration Example
Instead of fragmented
code samples, it's now time for a more comprehensive example showing how
the different steps required for programmatic configuration fit
together, on both the client and the host side. As mentioned previously,
since both the host and the client require the server's metadata, it's
best if the server is in a class library. The constructor brings up a message box so that you can tell when
a new object is created, which is especially useful when experimenting
with single-call or singleton objects. The single public method of MyClass is Count( ),
which pops up a message box showing the incremented value of a counter.
The counter indicates the state lifecycle in the different activation
modes. The message boxes have as their captions the name of the current
app domain. The RemoteServerHost application is a dialog-based application. Its Main( ) method registers TCP, IPC, and HTTP channels and then displays the dialog shown in Figure 1.
Displaying a form by calling Application.Run( ) is a blocking operation, and control returns to the Main( )
method only when the dialog is closed. This, of course, has no effect
on the channels registered, because worker threads are used to monitor
the channels. When the dialog is closed, the Main( ) method unregisters the channels and exits. The Server Host dialog lets you check how the host registers the MyClass type it exports: as client-activated, server-activated, or both. If you select Server Activated, you
need to choose between Single Call and Singleton, using the radio buttons. When you click the Register Object button, the OnRegister( ) method is called . OnRegister( )
simply registers the object based on the user-interface selections.
Note that you can register the object both as client- and
server-activated.
The Client application displays the dialog shown in Figure 2. You can select either Client Activated or Server Activated (but not both), and then register the type with the OnRegister( )
method. The interesting part of the client application is the way it
constructs the activation URL for the object registration. The helper
method GetActivationURL( ) constructs a URL based on the channel selected (starts with tcp://, http://, or ipc://) and appends the URI only if Server Activated mode is selected. OnRegister( ) calls GetActivationURL( ) and then registers the type accordingly. When you click "new" in the Client of Remote Object dialog, it simply uses new to create a new instance of MyClass and calls Count( ) twice, either remotely (if you registered) or locally (if no registration took place).
Example 1. Programmatic remoting configuration
///////////////////// ServerAssembly class library ////////////////////////////
namespace RemoteServer
{
public class MyClass : MarshalByRefObject
{
int m_Counter = 0;
public MyClass( )
{
string appName = AppDomain.CurrentDomain.FriendlyName;
MessageBox.Show("Constructor",appName);
}
public void Count( )
{
m_Counter++;
string appName = AppDomain.CurrentDomain.FriendlyName;
MessageBox.Show("Counter value is " + m_Counter,appName);
}
}
}
////////////////////// RemoteServerHost EXE assembly ///////////////////////////
using RemoteServer;
partial class ServerHostDialog : Form
{
Button RegisterButton;
RadioButton m_SingletonRadio;
RadioButton m_SingleCallRadio;
CheckBox m_ClientActivatedCheckBox;
CheckBox m_ServerActivatedCheckBox;
GroupBox m_GroupActivationMode;
public ServerHostDialog( )
{
InitializeComponent( );
}
void InitializeComponent( )
{...}
static void Main( )
{
//Registering TCP channel
IChannel tcpChannel = new TcpChannel(8005);
ChannelServices.RegisterChannel(tcpChannel);
//Registering http channel
IChannel httpChannel = new HttpChannel(8006);
ChannelServices.RegisterChannel(httpChannel);
//Registering IPC channel
IChannel ipcChannel = new IpcChannel("MyHost");
ChannelServices.RegisterChannel(ipcChannel);
Application.Run(new ServerHostDialog( ));
//Do not have to, but cleaner:
ChannelServices.UnregisterChannel(tcpChannel);
ChannelServices.UnregisterChannel(httpChannel);
ChannelServices.UnregisterChannel(ipcChannel);
}
void OnRegister(object sender,EventArgs e)
{
Type serverType = typeof(MyClass);
//if the client activated checkbox is checked:
if(m_ClientActivatedCheckBox.Checked)
{
RemotingConfiguration.RegisterActivatedServiceType(serverType);
}
//if the server activated checkbox is checked:
if(m_ServerActivatedCheckBox.Checked)
{
if(m_SingleCallRadio.Checked)
{
//Allow Server activation, single call mode:
RemotingConfiguration.RegisterWellKnownServiceType(serverType,
"MyRemoteServer",
WellKnownObjectMode.SingleCall);
}
else
{
//Allow Server activation, singleton mode:
RemotingConfiguration.RegisterWellKnownServiceType(serverType,
"MyRemoteServer",
WellKnownObjectMode.Singleton);
}
}
}
}
}
////////////////////////// Client EXE assembly /////////////////////////////////
using RemoteServer;
partial class ClientForm : Form
{
RadioButton m_HttpRadio;
RadioButton m_TCPRadio;
RadioButton m_ServerRadio;
RadioButton m_ClientRadio;
GroupBox m_ChannelGroup;
GroupBox m_ActivationGroup;
Button m_NewButton;
Button m_RegisterButton;
public ClientForm( )
{
InitializeComponent( );
}
void InitializeComponent( )
{...}
static void Main( )
{
Application.Run(new ClientForm( ));
}
string GetActivationURL( )
{
if(m_TCPRadio.Checked)
{
if(m_ServerRadio.Checked)
{
//Server activation over TCP. Note object URI
return "tcp://localhost:8005/MyRemoteServer";
}
else
{
//Client activation over TCP
return "tcp://localhost:8005";
}
}
if(m_HttpRadio.Checked)//http channel
{
if(m_ServerRadio.Checked)
{
//Server activation over http. Note object URI
return "http://localhost:8006/MyRemoteServer";
}
else
{
//Client activation over http
return "http://localhost:8006";
}
}
else//IPC channel
{
if(m_ServerRadio.Checked)
{
//Server activation over IPC. Note object URI
return "ipc://MyHost/MyRemoteServer";
}
else
{
//Client activation over IPC
return "ipc://MyHost";
}
}
}
void OnRegister(object sender,EventArgs e)
{
Type serverType = typeof(MyClass);
string url = GetActivationURL( );
if(m_ServerRadio.Checked)
{
RemotingConfiguration.RegisterWellKnownClientType(serverType,url);
}
else //Client activation mode
{
//Register just once:
RemotingConfiguration.RegisterActivatedClientType(serverType,url);
}
}
void OnNew(object sender,EventArgs e)
{
MyClass obj;
obj = new MyClass( );
obj.Count( );
obj.Count( );
}
}
|
4. Administrative Configuration
Both client and host
applications can take advantage of a configuration file to specify their
remoting settings instead of making programmatic calls. Although you
can use whatever name you want for the configuration file, the
convention is to give it the same name as the application, but suffixed
with .config, like so: <app
name>.exe.config. For example, if the host assembly is called RemoteServerHost.exe, the configuration file will be called RemoteServerHost.exe.config.
The configuration file is an XML file, and it can be the same
configuration file that captures custom version-binding policies. You'll find the remoting configuration section in the <application> tag, under the <system.runtime.remoting> tag:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<system.runtime.remoting>
<application>
<!--Remoting configuration setting goes here -->
</application>
</system.runtime.remoting>
</configuration>
Both the client and the host load the configuration file using the static Configure( ) method of the RemotingConfiguration class, providing the name of the configuration file:
public static void Configure(string filename);
Based on the
instructions in the configuration file, .NET programmatically registers
the channels and the objects, so that you don't need to write
appropriate code. If at runtime .NET can't locate the specified file, an
exception of type RemotingException is thrown. Typically, both the client and the host application call the Configure( ) method in their Main( ) methods, but you can call it anywhere else, as long as you call it before making any remote calls:
static void Main( )
{
RemotingConfiguration.Configure("RemoteServerHost.exe.config");
//Rest of Main( )
}
All the design directives
and limitations described in the context of programmatic configuration
also apply to administrative configuration. |
|
4.1. Visual Studio 2005 and configuration files
Visual Studio 2005
can automatically generate a configuration file prefixed with your
application name. Bring up the Add New Item dialog, and select the
Application Configuration File template, as shown in Figure 3. Name the file App.config. The name of the file does matter—it must be exactly App.config.
Visual Studio 2005 will add the App.config
file to the project (with a build action set to None). Open the file
and add the remoting settings to it. After every successful build,
Visual Studio 2005 will copy the App.config file to the project output folder and rename it <app name>.exe.config. The old copy of the config file is erased.
If you already have a configuration file in the format of <app name>.exe.config, but not the App.config file, Visual Studio 2005 will erase it (not substitute it) and you will lose your settings. |
|
If you adopt the convention of always naming your configuration files in the format of <app name>.exe.config, and you always place the file in the application directory, you can improve on RemotingConfiguration.Configure( ). Example 2 shows the static helper class RemotingConfigurationEx, with a parameter-less Configure( ) method.
Example 2. The RemotingConfigurationEx helper class
public static class RemotingConfigurationEx
{
public static void Configure( )
{
string fileName = AppDomain.CurrentDomain.FriendlyName + ".config";
RemotingConfiguration.Configure(fileName);
}
}
static void Main( )
{
RemotingConfigurationEx.Configure( ); //Automatically loads
//the correct file
//Rest of Main( )
}
|
Recall that the Main( ) method runs in the default app domain, whose name matches that of the application EXE assembly. RemotingConfigurationEx retrieves the friendly name of the app domain, appends .config to it, and passes that as the filename for RemotingConfiguration.Configure( ). In most cases, using RemotingConfiguration.Configure( ) avoids hardcoding the configuration filename.