共用方式為


WCF Extensibility – IExtension and IExtensibleObject

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 .

And we’re back from the short detour to the world of WCF RIA Services, and the longmini-seriesabout transport channels. We’ll go back to the WCF runtime to look at the extensible objects in WCF. Extensible objects are some objects in the WCF runtime which can be extended using the IExtensibleObject<T> pattern. The idea is that there are a few objects in WCF (the extensible objects) to which one can add extensions to it, and those extensions can, well, extend their functionality. That’s the “official” story, but extensible objects are simply a few classes in WCF which have a list where we can add the extensions, nothing fancier than that.

There are four extensible objects in WCF:

  • ServiceHostBase, the base class used for hosting WCF services
  • InstanceContext, the class which represents the running service, connecting the service class instance with the WCF runtime
  • OperationContext, the class which represents the operation information gathered by the runtime and passed (as a thread-local static variable)
  • IContextChannel, an interface which allows for the inspection of channels and proxies built by the WCF runtime.

The advantage of using the extensions is that those will have the same life time of the object to which they are attached. This way, one can, for example, add an extension to a context channel and have that extension flow along with the channel through the WCF pipeline, and somewhere in the stack retrieve that information. This will be shown in the example in this post. The example for the next post (on instance context providers) will also use an extension to track state for that.

Public implementations in WCF

There are three public implementations for the IExtension<T> interface in WCF:

  • System.ServiceModel.Activation.VirtualPathExtension: an extension for the ServiceHostBase class, which allows hosted applications to get data about the application path, site name and virtual path (the first two are new in 4.0; the last property is there since the original version).
  • System.ServiceModel.Description.ServiceMetadataExtension: another extension for the ServiceHostBase class, contains a property which contains the metadata for the service. This class also provides the implementation for the metadata publishing protocols, and is added to the service host when the ServiceMetadataBehavior is applied to it.
  • System.ServiceModel.Web.WebOperationContext (new in 3.5): an extension for the OperationContext, used to extend that context class with properties and methods to make the creation of WCF Web HTTP services easier.

There is one public implementation of IExtensionCollection<T> in WCF:

  • System.ServiceModel.ExtensionCollection<T>: a thread-safe implementation of the extension collection, which is used internally by the extensible objects in WCF.

As I mentioned before, there are four base public implementations of IExtensibleObject<T> in WCF: ServiceHostBase, InstanceContext, OperationContext and IContextChannel. Notice that all the classes (and interfaces) derived from those ones also are extensible objects themselves, such as ServiceHost / WebServiceHost, IClientChannel / IServiceChannel, and so on.

Interface definitions

  1. public interface IExtension<T> where T : IExtensibleObject<T>
  2. {
  3.     void Attach(T owner);
  4.     void Detach(T owner);
  5. }
  6.  
  7. public interface IExtensibleObject<T> where T : IExtensibleObject<T>
  8. {
  9.     IExtensionCollection<T> Extensions { get; }
  10. }
  11.  
  12. public interface IExtensionCollection<T> : ICollection<IExtension<T>>, IEnumerable<IExtension<T>>, IEnumerable
  13.     where T : IExtensibleObject<T>
  14. {
  15.     E Find<E>();
  16.     Collection<E> FindAll<E>();
  17. }

The three interfaces which enable the extensible object pattern in WCF are quite simple. The extensible objects implement IExtensibleObject<T>, which exposes a collection of the extensions attached to it on its Extensions property, of type IExtensionCollection<T>. That interface type defines the methods Find<E> and FindAll<E> to return either an extension of a specific type or all extensions of that type which are stored in the object’s extensions.

Finally, IExtension<T> is the interface which the extensions need to implement. It defines two methods, Attach and Detach, that are called when the extension is added to or removed from an extensible object, but they’re actually not used that often (they can be used to do some validation, which is the case in the ServiceMetadataExtension, which checks that the object is only added to one service host).

One interesting thing to notice are all the interfaces have restrictions on them, but that’s just to guarantee that an extension for a certain extensible objects can only be used for that object. This way, if a class implements IExtension<InstanceContext> it cannot be added to the operation context extension list.

How to add an extension object

Extension objects can only be added via code, by accessing the Extensions property in any of the extensible objects in WCF. There isn’t any place where an extension is typically added, I’ve seen examples of them being added in behaviors (such as the ServiceMetadataExtension), in thread-local static properties (as is the case with the WebOperationContext), or in the normal flow of the code, as will be shown in the example below.

