Welcome to MSDN Blogs Sign in | Join | Help

I was recently asked to have a look at some really nice How To topics on WCF security that the Patterns & Practices people have worked up, and am quite happy with them; so much so I wanna tell them to let us fold them back into the regular SDK documentation! The project's Web site is http://www.codeplex.com/WCFSecurity, but to have a quick list of the topics I had a chance to see (plus some nice walkthrough videos), check out J.D. Meier's blog entry on them at http://blogs.msdn.com/jmeier/archive/2008/03/27/patterns-and-practices-wcf-security-guidance-now-available.aspx.

Note: There are two very useful WCF security projects going on right now that I got a touch confused about because, unaware of them, I did not realize the distinction when I found them. The first is the documentation and videos provided by the project mentioned above. The second, WCF Security Guidance Package, is a Visual Studio based set of security automation tools -- and some documentation -- that help you apply the appropriate principles inside Visual Studio. Both are recommended, and I see them as complimentary.

 If you find them useful or woefully inadequate, do let the projects know. They'll do their best to make them as useful as possible. Back to what I'm really interested in.... Web Service Software Factory: Modeling Edition!

I haven't posted for a while because I've been working on other stuff, and much of that work has encouraged me to think hard about the concept of the ServiceDescription class, the root of what is called the Description Hierarchy that I posted about previously. The ServiceDescription object, when created, contains everything that is known about a WCF service, and is really all that is required by a ServiceHost to get one up and running. One could, if one wanted, override the ServiceHostBase.CreateDescription method and return a ServiceDescription object that has been created live, right there. Many people do in fact do exactly this, populating a ServiceDescription from information contained in a file, or a database, or obtained from another Web service, for example.

Note: If you do this, make sure to override ApplyConfiguration also to return immediately, disabling configuration files. :-)

Of course, you could go the other way, overriding ApplyConfiguration in order to consume any arbitrary file or configuration system, doing the same thing. Imagine that you serialize an entire ServiceDescription object, and in ApplyConfiguration deserialize it and hand it off to the system. I'm using this thought game to point out that the totality of information necessary to execute a WCF service is **collected** from disparate sources and assimilated and validated into a ServiceDescription object, and then THAT is used to construct a service runtime and execute it (if possible). In the default case, the information lives in:

  • Attributes on the WCF service implementation. (ServiceContractAttribute, MessageContractAttribute, DataContractAttribute, ServiceBehaviorAttribute, and so on.)
  • The structure of the implementation determined by managed reflection over the structure of the service implementation. (For example, WCF throws exceptions if you try to return two MessageContractAttribute types.)
  • "Configuration" values contained in the configuration files, including behaviors and bindings and so on.

In fact, if the WCF implementation didn't require some attributes on the implementation at runtime, it would be possible to build a system in which you implement an implementation without any attributes at all, and specify a huge amount of precise information in a very large configuration file. Then you could build a UI tool to handle that file more easily (SvcSuperConfigMaster.exe, anyone?) And then you could make that tool handle these configuration tables almost anywhere in a Windows domain -- or other domain, for that matter. The only thing you'd need to do at runtime, then, would be to point at an implementation that could be loaded at runtime and then configured from the SuperFile.config.

In .NET 3.0, we did a good job of introducing a relatively straightforward way to both program and "model" -- another way of saying "specify" or "describe" -- the service and it's runtime information both in a managed assembly (attributes and structure) and in a description file (configuration file). I once mentioned to some of the product managers how all of this I saw as "metadata", almost the entire thing. They laughed and said, no, no, no. But I still think I'm right. :-)

One of the things we didn't get a chance to write as much about as we wanted to by RTM -- but will fix soon -- is the description tree. The service-side description tree consists of the heirarchy of objects starting with ServiceDescription class. The client-side description tree heirarchy starts from the ServiceEndpoint class.

Uh-oh: The first thing about these classes that you should know -- and notice if you followed the link for ServiceDescription is that there are TWO ServiceDescription classes in .NET Framework 3.0. The System.Web.Services.Description.ServiceDescription class represents metadata for a service in XML (and is used by WCF in some metadata-exclusive situations. Mail me if you want details). The ServiceDescription class that I want to discuss today is the System.ServiceModel.Description.ServiceDescription class, which is the representation of everything that is known about a WCF service. This ServiceDescription is used by WCF to create:

  • A service runtime, including listeners and behaviors that configure ru ntime execution patterns when the runtime is built.
  • WSDL and XSD files (including policy assertions) returned by ?wsdl HTTP Get requests or WS-MetadataExchange requests.
  • Code that can represent compatible data types, contract interfaces, and WCF client classes in addition to configuration files containing binding information.

Typically, the word metadata in the Web services world means the WSDL/XSD and other associated XML files that describe the interoperable structures and signatures used to communicate -- that is, the stuff you get back from a ?wsdl querystring request. But of course, that kind of metadata is interoperable metadata because the language used to communicate it is public and any tool or platform can use it. Hopefully by now you know that this metadata describes a contract that an implementation must support in order to be usable by a client on another platform. If you take a step back, however, you should be able to see that an implementation -- for example, a WCF service implementation with configuration -- contains exactly the same information as its WSDL in addition to the information specific to the implementation.

