Service Station

Migrating to WSE 3.0

Aaron Skonnard

Code download available at:ServiceStation0604.exe(139 KB)

Contents

Major Changes in WSE 3.0
Migrating Projects from WSE 2.0 to WSE 3.0
General Class Library
DIME/MTOM
Security
Policy/Filters
Policy in WSE 3.0
Migrating Custom Filter Code
Migrating Custom Policy Assertions
Defining a Policy in Code
Applying a Policy to a Service
Applying a Policy to a Client
Defining a Policy in XML
Mapping Assertion Elements to Code
Configuring and Applying an XML Policy
Now Where?

You've probably heard that the new version of Web Services Enhancements (WSE) for the Microsoft® .NET Framework simplifies the process of building secure Web services. What you may not know is that most of these improvements derive from some core architectural changes made in WSE 3.0. This column discusses what's changed and examines the major migration issues you'll face in moving to WSE 3.0.

Major Changes in WSE 3.0

MSDN® recently published an article entitled "What's New in WSE 3.0," written by Mark Fussell, Lead Program Manager on the WSE team. This article summarizes the major changes and improvements to the product since WSE 2.0, including integration with the .NET Framework 2.0 and Visual Studio® 2005, simplified security through a new-and-improved policy architecture, and support for sending large amounts of data with Message Transmission Optimization Mechanism (MTOM). Additional changes include improved session management (via WS-SecureConversation), support for hosting ASMX services outside of IIS, and support for the latest WS-* specifications.

Although the WSE team tried hard to ensure backward compatibility, some of these changes affect the core programming model that you must address to update existing WSE 2.0 solutions.

The good news is, once you've migrated to WSE 3.0, you'll have more communications options. WSE 3.0 supports the latest WS-* specifications—the same ones supported by Windows® Communication Foundation (WCF)—making WSE 3.0 and WCF wire-compatible. This means WCF clients can interoperate with WSE 3.0 services and vice versa, removing the need for continued migration.

Migrating Projects from WSE 2.0 to WSE 3.0

When it comes to moving existing code forward to WSE 3.0, integration with the .NET Framework 2.0 and Visual Studio 2005 is most important. WSE 3.0 is built on the .NET Framework 2.0 and integrated with Visual Studio 2005—it does not run on the .NET Framework 1.1 nor does it integrate with Visual Studio .NET 2003. Hence, when migrating WSE 2.0 solutions forward, the first step is to move your .NET Framework 1.1 solutions forward to the .NET Framework 2.0. Luckily, Visual Studio 2005 provides automated migration tools that greatly simplify this step.

You can start migrating an existing Visual Studio .NET 2003 solution simply by opening it in Visual Studio 2005. This launches the Visual Studio Conversion Wizard, where automatic conversion to the new Visual Studio 2005 format begins. Once the conversion is complete, the wizard produces a report so you can see what it did and inspect any errors or warnings. Now you can build the solution, and the compiler will help with the code changes you'll need to get up and running against the .NET Framework 2.0.

Next, you need to re-integrate WSE 3.0 with your solution. Remove the reference to Microsoft.Web.Services2.dll, add a reference to Microsoft.Web.Services3.dll, update your configuration file, and revise any code affected by changes to the WSE 3.0 class library.

The easiest way to get started is to open the WSE configuration tool by right-clicking on each project and selecting WSE Settings 3.0. If the tool detects a WSE 2.0 configuration section in the existing config file, it displays a dialog asking you if you'd like it to automatically import the settings to the new WSE 3.0 configuration section. Say yes and the tool makes the necessary changes to the file (or at least as many as it can). It does things like register the WSE 3.0 configuration section, change the <soapExtensionType> element to the new <soapServerProtocolFactory> element, and move most of the existing WSE 2.0 settings over to the new WSE 3.0 configuration section. Also, the tool automatically updates the reference to Microsoft.Web.Services3.dll for you.

You can also launch the WSE 3.0 configuration tool in standalone mode (outside of Visual Studio .NET) from the Start menu. You'll need to select a configuration file manually (using the File menu), but it will still migrate WSE 2.0 settings forward.

