UML软件工程组织

Remoting Management Console
转自:www.codeproject.com 作者:Roman Kiss

Contents

Introduction
Features
Concept
Usage
Implementation
Test
Version
Conclusion

Introduction

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.

Features

Remoting Management Console

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.

Concept

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:

Design pattern between the snap-in node and users form

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.

Usage

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
  1. Select the Remoting Host Processes node in the snap-in area
  2. Right-click on the node
  3. Select New and click Process
  4. The following Form dialog will show-up

    Remoting Management Console

  5. Change the Name properties, for instance: HostProcess_Sample
  6. 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
  1. Select the RemoteObjects node in the HostProcess_Sample root
  2. Right-click on the node
  3. Select New and click Remote Object
  4. The following Form dialog will show-up

    Remoting Management Console

  5. Populate all attributes on the Form
  6. 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:

  1. Drag the assembly of the Remote Object, which you what to publish it from its folder
  2. Drop the assembly on the RemoteObjects result view (right panel)
  3. follow the above instruction steps 4 - 6 to finalize the entry process.
Step C. Configuring Channel
  1. Select the Channels node in the HostProcess_Sample root
  2. Right-click on the node
  3. Select New and click Remoting channel
  4. The following Form dialog will show-up

    Remoting Management Console

  5. Populate necessary attributes on the Form (see below notes)
  6. 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
  1. Select the HostProcess_Sample node in the snap-in area
  2. Right-click on the node
  3. Select All Task and click Start
  4. The Form dialog will show the progress of the starting process
  5. 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.

Implementation

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

Test

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:

Remoting Management Console

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.

Version

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

 

Conclusion

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.

 

版权所有:UML软件工程组织