Welcome to MSDN Blogs Sign in | Join | Help

Syndication

WCF December CTP: ExtensibilityWriter sample

Hi all. For documentation purposes one of the things I've done is write up a single sample that implements **almost** all of the ServiceModel extension interfaces in the December CTP and I'll post it here.

But first, I'll summarize the extension system this way. There are two **main** approaches to most "extensibility" in WCF applications. In truth, you can extend the whole thing pretty much from the ground up (rocket-scientists cheer in background), but there are two primary areas that most applications will extend.

  1. Certain customizations at the service model level of your application.
  2. Certain customizations at the channel level of your application.

For each of these types of extensibility, there is a two stage process.

  1. Build your extension (or identify which custom knob you want to tweak).
  2. Plug in your extension using one of a couple of approaches, depending upon whether you're inserting behaviors or bindings/binding elements:
    1. Implement a behavior or a binding/bindingelement.
    2. Insert your behavior or binding/bindingelement in the appropriate place programmatically.
    3. Insert your behavior using a custom attribute.
    4. Insert your behavior or binding/bindingelement using a configuration file.

The sample below demonstrates service model extensibility by implementing most custom extensions in a trivial way. It then attempts insert these custom extensions by adding every single one possible using custom behavior implementations. Not all customizations can be added by every custom behavior, and you can expect to see some streamlining of custom behaviors in the future. Nonetheless, this code uses all custom behaviors available in the December CTP to illustrate not only when things get inserted but also when they get invoked. Following the ExtensibilityWriter, I'll post service and client host code that adds these items.

In a future post, I'll add support for use from the configuration file, so you can see when you run the program when exactly config gets loaded, programmatic behaviors get loaded, and which one gets invoked. After that <phew> I'll add these using custom attributes, to bring it all together in one, completely bloated, whacked out sample. But I figure if it helps me understand the system, it might help someone else....

Here goes:

The ExtensibilityWriter:

using System;
using System.Collections.Generic;
using System.ServiceModel;
using System.Text;

namespace Microsoft.WCF.Documentation
{
  public class ExtensibilityWriter :

    /*
     *
     * Extension interfaces
     *
     */

    // Custom client extension.
    // Processes messages flowing through a proxy.
    IProxyMessageInspector,
    // Custom client or service extension.
    // Processes messages and objects scoped to a single operation.
    IParameterInspector,
    // Custom service-side extension.
    // Controls channel creation.
    IChannelInitializer,
    // Custom service-side extension.
    // Participates in closing input sessions.
    IInputSessionShutdown,
    // Custom service-side extension.
    // Participates in InstanceContext initialization.
    IInstanceContextInitializer,
    // Custom service-side extension.
    // Participates in providing service objects to the InstanceContext.
    IInstanceProvider,
    // Custom service-side extension.
    // Helps control the lifetime of shared sessions.
    ISharedSessionLifetime,
    // Custom service-side extension.
    // Processes messages flowing through a service.
    IStubMessageInspector, 
    // Custom service-side extension.
    // Controls the processing of errors on the service side.
    IErrorHandler,

