Welcome to MSDN Blogs Sign in | Join | Help

Fun with Duplex contracts

Well, Clemens threw the glove so here goes :-)

 

The first thing that we define is the contracts

 

using System.ServiceModel;

 

namespace DuplexSample.Interfaces

{

   [ServiceContract]

   interface IClientSide

   {

      [OperationContract(IsOneWay = true)]

      void WriteString(string s);

   }

 

   [ServiceContract(CallbackContract = typeof(IClientSide))]

   interface IServiceSide

   {

      [OperationContract(IsOneWay = true)]

      void Rotate(string s);

   }

 

}

 

The client side contract defines a single method that the client needs to implement on his side (WriteString). The service side defines a single method that the service must implement (Rotate). The ServiceContract attribute on the IServiceSide interface defines this to be a duplex contract, and requires that the caller would implement the IClientSide interface.

 

Both methods are “one-way” methods. None-one-way methods might throw, so even when you call a void method you need to wait for it to complete before you can say that the operation was successful. One-way methods are true “fire and forget”, and do not wait for the operation to complete.

 

Ok, now for the client implementation:

 

using System;

using System.ServiceModel;

using DuplexSample.Interfaces;

 

namespace DuplexClient

{

   class ClientImpl : IClientSide

   {

      public void WriteString(string s)

      {

         Console.WriteLine(s);

      }

   }

 

   class Program

   {

      static void Main(string[] args)

      {

         IServiceSide service;

         ServiceSite mySite = new ServiceSite(new ClientImpl());

         service = ChannelFactory.CreateDuplexChannel<IServiceSide>(mySite, "Default Client Config");

         Console.WriteLine("Enter strings to rotate, or hit [Enter] to end.");

         string str = " ";

         while (str.Length > 0)

         {

            str = Console.ReadLine();

            if (str.Length!=0) service.Rotate(str);

         }

      }

   }

}

 

We first define a class that implements the client contract (ClientImpl). When the service operation WriteString is called, the implementation writes the string to the screen.

 

The Main method defines a local variable that would be used to reference the service (service) as well as a service site for the incoming calls (mySite). The service site is the object that would dispatch the calls made by the server to the instance of the client-side-contract-implementation-object that was passed in (the ClientImpl object created there).

 

Next, we create a duplex channel to the service, passing in the service site we created. The samples that Clemens and Bruce created hard-coded the server address and binding information into the app. Guys, come on, you know better J. Where’s the address and binding in this sample? They’re in the application configuration file. We’ll look at the configuration file format a little later.

 

We then go into a loop where we call the service with the string entered by the user. For each time we call the service, it would call us back multiple times on the duplex interface, resulting in the following output:

 

Enter strings to rotate, or hit [Enter] to end.

Indigo Rocks!

!Indigo Rocks

s!Indigo Rock

ks!Indigo Roc

cks!Indigo Ro

ocks!Indigo R

Rocks!Indigo

 Rocks!Indigo

o Rocks!Indig

go Rocks!Indi

igo Rocks!Ind

digo Rocks!In

ndigo Rocks!I

Indigo Rocks!

 

Now, let’s look at the service side.

 

using System;

using System.Text;

using System.ServiceModel;

using DuplexSample.Interfaces;

 

namespace DuplexService

{

   class ServiceImpl : IServiceSide

   {

      public void Rotate(string s)

      {

         IClientSide client = OperationContext.Current.GetCallbackChannel<IClientSide>();

         char[] chars = s.ToCharArray();

         int len = chars.Length;

         for (int i = 0; i < len; i++)

         {

            StringBuilder sb = new StringBuilder(len);

            sb.Append(chars, len - i, i);

            sb.Append(chars, 0, len - i);

            client.WriteString(sb.ToString());

         }

      }

   }

}

 

The service implementation class (ServiceImpl) implements the service-side contract (IServiceSide). This contract defines a single method (Rotate). The first thing we do here is to get the callback interface to the client. This interface is exposed through the GetCallbackChannel method on the static member GetCallbackChannel of the class OperationContext, and it is set for us by Indigo before the infrastructure code calls into our implementation. We then have a loop where we rotate the string and call the client interface for each variation.

 

The code that hosts this service is:

 

using System;

using System.ServiceModel;

 

namespace DuplexService

{

   class Program

   {

      static void Main(string[] args)

      {

         ServiceHost<ServiceImpl> host = new ServiceHost<ServiceImpl>(new ServiceImpl());

         host.Open();

         Console.WriteLine("Service is ready. Press [Enter] to exit.");

         Console.ReadLine();

      }

   }

}

 

In this code, we first create the host (again, the endpoints are defined in the service’s configuration file) and then open it. We then wait for clients to connect and call the service method, or for the user to hit Enter.

 

