Exposing Custom WCF Headers through WCF Behaviors - Part 2
In part 1 we covered how to create a custom behavior to inject header data into the dynamically created WSDL.
In this part we will look at consuming the header data passed in.
By default BizTalk will take any custom header it finds in the incoming WCF message and automatically map them to the Message Context.
If it were really this simple we wouldn't need this posting.
So, what is the issue. The issue is that when BizTalk maps the header to the context it posts an xml fragment. This fragment could certainly be used as is and parsed each time you need to use it but that gets tedious quickly and certainly doesn't do good things to the performance of your solution.
What we need is to be able to parse the key and value of the header data when the message is submitted to BizTalk so that it looks like all of the other context entries (a key and a value pair).
There are a number of options to enable you to do this including creating a pipeline component. We are not going to go that route. Instead, we are going to add code directly to our behavior. I want everything to be encapsulated inside the behavior so that if developers decide to use the behavior they don't have to also remember to place a pipeline in the mix. By having a separate pipeline component we are creating an error prone system that won't be caught until after deployment has occurred.
To promote or write to the context when the message arrives we will modify the AfterReceiveRequest method on the SoapHeaderMessageInspector class. This class was created in Part 1 of this series. If you go back and look at that method you will see that we originally implemented it by returning null.
First lets look at what is required to write or promote to the Message Context in code. MSDN has a sample of how this can be done which I put below.
const string PropertiesToPromoteKey="https://schemas.microsoft.com/BizTalk/2006/01/Adapters/WCF-properties/Promote";
const string PropertiesToWriteKey="https://schemas.microsoft.com/BizTalk/2006/01/Adapters/WCF-properties/WriteToContext";
XmlQualifiedName PropName1=new XmlQualifiedName("Destination", "https://tempuri.org/2007/sample-properties");
XmlQualifiedName PropName2=new XmlQualifiedName("Source", "https://tempuri.org/2007/sample-properties");
//Create a List of KeyValuePairs that indicate properties to be promoted to BizTalk message context.
//A Property Schema must be deployed and string values have a limit of 256 characters
List<KeyValuePair<XmlQualifiedName, object>> promoteProps=new List<KeyValuePair<XmlQualifiedName, object>>();
promoteProps.Add(new KeyValuePair<XmlQualifiedName, object>(PropName1, "Property value"));
wcfMessage.Properties[PropertiesToPromoteKey]=promoteProps;
//Create a List of KeyValuePairs that indicate properties to be written to BizTalk message context
List<KeyValuePair<XmlQualifiedName, object>> writeProps=new List<KeyValuePair<XmlQualifiedName, object>>();
writeProps.Add(new KeyValuePair<XmlQualifiedName, object>(PropName2, "Property value"));
wcfMessage.Properties[PropertiesToWriteKey]=writeProps;
We are going to use this code but will format it a bit differently. As I said earlier we need to modify the AfterReceiveRequest method to incorporate this code.
Our method implementation will look like:
public object AfterReceiveRequest(ref System.ServiceModel.Channels.Message request, System.ServiceModel.IClientChannel channel, System.ServiceModel.InstanceContext instanceContext)
{
List<KeyValuePair<XmlQualifiedName, object>> writeProps = new List<KeyValuePair<XmlQualifiedName, object>>();
const string PropertiesToWriteKey = "https://schemas.microsoft.com/BizTalk/2006/01/Adapters/WCF-properties/WriteToContext";
Int32 headerPosition = OperationContext.Current.IncomingMessageHeaders.FindHeader(SoapHeaderNames.SoapHeaderName, SoapHeaderNames.SoapHeaderNamespace);
if (headerPosition < 0)
{
//Fault Condition
throw new ArgumentNullException(SoapHeaderNames.SoapHeaderNamespace + "#" + SoapHeaderNames.SoapHeaderName, "SoapHeader not found.");
}
// Get an XmlDictionaryReader to read the header content
XmlDictionaryReader reader = OperationContext.Current.IncomingMessageHeaders.GetReaderAtHeader(headerPosition);
XmlDocument d = new XmlDocument();
d.LoadXml(reader.ReadOuterXml());
foreach (XmlNode node in d.DocumentElement.ChildNodes)
{
if ((node.Name.ToLower().Equals(SoapHeaderNames.AppName.ToLower()) || node.Name.ToLower().Equals(SoapHeaderNames.UserName.ToLower())) && String.IsNullOrEmpty(node.InnerText))
{
throw new ArgumentNullException(node.Name, "Header value cannot be null.");
}
XmlQualifiedName PropName1 = new XmlQualifiedName(node.Name, SoapHeaderNames.SoapHeaderNamespace);
writeProps.Add(new KeyValuePair<XmlQualifiedName, object>(PropName1, node.InnerText));
}
if (writeProps.Count > 0)
{
request.Properties[PropertiesToWriteKey] = writeProps;
}
return null;
}
This code shows how we can select and read the header, and then loop through each element in the header and promote it.
In order to promote properties into the context you need to have a property schema. We took the SoapHeader.xsd that we created in Part 1 of this post and used that for our property schema.
When we take a look at the content of the incoming message after compiling and deploying our changes we can now see that our key name appears under the Name column and our value appears under the Value column of the Context dialog box. We no longer have an xml fragment and no longer have to deal with the need to parse the fragment each time we want to use it. Now that we have this data in the context we can utilize it in the same we would with any other data that appears in the context. The best part is that it was all done in one location, through one artifact, and won't require the developer to remember to utilize another artifact to make the solution work.
In the next post, we will cover the ability to create a behavior that exposes the properties through configuration to let you dynamically, per end point, set the header items as well as determine whether you want the values written or promoted dynamically as well.