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