Declarative WSE pipeline configuration
Note: this entry has moved.
Configuring the WSE pipeline (1.x) is explained in the Configure a Custom Filter section in an MSDN article that also explains how to manipulate programatically the pipeline. The configuration, however, only allows ADDING a filter, not removing or changing the filters execution order or position. In this post I will explain how to achieve what I explained at the end of a previous post, that is, have complete declarative control over WSE pipeline configuration.
Basically, what I wanted is to have a configuration file as follows:
<configuration>
<configSections>
<section name="wse.configuration"
type="Wse.WseConfiguration, Wse" />
</configSections>
<wse.configuration>
<!-- filters: selection of WSE filters that will be applied to incoming and outgoing messages. -->
<filters>
<!-- Disable routing features -->
<remove type="Microsoft.Web.Services.Routing.RoutingInputFilter, Microsoft.Web.Services, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
<remove type="Microsoft.Web.Services.Routing.RoutingOutputFilter, Microsoft.Web.Services, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
<!-- Disable referral features -->
<remove type="Microsoft.Web.Services.Referral.ReferralInputFilter, Microsoft.Web.Services, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
<remove type="Microsoft.Web.Services.Referral.ReferralOutputFilter, Microsoft.Web.Services, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
<!-- Input filters -->
<add at="0" type="Wse.SignatureInputFilter, Wse" />
<move to="0" type="Microsoft.Web.Services.Diagnostics.TraceInputFilter, Microsoft.Web.Services, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
<!-- Output filters -->
<add type="Wse.Filters.SignatureOutputFilter, Wse" />
<move to="0" type="Microsoft.Web.Services.Diagnostics.TraceOutputFilter, Microsoft.Web.Services, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
</filters>
</wse.configuration>
</configuration>
And have WSE pipeline configured accordingly. In a web application, I would have
to force the appropriate config. handler to run at the Application_Start
event by issuing one of the following:
FilterConfiguration filters = (FilterConfiguration)
ConfigurationSettings.GetConfig(WseConfiguration.SectionName);
//Or
WseConfiguration.Initialize();
This involves creating a section handler and processing each of the elements in
the <filters>
node. The method that is called with the
handler is created looks as follows:
public void Process(object parent, object configContext, XmlNode section)
{
CheckAtMostOneElement(section as XmlElement, ElementName);
//Process directives in document order.
XPathNodeIterator nodes = section.CreateNavigator().Select(_expression);
while (nodes.MoveNext())
{
XmlElement node = ((IHasXmlNode)nodes.Current).GetNode() as XmlElement;
if (String.Compare(node.LocalName, Add, true) == 0)
{
AddFilter(node);
}
else if (String.Compare(node.LocalName, Remove, true) == 0)
{
RemoveFilter(node);
}
else if (String.Compare(node.LocalName, Move, true) == 0)
{
MoveFilter(node);
}
else
{
ThrowUnknownElement(node.ParentNode as XmlElement, node);
}
}
}
I'm caching the expression that locates the filters so improve performance, and to make the code more maintainable. I keep all element and attribute names in constants:
const string ElementName = "filters";
const string Input = "input";
const string Output = "output";
const string Add = "add";
const string Move = "move";
const string Remove = "remove";
const string At = "at";
const string To = "to";
const string FilterType = "type";
Of course, the real work happens in AddFilter
, RemoveFilter
and MoveFilter
. As WSE has a clear distiction between input and
output filters by means of different base classes for them, SoapInputFilter
and SoapOutputFilter
, we don't need to bother to specify which
kind of filter we're adding/removing/moving, and we can instead infer this in
those helper methods. These methods use the approach explained in
the aforementioned
MSDN article to pipeline handling. Here's the AddFilter
one:
private void AddFilter(XmlElement node)
{
CheckRequiredAttribute(node, FilterType);
Type t = Type.GetType(node.Attributes[FilterType].Value);
if (!(typeof(SoapInputFilter).IsAssignableFrom(t) || typeof(SoapOutputFilter).IsAssignableFrom(t)))
{
throw new ArgumentException("Invalid filter type: " + t.AssemblyQualifiedName);
}
object filter = Activator.CreateInstance(t);
XmlAttribute at = node.Attributes[At];
//Add or insert the filter at the appropriate position if specified.
if (at != null)
{
//We already checked that it's either an input or an output filter.
if (filter is SoapInputFilter)
{
WebServicesConfiguration.FilterConfiguration.InputFilters.Insert(Convert.ToInt32(at.Value), (SoapInputFilter)filter);
}
else
{
WebServicesConfiguration.FilterConfiguration.OutputFilters.Insert(Convert.ToInt32(at.Value), (SoapOutputFilter)filter);
}
}
else
{
//We already checked that it's either an input or an output filter.
if (filter is SoapInputFilter)
{
WebServicesConfiguration.FilterConfiguration.InputFilters.Add((SoapInputFilter)filter);
}
else
{
WebServicesConfiguration.FilterConfiguration.OutputFilters.Add((SoapOutputFilter)filter);
}
}
}
Note that we insert or add depending on the presence of the @at attribute. The
RemoveFilter
does almost the same, so I won't show the full code.
The move filter is slightly more complicated as we can only move around filters
that already exist on the pipeline. Fortunately the SoapInputFilterCollection
(and the output too) class exposes an IndexOf
method that receives
the filter type to locate, which comes handy now:
I've provided this code as a C# project in the ASPNET2
Incubator (work-in-progress still), and you can download it from
here. I also included the two signature filters discussed in the
aforementioned post for reducing WSE message payload by avoiding
sending the binary security token when it is agreed and distributed previously
between secured parties/servers. Look for the SecurityTokenHelper.cs
file. Its static constructor should load the SecurityToken
you
will use to secure communication.