Service Station

Extending WCF with Custom Behaviors

Aaron Skonnard

Code download available at:ServiceStation2007_12.exe(165 KB)

Contents

WCF Extensibility
Dispatcher/Proxy Extensions
Implementing Custom Extensions
Parameter Inspectors
Message Inspectors
Operation Invokers
Applying Custom Extensions with Behaviors
Adding Behaviors to the Runtime
Adding Behaviors with Attributes and Configuration
Behavior Validation and Binding Configuration
Sharing State between Extensions
Take-Aways

Windows® Communication Foundation (WCF) provides numerous extensibility points that allow developers to customize the runtime behavior for service dispatching and client proxy invocation. You can tap into these extensibility points by writing custom behaviors that can be applied declaratively to your services. This month I'll show you how this process works.

WCF Extensibility

In my last column I focused on the WCF concept of a binding, which you can specify for each endpoint on your WCF service. The binding controls the messaging details (what happens on the wire) for that endpoint. It's essentially the recipe WCF follows to build a channel stack capable of transforming between a stream of bytes (the message on the wire) and a WCF message. There are countless extensibility points found throughout the WCF channel layer.

WCF also provides a higher-level runtime that sits on top of the channel layer, targeted more at the application developer. It is often referred to throughout the WCF documentation as the service model layer. This higher-level runtime consists primarily of a component called the dispatcher (within the context of a service host) and a component called the proxy (within the context of a client).

The dispatcher/proxy combination serves one primary purpose—to translate between WCF Message objects and Microsoft® .NET Framework method calls (see Figure 1). These components follow a well-defined sequence of steps to perform this process, and at each step along the way they provide extensibility points that you can plug into. You can use these extensibility points to implement a wide variety of custom behaviors including message or parameter validation, message logging, message transformations, custom serialization/deserialization formats, output caching, object pooling, error handling, and authorization, to name a few. Here I'll focus on the implementation of these types of custom behaviors.

Figure 1 WCF Runtime Architecture

Figure 1** WCF Runtime Architecture **

Dispatcher/Proxy Extensions

Both the dispatcher and proxy provide numerous extensibility points where you can inject your own code; these extensions are often referred to as interceptors because they allow you to intercept the default execution behavior of the runtime. However, I usually refer to them as runtime extensions.

Figure 2 shows the client-side proxy architecture and the extensibility points it makes available. The main responsibility of the proxy is to transform the caller-supplied objects (parameters) into a WCF Message object, which can then be supplied to the underlying channel stack for transmission on the wire.

Figure 2 Proxy (Client) Extensions

Figure 2** Proxy (Client) Extensions **

The first extensibility point available to you during this process allows you to perform custom parameter inspection, as you see in the first step. You could use this hook to perform custom validation, value modification, or special filtering. Next, the proxy leverages a serializer to transform the supplied parameters into a WCF Message object (Step 2 in the diagram). You can customize the serialization process here by using a custom formatter object.

Once the proxy has produced a Message object, it makes use of a final extensibility point for inspecting the resulting Message object (shown in Step 3) before it's submitted to the channel stack. As illustrated in Figure 2, this extension takes effect regardless of which operation was invoked. You might use this hook to implement cross-cutting messaging features such as message logging, validation, or transformations—functionality that wouldn't necessarily be specific to a single operation.

You configure these extensions on a proxy through the ClientOperation and ClientRuntime objects. You'll find one ClientOperation object for each service operation and a single ClientRuntime object for configuring the proxy as a whole. ClientOperation provides properties for managing the parameter inspection and message formatting extensions, while ClientRuntime provides a property for managing message inspection extensions.

Figure 3 shows the dispatcher extensibility points. You'll notice it looks very similar to Figure 2, but in this case the extensibility points are executed in reverse order and there are a few more points that didn't exist on the client side in the previous figure.

Figure 3 Dispatcher Extensions

Figure 3** Dispatcher Extensions **

When the dispatcher receives a Message object from the channel stack, the first extensibility point encountered is message inspection. Then the dispatcher must select an operation to invoke (Step 2) before it can continue—there's an extensibility point here for overriding the default operation selection behavior. Once the target operation has been identified, the dispatcher deserializes the message into objects that can be supplied as parameters when invoking the target method. At this point (Step 3), the dispatcher provides extensibility points for message formatting (deserialization) and parameter inspection (Step 4). The final step for the dispatcher is to invoke the target method supplying the prepared parameters. You can even override this step by providing a custom operation invoker object.