    /*
     *      Insertion mechanisms
     *
     */
    // Inserts custom service-side extensions.
    // Adds extensions during the construction of the run time.
    IServiceBehavior,
    // Inserts custom service-side extensions.
    // Adds customization components for all
    // messages flowing through a service endpoint.
    IEndpointBehavior,
    // Inserts custom client-side extensions.
    // Adds customization components for all
    // messages flowing through a proxy.
    IChannelBehavior,
    // Inserts custom extensions on client or service.
    // Adds customization components for a particular contract.
    IContractBehavior,
    // Inserts custom extensions on either client or service.
    // Adds customization components for a specific operation.
    IOperationBehavior      
  {
    private string creationString;
    private bool onClient;

    public ExtensibilityWriter(string creationString, bool onClient)
    {
      this.creationString = creationString;
      this.onClient = onClient;
      Console.ForegroundColor = ConsoleColor.Red;
      Console.WriteLine("ExtensibilityWriter {0} created.", this.GetHashCode().ToString());
      Console.WriteLine("Comment: {0}", creationString);
      Console.ResetColor();
    }
   
    #region IOperationBehavior Members

    public void ApplyBehavior(OperationDescription description, ProxyOperation proxy, BindingParameterCollection parameters)
    {
      Console.ForegroundColor = ConsoleColor.Blue;
      Console.WriteLine("IOperationBehavior.ApplyBehavior:");
      Console.WriteLine("\t{0}", this.creationString);
      Console.WriteLine("indexof: " + proxy.ParameterInspectors.IndexOf(this));
      if (proxy.ParameterInspectors.Contains(this))
      {
        Console.ForegroundColor = ConsoleColor.Blue;
        Console.WriteLine("ProxyOperation already has this parameter inspector.");
        Console.ResetColor();
      }
      else
        proxy.ParameterInspectors.Add(this);
    }

    public void ApplyBehavior(OperationDescription description, DispatchOperation dispatch, BindingParameterCollection parameters)
    {
      Console.ForegroundColor = ConsoleColor.Blue;
      Console.WriteLine("IOperationBehavior.ApplyBehavior:");
      Console.WriteLine("\t{0}", this.creationString);
      if (dispatch.ParameterInspectors.Contains(this))
        Console.WriteLine("ProxyOperation for {0} already has this parameter inspector.");
      else
        dispatch.ParameterInspectors.Add(this);
    }

    #endregion

    #region IContractBehavior Members

    public void BindDispatch(ContractDescription description, IEnumerable<ServiceEndpoint> endpoints, DispatchBehavior dispatch, BindingParameterCollection parameters)
    {
      Console.ForegroundColor = ConsoleColor.Yellow;
      Console.WriteLine("IContractBehavior.BindDispatch:");
      Console.WriteLine("\t{0}", this.creationString);
     
      if (dispatch.ChannelInitializers.Contains(this))
        Console.WriteLine("DispatchBehavior already has this channel initializer.");
      else
        dispatch.ChannelInitializers.Add(this);
      if (dispatch.ErrorHandlers.Contains(this))
        Console.WriteLine("DispatchBehavior already has this error handler.");
      else
      dispatch.ErrorHandlers.Add(this);
      if (dispatch.InstanceContextInitializers.Contains(this))
        Console.WriteLine("DispatchBehavior already has this instance context initializer.");
      else
        dispatch.InstanceContextInitializers.Add(this);
      if (dispatch.MessageInspectors.Contains(this))
        Console.WriteLine("DispatchBehavior already has this message inspector.");
      else
        dispatch.MessageInspectors.Add(this);

      // Add a DispatchOperation customization.
      foreach (DispatchOperation dispOp in dispatch.Operations)
      {
        if (dispOp.ParameterInspectors.Contains(this))
          Console.WriteLine("ProxyOperation for {0} already has this parameter inspector.");
        else
          dispOp.ParameterInspectors.Add(this);
      }

      // Add an endpoint behavior here or in code.
      foreach (ServiceEndpoint point in endpoints)
      {
        if (point.Behaviors.Contains(this.GetType()))
        {
          Console.WriteLine("Endpoint {0} already has an endpoint behavior of type ExtensibilityWriter.");
        }
        else
        {
            point.Behaviors.Add(this);
        }
      }
      Console.ResetColor();
    }
 
    public void BindProxy(ContractDescription description, ServiceEndpoint endpoint, ProxyBehavior proxy, BindingParameterCollection parameters)
    {
      Console.ForegroundColor = ConsoleColor.Yellow;
      Console.WriteLine("IContractBehavior.BindProxy:");
      Console.WriteLine("\t{0}", this.creationString);

      if (proxy.ChannelInitializers.Contains(this))
        Console.WriteLine(proxy.ContractName + " already has this channel initializer.");
      else
        proxy.ChannelInitializers.Add(this);
      if (proxy.MessageInspectors.Contains(this))
        Console.WriteLine(proxy.ContractName + " already has this message inspector.");
      else
        proxy.MessageInspectors.Add(this);
      foreach(ProxyOperation proxyOp in proxy.Operations)
      {
        if (proxyOp.ParameterInspectors.Contains(this))
          Console.WriteLine(proxyOp.Name + " already has this parameter inspector.");
        else
          proxyOp.ParameterInspectors.Add(this);
      }

      Console.ResetColor();

    }

    #endregion

    #region IChannelBehavior Members

    public void ApplyBehavior(ChannelDescription description, ProxyBehavior behavior, BindingParameterCollection parameters)
    {
      Console.ForegroundColor = ConsoleColor.Blue;
      Console.WriteLine("IChannelBehavior.ApplyBehavior:");
      Console.WriteLine("\t{0}", this.creationString);
      Console.ResetColor();
      behavior.ChannelInitializers.Add(this);
      behavior.MessageInspectors.Add(this);
      foreach (ProxyOperation proxyOp in behavior.Operations)
      {
        if (proxyOp.ParameterInspectors.Contains(this))
        {
          Console.ForegroundColor = ConsoleColor.Blue;
          Console.WriteLine("ProxyOperation already has a channel initializer.");
          Console.ResetColor();
        }
        else
          proxyOp.ParameterInspectors.Add(this);
      }
    }

    #endregion

    #region IEndpointBehavior Members

    public void BindServiceEndpoint(ServiceEndpoint serviceEndpoint, EndpointListener endpointListener, BindingParameterCollection parameters)
    {
      Console.ForegroundColor = ConsoleColor.DarkGray;
      Console.WriteLine("IEndpointBehavior.BindServiceEndpoint");
      foreach (DispatchOperation dispOp in ((Dispatcher)endpointListener.Dispatcher).Behavior.Operations)
      {
        if (dispOp.ParameterInspectors.IndexOf((IParameterInspector)this) < 0)
          Console.WriteLine("Parameter inspector already installed.");
        else
          dispOp.ParameterInspectors.Add(this);
      }
       Console.ResetColor();

    }

    #endregion

    #region IServiceBehavior Members

    public void ApplyBehavior(ServiceDescription description, ServiceHostBase serviceHostBase, System.Collections.ObjectModel.Collection<DispatchBehavior> behaviors, System.Collections.ObjectModel.Collection<BindingParameterCollection> parameters)
    {

      Console.ForegroundColor = ConsoleColor.DarkYellow;
      Console.WriteLine("IServiceBehavior.ApplyBehavior");
     
      foreach (DispatchBehavior dispatch in behaviors)
      {
        dispatch.ChannelInitializers.Add(this);
        dispatch.ErrorHandlers.Add(this);
        dispatch.InstanceContextInitializers.Add(this);
        dispatch.InstanceProvider = this;
        dispatch.MessageInspectors.Add(this);

        // Add a DispatchOperation customization.
        foreach (DispatchOperation dispOp in dispatch.Operations)
        {
          dispOp.ParameterInspectors.Add(this);
        }

        // Add an endpoint behavior here or in code.
        foreach (ServiceEndpoint point in description.Endpoints)
        {
          if (point != null)
          {
            if (point.Behaviors.Contains(this.GetType()))
              Console.WriteLine("Endpoint at {0} already has this endpoint behavior.", point.Address.Uri);
            else
              point.Behaviors.Add(this);
            if (point.Contract.Behaviors.Contains(this.GetType()))
              Console.WriteLine("Endpoint contract {0} already has this endpoint behavior.", point.Contract.Name);
            else
              point.Contract.Behaviors.Add(this);
          }
        }
      }
      Console.ResetColor();
    }

    #endregion

    #region IErrorHandler Members

    public bool HandleError(Exception error, MessageFault fault)
    {
      Console.ForegroundColor = ConsoleColor.DarkYellow;
      Console.WriteLine("IErrorHandler.HandleError");
      Console.WriteLine("\t{0}", this.creationString);
      Console.ResetColor();
      return false;
    }

    public void ProvideFault(Exception error, ref MessageFault fault, ref string faultAction)
    {
      Console.ForegroundColor = ConsoleColor.DarkYellow;
      Console.WriteLine("IErrorHandler.ProvideFault");
      Console.WriteLine("\t{0}", this.creationString);
      Console.ResetColor();
    }

    #endregion

    #region IStubMessageInspector Members

    public object AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext)
    {
      Console.ForegroundColor = ConsoleColor.Red;
      Console.WriteLine("IStubMessageInspector.AfterReceiveRequest");
      Console.WriteLine("\t{0}", this.creationString);
      Console.ResetColor();
      return null;
    }

