The XML Files

WS-Policy and WSE 2.0 Assertion Handlers

Aaron Skonnard

Code download available at:XMLFiles0403.exe(126 KB)

Contents

Enter WS-Policy
MessagePredicate Assertion
Policy in WSE 2.0
Custom Policy Assertion Handlers
The Custom MessagePredicateHandler
Policy Extension Functions
Where Are We?

Back in the July and August 2003 issues of MSDN® Magazine, Dan Sullivan and I covered various Web services validation techniques. We highlighted the fact that the ASP.NET Web services framework (ASMX) does not perform XML Schema validation on incoming messages and we illustrated the types of problems that can occur as a result. (Take a look at the end of this column for the URLs of these pieces.)

Even with XML Schema validation, however, there are a variety of business rules that cannot be expressed with complex type definitions. As a result, most developers end up embedding validation code in their WebMethod implementations. For example, the following WebMethod validates that length is greater than width before doing the real work:

WebService(Namespace="https://example.org/geometry/")] public class Geometry { [WebMethod] public double CalcArea(double length, double width) { if (length <= width) throw new Exception("length must be greater than width"); return length * width; } }

This is obviously a less than ideal solution since the validation code has to stay in sync with business specifications, which can change on a whim. Each time business rules change, so must the validation logic that is scattered throughout your code. Also, when you look at the code you really can't tell whether or not it correctly enforces a given requirement.

To remedy this situation, Dan and I showed how to implement a SoapExtension class (called ValidationExtension) that provides standard XML Schema validation as well as a more advanced validation scheme based on XPath assertions. Being able to express constraints with XPath assertions makes it possible to enforce a wide variety of business rules that cannot be expressed with XML Schema alone. Our sample makes it possible to "turn on" validation by sprinkling some attributes on your WebMethods:

[WebService(Namespace="https://example.org/geometry/")] [AssertNamespaceBinding("m","https://example.org/math")] public class Geometry { [WebMethod] [Validation] // turns on XML Schema validation [Assert("//m:length > //m:width", "length must be greater than width")] public double CalcArea(double length, double width) { return length * width; } }

This effectively moves the validation logic out of the WebMethod implementation and into a declarative expression (//t:length > //t:width). Now, whenever a SOAP message arrives targeting this operation, the ASMX framework calls our ValidationExtension and gives it a chance to validate the message against the declared assertions. In this case, if length isn't greater than width then the ValidationExtension returns a SOAP fault before calling the WebMethod. Doing things this way makes validation logic easier to express, identify, and maintain, compared to the code-centric approach.

Although this is a step in the right direction, it still requires the assertions to be compiled into the code, tightly coupling them to the operation logic. As a consequence, you must recompile and redeploy the assemblies each time you modify your assertions. It probably makes more sense to completely decouple the assertions from the code and put them in a separate file that can be maintained independently. This allows your WebMethod code to remain very simple, focusing only on the business logic at hand:

[WebService(Namespace="https://example.org/geometry/")] public class Geometry { [WebMethod] public double CalcArea(double length, double width) { return length * width; } }

In order to accomplish this, however, you must define a format for expressing your assertions in the separate file. With a standard assertion language in place, developers everywhere will be able to read your assertions and understand your requirements.

Enter WS-Policy

WS-Policy has emerged as a general framework for expressing the assertions of a Web service (see "Understanding WS-Policy" for more background information). A policy statement is simply an XML file that describes the requirements, capabilities, or preferences of your Web service operations. The following shows the basic structure of a policy statement:

<wsp:Policy xmlns:wsp="https://schemas.xmlsoap.org/ws/2002/12/policy" xmlns:wsu="https://schemas.xmlsoap.org/ws/2002/07/utility" wsu:Id="..." Name="..." TargetNamespace="..."> <!-- policy assertions go here --> </wsp:Policy>

You place policy assertion elements within wsp:Policy to define your specific requirements, capabilities, or preferences. You can define policy assertion elements to assert anything you want. However, defining your own policy assertion requires you to also write the code to validate the assertion. Therefore, it makes sense to standardize as many policy assertions as possible so infrastructure vendors can provide standard implementations out of the box.

WS-Policy doesn't define any policy assertions itself; it simply defines the framework for packaging and processing them in a standard way. WS-PolicyAssertions and WS-SecurityPolicy define some standard assertions that address the common needs of Web services applications. WS-PolicyAssertions also defines a valuable assertion called MessagePredicate, which validates user-defined expressions in a generalized way. MessagePredicate can be used to validate a variety of message requirements (such as business rules), so it's a nice fit for improving the ASMX validation solution described here.

MessagePredicate Assertion

The MessagePredicate assertion makes it possible to ensure that a message conforms to a given "predicate" (assertion). MessagePredicate has the following XML structure:

<wsp:MessagePredicate wsp:Usage="..."? Dialect="..."? > <!-- predicate goes here --> </wsp:MessagePredicate>

MessagePredicate is capable of supporting multiple predicate dialects. You specify the dialect you're using with the Dialect attribute. The specification currently defines only two dialects: XPath 1.0 and a special message part dialect (see Figure 1). Since XPath 1.0 happens to be what was used in the previous solution, I'll continue to use it with MessagePredicate:

<wsp:Policy xmlns:wsp="https://schemas.xmlsoap.org/ws/2002/12/policy" xmlns:wsu="https://schemas.xmlsoap.org/ws/2002/07/utility" xmlns:m=" https://example.org/math" wsu:Id="urn:CalcDistance" > <!-- policy assertions go here --> <wsp:MessagePredicate wsp:Usage="wsp:Required"> //m:length > //m:width </wsp:MessagePredicate> </wsp:Policy>

Figure 1 MessagePredicate Dialects

URI Description
https://www.w3.org/TR/1999/REC-xpath-19991116 This is the default. The contents of the <MessagePredicate> element is an XPath 1.0 expression. The XPath expression is evaluated against the soap:Envelope element node to determine a true or false result.
https://schemas.xmlsoap.org/2002/12/wsse#part The contents of the <wsp:MessagePredicate> element is a list of required message parts. The list of message parts is evaluated against the soap:Envelope element node to determine a true or false result; the expression is true if all the specified parts are non-empty.

With this policy file in place alongside the CalcDistance WebMethod implementation, you can achieve the same validation logic without having any of the logic compiled into the code. Now you just need to figure out how to enable support for MessagePredicate assertions using Web Services Enhancements (WSE) 2.0.

Policy in WSE 2.0

The ASP.NET Web services framework does not come with any built-in support for WS-Policy, WS-PolicyAssertions, or any other WS specification for that matter. Microsoft currently provides support for these specifications through the WSE 2.0 toolkit, which plugs into the ASMX framework as a SoapExtension. In addition to the ASP.NET integration, WSE 2.0 provides a transport-neutral messaging API that supports these specifications. This means that you can take advantage of WS-Policy when using other transport protocols like TCP.

After you've installed WSE 2.0 (selecting the Visual Studio® .NET Developer settings), you can add WSE support to your project through the WSE Settings tool. You access this tool by right-clicking on your project in Solution Explorer and selecting WSE Settings. Check both boxes to turn on support for WS-Policy. After doing this, your project will contain a reference to the Microsoft.Web.Services assembly and there will be a few new lines in your web.config file that makes it look something like Figure 2.

Figure 2 Modified Web.config

<configuration> <configSections> <section name="microsoft.web.services" type="Microsoft.Web.Services.Configuration.WebServicesConfiguration, Microsoft.Web.Services, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" /> </configSections> <system.web> <webServices> <soapExtensionTypes> <add type="Microsoft.Web.Services.WebServicesExtension, Microsoft.Web.Services, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" priority="1" group="0" /> </soapExtensionTypes> </webServices> </system.web> </configuration>

You can then start configuring support for WS-Policy via the Policy tab. WSE supports the notion of both "receiving side" and "sending side" policies. A "receiving side" policy enforces policy assertions on messages as they're received while a "sending side" policy enforces policy assertions on messages as they're sent.

You turn on WSE 2.0 policy support by specifying a policy cache file for the side(s) that you need. Add a policy element to your project's web.config specifying the policy cache for each side:

<configuration> <microsoft.web.services> <policy> <receive> <cache name="policyCache.xml" /> </receive> <send> <cache name="policyCache.xml" /> </send> </policy> </microsoft.web.services> ••• </configuration>

The format of the policy cache file makes it possible to map policy statements to specific Web service endpoints within your project. The WSE Settings tool also provides a way to create or edit policy files and specify endpoint mappings through the built-in policy editor. The policy editor can help you generate the basic structure of the policy cache file. It will generate IDs for your policy statements and automatically map them to the specified endpoint, as shown in Figure 3. Then you can start manually entering MessagePredicate assertions within the provided policy element.

Figure 3 Policy Cache File

<policyDocument xmlns="https://schemas.microsoft.com/wse/2003/06/Policy" xmlns:wse="https://schemas.microsoft.com/wse/2003/06/Policy" xmlns:wsu="https://schemas.xmlsoap.org/ws/2002/07/utility" xmlns:wsp="https://schemas.xmlsoap.org/ws/2002/12/policy" xmlns:m="https://example.org/math > <!-- this section maps endpoints to policy statements --> <mappings> <map to="https://localhost/mathservice/math.asmx"> <default policy="#policy-600686d0-b855-4f6b-9dac-9218547d462c"/> </map> </mappings> <!-- this section contains policy statements --> <policies > <wsp:Policy wsu:Id="policy-600686d0-b855-4f6b-9dac-9218547d462c"> <!-- MessagePredicate assertions go here, I had to manually add these elements --> <wsp:MessagePredicate wsp:Usage="wsp:Required"> //m:length > //m:width </wsp:MessagePredicate> ••• </wsp:Policy> </policies> </policyDocument>

Assuming your application is configured properly, WSE 2.0 will automatically process MessagePredicate assertions, but only those that use the SOAP message parts dialect (Dialect="https://schemas.xmlsoap.org/2002/12/wsse#part"). If that's the only dialect you need, simply add the MessagePredicate elements to your policy cache file and you're finished. The built-in policy support will validate your MessagePredicate assertions and return SOAP faults when they're not satisfied.

WSE 2.0 doesn't provide support for the complete XPath dialect (Dialect="https://www.w3.org/TR/1999/REC-xpath-19991116"). This is unfortunate because XPath is a very powerful language for expressing message constraints. And you really need the full XPath dialect to express traditional business rules in an XML world. Luckily, WSE 2.0 provides an extensibility model for writing custom assertion handlers. You can take advantage of this extensibility hook to override the built-in MessagePredicate functionality and introduce support for the full XPath dialect.

Custom Policy Assertion Handlers

The process of writing a custom policy assertion handler consists of deriving a new class from PolicyAssertion (in Microsoft.Web.Services.Policy) and then registering it in web.config. The following code illustrates how to derive a new class called MessagePredicateHandler from PolicyAssertion:

using Microsoft.Web.Services.Policy; public class MessagePredicateHandler : PolicyAssertion { // override members here ... }

Then you must add an assertion element to the policy section of web.config. This assertion element maps a policy assertion element name (in this case, wsp:MessagePredicate) to a handler class, as illustrated in the following code snippet:

<configuration> <microsoft.web.services> <policy> <receive> <cache name="policyCache.xml" /> </receive> <assertion name="wsp:MessagePredicate" type="MessagePredicateHandler,MessagePredicateHandler" xmlns:wsp="https://schemas.xmlsoap.org/ws/2002/12/policy" /> </policy> </microsoft.web.services> ••• </configuration>

Once the class is registered, the WSE pipeline will use your class to process the policy element specified in the config file.

PolicyAssertion has only two methods that you need to override in your derived class: IsSatisfiedBy and CanEnforce, as shown here:

public abstract class PolicyAssertion : PolicyElement { protected PolicyAssertion(XmlQualifiedName qName, XmlElement element); protected PolicyAssertion(XmlQualifiedName qName, XmlElement element, bool usageRequired); protected override void LoadXml(XmlElement element); public abstract bool IsSatisfiedBy(SoapEnvelope message); public abstract bool CanEnforce(ref IPolicyEnforcer enf); }

Your derived class must also contain a constructor that takes a single argument of type XmlElement. A new instance of your derived class is created for each policy statement found in the registered policy cache file. This happens the first time a message arrives at the endpoint. The same object is then used to process the policy statements as operations are invoked.

The constructor is your chance to save information found in the assertion element for future use. The supplied XmlElement object contains the parsed XML found in the policy statement. In the case of MessagePredicate, you need to save the MessagePredicate expression in a local field so that you can use it later in order to validate incoming messages.

IsSatsifiedBy is called for each message that passes through the pipeline to validate that the supplied message satisfies the assertion. If the message satisfies the assertion, it continues through the pipeline. If the message doesn't satisfy the assertion on receive, the policy framework returns a SOAP fault. If the message doesn't satisfy the assertion on send, the policy framework calls CanEnforce to determine whether the handler supports automatic enforcement (for example, some WS-SecurityPolicy assertions support this feature by automatically signing/encrypting a message before it's sent). If you want to support this feature in a custom handler, you must write another class that implements the IPolicyEnforcer interface, as shown here:

public interface IPolicyEnforcer { void Enforce(SoapEnvelope message); }

Simply return an instance of your custom class from CanEnforce and the policy framework will use it to call Enforce when a given message doesn't satisfy the assertion on send. Enforce gives you an opportunity to modify the message to satisfy the assertion.

Now let's look at the complete implementation of MessagePredicateHandler.

The Custom MessagePredicateHandler

Implementing a handler for the MessagePredicate assertion is straightforward if you're familiar with the System.Xml APIs. The bulk of the work is found in the constructor (see Figure 4). In my implementation, I call the base constructor so it can process the standard WS-Policy attributes such as wsp:Usage. Then I check for the Dialect attribute to determine which expression language has been used. Next I create an XPathNavigator that I can use to compile the expression contained in the MessagePredicate element. Notice that when the parts dialect is used, I convert the parts expression into an equivalent XPath expression so I can use the same validation logic from that point on.

Figure 4 MessagePredicateHandler Definition

public class MessagePredicateHandler : PolicyAssertion { private const string WP_NS = "https://schemas.xmlsoap.org/ws/2002/12/policy"; private const string DIALECT_XPATH = "https://www.w3.org/TR/1999/REC-xpath-19991116"; private const string DIALECT_PARTS = "https://schemas.xmlsoap.org/2002/12/wsse#part"; private static XmlQualifiedName qname = new XmlQualifiedName("MessagePredicate", WP_NS); private XPathExpression xe = null; private Dialect dialect = Dialect.XPath; private enum Dialect { XPath, Parts } public MessagePredicateHandler(XmlElement element) : base(qname, element) { int id = this.GetHashCode(); base.LoadXml(element); if (element.HasAttribute("Dialect")) { switch(element.GetAttribute("Dialect")) { case DIALECT_XPATH: dialect = Dialect.XPath; break; case DIALECT_PARTS: dialect = Dialect.Parts; break; } } XPathNavigator nav = element.CreateNavigator(); if (dialect == Dialect.Parts) { string[] parts = element.InnerText.Split(' '); StringBuilder partsExp = new StringBuilder(); for (int i=0; i<parts.Length; i++) { if (i!=0) partsExp.Append(" and "); partsExp.Append(String.Format("boolean({0})", parts[i])); } xe = nav.Compile(partsExp.ToString()); } else xe = nav.Compile(String.Format("boolean({0})", element.InnerText)); XmlNamespaceManager ctx = new XmlNamespaceManager(new NameTable()); XPathNodeIterator it = nav.Select("namespace::*"); while (it.MoveNext()) if (!it.Current.LocalName.StartsWith("xml")) ctx.AddNamespace(it.Current.LocalName, it.Current.Value); xe.SetContext(ctx); } public override bool CanEnforce(ref IPolicyEnforcer enforcer) { // we don't support automatic enforcement return false; } public override bool IsSatisfiedBy(SoapEnvelope message) { XPathNavigator nav = message.CreateNavigator(); return (bool)nav.Evaluate(xe); } }

Once I've generated the final XPath expression, I compile it into an XPathExpression object that is saved in a private instance field named xe. One tricky thing about the implementation is that you have to load the namespace bindings from the policy document into an XmlNamespaceManager object. This allows the XPathExpression object to resolve namespace prefixes used in the MessagePredicate expressions. With that in place, the XPathExpression object is ready for use in IsSatisfiedBy.

The implementation of IsSatisfiedBy simply creates a new XPathNavigator for the supplied message and calls Evaluate to process the cached XPathExpression object. If the expression returns true, the message satisfies the assertion. Automatic enforcement is not tractable for arbitrary XPath expressions, so CanEnforce simply returns false.

Policy Extension Functions

As you start writing MessagePredicate expressions, you'll notice that there are some nodes you need to identify over and over, such as the soap:Body and soap:Header elements, as illustrated here:

<wsp:MessagePredicate wsp:Usage="wsp:Required" xmlns:wsp='://schemas.xmlsoap.org/ws/2002/12/policy ' xmlns:s='https://schemas.xmlsoap.org/soap/envelope/' xmlns:m='https://example.org/math' > /s:Envelope/s:Body/m:CalcDistance/m:length > /s:Envelope/s:Body/m:CalcDistance/m:width </wsp:MessagePredicate>

Not only is spelling out "/s:Envelope/s:Body" cumbersome, it also ties the expression to a specific version of SOAP (in this case SOAP 1.1). When the infrastructure begins to support SOAP 1.2, this expression will always fail. In order to simplify the task of writing these expressions, the authors of WS-PolicyAssertion defined a suite of extension functions that provide for the most common SOAP selection needs in a version-independent way.

There are two sets of extension functions: one set is for use in XPath 1.0 expressions while the other is for use in message part expressions (see Figure 5). In order to fully support the MessagePredicate assertion, you have to extend the implementation of MessagePredicateHandler to support these additional functions.

Figure 5 Extension Functions

Policy Function Description
wsp:GetBody (node) Returns the SOAP envelope <S:Body> element from the specified Envelope element
wsp:IsInBody (node) Returns true if the specified node is in the SOAP envelope <S:Body> element from the specified Envelope element
wsp:GetHeader (node) Returns the SOAP envelop <S:Header> element from the specified Envelope element
wsp:IsInHeader (node) Returns true if the specified node is in the SOAP envelope <S:Header> element from the specified Envelope element
wsp:RoleURIForHeaderBlock (node) Returns the SOAP role for the specified header block
wsp:IsMandatoryHeaderBlock (node) Returns true/false depending on whether or not the specified header block is mandatory (mustUnderstand = true)
wsp:IsRoleURIForNext (node, string) Returns true/false depending on whether or not the specified role maps to the predefined "next" role for the version of SOAP used by the supplied message
wsp:IsRoleURIForUltimateReceiver (node, string) Returns true/false depending on whether or not the specified role maps to the predefined "ultimate receiver" for the version of SOAP used by the supplied message
wsp:GetNodesetForNode (node) Returns an XPath Node set for the node (this include the node, its attributes, all of its descendents and their attributes)
Message Part Selection Function Description
wsp:Body() Identifies the "body" of the message
wsp:Header(x) Identifies a "header" whose name is the specified QName

Luckily, System.Xml makes it possible to write custom functions that can be injected into the XPath processing engine. In order to accomplish this, you must first write a custom XsltContext class that you supply to your compiled XPathExpression object (see Figure 6). In your derived class, override ResolveFunction to specify which class the XPath engine should use to process each extension function.

Figure 6 MessagePredicateContext Definition

public class MessagePredicateContext : XsltContext { private const string WP_NS = "https://schemas.xmlsoap.org/ws/2002/12/policy"; public XmlNamespaceManager NamespaceManager; public MessagePredicateContext() : base() {} public MessagePredicateContext(NameTable nt):base(nt) {} public MessagePredicateContext(XmlNamespaceManager ns) { this.NamespaceManager = ns; } public override int CompareDocument(string baseUri, string nextbaseUri) { return 0; } public override bool PreserveWhitespace( XPathNavigator node) { return false; } public override IXsltContextFunction ResolveFunction( string prefix, string name, XPathResultType[] ArgTypes) { if (this.LookupNamespace(prefix).Equals(WP_NS)) { switch(name) { case "Body": return new BodyFunction(); case "Header": return new HeaderFunction(); case "GetBody": return new GetNodesetFunction("GetBody"); case "GetHeader": return new GetNodesetFunction("GetHeader"); case "GetNodesetForNode": return new GetNodesetFunction( "GetNodesetForNode"); case "IsInBody": return new IsFunction("IsInBody"); case "IsInHeader": return new IsFunction("IsInHeader"); case "IsMandatoryHeaderBlock": return new IsFunction( "IsMandatoryHeaderBlock"); case "RoleURIForHeaderBlock": return new RoleURIForHeaderBlockFunction(); case "IsRoleURIForNext": return new IsRoleURIFunction( "IsRoleURIForNext"); case "IsRoleURIForUltimateReceiver": return new IsRoleURIFunction( "IsRoleURIForUltimateReceiver"); default: return null; } } return null; } public override IXsltContextVariable ResolveVariable( string prefix, string name) { return null; } public override bool Whitespace { get { return false; } } public override string LookupNamespace(string prefix) { if (prefix == null || prefix == String.Empty) return String.Empty; else return NamespaceManager.LookupNamespace( this.NamespaceManager.NameTable.Get(prefix)); } public override string DefaultNamespace { get { return String.Empty; } } }

Then you write a separate class for handling each function (or at least each function with the same characteristics). This class must implement IXsltContextFunction. Your implementation specifies information about the arguments and return value, and provides an implementation of Invoke. See Figure 7 for a complete example of implementing the wsp:Body function.

Figure 7 BodyFunction Definition

public class BodyFunction : IXsltContextFunction { public XPathResultType[] ArgTypes { get { return null; } } public XPathResultType ReturnType { get { return XPathResultType.NodeSet; } } public int Minargs { get { return 0; } } public int Maxargs { get { return 0; } } public object Invoke(XsltContext xsltContext, object[] args, XPathNavigator docContext) { if (args.Length != 0) throw new ArgumentException("expected 0 args"); MessagePredicateContext mctx = xsltContext as MessagePredicateContext; if (mctx == null) throw new InvalidCastException( "MessagePredicateContext"); XPathExpression xe = docContext.Compile( SoapVersionManager.GetBodyExpression(docContext, mctx.NamespaceManager)); xe.SetContext(mctx.NamespaceManager); return docContext.Select(xe); } }

In order to use these functions within MessagePredicateHandler, you need to use MessagePredicateContext with the compiled XPath expression object. You can do this by inserting a new line of code toward the end of the constructor in Figure 4, like this:

XmlNamespaceManager ctx = new XmlNamespaceManager(new NameTable()); XPathNodeIterator it = nav.Select("namespace::*"); while (it.MoveNext()) if (!it.Current.LocalName.StartsWith("xml")) ctx.AddNamespace(it.Current.LocalName, it.Current.Value); // this line added to support the ext. functions MessagePredicateContext mctx = new MessagePredicateContext(ctx); xe.SetContext(mctx); }

Where Are We?

With this complete implementation along with the necessary WSE configuration settings in place, you're ready to start using the MessagePredicate assertion in your policy statements. Figure 8 shows a sample policy statement that I've mapped to the math.asmx endpoint shown earlier.

Figure 8 Policy for math.asmx

<wsp:Policy wsu:Id="policy-600686d0-b855-4f6b-9dac-9218547d462c" xmlns:wsp="https://schemas.xmlsoap.org/ws/2002/12/policy" xmlns:m="https://example.org/math" > <wsp:MessagePredicate wsp:Usage="wsp:Required"> GetBody(.)/m:CalcDistance/m:length div GetBody(.)/m:CalcDistance/m:width = 2 </wsp:MessagePredicate> <wsp:MessagePredicate wsp:Usage="wsp:Required" Dialect="https://schemas.xmlsoap.org/2002/12/wsse#part"> wsp:Body()/m:CalcDistance wsp:Body()/m:CalcDistance/m:length wsp:Body()/m:CalcDistance/m:width wsp:Header("m:MyCustomHeader") </wsp:MessagePredicate> </wsp:Policy>

This policy statement requires the message to contain the m:CalcDistance, m:length, and m:width elements along with the m:MyCustomHeader header. It also requires m:length to be double m:width. If you send in the following message, which satisfies the policy statement, the WebMethod returns successfully:

<soap:Envelope xmlns:soap="https://schemas.xmlsoap.org/soap/envelope/"> <soap:Header> <foo xmlns="https://example.org/math"/> </soap:Header> <soap:Body> <CalcDistance xmlns="https://example.org/math"> <length>5</length> <width>2.5</width> </CalcDistance> </soap:Body> </soap:Envelope>

If you tweak the length value so it's not double the width, you'll get the following SOAP fault back:

<soap:Fault> <faultcode>soap:Client</faultcode> <faultstring>The message does not conform to the policy it was mapped to.</faultstring> <faultactor>https://localhost/mathservice/math.asmx </faultactor> </soap:Fault>

The complete MessagePredicateHandler implementation is available for download from the link at the top of this article. (Note: the sample compiles against a prerelease version of WSE 2.0 and may not compile with WSE 2.0 when it's released.) The implementation includes all of the extension functions listed in Figure 5 and a sample application that shows them in action.

Using policy statements to express the requirements of your Web services makes it possible to completely decouple the validation logic and other valuable functionality from your code. WS-Policy, WS-PolicyAssertions, and WS-SecurityPolicy lay the groundwork for this model and WSE 2.0 provides a basic implementation of the core components. WSE 2.0 also offers a powerful extensibility model that allows you to write custom policy assertion handlers of your own.

For more information, visit the following resources:

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

Aaron Skonnard teaches at Northface University in Salt Lake City. Aaron coauthored Essential XML Quick Reference (Addison-Wesley, 2001) and Essential XML (Addison-Wesley, 2000), and frequently speaks at conferences. Reach him at https://www.skonnard.com.