You configure these extensions on the dispatcher through the DispatchRuntime and DispatchOperation objects, as per Figure 3. I'll show you how to gain access to these objects shortly, but first let's discuss how to implement them.

Implementing Custom Extensions

Each of the extensibility points described above is modeled by a .NET interface definition (see Figure 4). Note that in some cases the same logical extension type requires a different interface between the dispatcher and proxy sides. Let's take a closer look at how to implement a few of these interfaces.

Figure 4 Dispatcher/Proxy Extension Summary

Stage Interceptor Interface Description
Parameter Inspection IParameterInspector Called before and after invocation to inspect and modify parameter values.
Message Formatting IDispatchMessageFormatter IClientFormatter Called to perform serialization and deserialization.
Message Inspection IDispatchMessageInspector IClientMessageInspector Called before send or after receive to inspect and replace message contents.
Operation Selection IDispatchOperationSelector IClientOperationSelector Called to select the operation to invoke for the given message.
Operation Invoker IOperationInvoker Called to invoke the operation.

Let's assume you're building a ZIP code lookup service with the following contract:

[ServiceContract] public interface IZipCodeService { [OperationContract] string Lookup(string zipcode); }

The Lookup method takes a single parameter, zipcode, of type string and returns a string to the caller. The caller is expected to supply a ZIP code value and the service will return the location (in city, state format). What's not obvious to the user is that the supplied ZIP code must be in the official ZIP + 4 format: #####-####. For example, my home ZIP code is 84041-1501. Given this requirement, the service implementation must validate each incoming ZIP code value.

Parameter Inspectors

It wouldn't be hard to implement the ZIP + 4 validation logic within the Lookup method itself, but if the result is a large number of operations that accept ZIP codes, it would be better to implement the validation logic as an IParameterInspector extension that can be declaratively applied to any operation.

In order to do this, you must write a class that implements IParameterInspector, which defines two methods: AfterCall and BeforeCall. As their names suggest, the runtime invokes BeforeCall before calling the target method on the service instance and AfterCall after having made the call. This gives you pre- and post-interception points for inspecting the parameters and return values, which are supplied to these methods as arrays of objects.

Figure 5 shows a complete implementation of IParameterInspector that performs the necessary ZIP + 4 validation. The ZipCodeInspector class implements IParameterInspector, but I only implemented BeforeCall since I only need input validation. BeforeCall validates the supplied ZIP code value against the ZIP + 4 regular expression ("\d{5}-\d{4}"), and if it doesn't match, it goes ahead and throws a FaultException.

Figure 5 ZIP Code Validation Parameter Inspector

public class ZipCodeInspector : IParameterInspector { int zipCodeParamIndex; string zipCodeFormat = @"\d{5}-\d{4}"; public ZipCodeInspector() : this(0) { } public ZipCodeInspector(int zipCodeParamIndex) { this.zipCodeParamIndex = zipCodeParamIndex; } ... // AfterCall is empty public object BeforeCall(string operationName, object[] inputs) { string zipCodeParam = inputs[this.zipCodeParamIndex] as string; if (!Regex.IsMatch( zipCodeParam, this.zipCodeFormat, RegexOptions.None)) throw new FaultException( "Invalid zip code format. Required format: #####-####"); return null; } }

With ZipCodeInspector in place, you can easily apply this validation logic to any operation that accepts a ZIP code value. And you can use the implementation on both sides of the wire (within the client or service). I'll show you how to wire up this parameter inspector after a few more examples.

Message Inspectors

Instead of inspecting parameters, let's assume you want to inspect the messages flowing in and out of a service regardless of the operation. This is where you'd use the message inspection extensibility point. Unlike parameter inspection, the interfaces for message inspection are different for the dispatcher and proxy (IDispatchMessageInspector versus IClientMessageInspector). However, you can always implement both interfaces when you want to support both sides.

IDispatchMessageInspector has two methods: AfterReceiveRequest and BeforeSendReply, which means you get pre- and post-interception points for inspecting the WCF Message object. IClientMessageInspector also has two methods that provide the converse points: AfterReceiveReply and BeforeSendRequest.

Say you want to implement a diagnostic utility that prints all incoming and outgoing messages to the console window. Figure 6 provides a complete sample that does this. Notice ConsoleMessageTracer implements both message inspector interfaces, so it can be used on both sides of the wire. Each method simply copies the incoming message and prints it to the console window.

Figure 6 Console Tracing Message Inspector

public class ConsoleMessageTracer : IDispatchMessageInspector, IClientMessageInspector { private Message TraceMessage(MessageBuffer buffer) { Message msg = buffer.CreateMessage(); Console.WriteLine("\n{0}\n", msg); return buffer.CreateMessage(); } public object AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext) { request = TraceMessage(request.CreateBufferedCopy(int.MaxValue)); return null; } public void BeforeSendReply(ref Message reply, object correlationState) { reply = TraceMessage(reply.CreateBufferedCopy(int.MaxValue)); } public void AfterReceiveReply(ref Message reply, object correlationState) { reply = TraceMessage(reply.CreateBufferedCopy(int.MaxValue)); } public object BeforeSendRequest(ref Message request, IClientChannel channel) { request = TraceMessage(request.CreateBufferedCopy(int.MaxValue)); return null; } }