    public void BeforeSendReply(ref Message reply, object correlationState)
    {
      Console.ForegroundColor = ConsoleColor.Red;
      Console.WriteLine("IStubMessageInspector.BeforeSendReply");
      Console.WriteLine("\t{0}", this.creationString);
      Console.ResetColor();
    }

    #endregion

    #region ISharedSessionLifetime Members

    public bool IsIdle
    {
      get{
        Console.ForegroundColor = ConsoleColor.DarkRed;
        Console.WriteLine("ISharedSessionLifetime.IsIdle");
        Console.WriteLine("\t{0}", this.creationString);
        Console.ResetColor();
        return true;
      }
    }

    public void NotifyIdle(InstanceContextIdleCallback callback, InstanceContext instanceContext)
    {
      Console.ForegroundColor = ConsoleColor.DarkRed;
      Console.WriteLine("ISharedSessionLifetime.NotifyIdle");
      Console.WriteLine("\t{0}", this.creationString);
      Console.ResetColor();
    }

    #endregion

    #region IInstanceProvider Members
    public object GetInstance(InstanceContext instanceContext, Message message)
    {
      Console.ForegroundColor = ConsoleColor.Cyan;
      Console.WriteLine("IInstanceProvider.GetInstance");
      Console.WriteLine("\t{0}", this.creationString);
      Console.ResetColor();
      return new SampleService();
    }

