Implementing a Custom WSE Output Filter
John Bristowe
Empowered Software Solutions, Inc.
Summary
Web Services Enhancements 1.0 for Microsoft .NET (WSE) provides the ability to incorporate security, routing, and attachment capabilities in XML Web services. It also provides an extensible framework to incorporate other features such as custom message filtering. This article will briefly describe the WSE architecture along with its extensibility mechanism through custom filters. It will also provide an example of a custom WSE output filter that facilitates targeting Groove Web Services (GWS) SOAP endpoints.
Introduction
Web Services Enhancements 1.0 for Microsoft .NET (WSE) is a .NET class library (Microsoft.Web.Services.dll) that allows you to integrate the latest Web services protocols into your applications. In particular, it provides you the ability to incorporate protocols based on Global XML Web Services Architecture (GXA) specifications. WSE supports protocols that address security (WS-Security), routing (WS-Routing and WS-Referral), and binary attachment (WS-Attachments with DIME) capabilities and requirements. It also provides an extensible framework for you incorporate propriety protocols that WSE does not support.
SOAP headers are considered application-neutral elements that may be factored-in to provide richer semantics to a SOAP message. These headers provide a context for you to piggyback additional information that typically is used across multiple applications and problem domains. That stated, SOAP provides us a mechanism of augmenting this information into SOAP messages by providing a clean separation of application-level semantics (SOAP body) and protocol/infrastructure-level semantics (SOAP header):
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope">
<soap:Header>
<!-- infrastructure semantics go here -->
</soap:Header>
<soap:Body>
<!-- application-specifc semantics go here -->
</soap:Body>
</soap:Envelope>
|
Augmenting SOAP messages with headers "in the raw" is a pretty simple process; the .NET Framework provides numerous classes to manipulate an XML document/stream. However, manipulating SOAP message headers at this level often obscures application logic. A better solution would have headers applied at the application boundary through an interception mechanism a la MTS/COM+. Rather than forcing developers to apply infrastructure-style logic in their applications, a framework is required to support this type of extensibility. For this purpose, WSE provides a pipeline (Microsoft.Web.Service.Pipeline) of input and output filters to target these headers. These filters can modify elements in the body of the SOAP message (i.e. Microsoft.Web.Services.Security.SecurityOutputFilter) but in general, their role is to provide semantics in the SOAP header.
It should be noted that not many endpoints support GXA-based protocols at this point in time. In all likelihood, these endpoints may incorporate propriety SOAP headers that address the exact things that the GXA specifications were meant to address; authentication, routing, etc. As GXA is more broadly supported, you'll start to see familiar SOAP headers described in the service's operations outlined by its WSDL-based document. Until that day comes, you're best to implement a custom filter to deal with these headers so that you don't have to worry about them later on when you're developing your applications.
Pipeline and Filters
The WSE architecture consists of a filter processing model as follows:

Figure 1: Filter processing model of WSE
Inbound SOAP messages are processed through a pipeline (Microsoft.Web.Services.Pipeline) of one or more input filters (Microsoft.Web.Services.SoapInputFilter). These input filters are responsible for validating applicable elements found in the SOAP message header and loading its information into context (Microsoft.Web.Services.SoapContext). Applicable elements are those related to the processing filter in the pipeline. For example, the Microsoft.Web.Services.Routing.RoutingInputFilter class processes elements routing elements defined by the namespace URI, http://schemas.xmlsoap.org/rp/. When these elements are processed, their information is loaded into context. Context provides a means to communicate between filters and a hosting application; it can be thought of as a container that aggregates a set of filters.
Outbound SOAP messages are processed through a pipeline of one or more output filters (Microsoft.Web.Services.SoapOutputFilter). Output filters are similar to input filters in the sense that they are applied in a pipeline, loading relevant information into context. However, applying an output filter causes the WSE engine to add elements to the header of the outbound SOAP message. In some cases, output filters may also transform the body of the outbound SOAP message.

