次の方法で共有


WCF Extensibility – Policy Import / Export Extensions

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 to wrap up on the subject of metadata extensions, this post will talk about policy assertions, how we can define them to be exported, and how we can consume custom policies when we are creating clients for the services. Policies assertions are defined by the WS-Policy specification, and they basically define some required or optional features that a client need to adhere in order to talk to the service. For example, if we define an endpoint with a binding which has transport security, reliable messaging and a binary encoding, this information is exposed in the WSDL as policy assertions which the tools to generate the client will understand and be able to generate the appropriate bindings. The article Understanding Web Services Policy has a very in-depth description of this topic so I’ll skip it over here.

WCF exposes two interfaces for dealing with metadata policies. Like the extensions for import / export WSDL in general, WCF exposes two interfaces which can be used to control policy import and export: IPolicyImportExtension and IPolicyExportExtension. Again, similarly to WSDL import / export, the import extension is defined using the configuration extension, and the export extension is defined by implementing the extension interface in a class which is also a binding element. In this post I’ll go in details on those interfaces, and show an example of how this can be used in a real scenario.

Interface definitions

  1. public interface IPolicyExportExtension
  2. {
  3.     void ExportPolicy(MetadataExporter exporter, PolicyConversionContext context);
  4. }
  5. public interface IPolicyImportExtension
  6. {
  7.     void ImportPolicy(MetadataImporter importer, PolicyConversionContext context);
  8. }

The policy-related interfaces are quite simple, with a single method which needs to be implemented. During metadata export, when the contract is being exported, any binding element which is part of the endpoint binding which implements IPolicyExportExtension will have its ExportPolicy method called. The method is given two parameters: the instance of the MetadataExporter which is being used to export the service metadata (i.e., the “global” export instance, which gives you information such as the version of the WS-Policy specification which is being used), and a PolicyConversionContext that allows you to insert policy assertions in different parts of the WSDL metadata (bindings, operations, messages and faults).

On metadata import, classes which implement IPolicyImportExtension have their ImportPolicy method invoked to consume the assertions exposed in the WSDL. Like the export path, the method is given the global object (in this case, the MetadataImporter object which is being used to consume the metadata), and the same PolicyConversionContext object from the export path, which allows the import extension to query the policy assertions made in the WSDL and react appropriately, usually by removing the assertion (since it already consumed it) and adding a binding element which implements the policy on the client side to the context.

Public implementations in WCF

Those are the public ones (and I couldn’t find any internal ones). I don’t have a lot of experience with metadata in WCF, but I haven’t seen those public importers being used directly, so I wonder whether they could have been made internal…

How to add a policy export extension

To add one policy export extension, we need to define a binding element class which implements the IPolicyExportExtension interface (and, of course, use that binding element in the binding of an endpoint which is exposed by the service). When the service is exporting its metadata, the policy export code will be invoked for those binding elements.

  1. public class MyBindingElement : BindingElement, IPolicyExportExtension
  2. {
  3.     // implementation of the binding element logic ommitted
  4.  
  5.     public void ExportPolicy(MetadataExporter exporter, PolicyConversionContext context)
  6.     {
  7.         XmlDocument doc = new XmlDocument();
  8.         context.GetBindingAssertions().Add(
  9.             doc.CreateElement("p", "myElement", "https://my.element/policies"));
  10.     }
  11. }

The order where the exporters will be invoked is not specified, so you shouldn’t assume that one binding element in the stack has already exported its policies before your code is invoked (and in general, policies should have their own namespace / elements to avoid conflicts).

How to add a policy import extension

Just like WSDL import extensions, policy import extensions are typically used in conjunction with some tool such as svcutil. Since we can’t change the code from that tool, we can use configuration to add any extensions we define. The example below shows how a class which implements IPolicyImportExtension, called MyRotPolicyImporter (on namespace Library, in the Library assembly) is added to the list of extensions used by the metadata importer used by the code. If we’re using svcutil, then this block of configuration would go into svcutil.exe.config.

  1. <system.serviceModel>
  2.   <extensions>
  3.     <bindingElementExtensions>
  4.       <add name="myRotEncoder" type="Library.MyRotEncodingElement, Library, Version=1.0.0.0"/>
  5.     </bindingElementExtensions>
  6.   </extensions>
  7.   <client>
  8.     <metadata>
  9.       <policyImporters>
  10.         <extension type="Library.MyRotPolicyImporter, Library, Version=1.0.0.0"/>
  11.       </policyImporters>
  12.     </metadata>
  13.   </client>
  14. </system.serviceModel>

