Extending WSDL and Policy, Part 1. Exporting custom policy assertions.

I've been busy for a while trying to update the documentation for all the changes that have happened since the February release. That meant, definitely, that I hadn't blogged for a while. But now I've got some things that it's important to get on about.

The first is how to extend the policy system and import custom policy. The second is how to do the same with WSDL.

The external SDK documentation is not completely up to date (see https://windowssdk.msdn.microsoft.com/en-us/library/system.servicemodel.description.ipolicyexportextension.aspx) so I thought I'd try to correct that.

Policy is, in general, a statement about runtime binding support at a particular scope. For example, you might say that although the contract requires encryption and digital signing, your policy might say that we want to sign things with X.509. The contract discusses the requirement; the binding implements a supporting version of that requirement; the policy asserts the implementation that the binding uses. For very specific discussion of policies, see Dan Roth's article Understanding Web Services Policy.

By default, WCF supports security policy assertions, transaction policy assertions, and several others. However, for a first version, this is a reasonable but small subset of what will eventually be needed, and there is every likelihood that you or someone like you will want to sign your messages (or do something else with them) that requires support on both sides of a conversation. Custom policy extensions are then required.

In the normal case, a bindingelement implements IPolicyExportExtension, because the policy exporter is going to be inserting policy assertions about that binding into the WSDL that clients will consume. It is not, therefore, unsurprising that in WCF, IPolicyExportExtensions must be implemented by custom binding elements. This is done by extending System.ServiceModel.Channels.BindingElement. For example, here's a simple version:

  public class ExporterBindingElement : BindingElement, IPolicyExportExtension
{

    public const string name1 = "acme";
public const string ns1 = "https://Microsoft/WCF/Documentation/CustomPolicyAssertions";

    static XmlDocument doc = new XmlDocument();

    public ExporterBindingElement()
{
Console.WriteLine("Exporter created.");
}

    #region IPolicyExporter Members

    public void ExportPolicy(MetadataExporter exporter, PolicyConversionContext policyContext)
{
if (exporter == null)
throw new ApplicationException("The MetadataExporter object passed to the ExporterBindingElement is null.");
if (policyContext == null)
throw new ApplicationException("The PolicyConversionContext object passed to the ExporterBindingElement is null.");

XmlElement elem = doc.CreateElement(name1, ns1);
elem.InnerText = "My custom text.";
XmlAttribute att = doc.CreateAttribute("MyCustomAttribute", ns1);
att.Value = "ExampleValue";
elem.Attributes.Append(att);
XmlElement subElement = doc.CreateElement("MyCustomSubElement", ns1);
subElement.InnerText = "Custom Subelement Text.";
elem.AppendChild(subElement);
policyContext.GetBindingAssertions().Add(elem);
Console.WriteLine("The custom policy exporter was called.");
}

    #endregion

    public override BindingElement Clone()
{
// Note: All custom binding elements must return a deep clone
// to enable the run time to support multiple bindings using the
// same custom binding.
return this;
}

    // Call the inner property.
public override T GetProperty<T>(BindingContext context)
{
return context.GetInnerProperty<T>();
}
}

The next step is to wire your binding element into the binding that your service uses. The easiest way to do this is to create a System.ServiceModel.Configuration.BindingElementExtensionElement returns the ExporterBindingElement above, like so:

  public class ExporterBindingElementConfigurationSection : BindingElementExtensionElement
{

    public ExporterBindingElementConfigurationSection()
{
Console.WriteLine("Exporter configuration section created.");
}

    public override Type BindingElementType
{
get { return typeof(ExporterBindingElement); }
}

    protected override BindingElement CreateBindingElement()
{
return new ExporterBindingElement();
}
}

Then you can hook this element into the binding by using a configuration file, like so:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.serviceModel>
<services>
<service
name="Microsoft.WCF.Documentation.StatefulService"
behaviorConfiguration="addMetadata"
>
<host>
<baseAddresses>
<add baseAddress="https://localhost:8080/StatefulService"/>
</baseAddresses>
</host>
<endpoint
address="https://localhost:8080/StatefulService"
binding="customBinding"
bindingConfiguration="exporter"
contract="Microsoft.WCF.Documentation.IStatefulService"
/>
<endpoint
address="mex"
binding="mexHttpBinding"
contract="IMetadataExchange"
/>
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior name="addMetadata">
<serviceMetadata
httpGetEnabled="true"
httpGetUrl=""
/>
</behavior>
</serviceBehaviors>
</behaviors>
<bindings>
<customBinding>
<!--
Use the name attribute of the binding element as
the value of the bindingConfiguration attribute in
your endpoint.
-->
<binding name ="exporter">
<!-- Use the name attribute of your binding element extension specified below. -->
<!-- Be certain the order of your custom binding elements is correct. -->
<exporterBinding />
<reliableSession/>
<textMessageEncoding messageVersion="Default" />
<httpTransport/>
</binding>
</customBinding>
</bindings>
<extensions>
<bindingElementExtensions>
<!-- Use the add element to associate your bindingelement configuration handler and give it a name to use. -->
<add
type="Microsoft.WCF.Documentation.ExporterBindingElementConfigurationSection,PolicyExtensions"
name="exporterBinding" />
</bindingElementExtensions>
</extensions>
</system.serviceModel>
</configuration>

Note that in this configuration file, we use a custom binding because we haven't created a single custom binding that includes our custom policy exporter as a binding element. This is easy to do, but perhaps I'll discuss this another time.

When this code is executed, the following WSDL policy statements are generated:

<wsp:Policy wsu:Id="CustomBinding_IStatefulService_policy">

  <wsp:ExactlyOne>

    <wsp:All>

      <acme a:MyCustomAttribute="ExampleValue" xmlns="https://Microsoft/WCF/Documentation/CustomPolicyAssertions" xmlns:a="https://Microsoft/WCF/Documentation/CustomPolicyAssertions">

  My custom text.

  <MyCustomSubElement>Custom Subelement Text.</MyCustomSubElement>

  </acme>

      <wsrm:RMAssertion xmlns:wsrm="https://schemas.xmlsoap.org/ws/2005/02/rm/policy">

  <wsrm:InactivityTimeout Milliseconds="600000" />

  <wsrm:AcknowledgementInterval Milliseconds="200" />

  </wsrm:RMAssertion>

  <wsaw:UsingAddressing />

</wsp:All>

</wsp:ExactlyOne>

</wsp:Policy>

 

At this point, we have a binding element that exports some custom policy at the binding level, in addition to the standard RM policy assertion that the ReliableSessionBindingElement exports by default. This sample does not implement an actual bindingelement that has behavior; for clarity, I've only used the binding element to export custom policy information.

Tomorrow we'll import this policy statement and do something with it. Then we'll move on to customizing WSDL itself.

 

Hope it helps, me.