    public void ReleaseInstance(InstanceContext instanceContext, object instance)
    {
      Console.ForegroundColor = ConsoleColor.Blue;
      Console.WriteLine("IInstanceProvider.ReleaseInstance");
      Console.WriteLine("\t{0}", this.creationString);
      Console.ResetColor();
    }
    #endregion

    #region IInstanceContextInitializer Members

    public void Initialize(InstanceContext instanceContext, Message message)
    {
      Console.ForegroundColor = ConsoleColor.DarkCyan;
      Console.WriteLine("IInstanceContextInitializer.Initialize.");
      Console.WriteLine("\t{0}", this.creationString);
      Console.ResetColor();
    }

    #endregion

    #region IInputSessionShutdown Members

    public void DoneReceiving(IDuplexClientChannel channel)
    {
      Console.ForegroundColor = ConsoleColor.DarkGreen;
      Console.WriteLine("IInputSessionShutdown.DoneReceiving.");
      Console.ResetColor();
    }

    #endregion

    #region IChannelInitializer Members

    public void Initialize(IClientChannel channel)
    {
      Console.ForegroundColor = ConsoleColor.Magenta;
      Console.WriteLine("IChannelInitializer.Initialize: {0}", channel.ToString());
      Console.WriteLine("\t{0}", this.creationString);
      Console.ResetColor();
    }

    #endregion

    #region IParameterInspector Members

    public void AfterCall(string operationName, object[] outputs, object returnValue, object correlationState)
    {
      Console.ForegroundColor = ConsoleColor.Magenta;
      Console.WriteLine("IParameterInspector.AfterCall:");
      Console.WriteLine("\t{0}", this.creationString);
      Console.ResetColor();
    }

    public object BeforeCall(string operationName, object[] inputs)
    {
      Console.ForegroundColor = ConsoleColor.Magenta;
      Console.WriteLine("IParameterInspector.BeforeCall:");
      Console.WriteLine("\t{0}", this.creationString);
      Console.ResetColor();
      return null;
    }

    #endregion

    #region IProxyMessageInspector Members

    public void AfterReceiveReply(ref Message reply, object correlationState)
    {
      Console.ForegroundColor = ConsoleColor.Cyan;
      Console.WriteLine("IProxyMessageInspector.AfterReceiveReply.");
      Console.WriteLine("\t{0}", this.creationString);
      Console.ResetColor();
    }

    public object BeforeSendRequest(ref Message request, IClientChannel channel)
    {
      Console.ForegroundColor = ConsoleColor.Cyan;
      Console.WriteLine("IProxyMessageInspector.BeforeSendRequest.");
      Console.WriteLine("\t{0}", this.creationString);
      Console.ResetColor();
      return null;
    }

    #endregion
  }
}

Boy, that was fun. Now the HostApplication that uses it:

using System;
using System.Configuration;
using System.Collections.Generic;
using System.ServiceModel;
using System.Text;

namespace Microsoft.WCF.Documentation
{
  class HostApplication
  {

    static void Main()
    {
      HostApplication app = new HostApplication();
      app.Run();
    }

    private void Run()
    {
      // Get base address from app settings in configuration if you want.
      Uri baseAddress = new Uri(ConfigurationManager.AppSettings["baseAddress"]);

      // Create a ServiceHost for the service type and provide the base address.
      using (ServiceHost serviceHost = new ServiceHost(typeof(SampleService), baseAddress))
      {
          try
          {
            serviceHost.Description.Behaviors.Add(new ExtensibilityWriter("Added to host.desc.Behaviors.Add", false));
            foreach (ServiceEndpoint point in serviceHost.Description.Endpoints)
            {
              point.Behaviors.Add(new ExtensibilityWriter(
                String.Format("Added to host.desc.Endpoints[\"{0}\"].Behaviors.Add", point.Address.Uri),
                false)
              );
              point.Contract.Behaviors.Add(new ExtensibilityWriter(
                String.Format("Added to host.desc.Endpoints[\"{0}\"].Contract.Behaviors.Add", point.Contract.Name),
                false)
              );
            }
              // Open the ServiceHostBase to create listeners and start listening for messages.
              serviceHost.Open();

              // The service can now be accessed.
              Console.ForegroundColor = ConsoleColor.White;
              Console.WriteLine("The service is ready.");
              Console.WriteLine("Press <ENTER> to terminate service.");
              Console.WriteLine();
              Console.ReadLine();

              // Close the ServiceHostBase to shutdown the service.
              serviceHost.Close();
              Console.ResetColor();
          }
          catch (TimeoutException timeProblem)
          {
              Console.WriteLine("The service operation timed out. " + timeProblem.Message);
          }
          catch (CommunicationException commProblem)
          {
              Console.WriteLine("There was a communication problem. " + commProblem.Message);
          }
      }
    }
  }
}

