Hosting Tutorial 4 (C#) - Creating Service Instances Dynamically

To run DSS services you can either use the DssHost.exe tool or create your own application to host a DSS node. In the Hosting Tutorials we provide samples of such applications. You can use the DssEnvironment.dll assembly and the DssEnvironment static class to initialize the DSS runtime within an application of your choice or a .NET Windows Service.

This tutorial is provided in the C# language. You can find the project files for this tutorial at the following location under the Microsoft Robotics Developer Studio installation folder:

 Samples\HostingTutorials\Tutorial4\CSharp

This tutorial teaches you how to:

  • Creating an Instance of a Service
  • Dropping a Service

Prerequisites

This tutorial uses the services written in Service Tutorial 1 (C#) - Creating a Service and Service Tutorial 2 (C#) - Updating State.

Hardware

This tutorial requires no special hardware.

Software

This tutorial is designed for use with Microsoft Visual C#. You can use:

  • Microsoft Visual C# Express Edition
  • Microsoft Visual Studio Standard, Professional, or Team Edition.

Running the Program

The Visual Studio solution for this sample is called HostingTutorial4.sln and can be found in the sample location mentioned above. To run it in the debugger, open the solution file in Microsoft Visual Studio. Alternatively, create a new C# Console Application project in Microsoft Visual Studio and replace the contents of the source file with the code below. The program needs a reference to the proxy assembly of ServiceTutorial1.

To run the tutorial directly, open a DSS Command Prompt and enter the command:

 bin\HostingTutorial4.exe

When you run the application, it displays a message in the console window that asks you to open a web browser and browse to https://localhost:50000/console/output (the Debug and Trace Messages page) to see the DSS node running and verify that ServiceTutorial1 has started. Remain on this page so that you can refresh it while the program is running.

Next the program shuts down ServiceTutorial1 and then again attempts to get its state. This is expected to fail. Lastly, it waits for a short period of time before shutting down the DSS node.

Creating an Instance of a Service

As with the previous hosting tutorials, a DssEnvironment is created initially. The comments show a method for running the executable from a different folder than the one where RDS is installed. After launching a DssEnvironment, the program starts an iterator to perform run most of the code.

 using System;
using System.Collections.Generic;

using Microsoft.Ccr.Core;
using Microsoft.Dss.Core;
using Microsoft.Dss.Hosting;
using Microsoft.Dss.ServiceModel.Dssp;

using ds = Microsoft.Dss.Services.Directory;
using st1 = RoboticsServiceTutorial1.Proxy;
using Microsoft.Dss.Services.Constructor;
using W3C.Soap;

namespace HostingTutorial4
{
    class Program
    {
        // Used to set exit code
        static int _status = 1;

        /// <summary>
        /// Main entry to the program
        /// </summary>
        /// <returns>0 if program succeeds, otherwise a value that is not 0</returns>
        static int Main()
        {
            try
            {
                // Uncomment the lines below if you want to run the Hosting executable
                // in a different directory from the MRDS root directory. 
                // Note that you must set the root directory and the location of
                // the security settings file as appropriate for your installation.
                //DssRuntimeConfiguration dssRuntimeConfig = new DssRuntimeConfiguration();
                //dssRuntimeConfig.HostRootDir = @"C:\Microsoft Robotics Dev Studio 2008";
                //dssRuntimeConfig.PublicHttpPort = 50000;
                //dssRuntimeConfig.PublicTcpPort = 50001;
                //dssRuntimeConfig.SecuritySettings = @"C:\Microsoft Robotics Dev Studio 2008\store\SecuritySettings.xml";
                //DssEnvironment.Initialize(dssRuntimeConfig, null);

                // Initialize DSS without any manifests
                DssEnvironment.Initialize(50000, 50001);

                // Spawn RunTask
                Arbiter.Activate(DssEnvironment.TaskQueue, Arbiter.FromIteratorHandler(RunTask));

                // Wait here until DSS Environment has been shut down
                DssEnvironment.WaitForShutdown();

                // Exit program
                return _status;
            }
            catch
            {
                return -1;
            }
        }

The code demonstrates hosting a DSS node and creating an instance of a service to query. This sample is similar to Hosting Tutorial 2, except the DSS node is started with no manifest and instead the program attempts to create a new instance of the ServiceTutorial1 service by calling the DssEnvironment.CreateService method. It then sends a Get request to ServiceTutorial1 service to retrieve its state and logs the value of the Member property of ServiceTutorial1State object. If any request fails during this process the application will log an error, shut down the DSS node and exit.

 /// <summary>
/// The runtask function uses iterators to mimic sequential programming even though
/// it is actually running asynchronously.
/// </summary>
/// <returns></returns>
static IEnumerator<ITask> RunTask()
{
    // This is for debugging ONLY
    // You should not normally use Console.WriteLine.
    ConsoleColor save = Console.ForegroundColor;
    Console.ForegroundColor = ConsoleColor.Cyan;
    Console.WriteLine("Open a web browser and view https://localhost:50000/console/output");
    Console.ForegroundColor = save;

    // Create service tutorial 1 instance locally
    PortSet<CreateResponse,Fault> createResultPort = DssEnvironment.CreateService(st1.Contract.Identifier);

    // Uncomment lines below to create service on some remote node that is already running
    // createResultPort = CreateServiceRemotely(
    //    new Uri("https://machinename:port"),
    //    new ServiceInfoType(st1.Contract.Identifier));

    // wait for success or failure
    yield return createResultPort.Choice();
    // retrieve success result. If null, a fault was returned
    CreateResponse success = createResultPort;
    if (success == null)
    {
        // Request failed.
        DssEnvironment.LogError("Could not find Service Tutorial 1");
        _status = 1;
        yield break;
    }
    // Create Request succeeded.
    DssEnvironment.LogInfo("Created Service Tutorial 1 service: " + success.Service);
    // create forwarder port to service instance
    var serviceTutorial1Port =
        DssEnvironment.ServiceForwarder<st1.ServiceTutorial1Operations>(new System.Uri(success.Service));

    // Issue Get request to service instance
    var getResultPort = serviceTutorial1Port.Get();
    // Wait for either success or fault
    yield return getResultPort.Choice();
    // Check if we have success response
    st1.ServiceTutorial1State getResponse = getResultPort;
    if (getResponse != null)
    {
        // GET Request succeeded
        DssEnvironment.LogInfo("Service Tutorial1 state: " + getResponse.Member);
    }
    else
    {
        // GET Request failed
        // Retrieve the Fault from the port
        Fault f = getResultPort;
        DssEnvironment.LogError("Could not retrieve state: " + f.ToException().Message);
        _status = 2;
    }

Dropping a Service

After getting the state of ServiceTutorial1, the program then sends a Drop message to ServiceTutorial1. This is a deliberate shutdown of ServiceTutorial1 so that it is no longer running and does not respond to messages. Next the code does another Get, but this time with a TimeSpan set on the request so that it will time out if there is no response.

When the Get fails, the program logs an error message. It then waits for a little while so that you have time to refresh the browser window and view the log output on the Debug and Trace Messages page. Finally, it shuts down the DSS node and exits.

     // Now drop Service Tutorial 1
    var dropResultPort = serviceTutorial1Port.DsspDefaultDrop();
    yield return dropResultPort.Choice();
    DefaultDropResponseType dropResponse = dropResultPort;
    if (dropResponse != null)
    {
        // Drop was successful
        DssEnvironment.LogInfo("Service Tutorial1 dropped");
    }
    else
    {
        // Drop Request failed
        Fault f = dropResultPort;
        DssEnvironment.LogError("Could not drop service: " + f.ToException().Message);
        _status = 3;
    }

    // NOTE: Inside a service you should use LogInfo not Console.WriteLine
    Console.WriteLine("Issuing a Get with Timeout");

    // Try a Get again, but this time there is no service
    // This uses a more explicit approach to sending a message so
    // that the TimeSpan can be set
    st1.Get getRequest = new st1.Get();
    getRequest.TimeSpan = TimeSpan.FromMilliseconds(5000);
    serviceTutorial1Port.Post(getRequest);
    // Wait for either success or fault
    yield return getRequest.ResponsePort.Choice();
    // Check if we have success response
    getResponse = getRequest.ResponsePort;
    if (getResponse != null)
    {
        // GET Request succeeded
        DssEnvironment.LogInfo("Service Tutorial1 state: " + getResponse.Member);
        // Set exit code to 0 for success
        _status = 0;
    }
    else
    {
        // GET Request failed
        // Retrieve the Fault from the Response port
        Fault f = getRequest.ResponsePort;
        // Convert Fault to a suitable message and log it
        DssEnvironment.LogError("Could not retrieve state: " + f.ToException().Message);
        // The exit status should indicate an error, but in this example
        // an error is actually the expected result! 
        _status = 0;
    }

    // Shut down runtime
    DssEnvironment.LogInfo("Run task completed - sleeping for a while");

    // This is for debugging ONLY
    // You should not normally use Console.WriteLine.
    save = Console.ForegroundColor;
    Console.ForegroundColor = ConsoleColor.Cyan;
    Console.WriteLine("Refresh Debug and Trace Messages now in your web browser");
    Console.ForegroundColor = save;

    // Wait a little while
    yield return Wait(5);

    DssEnvironment.Shutdown();
}

The last section of the code contains some helper methods that are used in the main body of the code.

 /// <summary>
/// Helper that creates a service remotely. The DsspServiceBase.CreateService method has similar functionality
/// </summary>
/// <param name="serviceInfo"></param>
/// <param name="remoteHostAndPort">a uri of the form https://hostname:port</param>
/// <returns></returns>
static PortSet<CreateResponse,W3C.Soap.Fault> CreateServiceRemotely(Uri remoteHostAndPort, ServiceInfoType serviceInfo)
{
    var remoteConstructorPort = DssEnvironment.ServiceForwarder<ConstructorPort>(new Uri(remoteHostAndPort + ServicePaths.Constructor));
    var create = new Create(serviceInfo);
    remoteConstructorPort.Post(create);
    return create.ResponsePort;
}

/// <summary>
/// Wait - Helper that creates a timeout port, queues a timer and returns a receiver on the port
/// </summary>
/// <param name="seconds">Timeout period in seconds</param>
/// <returns></returns>
static Receiver<DateTime> Wait(int seconds)
{
    Port<DateTime> p = new Port<DateTime>();
    DssEnvironment.TaskQueue.EnqueueTimer(TimeSpan.FromSeconds(seconds), p);
    return Arbiter.Receive(false, p, delegate(DateTime t) { });
}

Summary

This tutorial covered:

  • Creating an Instance of a Service
  • Dropping a Service
See Also 

DSS User Guide: DSS Application Configuration File

 

 

© 2010 Microsoft Corporation. All Rights Reserved.