What is the sum total of all information about a WCF service? Well, naturally the contract must be modeled in code, typically as a service contract interface. In addition, a configuration file typically contains endpoint information in addition to any behaviors expected to run when the service is constructed. Where are these items brought together? Assuming a standard service application, when ServiceHost.Open is called a ServiceDescription is created -- and validated for contradictions that would prevent it from running -- that contains the totality of all information known about the service. In WCFs applications, the ServiceDescription object is effectively the internal WCF metadata for a WCF service. It can be converted into other "metadata" forms, such as a running service itself, code+configuration, and WSDL and XSD output. If you understand the ServiceDescription heirarchy, you'll understand all sorts of fun things about how WCF applications are configured, how they execute, and the forms into which such information can be put. The team doesn't necessarily think of the description tree as "uber-metadata" but I think doing so really helps. It helps you make some sense out of the fact that downloaded WSDL information (some of which is represented as XML-based System.Web.Services.Description.ServiceDescription information!) cannot simply be put into a System.ServiceModel.Description.ServiceDescription object, added to a ServiceHost, and run: they aren't the same things at all.

Here's the entire tree, pretty much, in a single, large (~270KB) .png file. Have some fun with it. Remember, on the client side, the description tree starts with the ServiceEndpoint class.

Two posts ago I wrote the following post about how to build a duplex service and client that does NOT use sessions. Once I wrote the sample, I wanted to extend it to provide some more flexibility. I've done that now, but in so doing, I ran across some issues that make this type of service useful in only a couple of circumstances -- IMHO. However, I want to throw the newer version out there to demonstrate some programmatic areas and to see whether any of you can figure out what scenarios this type of service is useful.

The first thing I wanted to do was to hook up custom extensions that tracked the lifetimes of the ServiceHost, the InstanceContext, and the IContextChannel when uses without sessions. To do this I used Extensible Objects (objects that implement IExtensibleObject<T> to which objects that implement IExtension<T> (where T is the specific extensible object) can be added). Michele Leroux Bustamante has a simple example clearly implemented here if you want to examine how this works very quickly.

I did the same thing. Here's the implementation for the ServiceHost tracker:

  class ServiceHostContext : IExtension<ServiceHostBase>, IDisposable
  {
    Guid id;

    public ServiceHostContext()
    { this.id = Guid.NewGuid(); }

    public string ID
    { get { return this.id.ToString(); } }
     
    #region IExtension<ServiceHost> Members

    public void Attach(ServiceHostBase owner)
    {
      Console.ForegroundColor = ConsoleColor.Red;
      Console.WriteLine("Attached to new ServiceHost.");
      Console.ResetColor();
    }

    public void Detach(ServiceHostBase owner)
    { throw new Exception("The method or operation is not implemented."); }

    #endregion

    #region IDisposable Members

    public void Dispose()
    {
      Console.WriteLine("Destroying service host: " + this.id);
    }

    #endregion
  }

This is made then used by a service behavior like so:

    public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
    {
      serviceHostBase.Extensions.Add(new ServiceHostContext());
    }

 I addition, I have an instancecontext tracker:

  public class MyInstanceContextExtension : IExtension<InstanceContext>
  {

    //Associate an Id with each Instance Created.
    String instanceId;

    public MyInstanceContextExtension()
    { this.instanceId = Guid.NewGuid().ToString(); }

    public String InstanceId
    {
      get
      { return this.instanceId; }
    }

    public void Attach(InstanceContext owner)
    {
      Console.ForegroundColor = ConsoleColor.Red;
      Console.WriteLine("Attached to new InstanceContext.");
      Console.ResetColor();
    }

    public void Detach(InstanceContext owner)
    {
      Console.WriteLine("Detached from InstanceContext.");
    }
  }

And this is added by and endpoint behavior (so it can be used on both sides!) that itself adds an instance context initializer. So it looks like this:

 public class MyInstanceContextInitializer : IInstanceContextInitializer
  {
    public void Initialize(InstanceContext instanceContext, Message message)
    {
      MyInstanceContextExtension extension = new MyInstanceContextExtension();

      //Add your custom InstanceContex extension that will let you associate state with this instancecontext
      instanceContext.Extensions.Add(extension);
    }
  }

And finally let's track channels by creating an IContextChannel extension:

  public class ChannelTrackerExtension : IExtension<IContextChannel>
  {
    <snipExtraneousIdentifyingStuff />

    #region IExtension<IContextChannel> Members

    public void Attach(IContextChannel owner)
    {
      Console.ForegroundColor = ConsoleColor.Red;
      Console.WriteLine("Attached to new IContextChannel {0}.", owner.GetHashCode());
      this.channel = owner;
      Console.ResetColor();
    }

    public void Detach(IContextChannel owner)
    {
      Console.WriteLine("Detached from IContextChannel {0}.", owner.GetHashCode());
    }

  }

And we use an endpoint behavior (again for both sides) that implements IChannelInitializer to detect the creation of channels.

  public class ChannelInitializer : IChannelInitializer
  {
    #region IChannelInitializer Members

    public void Initialize(IClientChannel channel)
    {
      Console.WriteLine("IClientChannel.Initialize called.");
      channel.Extensions.Add(new ChannelTrackerExtension());
    }
    #endregion
  }

The service and endpoint behaviors all implement System.ServiceModel.Configuration.BehaviorExtensionElement so that they can be wired up using a configuration file. The other thing I've done is to update both the contract and the client. First, the contract now specifies one request/response service and has one one-way callback operation, like so:

  [ServiceContract(
    Name = "SampleDuplexHello",
    Namespace = "http://microsoft.wcf.documentation",
    CallbackContract = typeof(IHelloCallbackContract),
    SessionMode = SessionMode.NotAllowed
  )]
  public interface IDuplexHello
  {
    [OperationContract]
    string Hello(string greeting);
  }

  public interface IHelloCallbackContract
  {
    [OperationContract(IsOneWay = true)]
    void Reply(string responseToGreeting);
  }