One more interesting thing to point is that the extensible object pattern doesn’t need to be restricted to WCF. This blog post shows how one can use the same pattern (and objects) in a WPF application to make controls more extensible. Not really in the topic for this series, but an interesting read nonetheless.

Real world scenario: flowing state through client proxies

This example came from Stack Overflow: the user wanted to get the information about the client binding on a channel returned by [Duplex]ChannelFactory. Those channels / proxies are normally cast as the service contract interface, but can also be cast to IClientChannel, which has a lot of information about the channel (local and remote address, sessions, and so on), but it didn’t have the information needed.

Well, the client channel itself doesn’t have it, but it’s one of the extensible objects in WCF so if we need that information later, we can add the information as we’re creating the channel, stuff it in its extension bag, then retrieve it when necessary. Notice that this can be used not only when you have an instance of the channel returned by the channel factory, but also in inspectors or other extensibility points which take the client channel as a parameter – in the example below I’ll add one of those as well.

So, let’s start with the usual calculator (simple enough not to take more than a second of attention out of the main topic). We also have a service which implement this contract with the expected operations, so I’ll skip this for now.

  1. [ServiceContract]
  2. public interface ICalculator
  3. {
  4.     [OperationContract]
  5.     double Add(double x, double y);
  6.     [OperationContract]
  7.     double Subtract(double x, double y);
  8.     [OperationContract]
  9.     double Multiply(double x, double y);
  10.     [OperationContract]
  11.     double Divide(double x, double y);
  12. }

Now we can start using it as in the code shown below. One can ask that, since the binding is on scope in that code, why not use it directly. That’s fair, but imagine that the function which will use the proxy is in a separate library, and it should support channels from different kinds of endpoints. That’s the behavior which I imagine was expected in the question from SO. To simulate this, we’re just passing the proxy to a separate method, as shown below.

  1. ServiceHost host = new ServiceHost(typeof(CalculatorService), new Uri(baseAddressHttp), new Uri(baseAddressPipe));
  2. host.AddServiceEndpoint(typeof(ICalculator), new BasicHttpBinding(), "");
  3. host.AddServiceEndpoint(typeof(ICalculator), new NetNamedPipeBinding(NetNamedPipeSecurityMode.None), "");
  4. host.Open();
  5. Console.WriteLine("Host opened");
  6.  
  7. ChannelFactory<ICalculator> factory = new ChannelFactory<ICalculator>(new BasicHttpBinding(), new EndpointAddress(baseAddressHttp));
  8. ICalculator proxy = factory.CreateChannel();
  9. DoCalculations(proxy);
  10.  
  11. ((IClientChannel)proxy).Close();
  12. factory.Close();
  13. host.Close();

The code shown above, however, doesn’t pass any information to the method, so we’ll need to update it a little to do that. But before continuing, it’s time for the usual disclaimer. This is a sample for illustrating the topic of this post, this is not production-ready code. I tested it for a few contracts and it worked, but I cannot guarantee that it will work for all scenarios (please let me know if you find a bug or something missing). Error checking is also kept to a minimum, to focus on the issue in this post. Back to the sample, let’s define our extension. We can define a few properties which will be used later on. The binding, as in the original SO question, and I’ll also add another property which will be checked in a message inspector down in the pipeline.

  1. class MyChannelExtension : IExtension<IContextChannel>
  2. {
  3.     public Binding Binding { get; set; }
  4.     public bool IntroduceErrors { get; set; }
  5.  
  6.     // Not used in this scenario
  7.     public void Attach(IContextChannel owner) { }
  8.     public void Detach(IContextChannel owner) { }
  9. }

Now the code which creates the proxy needs to be updated to add the extension to it.

  1. static ICalculator CreateProxy(bool useHttp, bool addErrors = false)
  2. {
  3.     Binding binding = useHttp ?
  4.         (Binding)new BasicHttpBinding() :
  5.         (Binding)new NetNamedPipeBinding(NetNamedPipeSecurityMode.None);
  6.     string address = useHttp ? baseAddressHttp : baseAddressPipe;
  7.     ChannelFactory<ICalculator> factory = new ChannelFactory<ICalculator>(binding, new EndpointAddress(address));
  8.     factory.Endpoint.Behaviors.Add(new MyInspector());
  9.     ICalculator result = factory.CreateChannel();
  10.     ((IClientChannel)result).Closed += delegate { factory.Close(); };
  11.     ((IClientChannel)result).Extensions.Add(new MyChannelExtension { Binding = binding, IntroduceErrors = addErrors });
  12.  
  13.     return result;
  14. }