Notice that in this case we also added a binding element extension (more information on the post on this topic). One thing which I haven’t seen in many examples – in the IPolicyImportExtension.ImportPolicy implementation, we usually consume policy assertions and add corresponding binding elements to the policy conversion context. But the output of a tool such as svcutil for the binding is a configuration file with information about the binding. If we use (as is often the case) policy extensions for new channels (or encodings), then WCF doesn’t know about those bindings. What the code which generates the binding (the ServiceContractGenerator) does is to try to find any binding element extension registered whose BindingElementType property matches the type of the binding element added by the policy importer.

Real world scenario: introducing a new encoding and announcing it in the description

This isn’t based on any specific example which I found in the forums, but for any custom channel or encoder (for more information about them, see the post about channels) which somehow wants to be exported in the service metadata, using an IPolicyExportExtension is the logical choice (the developer could also use a WSDL export extension, but that would require more work). For this example I’m using a simple custom encoder which isn’t very interesting by itself, so we can focus on the metadata part of the problem.

To start, the usual service which I’ve been using in this sub-series about metadata. Nothing new here.

  1. [ServiceContract]
  2. public interface IComplexCalculator
  3. {
  4.     [OperationContract]
  5.     ComplexNumber Add(ComplexNumber n1, ComplexNumber n2);
  6.  
  7.     [OperationContract]
  8.     ComplexNumber Subtract(ComplexNumber n1, ComplexNumber n2);
  9.  
  10.     [OperationContract]
  11.     ComplexNumber Multiply(ComplexNumber n1, ComplexNumber n2);
  12.  
  13.     [OperationContract]
  14.     ComplexNumber Divide(ComplexNumber n1, ComplexNumber n2);
  15. }

Now hosting the service. Instead of the normal XML encoding, I’ll use a new encoder which adds some “protection” to the message by applying a very rudimentary crypto to it, the ROT shift cypher (please don’t use it in any scenario which needs even an iota of security) – in the custom binding used by the endpoint, it’s represented by the binding element MyRotEncodingBindingElement (in the next post on this series I’ll visit custom encoders in detail).

  1. class Program
  2. {
  3.     static Binding GetBinding()
  4.     {
  5.         return new CustomBinding(
  6.             new MyRotEncodingBindingElement(5),
  7.             new HttpTransportBindingElement());
  8.     }
  9.     static void Main(string[] args)
  10.     {
  11.         string baseAddress = "https://" + Environment.MachineName + ":8000/Service";
  12.         ServiceHost host = new ServiceHost(typeof(CalculatorService), new Uri(baseAddress));
  13.         host.AddServiceEndpoint(typeof(IComplexCalculator), GetBinding(), "");
  14.         host.Description.Behaviors.Add(new ServiceMetadataBehavior { HttpGetEnabled = true });
  15.         host.Open();
  16.  
  17.         ChannelFactory<IComplexCalculator> factory = new ChannelFactory<IComplexCalculator>(GetBinding(), new EndpointAddress(baseAddress));
  18.         IComplexCalculator proxy = factory.CreateChannel();
  19.         Console.WriteLine(proxy.Add(new ComplexNumber { Real = 1, Imaginary = 2 }, new ComplexNumber { Real = 2, Imaginary = 2 }));
  20.         Console.WriteLine("Host opened. Press ENTER to close");
  21.         Console.ReadLine();
  22.         host.Close();
  23.     }
  24. }

And before I go any further, 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). And really, really, really do not use this “encryption” encoder in any scenario which requires security – it’s not secure at all. And as usual, I’ve kept error checking to a minimum.

Ok, now the server is set up. But if we try to consume it with a “normal” client, it simply wouldn’t work – the server is expected the data encoded in a certain way (in the case of the code above, with the letters rotated by 5 positions), and none of the standard encoders in WCF can “talk ROT”. So if we want to advertise this server via its metadata, we need to tell the world that we use this special encoder, and the way to do that is via policies. So our encoding binding element class should also implement IPolicyExportExtension.

  1. public class MyRotEncodingBindingElement : MessageEncodingBindingElement, IPolicyExportExtension
  2. {
  3.     MessageEncodingBindingElement inner;
  4.     int rotNumber;
  5.  
  6.     public MyRotEncodingBindingElement(int rotNumber)
  7.         : this(new TextMessageEncodingBindingElement(), rotNumber)
  8.     {
  9.     }
  10.  
  11.     private MyRotEncodingBindingElement(MessageEncodingBindingElement inner, int rotNumber)
  12.     {
  13.         this.inner = inner;
  14.         this.rotNumber = rotNumber;
  15.     }
  16.  
  17.     public int RotNumber
  18.     {
  19.         get { return this.rotNumber; }
  20.         set { this.rotNumber = value; }
  21.     }
  22. }