The client application now creates a new object for each client proxy. This is an artifact of the fact that it's a console application; if it were a Windows Form or WPF application I wouldn't need to do this. But while I was building it out, I stumbled across some interesting behavior that illuminates what is going on. The host creates a new Client object for each connection it wants to make:

  public class ClientApp
  {
    public static void Main()
    {
      Client client = new Client();
      client.Run();
      client = new Client();
      client.Run();

And each Client object creates an AutoResetEvent to hold open the client until all callbacks are received and then in Run() creates a duplex client, invokes the request/reply operation and waits for the callbacks.

   public void Run()
    {
      // Picks up configuration from the config file.
      InstanceContext callbackInstanceContext = new InstanceContext(this);
      SampleDuplexHelloClient wcfClient = new SampleDuplexHelloClient(callbackInstanceContext);
      try
      {
        using (OperationContextScope opScope = new OperationContextScope(wcfClient.InnerDuplexChannel))
        {
          // add replyto
          Console.WriteLine(wcfClient.InnerDuplexChannel.LocalAddress.ToString());
          OperationContext.Current.OutgoingMessageHeaders.ReplyTo
            = wcfClient.InnerDuplexChannel.LocalAddress;
          Console.ForegroundColor = ConsoleColor.White;
          Console.WriteLine("Enter a greeting to send and press ENTER: ");
          Console.Write(">>> ");
          Console.ForegroundColor = ConsoleColor.Green;
          string greeting = Console.ReadLine();
          Console.ForegroundColor = ConsoleColor.White;
          Console.WriteLine("Called service with: \r\n\t" + greeting);
          Console.ForegroundColor = ConsoleColor.Cyan;
          Console.WriteLine(wcfClient.Hello(greeting));
          Console.ResetColor();
          this.waitHandle.WaitOne();
          Console.WriteLine("Set was called.");
        }
      }
      catch (TimeoutException timeProblem)
      {
        Console.WriteLine("The service operation timed out. " + timeProblem.Message);
        wcfClient.Abort();
      }
      catch (CommunicationException commProblem)
      {
        Console.WriteLine("There was a communication problem. " + commProblem.Message);
        wcfClient.Abort();
      }
    }

The callback (Reply()) remains the same. Now we can talk. As soon as I figure out how to post .zips I will. Anyway, the service now looks like this:

   public string Hello(string greeting)
    {
      Console.ForegroundColor = ConsoleColor.Green;
      Console.WriteLine("Caller sent: " + greeting);
      Console.ResetColor();
      Console.WriteLine("Session ID: " + OperationContext.Current.SessionId);
      string response = "Service object " + this.GetHashCode().ToString() + " received: " + greeting;
     
      // Generate five callbacks
      CallbackArguments args = new CallbackArguments();
      args.numCallbacks = 5;
      args.to = OperationContext.Current.IncomingMessageHeaders.ReplyTo.Uri;
      args.callerClient
        = OperationContext.Current.GetCallbackChannel<IHelloCallbackContract>();
      args.incomingHeaders
        = OperationContext.Current.IncomingMessageHeaders.ReplyTo.Headers;
      // Do this on another thread.
      System.Threading.ThreadPool.QueueUserWorkItem(
        new WaitCallback(DuplexHello.GenerateCallbacks), args
      );
      Thread.Sleep(1000);
      //DuplexHello.GenerateCallbacks(args);
      return String.Format("Hi there. You sent {0}.", greeting); 
    }

Note that I have a GenerateCallbacks method that is supposed to invoke five callbacks to the caller (you can do this either on this thread or another). Note, also, that the request/reply portion of the call is handled simply by returning. The only thing of interest here is the callbacks generated by the service. For this, recall that we needed to add the inbound ReplyTo value to the outbound To header. But -- and this is one of those interesting things, when we did TWO separate clients, the second client failed to see the response and the service did send the messages. What happened?

What happened is that the first request does not need to append any address information to the callback To header. But subsequent callbacks DO append information, and that extra information does need to be appended. So here's the simple way to do this in GenerateCallbacks:

    private static void GenerateCallbacks(object parameters)
    {
      CallbackArguments args = parameters as CallbackArguments;
      IHelloCallbackContract callerClient
        = args.callerClient;
      using (
        OperationContextScope callbackOpContext
          = new OperationContextScope((IContextChannel)callerClient)
      )
      {
        OperationContext.Current.OutgoingMessageHeaders.To = args.to;
        foreach (AddressHeader ah in args.incomingHeaders)
        {
          // deal with ref params
          OperationContext.Current.OutgoingMessageHeaders.Add(ah.ToMessageHeader());
        }

We assign the To and then we take any extra information coming in from the client instance and make sure to add that information as well. In this case, the extra information is that on subsequent calls the client creates listeners on addresses that make use of reference parameters and these, too, must be added to the outbound headers collection. Whoohooo!

Now, let's make our callbacks.

     for(int i = 0; i < args.numCallbacks; ++i)
      {
        try
        {
          Console.WriteLine("callback {0} to: {1}", i, args.to);
          callerClient.Reply(String.Format("Notification {0}.", i.ToString()));
        }
        catch (TimeoutException timeout)
        {
          Console.WriteLine("There was a timeout exception on a callback.");
        }
        catch (CommunicationException commException)
        {
          Console.WriteLine("CommunicationException: {0}", commException.Message);
        }
        catch (Exception ex)
        {
          Console.WriteLine("General exception: {0}.", ex.Message);
        }
      }

Great, right? Um, no. Here's another interesting thing. The default service InstanceContextMode is PerSession. But we aren't using a session, remember? The behavior we get in this case ends up being "PerCall". So if we return from the operation prior to the completion of the callbacks you can guess what happens: The callbacks don't get there because the service infrastructure has cleaned up the operation context and service channel from underneath us.

One fix is to do what I did here: Throw a Thread.Sleep in the operation to enable the callbacks to reach their destination successfully. Obviously this is just a mitigation for the example. Try it yourself without the Sleep and see what you get. The other way to handle this is to realize that the service InstanceContextMode could be single, in which case the underlying service channel stays around for all clients. Mix and match solutions and enjoy yourself.

Let's get back to the extensions that track lifetimes. One of the things that building the sample this way accomplishes is to establish what happens on each side when multiple clients run. First of all, each client callback address is different, so clients are still separate entities for the purposes of duplex calls. We can see that for each new Client object, you get a brand new channel. For the service, however, there is only ever one channel for the lifetime of the service host with the exception of any timeouts. When the InstanceContextMode is functionally PerCall, you get the following:

Attached to new ServiceHost.
Channel tracker added.
The service is ready.
Press <ENTER> to terminate service.

IClientChannel.Initialize called.
Attached to new IContextChannel 42132014.
Attached to new InstanceContext.
Service object created: 45155606
Caller sent: Hello.
Session ID:
callback 0 to: http://localhost:8081/Callback/cea9fe7f-c3a6-41cf-9151-c54b1404d5b5
callback 1 to: http://localhost:8081/Callback/cea9fe7f-c3a6-41cf-9151-c54b1404d5b5
callback 2 to: http://localhost:8081/Callback/cea9fe7f-c3a6-41cf-9151-c54b1404d5b5
callback 3 to: http://localhost:8081/Callback/cea9fe7f-c3a6-41cf-9151-c54b1404d5b5
callback 4 to: http://localhost:8081/Callback/cea9fe7f-c3a6-41cf-9151-c54b1404d5b5
Service object destroyed: 45155606
Attached to new InstanceContext.
Service object created: 29447802
Caller sent: Hello again.
Session ID:
callback 0 to: http://localhost:8081/Callback/99928a92-2d3c-42cc-bd1b-34af14cc737b
callback 1 to: http://localhost:8081/Callback/99928a92-2d3c-42cc-bd1b-34af14cc737b
callback 2 to: http://localhost:8081/Callback/99928a92-2d3c-42cc-bd1b-34af14cc737b
callback 3 to: http://localhost:8081/Callback/99928a92-2d3c-42cc-bd1b-34af14cc737b
callback 4 to: http://localhost:8081/Callback/99928a92-2d3c-42cc-bd1b-34af14cc737b
Service object destroyed: 29447802

You can see that we get a new IntanceContext and service object for each call. But only one service channel is ever created. It's this channel that must be available for the callbacks. This means, also, that you cannot call Close on it when you're done with your callbacks. Go ahead: Close it and see what happens -- you're going to need it again later. ;-)

The client, however, has channels appearing all over. OK, for each new client:

Channel tracker added.
IClientChannel.Initialize called.
Attached to new IContextChannel 44419000.
http://localhost:8081/Callback/cea9fe7f-c3a6-41cf-9151-c54b1404d5b5
Enter a greeting to send and press ENTER:
>>> Hello first.
Called service with:
        Hello first.

        Notification 0.

        Notification 1.

        Notification 2.

        Notification 3.

        Notification 4.
Hi there. You sent Hello first..
Set was called.
Channel tracker added.
IClientChannel.Initialize called.
Attached to new IContextChannel 59109011.
http://localhost:8081/Callback/99928a92-2d3c-42cc-bd1b-34af14cc737b
Enter a greeting to send and press ENTER:
>>> Hello second.
Called service with:
        Hello second.

        Notification 0.

        Notification 1.

        Notification 2.

        Notification 3.

        Notification 4.
Hi there. You sent Hello second..
Set was called.
Press ENTER to exit...

Now, I think that based on what we've discovered that this a) isn't the application structure you'd want if you were building a sessionless duplex service and client, and b) that if you built it correctly it would still only be useful in limited scenarios given how hard you have to work to correlate things AND managed service infrastructure lifetimes. Because Christian Weyer had asked me about the sessionless duplex possibility before, now I'm going to challenge him to take a stance: How should this application be built, and once it's built correctly, what scenarios can make use of it? Christian?

 UPDATE: I forgot one other critical little piece. Note that in the callback section I generate a new OperationContext that is not the one used with request/reply. Why? Because if I use the same one I'll set the To values for the request/reply operation as well as the oneway callback -- and that would muck with the response to the operation. Therefore I only need to set the callback headers for the callback context. That's why I do that.....

I've been having a conversation with Scott Klein, who is busy writing a book on WCF (http://www.amazon.com/Professional-WCF-Pr%20ogramming-Development-Communication/dp/0470089849/sr=1-2/qid=1161195651/ref=sr_1_2/102-4898838-8936%20108?ie=UTF8&s=books). He'd been reading and following the documentation about publishing service metadata and had figured out how to do this in code, but for some reason he just didn't understand the errors resulting from his attempt to use the configuration file to do the same thing. Even the book writers don't get things first time! (But that's likely because I need to do a better job writing about it. ;-)

We got in a conversation about this, and it seemed to me that a couple of things made it difficult for him to pick up how this works.

  • In the programmatic version, you don't usually have to think about the base addresses; in config, you usually do.
  • The fact that HTTP/GET publication just "happens" but WS-MetadataExchange is a real contract with real endpoints.

With Scott's permission, I'm republishing the majority (minus the embarrasing dumb stuff I wrote here and there and with some small edits for sensibility) of a mail to Scott that walks through the process of taking a standard application configuration file and adding different sorts of metadata publication as we go along until at the end we're publishing service metadata at a HTTP/GET address using the ?wsdl convention AND over both HTTP and TCP using WS-MetadataExchange requests. The critical things to watch for:

  • All metadata publication using relative addressing requires supporting base addresses for the ServiceHost; otherwise, you can use absolute addresses.
  • HTTP/GET publication is an "artifact" of the ServiceMetadataBehavior and the HttpGetEnabled property.

As a side note, because the ServiceMetadataBehavior is a service behavior, it must be not only specified in the configuration file but also referenced by the \service@behaviorConfiguration attribute.

Here we go (and if you walk through this and catch me in a typo, let me know and I'll fix it!):

From: Ralph Squillace
Sent: Tuesday, October 17, 2006 10:42 AM
To: 'Scott Klein'
Subject: RE: ServiceMetadata -- the big picture and walkthrough

<snip stupid stuff I wrote/>

 

Automatic metadata publication must have an address at which to publish. A ServiceHost is not tied to any particular application domain. This means that unlike ASMX files, which always reside at an IIS virtual directory (for example, http://computer/vDirectory), ServiceHost when opened does not know where it is. It must therefore be passed (or infer from absolute addressing) a “base” or “root” address to which all relative addresses used by the host are appended.

 

When a Service.svc file is hosted in IIS/WAS, the ServiceHost acquires the base address from IIS/WAS, and is therefore automatically whatever the virtual directory is, just like ASMX, plus the “Service.svc” relative address that points to the service implementation.

 

In all other cases, you must provide a base address in order for automatic metadata publication to work unless you supply an absolute address in your endpoints or for the httpGetUrl property.

 

Let’s walk through this. Let’s say you haven’t configured any metadata for your service yet and it looks like the following. What is the address for this service?

 

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

<configuration>

  <system.serviceModel>

    <services>

      <service

        name="Microsoft.WCF.Documentation.SampleService"

        >

        <endpoint

          address=""

          binding="wsHttpBinding"

          contract="Microsoft.WCF.Documentation.ISampleService"

        />

      </service>

    </services>

  </system.serviceModel>

</configuration>

The answer is that this throws an exception on Open because no base address exists to create this class unless hosted in IIS/WAS, in which case the service is hosted at the virtual directory. In all other cases, this service throws.

 

We **could** do this:

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

<configuration>

  <system.serviceModel>

    <services>

      <service

        name="Microsoft.WCF.Documentation.SampleService"

        >

        <endpoint

          address="http://computer/Services/SampleService"

          binding="wsHttpBinding"

          contract="Microsoft.WCF.Documentation.ISampleService"

        />

      </service>

    </services>

  </system.serviceModel>

</configuration>

Now the service WILL be published, but at http://computer/Services/SampleService because we specified an absolute address. Trick question: what is the base address? The answer is that there isn’t one here. There may be one passed to the ServiceHost programmatically, but let's assume that's not the case here.

 

OK, this runs, now let’s add metadata support for HTTP/GET. We do this:

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

<configuration>

  <system.serviceModel>

    <services>

      <service

        name="Microsoft.WCF.Documentation.SampleService"

        >

        <endpoint

          address="http://computer/Services/SampleService"

          binding="wsHttpBinding"

          contract="Microsoft.WCF.Documentation.ISampleService"

        />

      </service>

    </services>

    <behaviors>

      <serviceBehaviors>

        <behavior name="metadataSupport">

          <serviceMetadata httpGetEnabled="true" httpGetUrl=""/>

        </behavior>

      </serviceBehaviors>

    </behaviors>

  </system.serviceModel>

</configuration>

What has changed? The ServiceMetadataBehavior is loaded and we’ve told it to publish HTTP/GET metadata at the address baseHttpAddress + httpGetUrl (because the address we’ve specified there is relative) + “?wsdl”. There are two problems here. First, we haven’t specified this behavior for any service in the configuration file yet, so it’s loaded but not invoked. Second, even when we specify the “metadataSupport” behavior in <service behaviorConfiguration=”metadataSupport”/> property, we’ll throw because – of course – the host does not HAVE an HTTP-based base address. How can we make this work? Well, we can specify an absolute address. Like so:

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

<configuration>

  <system.serviceModel>

    <services>

      <service

        name="Microsoft.WCF.Documentation.SampleService"

        behaviorConfiguration=”metadataSupport”

        >

        <endpoint

          address="http://computer/Services/SampleService"

          binding="wsHttpBinding"

          contract="Microsoft.WCF.Documentation.ISampleService"

        />

      </service>

    </services>

    <behaviors>

      <serviceBehaviors>

        <behavior name="metadataSupport">

          <serviceMetadata httpGetEnabled="true" httpGetUrl="http://computer/Services/SampleService/Metadata"/>

        </behavior>

      </serviceBehaviors>

    </behaviors>

  </system.serviceModel>

</configuration>

 

Note that here we now provide an absolute address for the HttpGetUrl property (and the behavior is wired up to the service) so that you can view the metadata at “http://computer/Services/SampleService/Metadata?wsdl”. But this is fairly fragile. What we WANT to do is to specify the base addresses so that we can use relative ones. Now we use the base addresses stuff. We do this:

 

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

<configuration>

  <system.serviceModel>

    <services>

      <service

        name="Microsoft.WCF.Documentation.SampleService"

        behaviorConfiguration="metadataSupport">

        <host>

          <baseAddresses>

            <add baseAddress="http://localhost:8080/SampleService" />

          </baseAddresses>

        </host>

        <endpoint

          address=""

          binding="wsHttpBinding"

          contract="Microsoft.WCF.Documentation.ISampleService"

        />

      </service>

    </services>

    <behaviors>

      <serviceBehaviors>

        <behavior name="metadataSupport">

          <serviceMetadata httpGetEnabled="true" httpGetUrl=""/>

        </behavior>

      </serviceBehaviors>

    </behaviors>

  </system.serviceModel>

</configuration>

Here we have specified that there is a base address of http://localhost:8080/SampleService. Note that the service endpoint HTTP-based address is empty and that it’s relative (because it’s not absolute), so the service is available at the base address.

  1. The ServiceMetadataBehavior is there...
  2. ...AND it’s wired up to the service
  3. ...AND the HttpGetUrl property is empty (and therefore relative)
  4. ...AND because the http/GET protocol requires an HTTP-transport based base address
  5. ...AND the base address IS an Http-based address

THEREFORE we can now see the metadata using a browser at http://localhost:8080/SampleService?wsdl. Whew. Lot's of ANDs.

 

Can we use WS-MetadataExchange yet? No. Can we retrieve metadata from any other transport? No. At this point we only support HTTP/GET at the previously mentioned address. Now let’s add WS-MetadataExchange support. We now add a metadata endpoint, one in which the contract is IMetadataExchange, the binding supports HTTP, and a relative (or absolute!) address. Here we go:

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

<configuration>

  <system.serviceModel>

    <services>

      <service

        name="Microsoft.WCF.Documentation.SampleService"

        behaviorConfiguration="metadataSupport">

        <host>

          <baseAddresses>

            <add baseAddress="http://localhost:8080/SampleService" />

          </baseAddresses>

        </host>

        <endpoint

          address=""

          binding="wsHttpBinding"

          contract="Microsoft.WCF.Documentation.ISampleService"

        />

        <endpoint

           address="mex"

           binding="mexHttpBinding"

           contract="IMetadataExchange"

        />

      </service>

    </services>

    <behaviors>

      <serviceBehaviors>

        <behavior name="metadataSupport">

          <serviceMetadata httpGetEnabled="true" httpGetUrl=""/>

        </behavior>

      </serviceBehaviors>

    </behaviors>

  </system.serviceModel>

</configuration>

We have added a metadata endpoint with the relative address “mex” (therefore we must have a supporting base address -- and we do), specified the mexHttpBinding (for basic HTTP support -- and the base address IS an HTTP-based address, so we're OK there), and referenced the IMetadataExchange contract. You can now (in addition to using HTTP/GET as before) point svcutil at http://localhost:8080/SampleService/mex and it will retrieve the metadata content in a WS-MEX message.

Now, do we have HTTP/GET support? Yes. Do we have WS-Mex support over HTTP? Yes. Do we have WS-Mex support over TCP? No. To do that, we’ll add another endpoint that uses TCP, like so:

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

<configuration>

  <system.serviceModel>

    <services>

      <service

        name="Microsoft.WCF.Documentation.SampleService"

        behaviorConfiguration="metadataSupport">

        <host>

          <baseAddresses>

            <add baseAddress="http://localhost:8080/SampleService" />

          </baseAddresses>

        </host>

        <endpoint

          address=""

          binding="wsHttpBinding"

          contract="Microsoft.WCF.Documentation.ISampleService"

        />

        <endpoint

           address="mex"

           binding="mexHttpBinding"

           contract="IMetadataExchange"

        />

        <endpoint

           address="tcpmex"

           binding="mexTcpBinding"

           contract="IMetadataExchange"

        />

      </service>

    </services>

    <behaviors>

      <serviceBehaviors>

        <behavior name="metadataSupport">

          <serviceMetadata httpGetEnabled="true" httpGetUrl=""/>

        </behavior>

      </serviceBehaviors>

    </behaviors>

  </system.serviceModel>

</configuration>

Now then, what happens when we run svcutil against net.tcp://localhost:8080/SampleService/tcpmex? We should get 300 exceptions, as you did (OK, it’s really just one). Why? You can guess: The service host does not have a TCP-transport base address AND the TCP-based metadata endpoint uses a relative address. Had we specified an absolute address there:

        <endpoint

           address="net.tcp://localhost:8081/SampleService/tcpmex"

           binding="mexTcpBinding"

           contract="IMetadataExchange"

        />

We would have been able to point svcutil at net.tcp://localhost:8081/SampleService/tcpmex and it will work fine. But again, absolute addresses are bad. So how do we use relative addresses? We add a tcp-transport based base address, like so:

        <host>

          <baseAddresses>

            <add baseAddress="http://localhost:8080/SampleService" />

            <add baseAddress=”net.tcp://localhost:8081/SampleService” />

          </baseAddresses>

        </host>

NOW we can point to net.tcp://localhost:8081/SampleService/tcpmex and it will work fine. From here you should be able to add any other WS-MEX endpoint AND you should be able to understand what to check when it doesn’t work. Let’s look at the exception you got in your example:

 

"Could not find a base address that matches scheme net.tcp for the endpoint with binding MetadataExchangeTcpBinding. Registered base address schemes are []."

 

<snip from Scott's mail with permission> 

So, I added the following section:

 

<host>

          <baseAddresses>

            <add baseAddress = "net.pipe://localhost/"/>

            <add baseAddress ="net.tcp://localhost:8000/"/>

          </baseAddresses>

</host>

 

Now I get the following on sh.Open()

 

“The HttpGetEnabled property of ServiceMetadataBehavior is set to true and the HttpGetUrl property is a relative address, but there is no http base address.  Either supply an http base address or set HttpGetUrl to an absolute address.”

</snip from Scott's mail with permission> 

 

 

The answer here is that you’ve set the HttpGetEnabled property to true BUT you have no <add baseAddress=/> property that supports HTTP. We have no transport to use for HTTP! You can either:

  1. Set the HttpGetUrl property to an absolute address OR
  2. add a HTTP-based base address to the <baseAddresses> element.

This put everything in perspective for Scott, who immediately went on to bigger and better things, but I wanted to make sure that no one else got stalled out by the simple service requirement. Two other notes. First, if you're wondering where the implementation of the IMetadataExchange contract is (because you know you didn't build one!) it's in the ServiceMetadataExtension, which the behavior adds to the ServiceHost. Second, here we added HTTP and TCP metadata support. But if you're following the abstract point, you should see that an IME endpoint can be any endpoint. You can secure it; auth it; encrypt it; whatever you want. We provide four basic supporting metadata bindings, but you can create custom bindings, too. Hope this all helps! I'll be folding this information back into the documentation as soon as I can...

 

 

Duplex is neato, definitely, because among other things it allows a service to push information at clients as it sees fit. You could just have two services, and one service throws an endpoint at the other and then listens for stuff coming back, too, but there are scenarios for this and scenarios for that. Sometimes a duplex client is the perfect thing but you don't really want the session that comes with the system-provided duplex-supporting bindings used to wire up the two-way communication system. For example, WsDualHttpBinding uses a secure conversation session by default and a reliable session if you turn it on; and the NetTcpBinding surfaces a session associated with the underlying TCP connection.

The problem with these is that there are some session lifetime and creation issues that you have to manage when you use sessions. What's the timeout period? What do I do when Abort happens? And so on. Wouldn't it be nice of you could do duplex without session overhead? Well, you can. But you may want to consider why you want to before you do it. I'll wait to see when Christian thinks it might be helpful. ;-)

A duplex contract is merely a service contract in which there are some operations that specify outbound calls from the service to the client. And a duplex client, then, is merely a WCF application that also hosts a client-side listener for return calls from the service to which it is connected. A client runtime is exposed for use by the -- surprise -- ClientRuntime class. If the client is a duplex client, however, it also hosts a service runtime, exposed for inspection or modification as a DispatchRuntime object available from the ClientRuntime.CallbackDispatchRuntime property. This exposes the entire service side runtime and can be thought of as a service in your client application but for one major exception: It cannot be activated by a call from a service. It must connect to a service first, and then it listens for callbacks from that service. In the WCF system-provided duplex bindings, the information about the callback listener is transferred to the service (so that the service knows where to direct the callback invocations) using the provided session support.

 Therefore, when -- for your own twisted reasons -- you want to build a sessionless duplex application, you have to do a little extra lifting. But not that much. Here's how to build a simple duplex application that does not establish a session using the HTTP transport and a few extra gadgets. First, let's get you up and running, and then we'll post more later about how this works and how to modify it. Or maybe someone will beat me to that.

Big Fat Warning. This sample does not do security. You'd be a fool to think it does, so don't think that.

OK, onward. First, a simple duplex contract.

  [ServiceContract(
    Name = "SampleDuplexHello",
    Namespace = "http://microsoft.wcf.documentation",
    CallbackContract = typeof(IHelloCallbackContract),
    SessionMode = SessionMode.NotAllowed
  )]
  public interface IDuplexHello
  {
    [OperationContract(IsOneWay = true)]
    void Hello(string greeting);
  }

  public interface IHelloCallbackContract
  {
    [OperationContract(IsOneWay = true)]
    void Reply(string responseToGreeting);
  }

Note that the ServiceContractAttribute.SessionMode property is set to SessionMode.NotAllowed. This means that if we make a mistake and supply a sessionful binding, the application throws an exception. Here is the service implementation.

  public class DuplexHello : IDuplexHello, IDisposable
  {
    public DuplexHello()
    {
      Console.WriteLine("Service object created: " + this.GetHashCode().ToString());
    }

    public void Hello(string greeting)
    {
      Console.WriteLine("Caller sent: " + greeting);
      Console.WriteLine("Session ID: " + OperationContext.Current.SessionId);
      Console.WriteLine("Waiting two seconds before returning call.");
      // Put a slight delay to demonstrate asynchronous behavior on client.
      Thread.Sleep(2000);
     
      // Get the callback client object.
      IHelloCallbackContract caller
        = OperationContext.Current.GetCallbackChannel<IHelloCallbackContract>();

      // Fetch out the client ReplyTo and place it in the outbound To header to
      // direct the callback message.
      Uri to = new Uri(OperationContext.Current.IncomingMessageHeaders.ReplyTo.ToString());
      OperationContext.Current.OutgoingMessageHeaders.To = to;
     
      string response = "Service object " + this.GetHashCode().ToString() + " received: " + greeting;
      Console.WriteLine("Sending back: " + response);
      caller.Reply(response);
    }

    #region IDisposable Members

    public void Dispose()
    {
      Console.WriteLine("Service object destroyed: " + this.GetHashCode().ToString());
    }

    #endregion
  }

If you've built a duplex application before, the only interesting code here is the section in which we find the incoming ReplyTo header and assign that value to the outgoing To header.

      // Fetch out the client ReplyTo and place it in the outbound To header to
      // direct the callback message.
      Uri to = new Uri(OperationContext.Current.IncomingMessageHeaders.ReplyTo.ToString());
      OperationContext.Current.OutgoingMessageHeaders.To = to; 

With the default sessionful bindings, you don't have to take this step. But without those sessions to help out, you do. If this application structure is important to you, you'd probably handle this wiring procedure in a combination extensible object and IDispatchMessageInspector. Here we simply do this in code.

 Now the tricky part. I'll just tell you how to build the binding and get back to discussing this later. We're going to layer a CompositeDuplexBindingElement over a OneWayBindingElement on top of a HttpTransportBindingElement. Oh yeah, we need an encoder, too. OK, let's use the default TextMessageEncodingBindingElement for that. I find config easier for this than code, so we'll assemble this binding using the CustomBinding. It looks like this:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.serviceModel>
    <services>
      <service
        name="Microsoft.WCF.Documentation.DuplexHello"
        behaviorConfiguration="mex">
        <host>
          <baseAddresses>
            <add baseAddress="http://localhost:8080/DuplexHello"/>
          </baseAddresses>
        </host>
        <endpoint
          address=""
          binding="customBinding"
          bindingConfiguration="duplexNoSession"
          contract="Microsoft.WCF.Documentation.IDuplexHello"
         />
        <endpoint
          address="mex"
          binding="mexHttpBinding"
          contract="IMetadataExchange"
        />
      </service>
    </services>
    <bindings>
      <customBinding>
        <binding name="duplexNoSession">
          <compositeDuplex />
          <oneWay/>
          <textMessageEncoding />
          <httpTransport />
        </binding>
      </customBinding>
    </bindings>
    <behaviors>
      <serviceBehaviors>
        <behavior name="mex" >
          <serviceMetadata httpGetEnabled="true" />
        </behavior>
      </serviceBehaviors>
    </behaviors>
  </system.serviceModel>
</configuration>

Note that here I enable metadata using the ServiceMetadataBehavior. Downloading and creating the client remains pretty simple. Now, create a host:

      // Create a ServiceHost for the service type and use the base address from configuration.
      using (ServiceHost serviceHost = new ServiceHost(typeof(DuplexHello)))
      {
        try
        {
          // Open the ServiceHostBase to create listeners and start listening for messages.
          serviceHost.Open();

and you're running. Let's get to the client, and then I'll sign off for now. Use Svcutil.exe to generate client code. Once you do that, the trick to the client is that you must create a new OperationContextScope to set the outbound ReplyTo header. Then, pass the local address of the underlying channel to the ReplyTo header and invoke the client.

      // Picks up configuration from the config file.
      InstanceContext callbackInstanceContext = new InstanceContext(this);
      SampleDuplexHelloClient wcfClient = new SampleDuplexHelloClient(callbackInstanceContext);
      try
      {
        using (OperationContextScope opScope = new OperationContextScope(wcfClient.InnerDuplexChannel))
        {
          // Add explicit replyto for the other side to pick up.
          OperationContext.Current.OutgoingMessageHeaders.ReplyTo
            = wcfClient.InnerChannel.LocalAddress;
 

In duplex clients you need to hold the application domain open to listen for callbacks. Without a session to fault, if a client vanishes the service will not know about it until a callback fails. The upside, however, is that the channel is not like to fault. Typically, if a callback fails, your service can simply ignore that result or retry the call on the same channel later. This is a handy way of getting services to support eventish behavior for client applications without worrying unduly about the channel lifetime and infrastructure. The complete client application follows. I learned most of this to write documentation for duplex applications but I was guided mainly by Dr. Nick and Kenny Wolf, who have written in their blogs about pieces and helped me understand how they go together to make this work. If I have to repost to clarify or modify what I've said, it's not their fault. :-) More soon.

using System;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.Threading;

namespace Microsoft.WCF.Documentation
{
  public class Client : SampleDuplexHelloCallback
  {
    AutoResetEvent waitHandle;

    public Client()
    {
      waitHandle = new AutoResetEvent(false);
    }

    public void Run()
    {
      // Picks up configuration from the config file.
      InstanceContext callbackInstanceContext = new InstanceContext(this);
      SampleDuplexHelloClient wcfClient = new SampleDuplexHelloClient(callbackInstanceContext);
      try
      {
        using (OperationContextScope opScope = new OperationContextScope(wcfClient.InnerDuplexChannel))
        {
          // Add explicit replyto for the other side to pick up.
          OperationContext.Current.OutgoingMessageHeaders.ReplyTo
            = wcfClient.InnerChannel.LocalAddress;

          Console.ForegroundColor = ConsoleColor.White;
          Console.WriteLine("Enter a greeting to send and press ENTER: ");
          Console.Write(">>> ");
          Console.ForegroundColor = ConsoleColor.Green;
          string greeting = Console.ReadLine();
          Console.ForegroundColor = ConsoleColor.White;
          Console.WriteLine("Called service with: \r\n\t" + greeting);
          wcfClient.Hello(greeting);
          Console.WriteLine("Execution passes service call and the client waits.");
          this.waitHandle.WaitOne();
          Console.ForegroundColor = ConsoleColor.Red;
          Console.WriteLine("Set was called.");
        }
      }
      catch (TimeoutException timeProblem)
      {
        Console.WriteLine("The service operation timed out. " + timeProblem.Message);
        // Sessionful channels should abort the channel unless it is a
        // specified fault. This duplex sample uses a datagram channel, which should be
        // reusable.
        wcfClient.Close();
      }
      catch (CommunicationException commProblem)
      {
        Console.WriteLine("There was a communication problem. " + commProblem.Message);
        // Sessionful channels should abort the channel unless it is a
        // specified fault. This duplex sample uses a datagram channel, which should be
        // reusable.
        wcfClient.Close();
      }
      finally
      {
        Console.ResetColor();
        Console.Write("Press ");
        Console.ForegroundColor = ConsoleColor.Red;
        Console.Write("ENTER");
        Console.ResetColor();
        Console.Write(" to exit...");
        Console.ReadLine();
      }
    }
   
    public static void Main()
    {
      Client client = new Client();
      client.Run();
    }

    public void Reply(string response)
    {
      Console.WriteLine("Received output.");
      Console.WriteLine("\r\n\t" + response);
      this.waitHandle.Set();
    }
  }
}