Contents
- Introduction
- Features
- Concept
- Usage
- Implementation
- Test
- Version
- Conclusion
The distributed object has to be properly hosted by the
application domain process such as Web Server, Console, Windows Form
or Windows Service before using it. The host process requires
configuration of a remoting infrastructure to enable consuming the
published objects. The remoting configuration can be done
programmatically or administratively. Using the config file to
administrate an application deployment is a preferable and
recommended way to easy mapped the logical application model to its
physical implementation.
The Remoting Management Console (RMC) is an administrative tool
to create and configure a remoting host process built as a windows
service. The console has a mechanism to create a windows service on
the fly including its configuration file. Using the properly snap-in
nodes the config file can be administratively finalized based on the
application requirements before the host process starts. On the
other hand, the RMC is also very useful tool for administrating
(tuning phase) already deployed the distributed application
modifying the contents of the host process config files. This
article describes a usage and implementation of the Remoting
Management Console tool.
Basically the RMC snap-in control is divided into the following
activities:
Host Process
- Scanning an installed host processes
- Creating and installing a new host process
- Creating a host process configuration file
- Controlling the host process such as start, stop and restart
- Receiving the host process Event Logs
Config File
- Creating or modifying the remoting section
- Creating or modifying appSettings
- Creating or modifying configSections
- Declaration of the configSections
- Declaration of the Channels
- Declaration of the ChannelSinkProviders
Inside of the remoting section the following sections are
managed:
- Lifetime
- Service
- Channels
- ClientProviders
- ServerProviders
Drag&Drop Feature
There are few places where the drag&drop can be used to enter
an existing assembly:
- HostProcesses result view panel to drop the host
process
- RemoteObjects result view panel to drop the assembly of
the remote object
Additionally, the drag&drop can be used within the configSection
to move or copy nodes in the root.
The concept of the RMC is based on the following:
- Remoting host process is a windows service
- Remoting configuration is driven by the config file
- Microsoft Management Console (MMC) to host the RMC snap-in
Using the windows service to host remoting objects is
straightforward. The standard boilerplate generated by .Net wizard
has been extended for a remoting registration and un-registration
parts like it's shown in the following code snippet:
protected override void OnStart(string[] args)
{
if(args.Length == 1) Thread.Sleep(Convert.ToInt32(args[0]));
RemotingConfiguration.Configure(
AppDomain.CurrentDomain.SetupInformation.ConfigurationFile);
}
protected override void OnStop()
{
foreach(IChannel objChannel in ChannelServices.RegisteredChannels)
ChannelServices.UnregisterChannel(objChannel);
}
When the windows service receives a Start command, the OnStart
action is invoked to perform a remoting configuration from the
config file. On the other side, the OnStop action will
clean-up all listeners hosted by this service included their worker
threads.
The host process (windows service) must have a unique name registered
on the local machine. The RMC has a mechanism to create a unique
host process on the fly based on the application requirements. Practically,
the host process template source is modified in the variable places
before its compiling (see Implementation)
Once the host process has been created, the RMC will control its
state such as start, stop and restart using the MMC environment.
In the case of the existing non-installed host process, the RMC
has a drag&drop capability making its entry to the catalog. The
other hand, the already installed host processes are scanned and
added into the snap-in. Notice that the host process need to have an
implementation of the derived Installer class attributed with
RunInstaller(true) to make its installation rsp. un-installation
service automatically.
The initiate config file image is generated from the template HostProcessTemplate.exe.config
file located in the %windir%/system32 folder. It's very easy to
replace it for another one with custom pre-built common
configuration. The RMC has many features for administrating this
config file using the snip-in layout.
The RMC is a stateless snap-in in the MMC environment. Each node
is updated on the runtime based on the host process status and contents
of the config file. Thanks for MMCLib library developed by http://www.ironringsoftware.com
to make my life easy to handle a unmanaged MMC code. I made some
slightly modification of the MMCLib for my needs, that's why I included
its source in my solution. Basically, the MMCLib handles a snap-in
such as creating nodes and processing their events. The following
picture show that:
Based on the menu selection, for instance: New -> Remote
Object, the snap-in delegates call to the properly event handler (OnNewTask)
to perform the specific action. In this case, the windows form is
popup to enter necessary attributes requested by the remoting
service section in the config file. After pressing the Apply button
on the form, the config file is updated and snap-in is notified via
its handler - OnUser. This scenario is repeated mostly for all
node's activities.
The RMC requires to be installed by the RemotingManagementConsole.msi
file. The windows installer will perform all necessary tasks such
as RMC snap-in, MMCLib.dll and MMCFormsShim.dll registration included
creating its desktop folder. Opening this folder and clicking on
the RemotingManagementConsole.msc icon, the Remoting
Management Console will show up with the snap-in of all the installed
host processes on your machine. The screen snippet has been shown
in the previously chapter.
Let me suppose that we have a remote object and its consumer
ready to deploy them. Before the actually work, the remoting object
is necessary be hosted and published by windows service host
process.
What we need to do?
- create a windows host process
- publish a remoting object in the remoting service tag
- create a channel to listen an incoming remoting messages (IMessages)
Additionally, based on the application requirements:
- adding server Sinks (formatter, provider)
- creating an appSettings for configuration purposes (key/value
pairs)
- creating specific config sections
When the configuration process is done, the host process is ready
to start. Using the Event Log we can see a process of the remoting
configuration. Based on the detail event log message is easy to
figure out which attribute in the config file caused the problem.
Now, if we know what the remoting configuration needs, let handle
this task do it by RMC. Here are its steps:
Step A. Create a new host process
- Select the Remoting Host Processes node in the snap-in
area
- Right-click on the node
- Select New and click Process
- The following Form dialog will show-up
- Change the Name properties, for instance: HostProcess_Sample
- Press button CREATE
Now we have a host process installed as a windows service and
initiate image of the host process config file. The RMC snap-in
created automatically static nodes for this host process:
- lifetime
- RemoteObjects
- Channels
- appSettings
- configSections
The above nodes represent xml sections in the host process
remoting config file. For the next step we are going to administrate
them based on the application requirements.
Step B. Publishing Remote Object
- Select the RemoteObjects node in the HostProcess_Sample
root
- Right-click on the node
- Select New and click Remote Object
- The following Form dialog will show-up
- Populate all attributes on the Form
- Press button APPLY
The other, shortcut way (skipping the steps 1-3) is to use a
Drag&Drop feature when the remote object exist. The following
steps explain that:
- Drag the assembly of the Remote Object, which you what to
publish it from its folder
- Drop the assembly on the RemoteObjects result view (right
panel)
- follow the above instruction steps 4 - 6 to finalize the entry
process.
Step C. Configuring Channel
- Select the Channels node in the HostProcess_Sample
root
- Right-click on the node
- Select New and click Remoting channel
- The following Form dialog will show-up
- Populate necessary attributes on the Form (see below notes)
- Press button APPLY
Notes:
- When your application using a standard channel, for instance:
tcp, enter the port number (must be unique on the machine) and
leave empty the other attributes.
- The channel can be configured using the already registered
channels (machine.config and host process config) or typing its
type in this element. See the checkBox and comboBox features.
- On the bottom of the Form is displayed the finally element for
config file.
- The textBox More can be used to add any specific
attributes using the name/value pair, for instance:
priority="1" myProperty="myValue"
Now, we have a basic configuration of the host process for our
remote object. Of course, we can continue to make more advance
configuration. I assume you have a knowledge of the remoting
configuration and MMC to allow you easy figure out usage of the
others nodes in the RMC snap-in.
Let's continue with our basic configuration. As the next step is
to start the host process.
Step D. Start Host Process
- Select the HostProcess_Sample node in the snap-in area
- Right-click on the node
- Select All Task and click Start
- The Form dialog will show the progress of the starting process
- Check the Host Process result view (right panel) for the Event
Log Messages
To Stop or Restart the host process follow the same steps as for
Start one.
Refreshing snap-in
Each static node in the snap-in has a Refresh menu item to
perform a updating result view and snap-in at the selected level.
There is one special Refresh at the snap-in root. In this case the
RMC is invoking the scanner of the installed host processes (windows
services) on the local machine and refreshing a completely snap-in.
You can use also the key F5 to invoke the refresh task.
The RMC Solution is divided into the following projects:
- MMCLib this is a modified 3rd party software to handle unmanaged
MMC code, http://www.ironringsoftware.com
- ConfigFileLib to handle contents of the configuration file
- HostProcessLib to handle host process such as start, stop,
create, install, etc.
- InstallClassRegAsm is a helper install class for custom action
- InstallClassRegsrv32 is a helper install class for custom
action
- RemotingManagementConsole is a msi installation project
- RemotingManagement this is a snap-in implementation.
There are 17 forms and user controls to handle the snap-in
activities.
Note that all projects except the RemotingManagement are
re-usable, for instance: HostProcessLib can be used for
programmatically creating a host process on the fly in your
application, etc.
The following code snippet show an implementation of the creating
a host process on the fly:
public string Create(string strServiceName, string strServiceDesc,
bool bStartAutomatic, bool bTrayIcon,
string strAssemblyName)
{
string strAssemblyFile = null;
//--- create service ---
ServiceSrcCode ssc = new ServiceSrcCode();
string strServiceSrcCode = ssc.GetSrcCodeForService(strServiceName,
strServiceDesc,
bStartAutomatic,
bTrayIcon);
string strOutputAssembly = string.Format(@"{0}.exe", strAssemblyName);
// assembly compilation.
string[] strArrayReferences = {
"System.dll",
"System.Data.dll",
"System.ServiceProcess.dll",
"System.Configuration.Install.dll" };
CompilerParameters cp = new CompilerParameters();
cp.ReferencedAssemblies.AddRange(strArrayReferences);
cp.GenerateExecutable = true;
cp.GenerateInMemory = false;
cp.OutputAssembly = strOutputAssembly;
cp.WarningLevel = 4;
cp.IncludeDebugInformation = false;
ICodeCompiler icc = new CSharpCodeProvider().CreateCompiler();
CompilerResults cr = icc.CompileAssemblyFromSource(cp, strServiceSrcCode);
if(cr.Errors.Count > 0)
{
foreach(string s in cr.Output)
{
Trace.WriteLine(s);
}
throw new Exception(string.Format("Build failed: {0} errors",
cr.Errors.Count));
}
//cr.TempFiles.KeepFiles = true;
strAssemblyFile = cp.OutputAssembly;
return strAssemblyFile;
}
The above function is using the ServiceSrcCode
class to generate a properly source code of the requested
host process from its template:
#region windows service template text
const string strSrcTmplService = "namespace RemotingHostService" +
"{ " +
"using System;" +
"using System.Collections;" +
"using System.ComponentModel;" +
"using System.Data;" +
"using System.Diagnostics;" +
"using System.ServiceProcess;" +
"using System.Runtime.Remoting;" +
"using System.Runtime.Remoting.Channels;" +
"using System.Threading;" +
"public class Service : ServiceBase" +
"{" +
"private Container components = null;" +
"public Service()" +
"{"+
"components = new Container();" +
"this.ServiceName = \"SERVICE_NAME\";" +
"}" +
"static void Main()" +
"{" +
"ServiceBase[] ServicesToRun;" +
"ServicesToRun = new ServiceBase[] { new Service() };" +
"Run(ServicesToRun);" +
"}" +
"protected override void Dispose(bool disposing)" +
"{" +
"if(disposing) { if(components != null) components.Dispose(); }" +
"base.Dispose(disposing);"+
"}" +
"protected override void OnStart(string[] args)" +
"{" +
"if(args.Length == 1) Thread.Sleep(Convert.ToInt32(args[0]));" +
"RemotingConfiguration.Configure(AppDomain.CurrentDomain." +
"SetupInformation.ConfigurationFile);" +
"}" +
"protected override void OnStop()" +
"{" +
"foreach(IChannel objChannel in ChannelServices.RegisteredChannels)" +
"ChannelServices.UnregisterChannel(objChannel);" +
"}" +
"}\r\n" +
"[RunInstaller(true)]" +
"public class ProjectInstaller : System.Configuration.Install.Installer" +
"{" +
"private ServiceProcessInstaller serviceProcessInstaller1;" +
"private ServiceInstaller serviceInstaller1;" +
"public ProjectInstaller() {InitializeComponent();}" +
"private void InitializeComponent()" +
"{" +
"this.serviceProcessInstaller1 = new ServiceProcessInstaller();" +
"this.serviceInstaller1 = new ServiceInstaller();"+
"this.serviceProcessInstaller1.Account = ServiceAccount.LocalSystem;" +
"this.serviceInstaller1.ServiceName = \"SERVICE_NAME\"; " +
"this.serviceInstaller1.StartType = SERVICE_START;" +
"this.Installers.AddRange(new System.Configuration.Install.Installer[]" +
"{this.serviceProcessInstaller1, this.serviceInstaller1});" +
"}" +
"public override void Install(IDictionary stateServer)" +
"{" +
"Microsoft.Win32.RegistryKey system, currentControlSet, services, " +
"service; " +
"base.Install(stateServer);" +
"system = Microsoft.Win32.Registry.LocalMachine.OpenSubKey(\"System\");" +
"currentControlSet = system.OpenSubKey(\"CurrentControlSet\");" +
"services = currentControlSet.OpenSubKey(\"Services\");" +
"service = services.OpenSubKey(this.serviceInstaller1.ServiceName, " +
"true);" +
"service.SetValue(\"Description\", \"SERVICE_DESCRIPTION\");" +
"SERVICE_TRAYICON" +
"}" +
"}" +
"}";
#endregion
The template requires to customize its entries
such as SERVICE_NAME, SERVICE_START, SERVICE_DESCRIPTION and
SERVICE_TRAYICON to finalize the host process code. This
task is performed using the string.replace function.
As I mentioned early, the RemotingManagement
contains many forms and user controls to handle a particular snap-in
node. Basically, their implementations has the same design pattern
based on the event driven mechanism. You can see it in the following
code snippet how the lifetime node handle it:
#region Lifetime
protected BaseNode CreateLifetimeNodeTree(BaseNode parentNode,
string nodeName, bool bRefresh)
{
FormNode node = new FormNode(this);
node.ControlType
= Type.GetType("RKiss.RemotingManagement.PropertiesControl");
node.DisplayName = nodeName;
node.Tag = parentNode.Tag;
node.OpenImageIndex = intLifetimeImage;
node.ClosedImageIndex = intLifetimeImage;
//---add/insert this node
if(bRefresh)
node.Insert(parentNode);
else
parentNode.AddChild(node);
node.OnSelectScopeEvent
+= new NodeNotificationHandler(OnSelectEvent_Lifetime);
node.OnQueryPropertiesEvent
+= new NodeNotificationHandler(OnQueryProperties_Lifetime);
//---this is a post-process notification from the user form
node.OnUserEvent += new NodeNotificationHandler(OnUserEvent_Lifetime);
return node;
}
private void OnSelectEvent_Lifetime(object sender, NodeEventArgs args)
{
BaseNode selNode = sender as BaseNode;
//---checkpoint
Trace.WriteLine(string.Format("OnSelectEvent: sender={0}",
selNode.DisplayName));
//---ask snap-in for the following buttons
IConsoleVerb icv;
selNode.Snapin.ResultViewConsole.QueryConsoleVerb(out icv);
icv.SetVerbState(MMC_VERB.PROPERTIES, MMC_BUTTON_STATE.ENABLED, 1);
//---status text
selNode.Snapin.ResultViewConsole.SetStatusText("The lifetime properties" +
"of the remote singleton and activated objects in the application.");
}
protected void OnQueryProperties_Lifetime(object sender, NodeEventArgs e)
{
BaseNode selNode = sender as BaseNode;
//---checkpoint
Trace.WriteLine(string.Format("OnQueryProperties: sender={0}",
selNode.DisplayName));
//---action
string strConfigFilePath = Convert.ToString(selNode.Tag) + ".config";
LifetimeForm formLT = new LifetimeForm(selNode, selNode.DisplayName,
strConfigFilePath);
formLT.Show();
}
private void OnUserEvent_Lifetime(object sender, NodeEventArgs args)
{
try
{
//---inputs
BaseNode node = sender as BaseNode;
NameValueArgs nva = args as NameValueArgs;
string strCheckpoint = nva.Key;
string strLifetimeName = Convert.ToString(nva.Val);
//---checkpoint
Trace.WriteLine(string.Format("User Event: sender={0}, checkpoint={1},
val={2}", node.DisplayName, strCheckpoint, strLifetimeName));
//---refresh and set scope
node.Snapin.ResultViewConsole.SelectScopeItem(node.HScopeItem);
}
catch(Exception ex)
{
Trace.WriteLine(string.Format("OnUserEvent_Lifetime - failed, " +
"error = {0}", ex.Message));
}
}
#endregion
The CreateLifetimeNodeTree method has responsibility to create
node in the snap-in and subscribe the requested delegates such as
- OnSelectEvent_Lifetime to handle activities when user click
this node
- OnQueryProperties_Lifetime to handle a request to pop-up a
properties form
- OnUserEvent_Lifetime to handle a post-process of the user form
I created a separate solution to test the RMC:
- InterfaceSample - interface contract
- RemotingObjectSample - remote object
- WindowsFormClient - consumer
As you can see this test solution doesn't have a host server
project, that's why the RMC comes to create one. The client is a
simple windows form to ask a remote object for the specific section
in the config file. Here is its screen shot:
Note that the msi file will install also this test sample,
so it's easy to click for the WindowsFormClient icon in the RMC
desktop folder to start test it.
This is a pre-view version of the Remoting Management Console. I
decided to release it before finishing all features what I have in
my plan:
- incorporate the Remoting Probe
- help project
- drag&drop pre-configured channels and sinks
- creating a library of the custom channels and sinks
- enterprise version
- improve users interface
In this article I described a tool hosted by MMC that allows to
administrate any remoting object without writing its host process.
The Remoting Management Console tool will create automatically a
remoting host process including its configuration file. The RMC
becomes very useful tool especially during the deployment phase,
where a configuration of the distributed objects need to be tuned
based on the deployment environment.
|