Although the WSE 3.0 configuration tool gives you a great start on the migration process, it doesn't take care of everything. When the tool detects configuration items that don't move forward automatically, it leaves them in place. Then it displays the warning dialog stating that some errors were found, advises you to take a look at the log, and asks if you want to continue. It writes the specific details that need attention to a log file (WseSettingsErrors.log). This is a cue that you have more work to do.

The next step in the process is to deal with any changes that break your code. Breaking changes can be classified into four groups: general class library, Direct Internet Message Encapsulation (DIME)/MTOM, security, and policy/filters.

General Class Library

Although the WSE 3.0 configuration tool will automatically update your project with the new assembly reference and the new configuration information, you still have to manually update your source code to handle some changes to the WSE 3.0 class library. For example, you'll have to update all source files to use the new WSE 3.0 namespace (Microsoft.Web.Services3). Global search-and-replace usually works for this.

Figure 1 highlights the main namespace differences between WSE 2.0 and WSE 3.0. Since most of the architectural changes occurred in the area of policy, WSE 3.0 changed the policy namespace to Microsoft.Web.Services3.Design. As you may have noticed, a few WSE 2.0 namespaces have been removed and a few new ones have been added.

Figure 1 WSE 2.0 to WSE 3.0 Namespace Changes

WSE 2.0 Namespace Corresponding WSE 3.0 Namespace
Microsoft.Web.Services2 Microsoft.Web.Services3
Microsoft.Web.Services2.Attachments (removed)
Microsoft.Web.Services2.Dime (removed)
Microsoft.Web.Services2.Policy Microsoft.Web.Services3.Design
Microsoft.Web.Services2.Policy.Assertions Microsoft.Web.Services3.Design
Microsoft.Web.Services2.Policy.Configuration Microsoft.Web.Services3.Configuration
Microsoft.Web.Services2.Security.Policy Microsoft.Web.Services3.Design
Microsoft.Web.Services2.Security.X509 System.Security.Cryptography.X509Certificates
(didn't exist—similar to Dime classes) Microsoft.Web.Services3.Messaging.Framing
(didn't exist) Microsoft.Web.Services3.Mime

Aside from namespace changes that have been made for WSE 3.0, many of the core library classes have remained intact. For example, typical WSE 2.0 messaging applications (those that use SoapSender/SoapReceiver) should migrate cleanly without a hitch. You simply have more choices now with the new support for ASMX. The areas of the class library that have changed are discussed in the following sections. If your solution compiles after making the necessary namespace changes, it should run as is on WSE 3.0, which still uses the WSE 2.0 pipeline by default when no policy has been applied to the application.

DIME/MTOM

Another area that will need attention is any WSE 2.0 code dealing with DIME attachments. For example, the following WSE 2.0 code returns a JPEG file to the caller as a DIME attachment:

[WebMethod] public void GetFile(string fileName) { SoapContext respContext = ResponseSoapContext.Current; DimeAttachment dimeAttach = new DimeAttachment( "image/jpeg", TypeFormat.MediaType, fileName); respContext.Attachments.Add(dimeAttach); }

In WSE 3.0, the Microsoft.Web.Services2.Dime namespace has been removed from the assembly (there is no longer a DimeAttachment class) and the Attachments property has been removed from the SoapContext class. Therefore, this code must be modified to use the new WSE 3.0 MTOM model.

With MTOM, you simply model binary data (what you used to pass as a DimeAttachment) as a byte array in the signature, as shown in the following:

[WebMethod] public byte[] GetFile(string fileName) { byte[] response = File.ReadAllBytes(filePath); return response; }

Next, you enable MTOM via the WSE 3.0 configuration section (take a look at the Messaging tab in the configuration tool) or by using the RequireMtom property on a proxy. Once enabled, all transmitted byte arrays are automatically encoded using the MTOM optimizations.

If you are currently passing multiple DimeAttachments with a single message, you can port this scenario to MTOM by encapsulating multiple byte arrays in a single class that you use in the signature. The MTOM support automatically encodes all byte arrays it finds in a message.

Security

WSE 3.0 greatly simplifies the process of securing Web services. A suite of turnkey security policy assertions covers the most common cases and scenarios. This new approach to security is built on the improved WSE 3.0 policy framework (more on this shortly).

If you currently use the WSE 2.0 policy framework, focus on moving your current policy files over to the new WSE 3.0 policy format and the new turnkey assertions. Manually running the WSE 3.0 security policy wizard is the easiest way to do this. Since the WSE 2.0 policy approach required very little code, you won't have to touch many source files, just the code that supplies tokens at run time. For example, you'll no longer use PolicyEnforcementSecurityTokenCache (removed in WSE 3.0); instead you'll use SetClientCredential<T>.

If you wrote imperative security code by interacting with the Security property of SoapContext directly, you'll have a little more work to do. The Security property has actually been deprecated in WSE 3.0, so if your code references it, you'll get a compiler warning that indicates you should consider writing a security filter instead. I'll explain this later.

The security classes themselves haven't actually changed much, just how you interact with them. Instead of performing security tasks directly within a WebMethod, WSE encourages you to do it within a security filter implementation that will reside in the WSE pipeline, which makes the security code more reusable. Most of your actual security code can be moved to a security filter class without changes. I'll discuss how to write custom filters and policy assertions in the sections that follow.

The only major class library change on the security front is that the Microsoft.Web.Services2.Security.X509 types have been replaced by equivalent types that now ship with the .NET Framework 2.0 (see System.Security.Cryptography.X509Certificates).

Policy/Filters

It seems lower-level extensibility points are always the hardest to migrate, and that definitely holds true for WSE 3.0. The biggest changes to the WSE 3.0 class library result from new policy architecture. The WSE 3.0 policy framework not only replaces the policy framework introduced with WSE 2.0, it also replaces the old mechanism for working with filters in the pipeline. You need to understand the differences between the WSE 2.0 and WSE 3.0 policy frameworks and how the WSE 3.0 pipeline works to migrate security, policy, and filter-related code.

The WSE 2.0 policy framework relied on WS-Policy as the format for expressing policy. It supported most of the policy assertions defined in the WS-PolicyAssertion and WS-SecurityPolicy specifications. In addition to these built-in assertions, there's an extensibility mechanism for custom policy assertions.

A WSE 2.0 wizard generated security policies containing the built-in security assertions (primarily <Integrity> and <Confidentiality>). The policy was then associated with an application via its configuration file. The policy file itself contained a mapping section to associate policy elements with specific service endpoints based on their URIs. At run time, WSE 2.0 inspected the configured policy file to determine which policy to enforce while sending/receiving messages at a particular URI.

The WSE 2.0 policy framework was implemented as a filter placed nearest to the application in the WSE pipeline (see Figure 2). All policy logic was performed during this particular step of the pipeline. WSE 2.0 made it possible to inject custom code into the policy step by writing and configuring custom policy assertions. WSE 2.0 also gave you direct control over the configuration and manipulation of the entire WSE pipeline. You could inject code into the pipeline before or after the policy step by writing and configuring custom filters.

Figure 2 Policy in the WSE 2.0 Pipeline

Figure 2** Policy in the WSE 2.0 Pipeline **

The WSE 2.0 policy framework contained several shortcomings. First, the different extensibility options often confused matters. It wasn't clear when to write a custom filter and when to write a custom policy assertion.

Second, using WS-Policy as the syntax for configuring policies was far too complicated and verbose for most developers. This forced developers to rely heavily on the security policy wizard and left them somewhat stranded when they had to tweak settings not exposed by the UI.

Third, the mapping mechanism for associating policies with endpoints sometimes led to mismatches. WSE 2.0 attempted to locate the appropriate policy (from the configured policy file) using the target URI of the incoming/outgoing message. Often, you weren't sure if the right policy was actually being applied due to potential URI differences. And each time you changed the location of your services (such as after deployment or by requiring HTTPS on a directory), you had to update all of your policy files to match the current addresses.

The last major shortcoming was that the supported assertions required you to think in terms of low-level building blocks, even while using the nice security policy wizard. For example, you had to specify the requirements of a request message independently from the corresponding response message. In each case, you had to make decisions about signatures and encryption and what type of token to use for each without much direction or best practices. This often led to security policies that weren't very secure.

Policy in WSE 3.0

The most fundamental change in WSE 3.0 is that the policy framework is no longer implemented as a filter in the pipeline. Instead, WSE 3.0 actually uses policy to drive the overall creation of the pipeline (see Figure 3). It essentially merges the worlds of policy assertions and pipeline filters, offering a single unified model for configuring and extending WSE behavior.

Figure 3 Policy in the WSE 3.0 Pipeline

Figure 3** Policy in the WSE 3.0 Pipeline **

WSE 3.0 accomplishes this by making policy assertions and pipeline filters one and the same. When WSE 3.0 initializes, it inspects the supplied policy and builds the pipeline out of the filters corresponding to the specified assertions. If the application is not configured to use policy, it simply creates a default pipeline configuration equivalent to what was used in WSE 2.0.

Since WSE 3.0 uses policy to configure the pipeline, it no longer includes a <filters> element in its configuration section and the configuration tool no longer includes a Filters tab. All pipeline configuration is now accomplished via policy. The corresponding WSE 3.0 classes have also been revised to accommodate this architectural shift.

Another significant change is how you express policy in WSE 3.0. Instead of using the vocabulary defined by WS-Policy and friends, WSE 3.0 decided to define its own vocabulary for expressing policy and built-in assertions. The resulting XML is much cleaner and easier to use—some developers will even feel comfortable editing it directly without the help of a tool. However, WSE 3.0 still provides a security policy wizard for generating policies in the new format, and this is the easiest way to migrate your existing WSE 2.0 policies.

WSE 3.0 also defines a new mechanism for mapping policies to service endpoints. The WSE 3.0 mechanism is more explicit about which policy will be applied at run time. You associate a policy identifier (either a name or a type) with your service or client code. At run time, WSE will find the specified policy using the provided identifier without ambiguity. This shift alleviates the issues that result from mapping policies to service endpoints by URI.

The last major change you'll notice is that WSE 3.0 supports a different set of built-in security assertions. The new security assertions no longer focus on the low-level building blocks of integrity and confidentiality. Instead, they focus on higher-level turnkey security scenarios that represent today's best practices (such as mutual certificate authentication). A single security assertion describes the requirements for both the request and response messages simultaneously using a best-practice approach. Think of it this way: WSE 2.0 assertions focused on message-level security requirements, while the new WSE 3.0 assertions focus on operation-level security requirements.

These WSE 3.0 changes address the shortcomings described earlier, but most of them require significant changes to your code and policy files. Let's dive into some code to see how it all works.

Migrating Custom Filter Code

The major policy concepts I've discussed so far map directly to classes in the WSE 3.0 class library as outlined in Figure 4.

Figure 4 WSE 3.0 Policy and Filter Classes

Concept Type
Policy Microsoft.Web.Services3.Design.Policy
Assertion Microsoft.Web.Services3.Design.PolicyAssertion
Filter Microsoft.Web.Services3.SoapFilter

If you are using custom filters or policy assertions in your existing WSE 2.0 solutions, you'll need to make several changes to bring your code forward to this new class design. Let's walk through the details.

Assume that your services require all incoming requests to be validated against their corresponding schema definitions, and that you've currently implemented that with a WSE 2.0 custom filter.

Writing a custom filter in WSE 3.0 is almost the same as in WSE 2.0. In WSE 2.0, there were separate base classes for input versus output filters (SoapInputFilter versus SoapOutputFilter). In WSE 3.0 there is just a single base class that you derive from: SoapFilter.

SoapFilter contains a single abstract method named ProcessMessage that you need to override, as shown here:

public class ValidationFilter : SoapFilter { public override SoapFilterResult ProcessMessage( SoapEnvelope envelope) { ... // process the message here } }

You write the code to implement the requirement at hand inside ProcessMessage. You might want to inspect the message, transform it, or modify the message context in different ways. The return type for ProcessMessage has also changed in WSE 3.0. You return a SoapFilterResult value (SoapFilterResult.Continue or SoapFilterResult.Stop) to indicate whether the pipeline should continue processing the message.

WSE 3.0 also provides some new statement management collections that are available as part of the SoapContext. You'll find properties called MessageState, OperationState, and SessionState that provide different scopes for sharing state between filters, across message exchanges, and even across multiple operations.

To continue the validation example, say I need to validate the incoming SoapEnvelope against its schema. I'll require the user of the filter to provide the XmlSchemaSet object containing the schemas required for validation in this case. I'll only allow the message to continue in the pipeline if it passes schema validation. The complete ValidationFilter implementation is shown in Figure 5.

Figure 5 ValidationFilter

public class ValidationFilter : SoapFilter { XmlSchemaSet schemas; public ValidationFilter(XmlSchemaSet schemas) { this.schemas = schemas; } public override SoapFilterResult ProcessMessage( SoapEnvelope envelope) { XmlReaderSettings settings = new XmlReaderSettings(); settings.Schemas = this.schemas; settings.ValidationType = ValidationType.Schema; using (XmlReader r = XmlReader.Create( new XmlNodeReader(envelope), settings)) { while (r.Read()) ; // just pass through document } return SoapFilterResult.Continue; } }

If your custom filter deals with security, you need to derive from ReceiveSecurityFilter or SendSecurityFilter, depending on the direction of communication. Both of these classes derive from SoapFilter and provide an extra method related to message security. ReceiveSecurityFilter defines an abstract ValidateMessageSecurity method, while SendSecurityFilter defines an abstract SecureMessage method. Both methods hand you the Microsoft.Web.Services3.Security.Security object that you can use to perform your imperative security tasks. Check out the article "Using the Policy Framework to Secure Web Apps with Web Services Enhancements" for a complete example of how to implement a security filter.

The changes you'll have to make to bring your existing custom filters forward are pretty straightforward—simply change the base class name, modify the return type of ProcessMessage to SoapFilterResult, and modify the implementation accordingly.

With the ValidationFilter example in place, you now need to inject it into the service's receive pipeline. In WSE 2.0, you could accomplish this via the <filters> configuration element or by programmatically injecting an instance into the pipeline at run time. In WSE 3.0, you use the policy framework to configure the pipeline. The first thing you need to do is write a custom policy assertion class to wrap the filter.

Migrating Custom Policy Assertions

In both WSE 2.0 and WSE 3.0, policy assertions are classes that derive from PolicyAssertion. However, the structure of the PolicyAssertion class and the role it plays in the pipeline are completely different.

In WSE 2.0, the role of a PolicyAssertion class was to validate and enforce requirements on incoming and outgoing messages. In WSE 3.0, the role of a PolicyAssertion class is to serve as a factory for SoapFilter objects—in this case the SoapFilter objects validate and enforce the requirements. WSE 3.0 calls each PolicyAssertion class within the configured policy as it builds the send/receive pipeline within a client/service.

In WSE 3.0, you write a custom policy assertion by deriving a new class from PolicyAssertion and overriding its abstract members. The code in Figure 6 illustrates how to get started on your ValidationAssertion.

Figure 6 ValidationAssertion

public class ValidationAssertion : PolicyAssertion { public override SoapFilter CreateClientInputFilter( FilterCreationContext context) { return null; } public override SoapFilter CreateClientOutputFilter( FilterCreationContext context) { return null; } public override SoapFilter CreateServiceInputFilter( FilterCreationContext context) { return null; } public override SoapFilter CreateServiceOutputFilter( FilterCreationContext context) { return null; } }

PolicyAssertion defines four abstract methods that you must implement: CreateClientInputFilter, CreateClientOutputFilter, CreateServiceInputFilter, and CreateServiceOutputFilter. When the policy assertion is used in a client policy, WSE calls the CreateClientXxx form, where Xxx is InputFilter while building the receive pipeline and OutputFilter when creating the send pipeline. When the assertion is used for a service policy, the calls take the form CreateServiceXxx.

These methods give you an opportunity to inject code into the various WSE pipelines as they're built at run time. Notice that each method returns a SoapFilter. WSE takes the returned SoapFilter object and places it in the corresponding pipeline in the order it occurs in the policy. Therefore, the order of assertions in a policy is significant. The first assertion always ends up nearest to the application in the pipeline, while the last assertion always ends up nearest to the wire (see Figure 4).

You don't have to return a SoapFilter from each method unless you actually want it in the corresponding WSE pipeline. For example, if a particular policy assertion never applies on the client side, you can return null from the CreateClientInputFilter and CreateClientOutputFilter methods. In the validation example, you only care about validating messages received by the service, so CreateServiceInputFilter is the only factory method that needs to return a ValidationFilter object.

In this particular example, you need to allow users of the ValidationAssertion class to specify the location of schemas needed for validation. This is so you can load them into an XmlSchemaSet object and supply it to the new ValidationFilter. One way to accomplish this is to have users specify the location in the constructor. The code in Figure 7 shows a sample implementation of the ValidationAssertion example.

Figure 7 ValidationAssertion Implementation

public class ValidationAssertion : PolicyAssertion { XmlSchemaSet schemas = null; public ValidationAssertion() { } public ValidationAssertion(string schemaLocation) { LoadSchemas(schemaLocation); } private void LoadSchemas(string schemaLocation) { schemas = new XmlSchemaSet(); foreach (string file in Directory.GetFiles( schemaLocation)) { using (FileStream fs = new FileStream(file, FileMode.Open, FileAccess.Read)) { schemas.Add(XmlSchema.Read(fs, null)); } } } public override SoapFilter CreateServiceInputFilter( FilterCreationContext context) { return new ValidationFilter(schemas); } ... // remaining methods omitted }

If you have custom policy assertions in your existing WSE 2.0 solutions, you'll need to rework them as SoapFilter implementations and then wrap them with a PolicyAssertion class like the one shown in Figure 7.

Once you have custom PolicyAssertion classes in place, you can begin using them in your client and service policies. In WSE 2.0, you have to define policies using the WS-Policy syntax. The new WSE 3.0 policy framework, however, allows you to define policies imperatively in code or declaratively using an XML vocabulary.

Defining a Policy in Code

In WSE 3.0, the Policy class models a policy containing assertions. It has a public property named Assertions (of type Collection<PolicyAssertion>) for managing a collection of PolicyAssertion instances. You can define a policy programmatically by instantiating a Policy object and adding PolicyAssertion objects to its Assertions collection, as illustrated here:

Policy myPolicy = new Policy(); myPolicy.Assertions.Add(new RequireActionHeaderAssertion()); myPolicy.Assertions.Add(new RequireSoapHeaderAssertion( "MyHeader", "https://example.org/myheader")); myPolicy.Assertions.Add(new ValidationAssertion( HttpContext.Current.Server.MapPath("xsd")));

This policy contains three assertions. The first two are built-in assertions (part of the WSE 3.0 class library), and the last is the custom validation assertion. The first requires the WS-Addressing <Action> header to be present in the message. The second requires a custom header named MyHeader to be present in the message. The last requires the message to pass schema validation. The order of these assertions in the Assertions collection determines the order in which they will appear in the pipeline.

Instead of building the policy from scratch each time you need it, you can also derive a class from Policy and add the PolicyAssertion objects in its constructor, as shown here:

public class MyPolicy : Policy { public MyPolicy() { this.Assertions.Add(new RequireActionHeaderAssertion()); this.Assertions.Add(new RequireSoapHeaderAssertion("MyHeader", "https://example.org/myheader")); this.Assertions.Add(new ValidationAssertion( HttpContext.Current.Server.MapPath("xsd"))); } }

This packages the policy definition into a class that users can simply instantiate whenever they need it. Now that there is a policy in place, you can apply it to either a service or a client proxy.

Applying a Policy to a Service

You apply a policy type to a particular service by using the new [Policy] attribute on your Web service class—this is the new mapping mechanism I referred to earlier. The attribute can be applied to either ASMX or SoapReceiver-derived classes. Here's an example that associates MyPolicy with a simple ASMX class:

[Policy(typeof(MyPolicy))] [WebService(Namespace = "https://example.org/math")] public class MathService: WebService { [WebMethod] public double Add(double x, double y) { return x + y; } ...

This tells WSE that the MyPolicy class should be used to build the pipeline for this service. For this to work, you need to enable WSE 3.0 within the application by using the WSE settings tool. Since it's an ASP.NET Web site in this case, you also need to enable the WSE Soap Protocol Factory (see the General tab). But you don't need to enable policy within the WSE settings tool (via the Policy tab)—you only need to do this when using XML-based policy files. In this case, I've defined the policy in code and associated it with the Web service class so that WSE has everything it needs.

Applying a Policy to a Client

You can apply a policy with a client using one of two techniques. The first technique is to call the new SetPolicy method that you'll find on all WSE 3.0-generated proxy classes. Here's an example:

MathServiceWse proxy = new MathServiceWse(); proxy.SetPolicy(new MyPolicy()); double sum = proxy.Add(2, 3);

If you want to permanently associate the policy with the proxy class, you can annotate it with the [Policy] attribute like you did on the service, alleviating the need to call SetPolicy. Here's an example of this technique:

// attributes omitted for clarity [Policy(typeof(MyPolicy))] [WebServiceBinding(Name="MathService", Namespace="https://example.org/math")] public partial class MathServiceWse : WebServicesClientProtocol { ... }

The ability to define policies in code and apply them like this offers a great deal of run-time flexibility. It allows you to dynamically build policies on the fly, choosing to include or omit assertions based on application state or configuration settings.

It's not hard to imagine how you could specify the entire list of policy assertions in your application configuration file and load them dynamically using the techniques shown in the previous sections. This would allow you to make changes to the policy itself without recompiling or redeploying the code. To provide this flexibility, WSE 3.0 offers a built-in solution for defining policies in XML.

Defining a Policy in XML

Unlike WSE 2.0, WSE 3.0 does not use the WS-Policy vocabulary for expressing policy. Instead, it uses the following vocabulary:

<policies xmlns="https://schemas.microsoft.com/ wse/2005/06/policy"> <policy name="Policy1"> <!-- assertions go here --> </policy> <policy name="Policy2"> <!-- assertions go here --> </policy> <!-- additional policies can go here --> </policies>

The policy file must contain a root <policies> element from the https://schemas.microsoft.com/wse/2005/06/policy namespace. It contains a list of <policy> elements (from the same namespace). You identify each <policy> by using the name attribute—you'll use this name to refer to the policy from within your code.

Each <policy> element contains a list of assertion elements. Each assertion element corresponds to a PolicyAssertion-derived class. The following example defines a policy named MyXmlPolicy with the same two built-in assertions used earlier:

<policies xmlns="https://schemas.microsoft.com/wse/2005/06/policy"> <policy name="MyXmlPolicy"> <requireActionHeader/> <requireSoapHeader name="MyHeader" namespace="https://example.org/myheader"/> </policy> <!-- additional policies can go here --> ... </policies>

Because I'm using built-in assertions, I don't have to specify what PolicyAssertion classes the elements represent—WSE already knows. If you're editing the policy file in Visual Studio 2005, you'll even get IntelliSense® tips showing you all of the built-in assertion elements the IDE knows about (see Figure 8). WSE 3.0 also provides a new wizard for generating security policies in this format; this is the easiest way to move WSE 2.0 security policies forward.

Figure 8 Policy IntelliSense

Figure 8** Policy IntelliSense **

Mapping Assertion Elements to Code

You can explicitly control the mapping between the assertion element names and the corresponding PolicyAssertion classes. The new <extension> element maps an element name to a PolicyAssertion class. The following code shows how to do this for the custom ValidationAssertion:

<policies xmlns="https://schemas.microsoft.com/wse/2005/06/policy"> <extensions> <extension name="validation" type="ValidationAssertion, ValidationAssertion" /> </extensions> <policy name="ValidationPolicy"> <requireActionHeader/> <requireSoapHeader name="MyHeader" namespace="https://example.org/myheader"/> <validation schemaLocation="C:\services\mathservice\xsd"/> </policy> </policies>

You must update the ValidationAssertion class to read the schemaLocation attribute from the <validation> element. PolicyAssertion defines the ReadXml method for this purpose. Figure 9 shows how to implement ReadXml within the ValidationAssertion class.

Figure 9 ReadXml

public class ValidationAssertion : PolicyAssertion { // remaining methods omitted public override void ReadXml(XmlReader reader, IDictionary<string, Type> extensions) { string schemaLocation = reader.GetAttribute("schemaLocation"); reader.Read(); // advance the reader if (null == schemaLocation) throw new Exception("Expected schemaLocation attribute"); LoadSchemas(schemaLocation); } }

This implementation expects to find the schema location in an attribute named schemaLocation. Notice how it advances the reader so that it will be positioned properly for the next assertion. With this in place, users can now choose whether to use this assertion in code or XML-based policy.

Configuring and Applying an XML Policy

To apply an XML-based policy to your services and clients, you must first configure the application with the location of the policy file. You can do this by using the Policy tab in the WSE settings tool, or you can just add the following section to your application configuration file:

<configuration> ... <microsoft.web.services3> <policy fileName="wse3policyCache.config" /> </microsoft.web.services3> </configuration>

Once WSE knows where to look for your XML-based policy, you can apply it to your code by specifying the policy name (found in the policy file) when using the [Policy] attribute, as shown here:

[Policy("MyXmlPolicy")] [WebService(Namespace = "https://example.org/math")] public class MathService: WebService { [WebMethod] public double Add(double x, double y) { return x + y; } ...

You can do the same thing when calling SetPolicy on a proxy class, as illustrated here:

MathServiceWse proxy = new MathServiceWse(); proxy.SetPolicy("MyXmlPolicy"); double sum = proxy.Add(2, 3);

Defining policies in XML and configuring your code this way offers the best compromise between flexibility and developer productivity—it's probably the approach most developers will take.

As you can see, once you've migrated your custom filters and policy assertions forward to WSE 3.0, you have to decide how to define your policies (in code or XML). Then you have to update your client/service code to apply the policies using the [Policy] attribute or the SetPolicy method, since the mapping is no longer part of the policy file itself. All of these changes require some manual intervention and even redesign.

Now Where?

WSE 3.0 Resources

To learn more about the WSE 3.0 policy framework, with a focus on the new approach to security, check out Tomasz Janczuk's article, "WSE Security: Protect Your Web Services Through the Extensible Policy Framework in WSE," in the February 2006 issue of MSDN Magazine.

Also read What's New in WSE 3.0, by Mark Fussell, and Security Features in WSE 3.0, by Keith Brown.

You will also find these resources helpful: WSE 3.0 Hands-On Lab—Exploring Security, and Web Services Policy Framework (WS-Policy).

Migrating existing solutions to WSE 3.0 requires more effort in some areas than in others. Now that you're armed with knowledge about some of the easier migration scenarios and the revamped WSE 3.0 policy framework, where the tougher migration issues exist around custom filters and policy assertions, you should be well prepared for your own migration.

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

Aaron Skonnard is a cofounder of Pluralsight, a Microsoft .NET training provider. Aaron is the author of Pluralsight's Applied Web Services 2.0, Applied BizTalk Server 2006, and Introducing Windows Communication Foundation courses. Aaron has spent years developing courses, speaking at conferences, and teaching professional developers. Reach him at pluralsight.com/aaron.