Now we can go into the DoCalculations method. Nothing interesting here, but it shows that we can query the extensions in the proxy for the channel extension, and if that extension is there, it can get the information from the extension.

  1. static void DoCalculations(ICalculator proxy)
  2. {
  3.     MyChannelExtension extension = ((IContextChannel)proxy).Extensions.Find<MyChannelExtension>();
  4.     if (extension != null)
  5.     {
  6.         if (extension.Binding != null)
  7.         {
  8.             Console.WriteLine("Sending requests over {0}", extension.Binding.Scheme);
  9.         }
  10.  
  11.         if (extension.IntroduceErrors)
  12.         {
  13.             Console.WriteLine("Errors will be introduced in the request");
  14.         }
  15.     }
  16.  
  17.     // Call the Add service operation.
  18.     double value1 = 100.00D;
  19.     double value2 = 15.99D;
  20.     double result = proxy.Add(value1, value2);
  21.     Console.WriteLine("Add({0},{1}) = {2}", value1, value2, result);
  22.  
  23.     // Call the Subtract service operation.
  24.     value1 = 145.00D;
  25.     value2 = 76.54D;
  26.     result = proxy.Subtract(value1, value2);
  27.     Console.WriteLine("Subtract({0},{1}) = {2}", value1, value2, result);
  28.  
  29.     // Call the Multiply service operation.
  30.     value1 = 9.00D;
  31.     value2 = 81.25D;
  32.     result = proxy.Multiply(value1, value2);
  33.     Console.WriteLine("Multiply({0},{1}) = {2}", value1, value2, result);
  34.  
  35.     // Call the Divide service operation.
  36.     value1 = 22.00D;
  37.     value2 = 7.00D;
  38.     result = proxy.Divide(value1, value2);
  39.     Console.WriteLine("Divide({0},{1}) = {2}", value1, value2, result);
  40.  
  41.     Console.WriteLine();
  42. }

Now let’s go to a client message inspector, which was added in the CreateProxy method, to see how the proxy can get information from the client. Again, this is a simple example, where I’m creating the inspector myself (so I could have passed the flag used in the inspector to its constructor directly), but you can think of a general inspector which is added to all applications in the system or one which is added by a behavior configured via configuration, so this is a simple way to pass data along the pipeline.

  1. public void AfterReceiveReply(ref Message reply, object correlationState)
  2. {
  3. }
  4.  
  5. public object BeforeSendRequest(ref Message request, IClientChannel channel)
  6. {
  7.     MyChannelExtension extension = channel.Extensions.Find<MyChannelExtension>();
  8.     if (extension != null && extension.IntroduceErrors)
  9.     {
  10.         this.IntroduceErrorToMessage(ref request);
  11.     }
  12.  
  13.     return null;
  14. }

With all pieces in place, we can now change our main method to create many proxies and test the system.

  1. static void Main(string[] args)
  2. {
  3.     ServiceHost host = new ServiceHost(typeof(CalculatorService), new Uri(baseAddressHttp), new Uri(baseAddressPipe));
  4.     host.AddServiceEndpoint(typeof(ICalculator), new BasicHttpBinding(), "");
  5.     host.AddServiceEndpoint(typeof(ICalculator), new NetNamedPipeBinding(NetNamedPipeSecurityMode.None), "");
  6.     host.Open();
  7.     Console.WriteLine("Host opened");
  8.  
  9.     ICalculator httpProxy1 = CreateProxy(useHttp: true, addErrors: false);
  10.     DoCalculations(httpProxy1);
  11.  
  12.     ICalculator httpProxy2 = CreateProxy(useHttp: true, addErrors: true);
  13.     DoCalculations(httpProxy2);
  14.  
  15.     ICalculator netPipeProxy1 = CreateProxy(useHttp: false, addErrors: false);
  16.     DoCalculations(netPipeProxy1);
  17.  
  18.     ((IClientChannel)httpProxy1).Close();
  19.     ((IClientChannel)httpProxy2).Close();
  20.     ((IClientChannel)netPipeProxy1).Close();
  21.  
  22.     host.Close();
  23. }

That’s it. This example showed how we can use an extension to, well, extend the client channel with more information which can be used when the channel is available.

Coming up

We’ll go back to the WCF runtime, with the instance context provider.

[Code in this post]

[Back to the index]