SOAWorld 2009 session
Thanks to all the folks who attended to my session about WOA at SOAWorld 2009. I got a lot of interesting questions from an always sharp New York audience :). You can get the slide deck here…(read more)
Thanks to all the folks who attended to my session about WOA at SOAWorld 2009. I got a lot of interesting questions from an always sharp New York audience :). You can get the slide deck here…(read more)
San Diego Code Camp is this weekend! I’ll be there on Saturday, doing two presentations:
If you’ve never been to a Code Camp before, you should check it out. It’s an “anything goes” community-driven conference-ish event. Lots of high quality sessions, at a recession-friendly cost (free). Lots of learning opportunities. What’s not to like?
As usual, this will be at UCSD La Jolla. See you there!
Full details are at http://www.socalcodecamp.com/
.style1 {
font-family: “Courier New”, Courier, monospace;
font-size: x-small;
}
.style2 {
font-family: “Courier New”, Courier, monospace;
font-size: x-small;
}
.style3 {
font-size: x-small;
}
.style5 {
font-size: x-small;
}
.style6 {
text-align: center;
}
In part 1, I covered how to create a custom behavior to inject headers into the dynamically created WSDL. In part 2, I showed how to either promote or write the header data to the BizTalk context.
What happens if I want different headers for different end points? What if I don’t want to create a custom header component for each end point? What if I want to set, through configuration, weather I want to promote or write to the context? What if I want to add a new header item without the need to recompile?
In this part of the series we will look at the ability to create a behavior that exposes the properties through configuration to let you dynamically, per end point, set the header items. The configuration is not through a configuration file but instead will hook into the end point behavior dialog box that appears in the adapter configuration in BizTalk.
The finished configuration will look like this:
Let’s start looking at code.
First we are going to look at a new class file that will represent the data that we need to set in the configuration section of the dialog box. The CustomHeader class is where much of the configuration dialog magic happens. The way you define the properties will define the way they appear in the configuration dialog box. If you define your property as a boolean or an enum then it will display as a drop down list box. If you define it as a class then you will get the ellipses.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Services.WCF.Behavior
{
public class CustomHeader
{
public string Name { get; set; }
public string Namespace { get; set; }
public bool Required { get; set; }
public ContextAction Action { get; set; }
}
}
Where the ContextAction type is the enum listed below:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Services.WCF.Behavior
{
public enum ContextAction
{
None = 0,
Write = 1,
Promote = 2
}
}
The CustomHeader class is accessed and ‘bound’ to the dialog box through the CustomHeaderEndpointBehavior class. This class is just a renamed and modified version of the SoapHeaderEndpointBehavior class we saw in the previous two articles.
using System;
using System.Collections.Generic;
using System.Text;
using System.ServiceModel.Description;
using System.ServiceModel.Configuration;
using System.Configuration;
using System.ServiceModel;
namespace Services.WCF.Behavior
{
public class CustomHeaderEndpointBehavior : BehaviorExtensionElement, IEndpointBehavior
{
#region BehaviorExtensionElement Methods
public override Type BehaviorType
{
get { return typeof(CustomHeaderEndpointBehavior); }
}
protected override object CreateBehavior()
{
return new CustomHeaderEndpointBehavior(this.Headers);
}
//Copies the content of the specified configuration element to this configuration element
public override void CopyFrom(ServiceModelExtensionElement extFrom)
{
base.CopyFrom(extFrom);
CustomHeaderEndpointBehavior element = extFrom as CustomHeaderEndpointBehavior;
if (element != null)
{
Headers = element.Headers;
}
}
//Both properties are returned as a collection.
protected override ConfigurationPropertyCollection Properties
{
get
{
if (_properties == null)
{
_properties = new ConfigurationPropertyCollection();
_properties.Add(new ConfigurationProperty(“Headers”, typeof(List<CustomHeader>), null,
new SerializationConverter(typeof(List<CustomHeader>)), null, ConfigurationPropertyOptions.None));
base[“Headers”] = new List<CustomHeader>();
}
return _properties;
}
}
#endregion
#region IEndpointBehavior Members
public void AddBindingParameters(ServiceEndpoint endpoint, System.ServiceModel.Channels.BindingParameterCollection bindingParameters)
{
bindingParameters.Add(this);
}
public void ApplyClientBehavior(ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.ClientRuntime clientRuntime)
{
//throw new NotImplementedException();
}
public void ApplyDispatchBehavior(ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.EndpointDispatcher endpointDispatcher)
{
endpointDispatcher.DispatchRuntime.MessageInspectors.Add(new CustomHeaderMessageInspector());
}
public void Validate(ServiceEndpoint endpoint)
{
//throw new NotImplementedException();
}
#endregion
#region Class properties
[ConfigurationProperty(“Headers”)]
public List<CustomHeader> Headers
{
get
{
return (List<CustomHeader>)base[“Headers”];
}
set
{
base[“Headers”] = value;
}
}
#endregion
#region Class Fields
private ConfigurationPropertyCollection _properties;
#endregion
#region Constructors
public CustomHeaderEndpointBehavior(List<CustomHeader> headers)
{
this.Headers = headers;
}
public CustomHeaderEndpointBehavior() : base()
{}
#endregion
}
}
First you will notice that there is a number of new methods. The first is the CopyFrom method. This method is needed to copy the contents of the configuration data entered in the dialog box so that we can gain access to it within our class. We then override ConfigurationPropertyCollection since the header properties are returned as a collection. We will add the properties to our internal collection based on the List<CustomHeader> object. We also add the class to the AddBindingParamters method.
One thing to note when creating the configuration class (in our case the CustomHeader class) is that the default behavior of the Transport Properties dialog box and underlying code expects that all configuration information will be of type string. If you are using other types then you need to create your own type converter to convert to a string representation and back. I have the code for the type converter at the bottom of this post called SerializationConverter.
In the code above, in the CustomHeaderEndpointBehavior class, there were a couple of custom objects. The first one we will dig into is in the ApplyDispatchBehavior where we add a new CustomHeaderMessageInspector.
The class implementation for the CustomHeaderMessageInspector looks like:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel.Dispatcher;
using System.Diagnostics;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.Xml;
namespace Services.WCF.Behavior
{
class CustomHeaderMessageInspector : IDispatchMessageInspector
{
#region IDispatchMessageInspector Members
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>>();
List<KeyValuePair<XmlQualifiedName, object>> promoteProps = new List<KeyValuePair<XmlQualifiedName, object>>();
string writeKey = “http://schemas.microsoft.com/BizTalk/2006/01/Adapters/WCF-properties/WriteToContext”;
string promoteKey = “http://schemas.microsoft.com/BizTalk/2006/01/Adapters/WCF-properties/Promote”;
CustomHeaderEndpointBehavior bhv = null;
if (instanceContext.Host.Description.Endpoints.Find(channel.LocalAddress.Uri) != null)
bhv = instanceContext.Host.Description.Endpoints.Find(channel.LocalAddress.Uri).Behaviors.Find<CustomHeaderEndpointBehavior>();
if (bhv != null)
{
foreach (CustomHeader hdr in bhv.Headers)
{
int headerPos = OperationContext.Current.IncomingMessageHeaders.FindHeader(hdr.Name, hdr.Namespace);
if (headerPos < 0)
{
if (hdr.Required)
{
//Fault Condition
throw new ArgumentNullException(hdr.Name, “Required soap header not found.”);
}
}
else
{
if (hdr.Action != ContextAction.None)
{
// Get an XmlDictionaryReader to read the header content
XmlDictionaryReader reader = OperationContext.Current.IncomingMessageHeaders.GetReaderAtHeader(headerPos);
XmlDocument d = new XmlDocument();
d.LoadXml(reader.ReadOuterXml());
XmlQualifiedName PropName1 = new XmlQualifiedName(hdr.Name, hdr.Namespace);
if (hdr.Action == ContextAction.Write)
{
writeProps.Add(new KeyValuePair<XmlQualifiedName, object>(PropName1, d.DocumentElement.InnerText));
}
else if (hdr.Action == ContextAction.Promote)
{
promoteProps.Add(new KeyValuePair<XmlQualifiedName, object>(PropName1, d.DocumentElement.InnerText));
}
}
}
}
}
else
{
//Debug.WriteLine(“*****AfterReceiveRequest: No Behavior found of type CustomHeaderEndpointBehavior.*****”);
}
if (writeProps.Count > 0)
{
request.Properties[writeKey] = writeProps;
}
if (promoteProps.Count > 0)
{
request.Properties[promoteKey] = promoteProps;
}
return null;
}
public void BeforeSendReply(ref System.ServiceModel.Channels.Message reply, object correlationState)
{
}
#endregion
}
}
As we look at this code, in the AfterReceiveRequest method we have included both the writeProps and the promoteProps variables as well as both namespaces. We then look in the instanceContext.Host.Description.Endpoints collection to find, within the behaviors collection, our CustomerHeaderEndPointBehavior object. Then we loop through each of the headers that was setup through configuration and look for them in the message headers collection. Finally, check if the header was required and if we need to write or promote the values.
Lastly, here is the code that does our type conversion from the CustomHeader class to a string representation (in our case this will be XML).
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using System.Xml.Serialization;
using System.IO;
using System.Xml;
using System.Globalization;
namespace Services.WCF.Behavior
{
public class SerializationConverter : TypeConverter
{
private Type _type;
public SerializationConverter(Type type)
{
_type = type;
}
#region Helper Utilities
private object Deserialize(object value)
{
if (_type == null) throw new ArgumentNullException(“”, “Serialization type was not set. Use the SerializationConvertor(Type) constructor.”);
return Deserialize(value, _type);
}
private object Deserialize(object value, Type destinationType)
{
StringReader strRdr = null;
XmlReader xmlRdr = null;
try
{
if (!(value is string))
throw new ApplicationException(“Expecting parameter ‘value’ to be of type string. ‘Value’ is of type ” + value.GetType().ToString());
XmlSerializer serializer = new XmlSerializer(destinationType);
strRdr = new StringReader(value.ToString());
xmlRdr = XmlReader.Create(strRdr);
return serializer.Deserialize(xmlRdr);
}
finally
{
strRdr.Close();
xmlRdr.Close();
}
}
private object Serialize(object value)
{
StringWriter wtr = null;
try
{
if (value == null)
throw new ArgumentNullException(“value”);
StringBuilder sb = new StringBuilder();
XmlSerializer serializer = new XmlSerializer(value.GetType());
wtr = new StringWriter(sb);
serializer.Serialize(wtr, value);
return sb.ToString();
}
finally
{
wtr.Close();
}
}
#endregion
#region Collection code.
//Class to string
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (value is string)
{
return Deserialize(value);
}
else if (value.GetType() == _type)
{
return Serialize(value);
}
else
{
string msg = (_type == null ? “Value is not of type System.string and no type was specified at construction.” : “Value is not of type System.string or ” + _type.ToString() + “.”);
throw new ApplicationException(msg);
}
}
//String to class
public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
{
if (value is string)
{
return Deserialize(value, destinationType);
}
else if (value.GetType() == _type)
{
return Serialize(value);
}
else
{
string msg = (_type == null ? “Value is not of type System.string and no type was specified at construction.” : “Value is not of type System.string or ” + _type.ToString() + “.”);
throw new ApplicationException(msg);
}
}
#endregion
}
}
At this point we have all the code that is part of our project. Once this is compiled, we need to add the assembly to the machine config and add it to the BizTalk WCF endpoint. The process to implement this remains the same as that described at the bottom of part 1 of this series.
As always, the code in this post and this series are for reference only and are provided as is. Now that we have that out of the way, I hope that these posts have been helpful and have shown how to deal with header values in the WCF stack and shown the ability to create a behavior that exposes the properties through configuration to let you dynamically, per end point, set the header items.
[Source: http://geekswithblogs.net/EltonStoneman]
Hitachi Consulting UK has won worldwide SOA and Business Process Partner of the Year in Microsoft’s 2009 WPC awards program. The award was presented for the ESB Guidance solution I’ve been working on for the last 18 months, which has also featured in a walkthrough at the UK SOA/BPM User Group, and in various blog posts.
This is great news for Hitachi, and Microsoft gave us a ringing endorsement:
“Microsoft is proud to recognize Hitachi Consulting UK as the SOA and Business Process Partner of the Year. Hitachi Consulting UK’s solutions have significantly reduced the time and investment needed to develop, test, and deploy new projects for its clients.”
BizTalk has out of the box abilities to expose itself as a web service (exposing as a schema / orchestration) or consume custom web services. The web service wizard is an easy way to setup the web services during development but what if you would like to deploy the web service. The most obvious way […]
I’m in San Diego attending the Drug Information Association conference with the goal of getting smarter in the functional areas that make up a bio-pharma company. I’m here with two exceptional architecture colleagues which means that most meals have consisted of us talking shop.
During dinner tonight, we were discussing the importance (or imperative, really) […]
Recently on our project we created a “functional” unit test for some services. The test was designed to check the entire call stack from the service layer to the DAO and return some expected results. We are using Spring for dependency injection through our entire application. This functional test initially only referenced the service DLL’s […]
Hi all
Some times people need to convert a list of elements into a comma separated list of
values. In the forums this questions arises every now and then, so here goes a solution.
Suppose this input:
<ns0:MultipleElementsRoot xmlns:ns0="http://MapCommaSeparatedValue.MultipleElementsSchema">
<MyField>Jan</MyField>
<MyField>Eliasen</MyField>
<MyField>BizTalk</MyField>
</ns0:MultipleElementsRoot>
And I want this output:
<ns0:CommaSeparatedRoot xmlns:ns0="http://MapCommaSeparatedValue.CommaSeparatedSchema">
<CommaField>Jan,Eliasen,BizTalk</CommaField>
</ns0:CommaSeparatedRoot>
Inside a map I would handle it like this (2 approached, that share the first two functoids):
The reoccurring element is used as input to a string concatenate functoid. The functoid
takes a comma as the second input. The output of the concatenate functoid is then
used as input for a cumulative concatenate functoid, effectively creating a comma
separated list of values. The issue with this list is, that it will have a comma at
the end of the line, which needs to be removed.
One way of doing this is to use three functoids to pull this last comma away. First,
I take the length of the string, then I subtract one from that, and use the string
extract functoid to extract the string beginning at position 1 and ending at (length
– 1) and send that to the destination element. Works like a charm.
Now, plenty of people out there (Henrik, you know who you are) would have no moral
qualms using a C# scripting functoid to get rid of the extra comma. Like this:
public string RemoveLastComma(string input)
{
return input.Substring(0, input.Length-1);
}
That one scripting functoid would eliminate the last three functoids I added to remove
the last comma. But I still prefer the functoid way of doing it, mostly for maintenance
reasons. A new BizTalk developer looking at a map with lots of scripting functoids
has no idea what is happening, and even after looking at the code in all the scripting
functoids, he can’t remember what is happening in more than a couple of them. In my
opinion; Always use the built-in functoids if possible.
Now, just for the kick, I thought that some times the opposite could be the case –
going from a comma separated list to lots of elements. This, I cannot solve using
built-in functoids, unfortunately, so the map ended up like this:
Just one scripting functoid. The type is an “Inline XSLT Call Template” and the script
is:
<xsl:template name="MyXsltSplitTemplate">
<xsl:param name="param1" />
<xsl:if test="$param1 != ”">
<xsl:element name="MyField">
<xsl:choose>
<xsl:when test="contains($param1,
‘,’)">
<xsl:value-of select="substring-before($param1,
‘,’)" />
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$param1"
/>
</xsl:otherwise>
</xsl:choose>
</xsl:element>
<xsl:call-template name="MyXsltSplitTemplate">
<xsl:with-param name="param1" select="substring-after($param1,
‘,’)" />
</xsl:call-template>
</xsl:if>
</xsl:template>
Basically, the function substring-before is used to get any string before a comma
and output it. If no comma is in the string, the string is output. The string to the
right of a comma is then used as a recursive call to the same template. Again; Works
like a charm 🙂
So, with any luck, this question can be answered by googling soon instead of asking
in the forums 🙂
Edit on 27’th June:
>
—
eliasen
Today I tried to access a WorkflowService hosted in IIS on Windows 7, and got this nice little error message:
Error: Cannot import wsdl:portType
Detail: An exception was thrown while running a WSDL import extension: System.ServiceModel.Description.DataContractSerializerMessageContractImporterError: Schema with target namespace ‘http://schemas.microsoft.com/2003/10/Serialization/’ could not be found. XPath to Error Source: //wsdl:definitions[@targetNamespace=’http://tempuri.org’]/wsdl:portType[@name=’IService’]
[… lot more stuff ]
Warning: No code was […]