and finally the client that does.

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

namespace Microsoft.WCF.Documentation
{
  public class Client
  {
    public static void Main()
    {
      // Picks up configuration from the config file.
      using (SampleServiceProxy proxy = new SampleServiceProxy())
      {
        try
        {
          // Note: I can add the custom IEndpointBehavior here, but because it is only
          // invoked on the service-side, it is never invoked.
          if (!proxy.Endpoint.Behaviors.Contains(typeof(ExtensibilityWriter)))
            proxy.Endpoint.Behaviors.Add(new ExtensibilityWriter("Client added to proxy.Endpoint.Behaviors.Add", true));

          // Add a IContractBehavior that can modify all messages traveling through a proxy.
          if (!proxy.Endpoint.Contract.Behaviors.Contains(typeof(ExtensibilityWriter)))
            proxy.Endpoint.Contract.Behaviors.Add(new ExtensibilityWriter("Client added to proxy.Endpoint.Contract.Behaviors.Add", true));
          foreach (OperationDescription opDesc in proxy.Endpoint.Contract.Operations)
          {
            if (!opDesc.Behaviors.Contains(typeof(ExtensibilityWriter)))
              opDesc.Behaviors.Add(new ExtensibilityWriter(String.Format("Client added to proxy.Endpoint.Contract.Operations[\"{0}\"].", opDesc.Name), true));
          }
          // Making calls.
          Console.ForegroundColor = ConsoleColor.White;
          Console.WriteLine("Enter the greeting to send: ");
          string greeting = Console.ReadLine();
          Console.WriteLine("The service responded: " + proxy.SampleMethod(greeting));

          Console.WriteLine("Press ENTER to exit:");
          Console.ReadLine();

          // Done with service.
          proxy.Close();
          Console.WriteLine("Done!");
        }
        catch (TimeoutException timeProblem)
        {
          Console.WriteLine("The service operation timed out. " + timeProblem.Message);
        }
        catch (CommunicationException commProblem)
        {
          Console.WriteLine("There was a communication problem. " + commProblem.Message);
        }
      }
      Console.ResetColor();
    }
  }
}

Cheers, Ralph

Published Thursday, January 12, 2006 9:33 AM by ralph.squillace

Comments

# WCF February CTP: Tids and Bits. Post 3 -- Dispatchers, abound, Behaviors are Runtimes. @ Monday, February 27, 2006 1:42 AM

I'll prepost this breaking change in the Feb CTP and write more about it later. In a previous post&amp;nbsp;I...

Ralph Squillace -- Docs, Samples, Docs, Samples....

# re: WCF December CTP: ExtensibilityWriter sample @ Friday, March 17, 2006 6:48 AM

Can you update this great code for Feb CTP ?

Aelxnaldo Santos

# WCF Extensibility Cornucopia - show it all in one @ Thursday, April 27, 2006 8:34 AM

WCF is extensible, very extensible. If you need a feature which is not in V1 (and obviously there is...

Christian Weyer: Smells like service spirit

# .NET Resources @ Saturday, May 06, 2006 4:32 AM

The following links to .NET resources have been collated over time with the assistance of colleagues.&amp;nbsp;...

mattonsoftware.com

# Ralph Squillace Docs Samples Docs Samples WCF December CTP | fire pit @ Friday, June 19, 2009 2:00 AM

PingBack from http://firepitidea.info/story.php?id=1014

Ralph Squillace Docs Samples Docs Samples WCF December CTP | fire pit

Anonymous comments are disabled
© 2009 Microsoft Corporation. All rights reserved. Terms of Use  |  Trademarks  |  Privacy Statement  
Page view tracker