Udostępnij za pośrednictwem


WCF Extensibility – Behaviors

This post is part of a series about WCF extensibility points. For a list of all previous posts and planned future ones, go to the index page.

The first part of this series will focus on the behaviors. There are four kinds of behaviors, depending on the scope to which they apply: service, endpoint, contract and operation behaviors. The behavior interfaces are the main entry points for almost all the other extensibility points in WCF – it’s via the Apply[Client/Dispatch]Behavior method in the behavior interfaces where a user can get a reference to most of them.

Description vs. Runtime

The WCF behaviors are part of a service (or endpoint / contract / operation) description – as opposed to the service runtime. The description of a service are all the objects that, well, describe what the service will be when it starts running – which happens when the host for the service is opened (e.g., in a self-hosted service, when the program calls ServiceHost.Open). When the service host instance is created, and endpoints are being added, no listeners (TCP sockets, HTTP listeners) have been started, and the program can modify the service description to define how it will behave once it’s running. That’s because, during the service setup, there are cases where the service is in an invalid state (for example, right after the ServiceHost instance is created, no endpoints are defined), so it doesn’t make sense for the host to be started at that point.

When Open is called on the host (or, in the case of an IIS/WAS-hosted service, when the first message arrives to activate the service), it initializes all the appropriate listeners, dispatchers, filters, channels, hooks, etc. that will cause an incoming message to be directed to the appropriate operation. Most of the extensibility points in WCF are actually part of the service runtime, and since the runtime is not initialized until the host is opened, the user needs a callback to notify it that the runtime is ready, and the hooks can be used.

Behaviors

The WCF behaviors are defined by four interfaces (on the System.ServiceModel.Description namespace): IServiceBehavior, IEndpointBehavior, IContractBehavior and IOperationBehavior. They all share the same pattern:

public interface I[Service/Endpoint/Contract/Operation]Behavior {
                void Validate(DescriptionObject);
                void AddBindingParameters(DescriptionObject, BindingParameterCollection);
                void ApplyDispatchBehavior(DescriptionObject, RuntimeObject);
                void ApplyClientBehavior(DescriptionObject, RuntimeObject); // not on IServiceBehavior
}

The order in which the methods of the interfaces are called is the following:

  • Validate: This gives the behavior an opportunity to prevent the host (or the client) from opening (by throwing an exception) if the validation logic finds something in the service / endpoint / contract / operation description which it deems invalid.
  • AddBindingParameters: This gives the behavior an opportunity to add parameters to the BindingParameterCollection, which is used by the binding elements when they’re creating the listeners / factories at runtime. Useful to add correlation objects between behaviors and bindings. For service behaviors, this is actually called once per endpoint, since the BindingParameterCollection is used when creating the listeners for each endpoint.
  • Apply[Client/Dispatch]Behavior: This is where we can get reference to the runtime objects, and modify them. This is the most used of the behavior methods (in most cases the other two methods are left blank). On the posts about each specific behavior I’ll have examples of them being used in real scenarios.

Among each method, first the service behavior is called, then the contract, then the endpoint, and finally the operation behaviors within that contract.

Adding behaviors to WCF

