Service Tutorial 7 (C#) - Advanced Topics

Glossary Item Box

Microsoft Robotics Developer Studio Send feedback on this topic

Service Tutorial 7 (C#) - Advanced Topics

Writing an application using Decentralized Software Services (DSS) is a simple matter of orchestrating the input and output between a set of services. Services represent the interface to software or hardware and allow you to communicate between processes that perform specific functions.

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\ServiceTutorials\Tutorial7\CSharp

This tutorial teaches you how to:

  • Subscribe to a Service Running on Another Node.
  • Start a Service on Another Node.
  • Configure Multiple Services.

Prerequisites

This tutorial uses the services written in Service Tutorial 4 (C#) - Supporting Subscriptions and Service Tutorial 6 (C#) - Retrieving State and Displaying it Using an XML Transform. The service you will create is referred to as ServiceTutorial6. Feel free to start a new project or keep working in the previous project, keeping in mind that some names may differ.

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.

You will also need Microsoft Internet Explorer or another conventional web browser.

Step 1: Subscribe to a Service Running on Another Node

In the service created in Service Tutorial 5 (C#) - Subscribing, and subsequently modified in Service Tutorial 6 (C#) - Retrieving State and Displaying it Using an XML Transform a partner is defined called "Clock". That partner is declared with the following properties:

When the service from Service Tutorial 5 or 6 is started this partner declaration causes the Service Tutorial 4 service to be started also, unless it is already running on the current node.

A manifest file can configure the partnerships that a service has. One use of this, as the following manifest demonstrates, can be to look for partner services on a node other than the current node.

<?xml version="1.0" ?>
<Manifest
    xmlns="https://schemas.microsoft.com/xw/2004/10/manifest.html"
    xmlns:d="https://schemas.microsoft.com/xw/2004/10/dssp.html"
    xmlns:st6="https://schemas.microsoft.com/2006/06/servicetutorial6.html">
  <CreateServiceList>
    <!-- Service Tutorial 6 -->
    <ServiceRecordType>
      <d:Contract>https://schemas.microsoft.com/2006/06/servicetutorial6.html</d:Contract>
      <d:PartnerList>
        <d:Partner>
          <d:Service>https://localhost:40000/servicetutorial4</d:Service>
          <d:Name>st6:Clock</d:Name>
        </d:Partner>
      </d:PartnerList>
    </ServiceRecordType>
  </CreateServiceList>
</Manifest>

To demonstrate this we need two nodes to be running:

  1. A node using port 40000 for HTTP and running Service Tutorial 4
  2. A node using another port (say 50000) for HTTP and running the manifest above

To start these nodes in sequence, run two DSS Command Prompts.

In the first run:

dsshost /p:40000 /t:40001 /m:"Samples\Config\ServiceTutorial4.manifest.xml"

This starts a node, running on port 40000, and starts the Service Tutorial 4 service.

In the .\Samples\Config directory save the above manifest as ServiceTutorial7.manifest.xml

Now in the second DSS Command Prompt run:

dsshost /p:50000 /t:50001 /m:"Samples\Config\ServiceTutorial7.manifest.xml"

This starts a node running on port 50000, using the manifest above, starts the Tutorial 6 service and, instructs the service to look for the Clock partner on a node running on port 40000.

Bb483059.hs-note(en-us,MSDN.10).gif

The Clock partner could be running on a another machine accessible over the network; it is demonstrated here on the same machine for clarity.

Step 2: Start a Service on Another Node

Using a manifest it is possible to use a service running on another node as a partner. However, it is not possible to start a service on another node this way. To do that requires changes to the code of Service Tutorial 6.

The first step is to remove the partner attribute from the clock partner. This means that the partner will no longer be established when the service is started. The service will have to establish the partnership itself.

Remove the following attribute line from top of the _clockPort:

[Partner("Clock", Contract = rst4.Contract.Identifier, CreationPolicy = PartnerCreationPolicy.UseExistingOrCreate)]

The second step is then to find the node on which to start the service. This is done by specifying a path to a Directory service in a manifest partner.

<?xml version="1.0" ?>
<Manifest
    xmlns="https://schemas.microsoft.com/xw/2004/10/manifest.html"
    xmlns:d="https://schemas.microsoft.com/xw/2004/10/dssp.html"
    xmlns:st6="https://schemas.microsoft.com/2006/06/servicetutorial6.html">
  <CreateServiceList>
    <!-- Service Tutorial 6 -->
    <ServiceRecordType>
      <d:Contract>https://schemas.microsoft.com/2006/06/servicetutorial6.html</d:Contract>
      <d:PartnerList>
        <d:Partner>
          <d:Service>https://localhost:40000/directory</d:Service>
          <d:Name>st6:Remote</:Name>
        </d:Partner>
      </d:PartnerList>
    </ServiceRecordType>
  </CreateServiceList>
</Manifest>

This information can be read by the service at runtime by calling base.FindPartner("Remote"), the Service field in the PartnerType object returned will contain the Uniform Resource Identifier (URI)specified in the manifest.

That service URI can then be used to create a service forwarder using base.ServiceForwarder<T>()

To do this requires a using declaration to introduce the Directory namespace. At the same time this code introduces the Constructor namespace which will be used below.

In ServiceTutorial6.cs add the following aliases:

using ds = Microsoft.Dss.Services.Directory;
using cs = Microsoft.Dss.Services.Constructor;

Next in OnStartup() the following changes create a forwarder to the remote directory. Add the following code snippet at the beginning of the OnStartup() method.

PartnerType remote = FindPartner("Remote");
ds.DirectoryPort remoteDir = DirectoryPort;

if (remote != null &amp;&amp; !string.IsNullOrEmpty(remote.Service))
{
    remoteDir = ServiceForwarder<ds.DirectoryPort>(remote.Service);
}

Now that the service has a forwarder to a Remote Directory service, or in the case of failure of the Local Directory service, it can query the directory for an instance of the Constructor service.

Continue from the last point in OnStartup() adding the following code:

cs.ConstructorPort remoteConstructor = ConstructorPort;
ds.Query query = new ds.Query(
    new ds.QueryRequestType(
        new ServiceInfoType(cs.Contract.Identifier)
    )
);

remoteDir.Post(query);
yield return (Choice)query.ResponsePort;
ds.QueryResponseType queryRsp = query.ResponsePort;
if (queryRsp != null)
{
    remoteConstructor = ServiceForwarder<cs.ConstructorPort>(queryRsp.RecordList[0].Service);
}

This code sends a Query message to the directory service asking for the constructor service, identified by its contract. If this fails then the fallback is to use the local Constructor service.

The final step is to send a Create message to the constructor service.

string clockService = null;
cs.Create create = new cs.Create(new ServiceInfoType(rst4.Contract.Identifier));
remoteConstructor.Post(create);
yield return (Choice)create.ResponsePort;
CreateResponse createRsp = create.ResponsePort;
if (createRsp != null)
{
    clockService = createRsp.Service;
}
else
{
    LogError((Fault)create.ResponsePort);
    yield break;
}

_clockPort = ServiceForwarder<rst4.ServiceTutorial4Operations>(clockService);

If the constructor succeeds in creating the service this then creates a forwarder to the newly created service which will be used for the rest of the service.

For completeness the following change, a little lower in OnStartup(), will set the correct path to the Service Tutorial 4 service in the state.

Replace the following code

PartnerType partner = FindPartner("Clock");
if (partner != null)
{
    initState.Clock = partner.Service;
}

with this line:

initState.Clock = clockService;
Bb483059.hs-note(en-us,MSDN.10).gif

The final version of Service Tutorial 7 differs from Service Tutorial 6 (C#) - Retrieving State and Displaying it Using an XML Transform with the changes explained above in some parts that is described in the next section.

To demonstrate this requires:

  • The changes above to be made to ServiceTutorial6
  • The manifest above saved to .\Samples\Config\ServiceTutorial7.manifest.xml
  • Two nodes to be running:
    • A node using port 40000 for HTTP
    • A node using another port (say 50000) for HTTP and running the manifest above

To start these nodes in sequence, run two DSS Command Prompts.

In the first run:

dsshost /p:40000 /t:40001

This starts a node, running on port 40000.

Bb483059.hs-note(en-us,MSDN.10).gif

This does not start any services, other than the system services that start with every node.

Now in the second DSS Command Prompt run:

dsshost /p:50000 /t:50001 /m:"Samples\Config\ServiceTutorial7.manifest.xml"

This starts a node running on port 50000, using the manifest above, starts the modified Service Tutorial 6 service and, instructs the service to look for the Constructor service on a node running on port 40000.

Bb483059.hs-note(en-us,MSDN.10).gif

If you are running the DSS nodes on different machines, then you will need to open the DSS HTTP ports in firewalls and also configure the security of each node in order for the services to be able to communicate across nodes. To edit security settings, when viewing the DSS node in the browser click on Security Manager on the left navigation pane.

For more information about DSS security see DSS Node Security Model.

A short time after a line appears on the second console saying…

*   Service uri: [https://localhost:50000/servicetutorial6]

A line will appear on the first console saying…

*   Service uri: [https://localhost:40000/servicetutorial4]
Bb483059.hs-note(en-us,MSDN.10).gif

The Constructor service could be running on another machine accessible over the network; it is demonstrated here on the same machine for clarity.

Step 3: Configure Multiple Services

The final version of Service Tutorial 7 differs from Service Tutorial 6 in a number of other respects…

  1. In the file ServiceTutorial7Types.cs the ServiceTutorial7State type contains a list of strings and a list of TickCount objects.

    [DataContract]
    public class ServiceTutorial7State
    {
        private List<string> _clocks = new List<string>();
    
        [DataMember(IsRequired = true)]
        public List<string> Clocks
        {
            get { return _clocks; }
            set { _clocks = value; }
        }
    
        private List<TickCount> _tickCounts = new List<TickCount>();
    
        [DataMember(IsRequired = true)]
        public List<TickCount> TickCounts
        {
            get { return _tickCounts; }
            set { _tickCounts = value; }
        }
    }
    
        [DataContract]
        public class TickCount
        {
        public TickCount()
        {
        }
    
        public TickCount(int initial, string name)
        {
            _initial = initial;
            _count = initial;
            _name = name;
        }
    
        private string _name;
        [DataMember]
        public string Name
        {
            get { return _name; }
            set { _name = value; }
        }
    
        private int _count;
        [DataMember]
        public int Count
        {
            get { return _count; }
            set { _count = value; }
        }
    
        private int _initial;
    
        public int Initial
        {
            get { return _initial; }
            set { _initial = value; }
        }
    }
    
  2. In the file ServiceTutorial7Types.cs the operations port (ServiceTutorial7Operations) uses an IncrementTick message defined within Service Tutorial 7, (whereas Service Tutorial 6 uses one defined by Service Tutorial 4.

    [ServicePort]
    public class ServiceTutorial7Operations : PortSet<
        DsspDefaultLookup,
        DsspDefaultDrop,
        Get,
        HttpGet,
        Replace,
        IncrementTick,
        SetTickCount>
    {
    }
    

    Definitions for IncrementTick message and IncrementTickRequest request type:

    public class IncrementTick : Update<IncrementTickRequest, PortSet<DefaultUpdateResponseType, Fault>>
    {
        public IncrementTick()
        {
        }
    
        public IncrementTick(string source)
            : base(new IncrementTickRequest(source))
        {
        }
    }
    
        [DataContract]
        [DataMemberConstructor]
        public class IncrementTickRequest
        {
        public IncrementTickRequest()
        {
        }
    
        public IncrementTickRequest(string name)
        {
            _name = name;
        }
    
        private string _name;
        [DataMember]
        public string Name
        {
            get { return _name; }
            set { _name = value; }
        }
    }
    
  3. In the file ServiceTutorial7Types.cs the message SetTickCount uses the TickCount object as its body.

    public class SetTickCount : Update<TickCount, PortSet<DefaultUpdateResponseType, Fault>>
    {
        public SetTickCount()
        {
        }
    
        public SetTickCount(int tickCount, string source)
            : base(new TickCount(tickCount, source))
        {
        }
    }
    
  4. In the file ServiceTutorial7.cs a partner "Local" is defined.

    [Partner("Local", Contract = rst4.Contract.Identifier, CreationPolicy = PartnerCreationPolicy.UsePartnerListEntry)]
        rst4.ServiceTutorial4Operations _localClockPort = new rst4.ServiceTutorial4Operations();
        rst4.ServiceTutorial4Operations _localClockNotify = new rst4.ServiceTutorial4Operations();
    
  5. In the file ServiceTutorial7.cs, in the Start() method notifcation handlers are established for the Local clock partner. The handlers for notification messages from the local and remote partners are handled differently.

    Activation in the Start() method:

    Activate<ITask>(
        Arbiter.Receive<rst4.IncrementTick>(true, _clockNotify, RemoteNotifyTickHandler),
        Arbiter.Receive<rst4.Replace>(true, _clockNotify, RemoteNotifyReplaceHandler),
        Arbiter.Receive<rst4.IncrementTick>(true, _localClockNotify, LocalNotifyTickHandler),
        Arbiter.Receive<rst4.Replace>(true, _localClockNotify, LocalNotifyReplaceHandler)
        );
    

    Remote Handlers:

    private void RemoteNotifyTickHandler(rst4.IncrementTick incrementTick)
    {
        LogInfo("Got Tick from remote");
        _mainPort.Post(new IncrementTick("Remote"));
    }
    
        private void RemoteNotifyReplaceHandler(rst4.Replace replace)
        {
        LogInfo("Remote Tick Count: " + replace.Body.Ticks);
        _mainPort.Post(new SetTickCount(replace.Body.Ticks, "Remote"));
        }
    

Local Handlers:

```C#
private void LocalNotifyTickHandler(rst4.IncrementTick incrementTick)
    {
    LogInfo("Got Tick from local");
    _mainPort.Post(new IncrementTick("Local"));
    }

    private void LocalNotifyReplaceHandler(rst4.Replace replace)
    {
    LogInfo("Local Tick Count: " + replace.Body.Ticks);
    _mainPort.Post(new SetTickCount(replace.Body.Ticks, "Local"));
    }

```
  1. In the file ServiceTutorial7.cs a drop handler is implemented which drops the local and remote partners.

    [ServiceHandler(ServiceHandlerBehavior.Teardown)]
    public IEnumerator<ITask> DropHandler(DsspDefaultDrop drop)
    {
        _clockPort.DsspDefaultDrop();
        _localClockPort.DsspDefaultDrop();
    
        base.DefaultDropHandler(drop);
        yield break;
    }
    

These changes allow the service to have two partners: One works as in service tutorial 5 and 6 and is refered to in the code as the local partner. The other works as in Step 2 above and is refered to in the code as the remote partner.

This service uses the following manifest:

<Manifest
    xmlns="https://schemas.microsoft.com/xw/2004/10/manifest.html"
    xmlns:d="https://schemas.microsoft.com/xw/2004/10/dssp.html"
    xmlns:s="https://schemas.microsoft.com/2006/06/servicetutorial7.html"
    >
  <CreateServiceList>
    <ServiceRecordType>
      <d:Contract>https://schemas.microsoft.com/2006/06/servicetutorial7.html</d:Contract>
      <d:PartnerList>
        <d:Partner>
          <d:Service>https://localhost:40000/directory</d:Service>
          <d:Name>s:Remote</d:Name>
        </d:Partner>
        <d:Partner>
          <d:Name>s:Local</d:Name>
        </d:Partner>
      </d:PartnerList>
    </ServiceRecordType>
    <ServiceRecordType>
      <d:Contract>https://schemas.microsoft.com/2006/06/servicetutorial4.html</d:Contract>
      <d:Service>https://localhost/localclock</d:Service>
      <d:PartnerList>
        <d:Partner>
          <d:Service>ServiceTutorial7.LocalClock.config.xml</d:Service>
          <d:Name>d:StateService</d:Name>
        </d:Partner>
      </d:PartnerList>
      <Name>s:Local</Name>
    </ServiceRecordType>
  </CreateServiceList>
</Manifest>

This manifest configures the service in the following ways:

  1. It specifies that the remote instance directory will be found at https://localhost:40000/directory

  2. It specifies that the local partner will have a service path of /localclock

  3. It specifies that the local partner will use an initial state specified in the file ServiceTutorial7.LocalClock.config.xml

    Note: That config file path is relative to the manifest path. So the config file will be found in ./Samples/Config/ServiceTutorial7.LocalClock.config.xml

When the remote partner is started it uses the initial state configuration file declared by the InitialStatePartner attribute, as described in Service Tutorial 3 (C#) - Persisting State. For Service Tutorial 4 that file is ./store/ServiceTutorial4.xml.

Even if both nodes are running from the same deployment directory the two instances of the Service Tutorial 4 service, one on the node at port 40000, the other on the node at port 50000 will use different configuration files. This can be confirmed by modifying the files so that the Ticks and Member fields of the two instances differ and then running the orchestration and examining the states.

For example in the directory ./Samples/Config copy the following file as ServiceTutorial7.LocalClock.Config.xml

<?xml version="1.0" encoding="utf-8"?>
<ServiceTutorial4State xmlns="https://schemas.microsoft.com/2006/06/servicetutorial4.html">
  <Member>Local Clock Configuration</Member>
  <Ticks>1000</Ticks>
</ServiceTutorial4State>

Then in one DSS Command Prompt start a node using HTTP port 40000.

dsshost /p:40000 /t:40001

When that node is running, start another node in another DSS Command Prompt using port 50000 and specifiying the manifest above.

dsshost /p:50000 /t:50001 /m:"Samples\Config\ServiceTutorial7.manifest.xml"

The second node will display…

*   Service uri:  [https://localhost:50000/localclock]
*   Service uri:  [https://localhost:50000/servicetutorial7]

then shortly afterward the first node will display…

*   Service uri:  [https://localhost:40000/servicetutorial4]

Visiting the URL's https://localhost:50000/localclock and https://localhost:40000/servicetutorial4 should demonstrate that the services were started using different configurations.

Bb483059.hs-note(en-us,MSDN.10).gif

The current machine name will be displayed where this tutorials states localhost

Summary

In this tutorial, you learned how to:

  • Subscribe to a Service Running on Another Node.
  • Start a Service on Another Node.
  • Configure Multiple Services.

 

 

© 2012 Microsoft Corporation. All Rights Reserved.