Service Tutorial 9 (C#) - Implementing and Extending Service Contracts

Glossary Item Box

Microsoft Robotics Developer Studio Send feedback on this topic

Service Tutorial 9 (C#) - Implementing and Extending Service Contracts

This tutorial shows three different ways of implementing and/or extending a generic service contract: Implementing, extending, and using as part of a multi-headed service. The tutorial builds on and uses the generic service declaration shown in Service Tutorial 8 (C#) - Generic Service Declaration.

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\Tutorial9\CSharp

This tutorial teaches you how to:

  • Create a Generic Service Project.
  • Implementing a Generic Service Contract.
  • Extending a Generic Service Contract.
  • Implementing a Multi-Headed Service.

Prerequisites

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: Create a Generic Service Project

Start by creating a service project called "ServiceTutorial9" following the process described in Service Tutorial 1 (C#) - Creating a Service. In this case we will need three sets of service files, one for each service that we write either implementing or extending the generic service contract declared in Service Tutorial 8 (C#) - Generic Service Declaration. The only constraint of having multiple services within a single project is that they must be in different CLR namespaces (see DSS Service Projects Overview).

Step 2: Implementing a Generic Service Contract

The first service (located in the files GenericServiceImplementation.cs and GenericServiceImplementationTypes.cs) simply provides an implementation for the generic service contract.

Service Type Declarations

As it is an implementation of the generic contract, it does not need to have any additional state or operations definitions. However, it does need its own contract identifier as this allows us to refer to that particular implementation in service manifests and other places. As for any other service, the contract identifier is declared as follows:

/// <summary>
/// Generic Service Implementation Contract Identifier
/// </summary>
public sealed class Contract
{
    /// The Unique Contract Identifier for this service
    [DataMember()]
    public const String Identifier = "https://schemas.microsoft.com/2007/08/servicetutorial9/genericservice/implementation.html";
}

Referencing the Generic Contract

By defining an alias for the generic contract's namespace we can clarify its use throughout the service implementation. Make sure to have the generic contract's proxy assembly referenced in the implementing service's project if it's located externally, and then define an alias.

using generic = ServiceTutorial8.Proxy;

Service Implementation

The service implementation of a generic service contract looks at first glance like any other service implementation; however, there are a few important things to note:

  • The service implementation class is annotated with the AlternateContract attribute indicating that the alternate contract for this service is the generic service.

    /// <summary>
    /// This service provides an implementation of the generic service
    /// </summary>
    [Contract(Contract.Identifier)]
    [AlternateContract(generic.Contract.Identifier)]
    [DisplayName("Service Tutorial 9: Generic Service Implementation")]
    [Description("This service implements the generic service provided in Service Tutorial 8.")]
    [DssServiceDescription("https://msdn.microsoft.com/library/bb727257.aspx")]
    public class ImplementationService : DsspServiceBase
    
  • As a natural consequence of not having any service state or operation definitions local to this service, the service initialization uses the generic state and operations port as shown below:

    // The state of this service is exactly that of the generic service
        [ServiceState]
        private generic.GenericState _state = new generic.GenericState();
    
        // The operations port is exactly that of the generic service
        [ServicePort("/GenericServiceImplementation", AllowMultipleInstances = false)]
        private generic.GenericServiceOperations _mainPort = new generic.GenericServiceOperations();
    
        /// <summary>
        /// Default Service Constructor
        /// </summary>
        public ImplementationService(DsspServiceCreationPort creationPort)
            :
            base(creationPort)
        {
        }
    

The remainder of the service, i.e. the service handlers, is similar to any other service implementation with the exception that they use the message operations defined by the generic service.

Running the Service

When running a service that is implementing a generic service contract it shows up as two entries in the directory (genericserviceimplementation and genericserviceimplementation/servicetutorial8. As the service implementation does not extend the generic service contract, the state and behavior of both services are exactly the same. Further, the two services are connected as partners allowing the relationships between the two services to be determined remotely using DSSP.

Figure 1

Figure 1 - Running a service that is implementing a generic service contract shows up as two entries in the directory (genericserviceimplementation and genericserviceimplementation/servicetutorial8 with identical service state and behavior types.

Step 3: Extending a Generic Service Contract

The second service (located in the files GenericServiceExtension.cs and GenericServiceExtensionTypes.cs) provides an implementation for the generic service contract but extends it by adding both additional state as well as message operations. The purpose here is to enable consumers that only know the generic service contract to use that to talk to the service and at the same time allow consumers that know the extended service to use that functionality.

Service Type Declarations

In contrast to the first service, this service does have its own service type declarations consisting of a contract identifier, service state which is derived from the generic service state, and service operations that use the derived service state. As before, the contract identifier is declared as follows:

/// <summary>
/// Generic Service Extension Contract Identifier
/// /// </summary>
public static class Contract
{
    public const string Identifier = "https://schemas.microsoft.com/2007/08/servicetutorial9/genericservice/extension.html";
}

In this case the service state is derived from the generic state and adds a new field (Age) to the state. This is all done using regular CLR type derivation as illustrated below:

[DataContract]
[DisplayName("Service Tutorial 9: Extension Service State")]
[Description("This service state extends the generic service state provided in Service Tutorial 8.")]
public class ExtensionState : generic.GenericState
{
    int _age;

    [DataMember]
    [DisplayName("Age")]
    [Description("Specifies the age of a person.")]
    public int Age
    {
        get { return _age; }
        set { _age = value; }
    }

In addition to the new field we also add a constructor that takes a base type as initializer which merely helps initialize the derived type using the base type. More interestingly, however, is that we also add a converter from the derived type to the generic type. The reason for this is that we want to be able to generate a complete generic state serialization which can not contain the members of the derived type. If we merely used implicit CLR type conversion, because of CLR polymorphism the serialization would be that of the derived type and not the base type which is what we want to be serialized. So we must do it explicitly. This will become apparent when running the service below.

internal ExtensionState(generic.GenericState state)
{
    this.FirstName = state.FirstName;
    this.LastName = state.LastName;
    this.Age = -1;
}

internal generic.GenericState ToGenericState()
{
    generic.GenericState gen = new generic.GenericState();
    gen.FirstName = this.FirstName;
    gen.LastName = this.LastName;
    return gen;
}

In this tutorial we also extend the message operations by adding an UpdateAge UPDATE operation to the mix. If we had not extended the service state but only added an operation we could have reused the generic message operations but in this case we have to define separate service operations that use the derived types.

/// <summary>
/// Generic Service Extension Main Operations Port
/// </summary>
[ServicePort]
public class GenericServiceExtensionOperations :
    PortSet<DsspDefaultLookup, DsspDefaultDrop, Get, Replace, UpdateAge>
{
}

Service Implementation

Unlike the previous example where the service state and operations exactly matched that of the generic service, the state and main port operations of this service are that of the extended service type declaration. Since the main port represents the extended operations it will not represent the generic contract. Because of this, unlike the previous example, we don’t declare the AlternateContract attribute for the ExtensionService class. However, we still want to expose the generic contract so that other services can use either the extended service or the generic service depending on their capability. We do this by declaring a separate operations port of the generic type and decorating it using the AlternateServicePort attribute.

// The state of this service is an extension of the generic service
private ExtensionState _state = new ExtensionState();

// The operations port is an extension of the generic service in that it supports UPDATE as well
[ServicePort("/GenericServiceExtension", AllowMultipleInstances = false)]
private GenericServiceExtensionOperations _mainPort = new GenericServiceExtensionOperations();

// The alternate port where we only have the generic service operations (without UPDATE)
[AlternateServicePort(AlternateContract = generic.Contract.Identifier)]
private generic.GenericServiceOperations _altPort = new generic.GenericServiceOperations();
Bb727257.hs-note(en-us,MSDN.10).gif

Always use the AlternateContract attribute and AlternateServicePort attribute exclusively. Use [AlternateContract] on the service declaration when exposing the generic contract on the main port. Use [AlternateServicePort] on a secondary operations port declaration when the generic contract is not exposed on the main port.

The only thing left is now to implement the message handlers. Because we have two types of messages (the generic messages and the extensions provided by this service), we need two sets of handlers: one for each set of messages. First we do the extension handlers:

/// <summary>
/// Get Handler
/// </summary>
/// <param name="get"></param>
/// <returns></returns>
[ServiceHandler(ServiceHandlerBehavior.Concurrent)]
public virtual IEnumerator<ITask> GetHandler(Get get)
{
    get.ResponsePort.Post(_state);
    yield break;
}

/// <summary>
/// Replace Handler
/// </summary>
/// <param name="replace"></param>
/// <returns></returns>
[ServiceHandler(ServiceHandlerBehavior.Exclusive)]
public virtual IEnumerator<ITask> ReplaceHandler(Replace replace)
{
    _state = replace.Body;
    replace.ResponsePort.Post(DefaultReplaceResponseType.Instance);
    yield break;
}

/// <summary>
/// Update Handler
/// </summary>
/// <param name="update"></param>
/// <returns></returns>
[ServiceHandler(ServiceHandlerBehavior.Exclusive)]
public virtual IEnumerator<ITask> UpdateAgeHandler(UpdateAge update)
{
    _state.Age = update.Body.Age;
    update.ResponsePort.Post(DefaultUpdateResponseType.Instance);
    yield break;
}

Second we do the handlers for the generic operations and here we use the explicit type conversion that we provided in the service type declaration to go from the generic types to the extension types and back. The important thing is that we have one service state that all handlers operate on. Note also the use of the PortFieldName parameter which tells the DSS runtime to attach the handler to the operations port referenced by the _altPort class field.

/// <summary>
/// Get Handler
/// </summary>
/// <param name="get"></param>
/// <returns></returns>
[ServiceHandler(ServiceHandlerBehavior.Concurrent, PortFieldName = "_altPort")]
public virtual IEnumerator<ITask> GenericGetHandler(generic.Get get)
{
    get.ResponsePort.Post(_state.ToGenericState());
    yield break;
}

/// <summary>
/// Replace Handler
/// </summary>
/// <param name="replace"></param>
/// <returns></returns>
[ServiceHandler(ServiceHandlerBehavior.Exclusive, PortFieldName = "_altPort")]
public virtual IEnumerator<ITask> GenericReplaceHandler(generic.Replace replace)
{
    _state = new ExtensionState(replace.Body);
    replace.ResponsePort.Post(DefaultReplaceResponseType.Instance);
    yield break;
}

Running the Service

When running a service that is extending a generic service contract it also shows up as two entries in the directory (genericserviceextension and genericserviceextension/servicetutorial8. However, as this service implementation extends the generic service contract, the service type declarations of the two services differ. As before, the two services are connected as partners allowing the relationships between the two services to be determined remotely using DSSP.

Figure 2

Figure 2 - Running a service that is extending a generic service contract shows up as two entries in the directory (genericserviceextension and genericserviceextension/servicetutorial8 with different service state and behavior types containing the same instance state.

The first service (genericserviceextension) contains the extended state and operations and the second (genericserviceextension/servicetutorial8) contains the generic state and operations.

Figure 3

Figure 3 - The second (genericserviceextension/servicetutorial8) contains the generic state and operations allowing services that only talk to the generic contract to find this service and communicate with it.

Step 4: Implementing a Multi-Headed Service

The last service in this tutorial (located in the files MultiFunctionService.cs and MultiFunctionServiceTypes.cs ) is an example of a multi-headed service where the service is implemented as if it contained multiple related but independent service instances operating completely side-by-side. Even though the services from the outside are completely independent they may both manage a shared resource or otherwise require a high degree of interconnectedness. When designing multi-headed services, it is always important to carefully evaluate whether a multi-headed service is better to be implemented as separate services as it may provide more flexibility for future revisions.

There is nothing fundamentally new in how a multi-headed service is put together in that it uses the same principles as the previous service examples in this tutorial. That is, just like the extension service it has two (in this case) operations ports and a set of handlers that operate on messages arriving on the main port and another set that operates on the alternate port identified by the AlternateServicePort attribute.

The difference from the previous services is that a multi-headed service contains multiple state instances that when seen externally does not seem related other than through the explicit partner relationship between the two services.

// The first state of this multi headed service
private AddressState _address = new AddressState();

// The operations port used for the first service
[ServicePort("/AddressService", AllowMultipleInstances = false)]
private AddressServiceOperations _mainPort = new AddressServiceOperations();

// The second state of this multi headed service
private generic.GenericState _name = new generic.GenericState();

// The alternate port used for the second service
[AlternateServicePort(AlternateContract = generic.Contract.Identifier)]
private generic.GenericServiceOperations _altPort = new generic.GenericServiceOperations();

As in the previous example, we have two sets of message handlers attached to each their operations port, but this time they operate on separate state types.

// Service handlers for service one

/// <summary>
/// Get Handler
/// </summary>
/// <param name="get"></param>
/// <returns></returns>
[ServiceHandler(ServiceHandlerBehavior.Concurrent)]
public virtual IEnumerator<ITask> GetAddressHandler(Get get)
{
    get.ResponsePort.Post(_address);
    yield break;
}

/// <summary>
/// Replace Handler
/// </summary>
/// <param name="replace"></param>
/// <returns></returns>
[ServiceHandler(ServiceHandlerBehavior.Exclusive)]
public virtual IEnumerator<ITask> ReplaceAddressHandler(Replace replace)
{
    _address = replace.Body;
    replace.ResponsePort.Post(DefaultReplaceResponseType.Instance);
    yield break;
}

// Service handlers for service two

/// <summary>
/// Get Handler
/// </summary>
/// <param name="get"></param>
/// <returns></returns>
[ServiceHandler(ServiceHandlerBehavior.Concurrent, PortFieldName = "_altPort")]
public virtual IEnumerator<ITask> GetNameHandler(generic.Get get)
{
    get.ResponsePort.Post(_name);
    yield break;
}

/// <summary>
/// Replace Handler
/// </summary>
/// <param name="replace"></param>
/// <returns></returns>
[ServiceHandler(ServiceHandlerBehavior.Exclusive, PortFieldName = "_altPort")]
public virtual IEnumerator<ITask> ReplaceNameHandler(generic.Replace replace)
{
    _name = replace.Body;
    replace.ResponsePort.Post(DefaultReplaceResponseType.Instance);
    yield break;
}

Running the Service

When running a multi-headed service it again shows up as multiple entries in the directory (addressservice and addressservice/servicetutorial8. However, as this service implementation extends the generic service contract, the state and behavior of the two services differ. As before, the two services are connected as partners allowing the relationships between the two services to be determined remotely using DSSP.

Summary

In this tutorial, you learned how to:

  • Create a Generic Service Project.
  • Implementing a Generic Service Contract.
  • Extending a Generic Service Contract.
  • Implementing a Multi-Headed Service.

 

 

© 2012 Microsoft Corporation. All Rights Reserved.