The behaviors can be added in three different ways:

  • Code: The description of the service / endpoint / contract / operation objects have a property with a collection of behaviors associated with that object; by using this reference you can simply add one of the behaviors.
  • Configuration: Available for service and endpoint behaviors, it’s possible to add them via the system.serviceModel/behaviors section. It’s possible to specify service behaviors or endpoint behaviors  this way.
  • Attributes: An easy way to add behaviors is by using attributes – except endpoint behaviors. By creating an attribute class (base type == System.Attribute) and implementing one of the behavior interfaces, you can simply apply the attribute to the appropriate element, and the behavior is added to the element’s description. The code below shows a simple service, with attribute-based behaviors added which print the order in which they’re called (plus an endpoint behavior added via code).

 

  1. using System;
  2. using System.Collections.ObjectModel;
  3. using System.Reflection;
  4. using System.ServiceModel;
  5. using System.ServiceModel.Channels;
  6. using System.ServiceModel.Description;
  7. using System.ServiceModel.Dispatcher;
  8.  
  9. namespace Behaviors
  10. {
  11.     class MyServiceBehaviorAttribute : Attribute, IServiceBehavior
  12.     {
  13.         public void AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, Collection<ServiceEndpoint> endpoints, BindingParameterCollection bindingParameters)
  14.         {
  15.             Console.WriteLine("Inside {0}.{1}", this.GetType().Name, MethodBase.GetCurrentMethod().Name);
  16.         }
  17.  
  18.         public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
  19.         {
  20.             Console.WriteLine("Inside {0}.{1}", this.GetType().Name, MethodBase.GetCurrentMethod().Name);
  21.         }
  22.  
  23.         public void Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
  24.         {
  25.             Console.WriteLine("Inside {0}.{1}", this.GetType().Name, MethodBase.GetCurrentMethod().Name);
  26.         }
  27.     }
  28.  
  29.     class MyEndpointBehavior : IEndpointBehavior
  30.     {
  31.         public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
  32.         {
  33.             Console.WriteLine("Inside {0}.{1}, endpoint {2}", this.GetType().Name, MethodBase.GetCurrentMethod().Name, endpoint.Name);
  34.         }
  35.  
  36.         public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
  37.         {
  38.             Console.WriteLine("Inside {0}.{1}, endpoint {2}", this.GetType().Name, MethodBase.GetCurrentMethod().Name, endpoint.Name);
  39.         }
  40.  
  41.         public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
  42.         {
  43.             Console.WriteLine("Inside {0}.{1}, endpoint {2}", this.GetType().Name, MethodBase.GetCurrentMethod().Name, endpoint.Name);
  44.         }
  45.  
  46.         public void Validate(ServiceEndpoint endpoint)
  47.         {
  48.             Console.WriteLine("Inside {0}.{1}, endpoint {2}", this.GetType().Name, MethodBase.GetCurrentMethod().Name, endpoint.Name);
  49.         }
  50.     }
  51.  
  52.     class MyContractBehaviorAttribute : Attribute, IContractBehavior
  53.     {
  54.         public void AddBindingParameters(ContractDescription contractDescription, ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
  55.         {
  56.             Console.WriteLine("Inside {0}.{1}, contract {2}", this.GetType().Name, MethodBase.GetCurrentMethod().Name, contractDescription.ContractType.Name);
  57.         }
  58.  
  59.         public void ApplyClientBehavior(ContractDescription contractDescription, ServiceEndpoint endpoint, ClientRuntime clientRuntime)
  60.         {
  61.             Console.WriteLine("Inside {0}.{1}, contract {2}", this.GetType().Name, MethodBase.GetCurrentMethod().Name, contractDescription.ContractType.Name);
  62.         }
  63.  
  64.         public void ApplyDispatchBehavior(ContractDescription contractDescription, ServiceEndpoint endpoint, DispatchRuntime dispatchRuntime)
  65.         {
  66.             Console.WriteLine("Inside {0}.{1}, contract {2}", this.GetType().Name, MethodBase.GetCurrentMethod().Name, contractDescription.ContractType.Name);
  67.         }
  68.  
  69.         public void Validate(ContractDescription contractDescription, ServiceEndpoint endpoint)
  70.         {
  71.             Console.WriteLine("Inside {0}.{1}, contract {2}", this.GetType().Name, MethodBase.GetCurrentMethod().Name, contractDescription.ContractType.Name);
  72.         }
  73.     }
  74.  
  75.     class MyOperationBehaviorAttribute : Attribute, IOperationBehavior
  76.     {
  77.         public void AddBindingParameters(OperationDescription operationDescription, BindingParameterCollection bindingParameters)
  78.         {
  79.             Console.WriteLine("Inside {0}.{1}, operation {2}", this.GetType().Name, MethodBase.GetCurrentMethod().Name, operationDescription.Name);
  80.         }
  81.  
  82.         public void ApplyClientBehavior(OperationDescription operationDescription, ClientOperation clientOperation)
  83.         {
  84.             Console.WriteLine("Inside {0}.{1}, operation {2}", this.GetType().Name, MethodBase.GetCurrentMethod().Name, operationDescription.Name);
  85.         }
  86.  
  87.         public void ApplyDispatchBehavior(OperationDescription operationDescription, DispatchOperation dispatchOperation)
  88.         {
  89.             Console.WriteLine("Inside {0}.{1}, operation {2}", this.GetType().Name, MethodBase.GetCurrentMethod().Name, operationDescription.Name);
  90.         }
  91.  
  92.         public void Validate(OperationDescription operationDescription)
  93.         {
  94.             Console.WriteLine("Inside {0}.{1}, operation {2}", this.GetType().Name, MethodBase.GetCurrentMethod().Name, operationDescription.Name);
  95.         }
  96.     }
  97.  
  98.     [ServiceContract]
  99.     [MyContractBehavior]
  100.     public interface ITest
  101.     {
  102.         [OperationContract]
  103.         [MyOperationBehavior]
  104.         int Add(int x, int y);
  105.         [OperationContract]
  106.         int Multiply(int x, int y);
  107.     }
  108.  
  109.     [ServiceContract]
  110.     [MyContractBehavior]
  111.     public interface ITest2
  112.     {
  113.         [OperationContract]
  114.         [MyOperationBehavior]
  115.         string Echo(string text);
  116.     }
  117.  
  118.     [MyServiceBehavior]
  119.     public class Service : ITest, ITest2
  120.     {
  121.         public int Add(int x, int y) { return x + y; }
  122.         public int Multiply(int x, int y) { return x * y; }
  123.         public string Echo(string text) { return text; }
  124.     }
  125.  
  126.     class Program
  127.     {
  128.         static void Main(string[] args)
  129.         {
  130.             string baseAddress = "https://" + Environment.MachineName + ":8000/Service";
  131.             ServiceHost host = new ServiceHost(typeof(Service), new Uri(baseAddress));
  132.             ServiceEndpoint ep1 = host.AddServiceEndpoint(typeof(ITest), new BasicHttpBinding(), "basic");
  133.             ep1.Name = "Endpoint1-BasicHttp";
  134.             ep1.Behaviors.Add(new MyEndpointBehavior());
  135.             ServiceEndpoint ep2 = host.AddServiceEndpoint(typeof(ITest2), new WSHttpBinding(), "ws");
  136.             ep2.Name = "Endpoint2-WSHttp";
  137.             ep2.Behaviors.Add(new MyEndpointBehavior());
  138.             Console.WriteLine("Opening the host...");
  139.             host.Open();
  140.             Console.WriteLine("Host opened");
  141.         }
  142.     }
  143. }

I don’t think there’s any “preferred” way of adding behaviors, it depends on the scenario. For example, if one wants to let an admin configure the behaviors without recompiling, configuration extensions are a good option; choosing between attribute and code is a choice between declarative and imperative implementation (I won’t go into that quasi-religious discussion here :)

Coming up

Posts for each of the specific behavior interfaces, in which I’ll try to find real questions posted in the forum to show how those behaviors can be used to solve a realistic scenario.

[Code in this post]

[Back to the index]