The implementation of IPolicyExportExtension.ExportPolicy is usually fairly straightforward: simply add some XML elements to the specific policy we want to export and that’s it.  In the policy for this encoding, we’re adding a new XML element, with an attribute specifying the number of positions to shift in the ROT cypher.

  1. public void ExportPolicy(MetadataExporter exporter, PolicyConversionContext context)
  2. {
  3.     XmlDocument doc = new XmlDocument();
  4.     XmlElement rotAssertion = doc.CreateElement(
  5.         Constants.EncodingPrefix,
  6.         Constants.EncodingName,
  7.         Constants.EncodingNamespace);
  8.     XmlAttribute rotNumberAttribute = doc.CreateAttribute(Constants.EncodingPrefix, Constants.RotNumberAttributeName, Constants.EncodingNamespace);
  9.     rotNumberAttribute.Value = this.rotNumber.ToString();
  10.     rotAssertion.Attributes.Append(rotNumberAttribute);
  11.  
  12.     context.GetBindingAssertions().Add(rotAssertion);
  13. }

And that’s pretty much it, if the server is running we can see the metadata exposed by the service WSDL that our policy is shown, and referenced by the binding used by the endpoint.

  1. <wsp:Policy wsu:Id="CustomBinding_IComplexCalculator_policy">
  2.   <wsp:ExactlyOne>
  3.     <wsp:All>
  4.       <rot:RotEncoder xmlns:rot="https://my.policy.sample" rot:rotNumber="5"/>
  5.       <wsaw:UsingAddressing/>
  6.     </wsp:All>
  7.   </wsp:ExactlyOne>
  8. </wsp:Policy>
  9. <wsdl:binding name="CustomBinding_IComplexCalculator" type="tns:IComplexCalculator">
  10.   <wsp:PolicyReference URI="#CustomBinding_IComplexCalculator_policy"/>
  11.   <soap12:binding transport="https://schemas.xmlsoap.org/soap/http"/>
  12.   <wsdl:operation name="Add">

Now onto the client part. The implementation of the importer itself is fairly simple: if the expected policy is there, then add the corresponding binding element to the list. Since we’re dealing with a message encoding binding element, WCF always adds one by default, so we need to remove it (it’s a runtime error for a binding to have multiple instances of classes derived from MessageEncodingBindingElement).

  1. public class MyRotPolicyImporter : IPolicyImportExtension
  2. {
  3.     public void ImportPolicy(MetadataImporter importer, PolicyConversionContext context)
  4.     {
  5.         PolicyAssertionCollection policies = context.GetBindingAssertions();
  6.         XmlElement rotAssertion = policies.Find(Constants.EncodingName, Constants.EncodingNamespace);
  7.         if (rotAssertion != null)
  8.         {
  9.             // Found the expected assertion, adding my binding element
  10.             policies.Remove(rotAssertion);
  11.             XmlAttribute rotNumberAttr = rotAssertion.Attributes[Constants.RotNumberAttributeName, Constants.EncodingNamespace];
  12.             int rotNumber = MyRotEncodingElement.DefaultRotNumber;
  13.             if (rotNumberAttr != null)
  14.             {
  15.                 rotNumber = int.Parse(rotNumberAttr.Value);
  16.             }
  17.  
  18.             bool replaced = false;
  19.             for (int i = 0; i < context.BindingElements.Count; i++)
  20.             {
  21.                 if (context.BindingElements[i] is MessageEncodingBindingElement)
  22.                 {
  23.                     replaced = true;
  24.                     context.BindingElements[i] = new MyRotEncodingBindingElement(rotNumber);
  25.                     break;
  26.                 }
  27.             }
  28.  
  29.             if (!replaced)
  30.             {
  31.                 context.BindingElements.Insert(0, new MyRotEncodingBindingElement());
  32.             }
  33.         }
  34.     }
  35. }