If you're curious about why I've employed the message copying technique in Figure 6, see my previous column, "WCF Messaging Fundamentals," and review the section on message lifetime (see msdn.microsoft.com/msdnmag/issues/07/04/ServiceStation).

Operation Invokers

For a final example, let's consider the operation invoker extensibility point. This hook allows you to override the default process with a custom invoker object. In the ZIP code example, you could use the operation invoker to implement a simple output caching feature. For a given ZIP code, the result is always going to be the same, so if you cache the results, you'll never have to invoke the service instance more than once for a particular ZIP code value. This could greatly improve performance and response times in certain situations where the service logic is either costly or takes a long time to complete.

Figure 7 shows a complete example. Here, the ZipCodeCacher class implements IOperationInvoker and the ZIP code caching functionality is found in the Invoke method. The Invoke method first attempts to look up the ZIP code location in its cache, and if it's not found, it invokes the method on the service instance (using the default invoker). It stores new results in the cache for future invocations. The user of this extension must supply the dispatcher's default invoker object upon construction as the remaining methods delegate to it.

Figure 7 ZIP Code Caching Operation Invoker

public class ZipCodeCacher : IOperationInvoker { IOperationInvoker innerOperationInvoker; Dictionary<string, string> zipCodeCache = new Dictionary<string, string>(); public ZipCodeCacher(IOperationInvoker innerOperationInvoker) { this.innerOperationInvoker = innerOperationInvoker; } public object Invoke(object instance, object[] inputs, out object[] outputs) { string zipcode = inputs[0] as string; string value; if (this.zipCodeCache.TryGetValue(zipcode, out value)) { outputs = new object[0]; return value; } else { value = (string)this.innerOperationInvoker.Invoke( instance, inputs, out outputs); zipCodeCache[zipcode] = value; return value; } } ... // remaining methods elided // they simply delegate to innerOperationInvoker }

I've shown you some common examples of building custom extensions. There are others I didn't have time to cover, but I'll leave those for your own personal study. A more important matter I need to discuss is how you wire up these extensions to the dispatcher/proxy. This is where behaviors enter the picture.

Applying Custom Extensions with Behaviors

A behavior is a special type of class that extends runtime behavior during the ServiceHost/ChannelFactory initialization process. There are four types of behaviors: service, endpoint, contract, and operation. Each type allows you to apply extensions at different scopes (see Figure 8). Each type of behavior is also modeled by a different interface definition, but they all share the same set of methods (see Figure 9). One exception is that IServiceBehavior doesn't have an ApplyClientBehavior method because service behaviors can't be applied to clients.

Figure 9 Behavior Interface Methods

Method Description
Validate Called just before the runtime is built—allows you to perform custom validation on the service description.
AddBindingParameters Called in the first step of building the runtime, before the underlying channel is constructed—allows you to add parameters to influence the underlying channel stack.
ApplyClientBehavior Allows behavior to inject proxy (client) extensions. Note that this method is not present on IServiceBehavior.
ApplyDispatchBehavior Allows behavior to inject dispatcher extensions.

Figure 8 Types of Behaviors

     
Scope Interface Potential Impact
    Service
Service IServiceBehavior
Endpoint IEndpointBehavior  
Contract IContractBehavior  
Operation IOperationBehavior  

As illustrated in Figure 8, service behaviors are used for applying extensions across the entire service; you can apply them to the service itself or to specific endpoints, contracts, and operations. Endpoint behaviors, on the other hand, are used to apply extensions to a particular endpoint (or perhaps to the endpoint's contract or operations). Contract and operation behaviors are used to apply extensions to contracts and operations. Endpoint, contract, and operation behaviors can be applied to both services and clients, but service behaviors can only be applied to services.

Although the methods are the same for each behavior interface, the signatures are indeed different. They are tailored to provide the appropriate runtime objects for that particular scope. ApplyDispatchBehavior and ApplyClientBehavior are the core methods that allow you to apply custom extensions to the dispatcher and proxy, respectively. When the runtime calls these methods, it provides the DispatchRuntime, DispatchOperation, ClientRuntime, and ClientOperation objects to you for injecting your extensions (refer back toFigures 2 and 3).

Figure 10 shows how to implement a few operation behaviors. It makes sense to use an operation behavior to apply the ZipCodeInspector and ZipCodeCacher extensions because you only want to use them on methods that deal with ZIP codes. As you can see, ZipCodeValidation adds an instance of ZipCodeInspector to the ParameterInspectors collection found on the supplied DispatchOperation and ClientOperation objects. ZipCodeCaching assigns the custom ZipCodeCacher to the Invoker property on the supplied DispatchOperation object.

Figure 10 Sample Operation Behaviors

public class ZipCodeValidation : Attribute, IOperationBehavior { public void ApplyClientBehavior(OperationDescription operationDescription, ClientOperation clientOperation) { ZipCodeInspector zipCodeInspector = new ZipCodeInspector(); clientOperation.ParameterInspectors.Add(zipCodeInspector); } public void ApplyDispatchBehavior( OperationDescription operationDescription, DispatchOperation dispatchOperation) { ZipCodeInspector zipCodeInspector = new ZipCodeInspector(); dispatchOperation.ParameterInspectors.Add(zipCodeInspector); } ... // remaining methods empty } public class ZipCodeCaching : Attribute, IOperationBehavior { public void ApplyDispatchBehavior( OperationDescription operationDescription, DispatchOperation dispatchOperation) { dispatchOperation.Invoker = new ZipCodeCacher(dispatchOperation.Invoker); } ... // remaining methods empty }

Now it's time to decide what type of behavior to use for the ConsoleMessageTracer message inspector. I could apply it using a contract, endpoint, or service behavior, depending on the desired usage. Figure 11 shows an example of how to implement a class that serves as both a service and endpoint behavior for applying ConsoleMessageTracer into the appropriate MessageInspectors collection.

Figure 11 Sample Endpoint and Service Behaviors

public class ConsoleMessageTracing : Attribute, IEndpointBehavior, IServiceBehavior { void IEndpointBehavior.ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime) { clientRuntime.MessageInspectors.Add(new ConsoleMessageTracer()); } void IEndpointBehavior.ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher) { endpointDispatcher.DispatchRuntime.MessageInspectors.Add( new ConsoleMessageTracer()); } ... // remaining methods empty void IServiceBehavior.ApplyDispatchBehavior( ServiceDescription desc, ServiceHostBase host) { foreach ( ChannelDispatcher cDispatcher in host.ChannelDispatchers) foreach (EndpointDispatcher eDispatcher in cDispatcher.Endpoints) eDispatcher.DispatchRuntime.MessageInspectors.Add( new ConsoleMessageTracer()); } ... // remaining methods empty }

Now that you've seen how to implement a few behaviors, you're ready to learn how to add behaviors to the WCF runtime.

Adding Behaviors to the Runtime

When you construct a ServiceHost or client-side ChannelFactory, the runtime reflects over the service types, reads the configuration file, and starts building an in-memory description of the service. Within ServiceHost, this description is made available to you via the Description property (of type ServiceDescription). Within ChannelFactory, it's made available via the Endpoint property (of type ServiceEndpoint); the client-side description is limited to the target endpoint.

The ServiceDescription contains a full description of the service and each endpoint (ServiceEndpoint), including contracts (ContractDescription) and operations (OperationDescription). ServiceDescription provides a Behaviors property (a collection of type IServiceBehavior) that models a collection of service behaviors. Each ServiceEndpoint also has a Behaviors property (a collection of type IEndpointBehavior) that models the individual endpoint behaviors. Likewise, ContractDescription and OperationDescription each have an appropriate Behaviors property.

These behavior collections are automatically populated during the ServiceHost and ChannelFactory construction process with any behaviors that are found in your code (via attributes) or within the configuration file (more on this shortly). You can also add behaviors to these collections manually after construction. The following example shows how to add the ConsoleMessageTracing to the host as a service behavior:

ServiceHost host = new ServiceHost(typeof(ZipCodeService)); host.Description.Behaviors.Add(new ConsoleMessageTracing());

This example iterates through all the ServiceEndpoint objects and adds ConsoleMessageTracing as an endpoint behavior:

ServiceHost host = new ServiceHost(typeof(ZipCodeService)); foreach (ServiceEndpoint se in host.Description.Endpoints) se.Behaviors.Add(new ConsoleMessageTracing());

It's similar on the client side, but, as I mentioned, the description is focused on a single endpoint and there are no service behaviors. The following example illustrates how to add the ConsoleMessageTracing as a client-side endpoint behavior:

ZipCodeServiceClient client = new ZipCodeServiceClient(); client.ChannelFactory.Endpoint.Behaviors.Add( new ConsoleMessageTracing());

You could use similar techniques to manually add behaviors to specific contracts (use ServiceEndpoint.Contract.Behaviors) or to individual operations on a contract (iterate over the ServiceEndpoint.Contract.Operations collection and access the Behaviors collection on OperationDescription).

Once you open the ServiceHost/ChannelFactory (via ICommunicationObject.Open), the runtime walks through the ServiceDescription and gives each behavior a chance to apply its dispatcher/proxy extensions by calling ApplyDispatchBehavior and ApplyClientBehavior (see Figure 12). Once this process is complete, you don't have the ability to add additional behaviors or extensions to the runtime.

Figure 12 Adding Behaviors to the Runtime

Figure 12** Adding Behaviors to the Runtime **(Click the image for a larger view)

Adding Behaviors with Attributes and Configuration

During the ServiceHost/ChannelFactory construction process, the runtime reflects over the service types and configuration file and automatically adds any behaviors it finds to the appropriate behavior collections in the ServiceDescription.

The runtime first looks for .NET attributes on your service code that derive from one of the behavior interfaces listed in Figure 8. Whenever the runtime finds one of these attributes, it automatically adds that attribute to the appropriate collection. For example, here I've annotated my service with three attributes that correspond to the behaviors I defined earlier:

[ServiceContract] public interface IZipCodeService { [ZipCodeCaching] [ZipCodeValidation] [OperationContract] string Lookup(string zipcode); } [ConsoleMessageTracing] public class ZipCodeService : IZipCodeService { ... }

When I defined these behavior classes, I made sure to derive them from Attribute (in addition to IServiceBehavior and IOperationBehavior) so I'd be able to configure them this way. When constructing a ServiceHost for the ZipCodeService class shown above, the runtime automatically adds one service behavior (ConsoleMessageTracing) and two operation behaviors (ZipCodeCaching and ZipCodeValidation) to the ServiceDescription.

Contract behavior attributes can be applied to service contract interfaces or to the service class. When applied to a service class, you may want to restrict the contract behavior to take affect only when an endpoint uses a particular contract. You can control this by implementing IContractBehaviorAttribute on your contract behavior attribute class and specifying the desired contract via the TargetContract property.

After the reflection process is complete, the runtime also inspects the application configuration file and loads the information found in the <system.serviceModel> section into the ServiceDescription. WCF provides a <behaviors> section for configuring service and endpoint behaviors. Any service/endpoint behaviors found in this section are automatically added to the ServiceDescription.

In order to place custom behaviors within this configuration section, you must first write a class that derives from BehaviorElementExtension, like this one:

public class ConsoleMessageTracingElement : BehaviorExtensionElement { public override Type BehaviorType { get { return typeof(ConsoleMessageTracing); } } protected override object CreateBehavior() { return new ConsoleMessageTracing(); } }

Then you must register your BehaviorExtensionElement in the <extensions> section and map it to an element name. With that in place, you can use your registered element name within the <behaviors> section in order to configure the behavior. Figure 13 provides a complete example showing how to configure the ConsoleMessageTracing behavior.

Figure 13 Configuring Behaviors

<configuration> <system.serviceModel> <services> <service name="ZipCodeServiceLibrary.ZipCodeService" behaviorConfiguration="Default"> <endpoint binding="basicHttpBinding" contract="ZipCodeServiceLibrary.IZipCodeService"/> </service> </services> <behaviors> <serviceBehaviors> <behavior name="Default"> <serviceMetadata httpGetEnabled="true"/> <consoleMessageTracing/> </behavior> </serviceBehaviors> </behaviors> <extensions> <behaviorExtensions> <add name="consoleMessageTracing" type="Extensions. ConsoleMessageTracingElement, Extensions, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"/> </behaviorExtensions> </extensions> </system.serviceModel> </configuration>

You can add service, contract, or operation behaviors using attributes, but you can't use them to add endpoint behaviors. You can add service and endpoint behaviors via the configuration file, but you can't use it to add contract or operation behaviors. Finally, you can manually add any type of behavior to the ServiceDescription. Figure 14 summarizes these differences.

Figure 14 Behavior Configuration Options

   
Behavior Type Configuration Options
  Attribute
Service
Endpoint  
Contract
Operation

Also, it's important to note that you can leverage contract and operation behaviors on the client side by applying attributes to your proxy types, but endpoint behaviors are the only type you can apply to clients via configuration.

Behavior Validation and Binding Configuration

In addition to adding custom runtime extensions, behaviors are also designed to let you perform two additional tasks: custom validation and binding configuration. Notice the Validate and AddBindingParameters in Figure 9.

The Validate method gives you a chance to perform custom validation on the ServiceDescription after it has been initialized but before the rest of the runtime has been built. This is your chance to traverse the ServiceDescription tree (or ServiceEndpoint on the client side) and validate it against your own criteria. If something doesn't meet your requirements, you can throw an exception to prevent the ServiceHost/ChannelFactory from opening.

The following service behavior validates the ServiceDescription to ensure that no endpoints use BasicHttpBinding:

public class NoBasicEndpointValidator : Attribute, IServiceBehavior { #region IServiceBehavior Members public void Validate(ServiceDescription desc, ServiceHostBase host) { foreach (ServiceEndpoint se in desc.Endpoints) if (se.Binding.Name.Equals("BasicHttpBinding")) throw new FaultException( "BasicHttpBinding is not allowed"); } ... //remaining methods empty }

Once you've applied this behavior to a service, the runtime will no longer allow you to use BasicHttpBinding when configuring endpoints, and it will force you to choose a secure binding instead.

AddBindingParameters gives you a chance to add additional binding parameters during runtime initialization. The binding parameters are supplied to the underlying channel layer in order to influence the creation of the channel stacks. Custom binding elements have access to these binding parameters and can be designed to look for them (see my column, "WCF Bindings In-Depth," in the July 2007 issue at msdn.microsoft.com/msdnmag/issues/07/07/ServiceStation for more information on custom bindings). This is a more advanced extensibility point that you're not as likely to use as the others I've discussed.

Sharing State between Extensions

Once you begin employing multiple extensions within the dispatcher/proxy, you will need to learn how to share state across them. Thankfully, WCF provides extension objects that can be used to store user-defined state.

Where you store an extension object determines how long it will stick around. You can store it globally on the ServiceHost, on an InstanceContext, or on an OperationContext. Each of these classes provides an Extensions collection that manages objects derived from IExtension<T> (where T is ServiceHostBase, InstanceContext, or OperationContext, depending on the collection).

ServiceHost extension objects remain in memory for the lifetime of the ServiceHost while InstanceContext and OperationContext extension objects only remain in memory for the lifetime of the service instance or operation invocation. Your custom dispatcher/proxy extensions can use these collections to store (and look up) user-defined state throughout the pipeline.

Take-Aways

WCF provides a powerful extensibility architecture that allows for significant runtime customizations. It provides some key extensibility stages throughout the dispatcher/proxy for performing tasks such as parameter inspection, message formatting, message inspection, operation selection, and invocation. You write these custom extensions by implementing the appropriate extension interface, and then you can apply your extension to the dispatcher/proxy via a custom behavior.

There are some more advanced extensibility points made available on the dispatcher that I didn't have space to cover here. They deal with matters such as instancing, concurrency, addressing, and, of course, security. Although the built-in [ServiceBehavior] and [OperationBehavior] behaviors do supply most of what you'll need in these areas, you can write custom behaviors to extend those aspects of the runtime when they don't provide everything you need.

Be sure to download the sample code associated with this column so that you're able to study the examples in more detail and see them in action.

Send your questions and comments for Aaron to sstation@microsoft.com.

Aaron Skonnard is a cofounder of Pluralsight, a Microsoft .NET training provider. Aaron is the author of Pluralsight's Applied Web Services 2.0, Applied BizTalk Server 2006, and Introducing Windows Communication Foundation courses. Aaron has spent years developing courses, speaking at conferences, and teaching professional developers.