That’s it for the code. Now, let’s look at the configuration files:

 

Let’s start with the service’s configuration:

 

<?xml version="1.0" encoding="utf-8"?>

<configuration>

   <system.serviceModel>

      <services>

         <service serviceType="DuplexService.ServiceImpl, DuplexService">

            <endpoint contractType="DuplexSample.Interfaces.IServiceSide, DuplexService, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"

                    address="net.tcp://localhost:50210/DuplexService"

                    bindingSectionName="customBinding"

                    bindingConfiguration="ServiceCustomBinding"

                    />

         </service>

      </services>

      <bindings>

         <customBinding>

            <binding configurationName="ServiceCustomBinding">

               <tcpTransport />

            </binding>

         </customBinding>

      </bindings>

   </system.serviceModel>

</configuration>

 

A single application can host many services. Those services are defined inside the <services> element. In our case, we have a single service. Each service can have one or more endpoints, defined using the <endpoint .../> element. This element defines the 3 elements of the endpoint (Address, Binding, and Contract) as follows:

o       The address we use is a TCP address. The URI consists of the schema (net.tcp), host + port, and the path for the service. A single application can listen on multiple paths on the same TCP port.

o       The contract type is a strongly named CLR type, consisting of the fully qualified interface name, the assembly in which it is defines, and the assembly version, culture, and public key token.

o       The binding, referred to by type (in our case, a custom binding) and the name of the specific configuration section for that type (ServiceCustomBinding). For each type we can have multiple named configurations, and the name here helps distinguish between them.

 

The next section is the binding section. Each binding type has a section; in our case there’s a single binding type (we’re using a custom binding that we define ourselves (customBinding) and therefore a there’s a single binding section inside the bindings element. This custom binding has a single named configuration section that says that we’re only going to use TCP. In future samples I’ll show how to create more interesting custom bindings.

 

Now to the client configuration file:

 

<?xml version="1.0" encoding="utf-8" ?>

<configuration>

   <system.serviceModel>

      <client>

         <endpoint address="net.tcp://localhost:50210/DuplexService"

            bindingSectionName="customBinding"

            bindingConfiguration="ClientCustomBinding"

            configurationName="Default Client Config"

            contractType="DuplexSample.Interfaces.IServiceSide, DuplexClient, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null">

         </endpoint>

      </client>

      <bindings>

         <customBinding>

            <binding configurationName="ClientCustomBinding">

               <tcpTransport />

            </binding>

         </customBinding>

      </bindings>

   </system.serviceModel>

</configuration>

 

The client side defines one endpoint that allows it to connect to the service. It specifies the same address as the service, the same contract (this one is compiled into a different assembly since the client and service do not share implementation – see if you can spot the difference). The binding type and configuration are defined in the same way they are defined on the service, and the interesting thing remaining is the configuration name. This is the name that was passed in when we called the ChannelFactory.CreateDuplexChannel method on the client. The client configuration file can contain many endpoints, but the client only picks one (by name) and uses it.

 

That’s it! Clemens, bring on the next challenge J

Published Friday, February 11, 2005 12:59 AM by ShyCohen
Filed under:

Comments

# All Things Indigo - Indigo Resources I've Found

All Things Indigo - Indigo Resources I've Found
Sunday, February 13, 2005 7:21 PM by Mike Lorengo's Weblog

# Today and tomorrow: Will Indigo heal...? Powerful object model for contract-first glue

Monday, February 14, 2005 10:20 AM by Christian Weyer: Smells like service spirit

# Design Smell?

Dude, this :
OperationContext.Current
is a design smell....
How do you test / mock it ?

You guys need to look at inversion of control :

http://www.martinfowler.com/articles/injection.html

class ServiceImpl : IServiceSide
{
public property OperationContext ctx{
set{ ctx = value; }
}

public void Rotate(string s)
{

IClientSide client = ctx.GetCallbackChannel<IClientSide>();

etc....


simple and testable.

you can use annotations instead of signature if you prefer :

[RequiresContext]
public void thisIsWhereWeGetTheContext(OperationContext ctx)

Tuesday, February 15, 2005 6:36 AM by Zohar

# re: Fun with Duplex contracts

Zohar, I believe that you assume that the context and the CLR object instance are the same across calls - this may not be true in all cases. I guess this calls for a blog entry about instancing :-)
Tuesday, February 15, 2005 8:32 AM by Shy

# Today and tomorrow: Will Indigo heal...? Powerful object model for contract-first glue

Saturday, February 19, 2005 8:06 AM by Christian Weyer: Smells like service spirit

# Don Box's Writing Itch

Sunday, February 27, 2005 6:41 PM by Jeff Donnici
Anonymous comments are disabled
 
Page view tracker