So everything is fine, and we can now hook the importer in the tool used to generate the proxy by adding the snippet below in its configuration (in the case of svcutil, it would be svcutil.exe.config).

  1. <system.serviceModel>
  2.   <client>
  3.     <metadata>
  4.       <policyImporters>
  5.         <extension type="Library.MyRotPolicyImporter, Library, Version=1.0.0.0"/>
  6.       </policyImporters>
  7.     </metadata>
  8.   </client>
  9. </system.serviceModel>

That should be it. But when we try running svcutil against the service, it fails with the following (no so helpful) error:

Unhandled Exception: System.ArgumentException: The binding on the service endpoint cannot be configured.
Parameter name: endpoint.Binding

Essentially it saw the binding element, tried to save it to configuration but couldn’t, because the tool simply doesn’t know which binding element extension element should be used – after all, it doesn’t exist yet. So let’s create it. The code is a simple configuration extension, with a numeric property. The interesting piece (which I didn’t know at the time I was writing about binding element configuration extensions) is the protected internal method InitializeFrom. The extension elements are normally used to produce an actual binding element from the information in the application configuration, but for policy import scenarios, we want to do the opposite – and this is where this method comes along. Besides searching for all registered extensions, the service contract generator will also call this method on any extension element it creates, so it can save the proper configuration (if this class didn’t implement the method, the “ROT number” 5 wouldn’t be emitted in the configuration, thus creating a client which is incompatible with the server).

  1. public class MyRotEncodingElement : BindingElementExtensionElement
  2. {
  3.     const string RotNumberAttributeName = Constants.RotNumberAttributeName;
  4.     public const int DefaultRotNumber = 13;
  5.  
  6.     public override Type BindingElementType
  7.     {
  8.         get { return typeof(MyRotEncodingBindingElement); }
  9.     }
  10.  
  11.     protected override BindingElement CreateBindingElement()
  12.     {
  13.         return new MyRotEncodingBindingElement(this.RotNumber);
  14.     }
  15.  
  16.     [ConfigurationProperty(RotNumberAttributeName, DefaultValue = DefaultRotNumber, IsRequired = false)]
  17.     public int RotNumber
  18.     {
  19.         get { return (int)base[RotNumberAttributeName]; }
  20.         set { base[RotNumberAttributeName] = value; }
  21.     }
  22.  
  23.     protected override void InitializeFrom(BindingElement bindingElement)
  24.     {
  25.         base.InitializeFrom(bindingElement);
  26.  
  27.         MyRotEncodingBindingElement rotElement = bindingElement as MyRotEncodingBindingElement;
  28.         if (rotElement != null)
  29.         {
  30.             this.RotNumber = rotElement.RotNumber;
  31.         }
  32.     }
  33. }

Now we can revisit the configuration for the client code:

  1. <system.serviceModel>
  2.   <extensions>
  3.     <bindingElementExtensions>
  4.       <add name="myRotEncoder" type="Library.MyRotEncodingElement, Library, Version=1.0.0.0"/>
  5.     </bindingElementExtensions>
  6.   </extensions>
  7.   <client>
  8.     <metadata>
  9.       <policyImporters>
  10.         <extension type="Library.MyRotPolicyImporter, Library, Version=1.0.0.0"/>
  11.       </policyImporters>
  12.     </metadata>
  13.   </client>
  14. </system.serviceModel>

And that’s it. When we use the tool to generate a client for the server, it will now generate the appropriate binding in the configuration.

  1. <system.serviceModel>
  2.     <bindings>
  3.         <customBinding>
  4.             <binding name="CustomBinding_IComplexCalculator">
  5.                 <myRotEncoder rotNumber="5" />
  6.                 <httpTransport />
  7.             </binding>
  8.         </customBinding>
  9.     </bindings>
  10.     <client>
  11.         <endpoint address="https://MACHINE_NAME:8000/Service" binding="customBinding"
  12.             bindingConfiguration="CustomBinding_IComplexCalculator" contract="IComplexCalculator"
  13.             name="CustomBinding_IComplexCalculator" />
  14.     </client>
  15.     <extensions>
  16.         <bindingElementExtensions>
  17.             <add name="myRotEncoder" type="Library.MyRotEncodingElement, Library, Version=1.0.0.0" />
  18.         </bindingElementExtensions>
  19.     </extensions>
  20. </system.serviceModel>

As with the post on WSDL import extensions, the sample in the gallery uses the ServiceContractGenerator / WsdlImporter / MetadataExchangeClient directly, but the same configuration can be used for svcutil and works just as well.

[Code in this post]

[Back to the index]