Figure 2: The output filter processing model of WSE
A Deeper Look at Output Filters
Output filters inherit from the Microsoft.Web.Services.SoapOutputFilter class:
public abstract class SoapOutputFilter
{
public SoapOutputFilter : base() { }
public virtual abstract void ProcessMessage(SoapEnvelope envelope);
}
|
SoapOutputFilter contains one abstract method which you are responsible to implement; ProcessMessage. This method is called by the WSE pipeline to modify the contents - namely, the SOAP message header - of the SOAP envelope (Microsoft.Web.Services.SoapEnvelope) passed in as an input parameter. SoapEnvelope is a derived type of System.Xml.XmlDocument and is represented as an in-memory tree structure or Document Object Model (DOM). This makes writing a custom filter simple because the DOM greatly facilitates creating and manipulating nodes.
While implementing a custom output filter, the important thing to keep in mind is that most - if not, all - of your data will be obtained via SoapContext. Communication between filters and an application is achieved through properties stored in context. (Internally, the SoapContext class contains a System.Collections.Hashtableobject for storing the data that is passed between filters and your application.) Therefore, it is important to perform validation and fail when anomalies occur to prevent unnecessary round-trips to the endpoint.
GrooveOutputFilter
I recently downloaded and installed the Preview Edition of Groove Workspace v2.5, a real-time collaboration application. (For those of you who haven't tried it yet I highly recommend you give it a whirl.) While reading through its documentation, I stumbled upon Groove Web Services (GWS). As it turns out, Groove Workspace v2.5 supports an open and extensible API that provides programmatic access to various Groove objects including Files, Discussion, and many others.
For a more detailed discussion of about GWS, please reference the tech notes listed at the Groove DevZone.
All requests made against the Groove Access Point require a mandatory header for authentication purposes:
<GrooveHeader>
<GrooveTargetName />
<TimeToLive />
<GrooveIdentityURL />
<GrooveNonce />
</GrooveHeader>
|
/GrooveHeader
This element contains credentials for a Groove account
/GrooveHeader/GrooveTargetName
A URI that denotes the target name registered on the Groove Access Point and is used for remote access
/GrooveHeader/TimeToLive
The lifetime of the request made by the client
/GrooveHeader/GrooveIdentityURL
The Groove identity making the request and is expressed as a URI
/GrooveHeader/GrooveNonce
A nonce value obtained from a registry key persisted in the registry of the client machine. Its value is auto-generated every time Groove Workspace is launched.
With this schema defined, one can write an output filter (Groove.Web.Services.GrooveOutputFilter) that inserts a GrooveHeader element in the header of all outbound SOAP messages:
using Microsoft.Web.Services;
using Microsoft.Win32;
using System;
using System.Xml;
namespace Groove.Web.Services
{
public class GrooveOutputFilter : SoapOutputFilter
{
public override void ProcessMessage(SoapEnvelope envelope)
{
if (envelope == null) throw new ArgumentNullException("envelope");
bool isLocalInvocation = envelope.Context["target"] == null;
XmlElement soapHeader = envelope.CreateHeader();
XmlElement grooveHeader = envelope.CreateElement("GrooveHeader", "http://webservices.groove.net/Groove/1.0/Core/");
soapHeader.AppendChild(grooveHeader);
XmlElement grooveHeaderTtl = envelope.CreateElement("TimeToLive", "http://webservices.groove.net/Groove/1.0/Core/");
if (envelope.Context["ttl"] == null) grooveHeaderTtl.InnerText = 30";
else grooveHeaderTtl.InnerText = envelope.Context["ttl"].ToString();
grooveHeader.AppendChild(grooveHeaderTtl);
string identityUrl = envelope.Context["identityUrl"] as string;
if (identityUrl != null)
{
XmlElement grooveIdentityUrl = envelope.CreateElement("GrooveIdentityURL", "http://webservices.groove.net/Groove/1.0/Core/");
grooveIdentityUrl.InnerText = identityUrl;
grooveHeader.AppendChild(grooveIdentityUrl);
}
if (isLocalInvocation)
{
RegistryKey grooveRegistryKey = Registry.CurrentUser.OpenSubKey("Software\\Groove Networks, Inc.\\Groove\\WebServices");
if (grooveRegistryKey != null)
{
string nonce = (string) grooveRegistryKey.GetValue("WebServicesNonce");
XmlElement grooveNonce = envelope.CreateElement("GrooveNonce", "http://webservices.groove.net/Groove/1.0/Core/");
grooveNonce.InnerText = nonce;
grooveHeader.AppendChild(grooveNonce);
}
}
else
{
string target = envelope.Context["target"] as string;
XmlElement grooveTarget = envelope.CreateElement("TargetURL", "http://webservices.groove.net/Groove/1.0/Core/");
grooveTarget.InnerText = target;
}
}
}
}
|
In this code, the key values obtained from the SoapContext class are names I chose to closely represent their associated elements. You can name these keys anything you want provided that they are referenced correctly by the application when storing data into context. Furthermore, it should be noted that local invocations of GWS (denoted by a "target" key value in the SoapContext's Hashtable collection) do not require specifying the TargetURL of an endpoint. However, they do require the nonce value obtained from the registry.
Once the code has been written and verified, we need to configure our application to make use this filter for outbound SOAP messages. Normally, this task would require us to open our configuration file (applicationexe.config or web.config) and add the appropriate elements. Fortunately, Microsoft has provided the Web Services Enhancements Settings Tool to make this procedure a lot simpler.
Web Services Enhancements Settings Tool
WSE features a Visual Studio .NET add-in (official termed the Web Services Enhancements Settings tool) that allows you to manage your WSE-specific application settings:

Figure 3: WSE Settings Tool VS.NET Add-in
The WSE Settings tool is super-convenient, especially when you consider that most of the WSE "plumbing" is driven through configuration.
The tool features five tabs, some of which target a particular aspect of the WSE processing model:

Figure 4: WSE Settings Tool
Note that in order for our application to use WSE, we need to add an assembly reference to Microsoft.Web.Services.dll. Checking the first checkbox in Figure 3 performs this operation on our behalf.

Figure 5: Microsoft.Web.Services assembly reference
Next, we need to add our output filter to the pipeline by adding a filter element to our application's configuration:
<microsoft.web.services>
<filters>
<output>
<add type="Groove.Web.Services.GrooveOutputFilter, GrooveClient"/>
</output>
</filters>
</microsoft.web.services>
|
Alternatively, we could use the WSE Settings Tool:

Figure 6: Customized filters
At this point, it's simply a matter of invoking the endpoint:
GrooveAccounts grooveAccounts = new GrooveAccounts();
grooveAccounts.Url = "http://localhost:9080/GWS/Groove/1.0/Accounts/";
Account[] accounts = grooveAccounts.Read();
|
Let's take a look at what was sent across the wire:
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Header>
<GrooveHeader xmlns="http://webservices.groove.net/Groove/1.0/Core/">
<TimeToLive>30</TimeToLive>
<GrooveNonce>zykws89vuafczwydq4328pt7j9yyc7qf9tnkdii</GrooveNonce>
</GrooveHeader>
<wsrp:path soap:actor="http://schemas.xmlsoap.org/soap/actor/next" soap:mustUnderstand="1" xmlns:wsrp="http://schemas.xmlsoap.org/rp">
<wsrp:action>http://www.groove.net/Groove/1.0/Account#Read</wsrp:action>
<wsrp:to>http://localhost:9080/GWS/Groove/1.0/Accounts/</wsrp:to>
<wsrp:id>uuid:d8fb4b99-ff28-4d47-87ab-421263f4d7bd</wsrp:id>
</wsrp:path>
<wsu:Timestamp xmlns:wsu="http://schemas.xmlsoap.org/ws/2002/07/utility">
<wsu:Created>2003-02-13T05:26:26Z</wsu:Created>
<wsu:Expires>2003-02-13T05:31:26Z</wsu:Expires>
</wsu:Timestamp>
</soap:Header>
<soap:Body>
<Read xmlns="http://webservices.groove.net/Groove/1.0/Accounts/" />
</soap:Body>
</soap:Envelope>
|
Some of you may have noticed that routing and timestamp-related headers are added to the outbound SOAP message. This is because the pipeline is pre-configured (by default) with the following output filters:
SecurityOutputFilter
TimestampOutputFilter
ReferralOutputFilter
RoutingOutputFilter
|
Of course, nothing prevents you from removing these headers from your outbound message:
GrooveAccounts grooveAccounts = new GrooveAccounts();
grooveAccounts.Url = "http://localhost:9080/GWS/Groove/1.0/Accounts/";
grooveAccounts.Pipeline.OutputFilters.Remove(typeof(RoutingOutputFilter));
grooveAccounts.Pipeline.OutputFilters.Remove(typeof(TimestampOutputFilter));
Account[] accounts = grooveAccounts.Read();
|
Here's what the output looks like now:
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Header>
<GrooveHeader xmlns="http://webservices.groove.net/Groove/1.0/Core/">
<TimeToLive>30</TimeToLive>
<GrooveNonce>zykws89vuafczwydq4328pt7j9yyc7qf9tnkdii</GrooveNonce>
</GrooveHeader>
</soap:Header>
<soap:Body>
<Read xmlns="http://webservices.groove.net/Groove/1.0/Accounts/" />
</soap:Body>
</soap:Envelope>
|
Notice how the routing and timestamp-related headers have now been removed from the SOAP message header.
Here's the response we got back from GWS after invoking the Accounts endpoint:
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Header />
<soap:Body>
<ReadResponse xmlns="http://webservices.groove.net/Groove/1.0/Accounts/">
<ReadResult xmlns="http://webservices.groove.net/Groove/1.0/Accounts/">
<Account>
<URI>2dgf4z9g8jfsd3nqmxv9w7gg93fcazzqr45r6fi</URI>
<Name>jbristowe</Name>
<Identities>
<Identity>
<URI>grooveIdentity://asjd2tqkw23jsffwzdnhe2bvwbsi1ed@</URI>
<Name>jbristowe</Name>
<Spaces>/GWS/Groove/1.0/Spaces/grooveIdentity/asjd2tqkw23jsffwzdnhe2bvwbsi1ed@</Spaces>
</Identity>
</Identities>
<Contacts>/GWS/Groove/1.0/Contacts/grooveIdentity/asjd2tqkw23jsffwzdnhe2bvwbsi1ed@</Contacts>
</Account>
</ReadResult>
</ReadResponse>
</soap:Body>
</soap:Envelope>
|
The response contains the following information about the account (jbristowe):
/Account
This element contains information about a Groove account
/Account/URI
The Groove account URL (excluding the grooveAccount: URI scheme)
/Account/Name
The name of the Groove account
/Account/Identities
A collection of one of more Groove identities
/Account/Identities/Identity
This element contains information about a Groove identity
/Account/Identities/Identity/URI
The URI of the identity
/Account/Identities/Identity/Name
The name of the Groove account for this particular identity
/Account/Identities/Identity/Spaces
URI that provides access to the GrooveSpaces service for this identity (excluding the server URL)
/Account/Contacts
URI that provides access to the GrooveContacts service for this identity (excluding the server URL)
With the identity URI, we can conduct some interesting queries against GWS:
Account[] accounts = grooveAccounts.Read();
foreach (Account account in accounts)
{
foreach (Identity identity in account.Identities)
{
GrooveContacts grooveContacts = new GrooveContacts();
grooveContacts.Url = "http://localhost:9080/GWS/Groove/1.0/Contacts";
grooveContacts.RequestSoapContext["identityUrl"] = identity.URI;
Contact[] contacts = grooveContacts.Read();
foreach (Contact contact in contacts)
{
Console.WriteLine("Name: {0}", contact.Name);
}
}
}
Console.ReadLine();
|
Here's the output of this query:

Figure 7: Console output
As expected, this output (above) matches what I have in my Groove contact list:

Figure 8: My Contacts (Groove Workspace)
You can achieve very high levels of integration between applications and Groove via GWS. For example, you could write a service that moves contact information to-and-from CRM applications you may have running at your business. With GWS, the sky's the limit in terms of what you can do. This is evidenced by various individuals who have already begun integrating Groove and applications they use on a day-to-day basis. One great example of this integration is a tool written by Tim Knip (Groove) that integrates with Radio Userland: http://radio.weblogs.com/0107414/stories/2003/01/14/grooveInteropToolForRadio.html.
Summary
WSE is a powerful addition to the ASP.NET Web services infrastructure that supports the latest Web services protocols while providing an extensible framework to augment addition headers into SOAP messages. By leveraging off this extensibility through custom filters, developers can achieve higher levels of reusability when writing applications that target SOAP endpoints.
About the Author
John Bristowe is a Senior Solution Developer Architect with Empowered Software Solutions (ESS), a .NET consulting firm based in Burr Ridge, Illinois. He is a member of the International .NET Association (INETA) Infrastructure Committee and an active member of the Chicago .NET User Group (CNUG). John is presently focused on the .NET Framework and XML Web service plumbing and protocols. He may be reached at john.bristowe@empowered.com.