Welcome to MSDN Blogs Sign in | Join | Help

Exposing Custom WCF Headers through WCF Behaviors - Part 3

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.

 

Posted by skaufman | 1 Comments
Filed under: , ,

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="http://schemas.microsoft.com/BizTalk/2006/01/Adapters/WCF-properties/Promote";
const string PropertiesToWriteKey="http://schemas.microsoft.com/BizTalk/2006/01/Adapters/WCF-properties/WriteToContext";

XmlQualifiedName PropName1=new XmlQualifiedName("Destination", "http://tempuri.org/2007/sample-properties");
XmlQualifiedName PropName2=new XmlQualifiedName("Source", "http://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 = "http://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.

Posted by skaufman | 0 Comments
Filed under: , ,

Exposing Custom WCF Headers through WCF Behaviors

Since the WCF Publishing Wizard in BizTalk does not support adding custom headers defined at the server, we need to programmatically modify what gets created by the wizard to add custom headers.  However, from the client you have the option to pass in header values at will.  If you are passing in headers generated at the client BizTalk will take them and map them to the context.  However, they show up as an XML fragment and not as individual data items.  It becomes annoying to constantly parse the fragment each and every time you want to get to the data.

What we are really interested in is the ability to expose the end point with the header values already defined, accept the header values from the client and either promote or write the values to the context and lastly, be able to create a behavior that you can attach to your WCF endpoint that exposes the properties through configuration to let you dynamically, per end point, set the header items and what you want to do with them as they are submitted.  This will be a three part posting with a post covering each of these features.

For this first post, we will focus on the ability to expose the end point with the header values already defined.  What makes this even more interesting is that there is no WSDL file as this gets generated dynamically when you access the SVC file.  If you wish you can create a static WSDL file and then use the externalMetadataLocation attribute of the element in the Web.config file that the wizard generates to specify the location of the WSDL file.  Then the static WSDL file will be sent to the user in response to WSDL and metadata exchange (MEX) requests instead of the auto-generated WSDL. 

In our solution, we did not want to have to create WSDL files for each of our endpoints, nor did we want to maintain them.  We needed a way to hook in to the dynamic WSDL creation process.

There are a number of posts out there that talk about this but after reviewing them I found that none of them gave the whole picture.  They were all very good and they provided enough information to fill in many missing pieces but there was enough missing that I though it warranted looking at the whole picture.

We are going to start by creating our own EndPointBehavior.  The EndPointBehavior allows us to inject custom functionality in the WCF execution pipeline. 

To create the EndPointBehavior we need to create a solution that references System.ServiceModel.dll and includes a class that derives from BehaviorExtensionElement, IWsdlExportExtension and IEndpointBehavior.  We need the functionality of the BehaviorExtensionElement to implement the configuration of the behavior, the functionality of the IWsdlExportExtension to change the generated WSDL and the functionality of the IEndPointBehavior to define the endpoint and its behavior.

Lets add a class file to our solution called SoapHeaderEndpointBehavior.  After we create the class and inherit from our objects we need to add the following line of code to the ExportEndPoint method

SoapHeaderWsdlExport.ExportEndpoint(exporter,context);

and we need to add the following two lines of code to the ApplyDispatchBehavior method.

SoapHeaderMessageInspector headerInspector = new SoapHeaderMessageInspector();
endpointDispatcher.DispatchRuntime.MessageInspectors.Add(headerInspector);

Our code should look like this:

using System;
using System.Collections.Generic;
using System.Collections;
using System.Configuration;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Configuration;
using System.ServiceModel.Description;
using System.ServiceModel.Dispatcher;
using System.Text;
using System.Xml;
using System.Xml.Schema;
using System.Web.Services;
using System.Web.Services.Description;
using WsdlDescription = System.Web.Services.Description.ServiceDescription;

namespace Services.WCF.ServiceBehavior
{
    public class SoapHeaderEndpointBehavior : BehaviorExtensionElement, IWsdlExportExtension, IEndpointBehavior
   
{
        #region BehaviorExtensionElement Members

            public override Type BehaviorType
           
{
                get
               
{
                    return typeof(SoapHeaderEndpointBehavior);
               
}
           
}

            protected override object CreateBehavior()
           
{
                return new SoapHeaderEndpointBehavior();
           
}
       
#endregion

        #region IWsdlExportExtension Members

            public void ExportContract(WsdlExporter exporter, WsdlContractConversionContext context)
           
{
                //throw new NotImplementedException();
           
}

            public void ExportEndpoint(WsdlExporter exporter, WsdlEndpointConversionContext context)
           
{
                SoapHeaderWsdlExport.ExportEndpoint(exporter,context);
           
}

       
#endregion

        #region IEndpointBehavior Members

            public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
           
{
                //throw new NotImplementedException();
           
}

            public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
           
{
                //throw new NotImplementedException();
           
}

            public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
           
{
                SoapHeaderMessageInspector headerInspector = new SoapHeaderMessageInspector();
                endpointDispatcher.DispatchRuntime.MessageInspectors.Add(headerInspector);
           
}

            public void Validate(ServiceEndpoint endpoint)
           
{
               //throw new NotImplementedException();
           
}

       
#endregion
   
}
}

When we added code to the ExportEndpoint method, we utilized a custom object.  Let's add another class to our solution to implement the SoapHeaderWsdlExport.  This class will add a header schema and its namespace, create and add a header message description and finally add the header to the operation.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Schema;
using System.Reflection;
using System.Web.Services.Description;
using System.Xml;
using System.ServiceModel.Description;
using WsdlDescription = System.Web.Services.Description.ServiceDescription;

namespace Services.WCF.ServiceBehavior
{
    class SoapHeaderWsdlExport
   
{
        public static void ExportEndpoint(WsdlExporter exporter, WsdlEndpointConversionContext context)
       
{
            // Read the schema of the custom header message
           
XmlSchema customSoapHeaderSchema = XmlSchema.Read(Assembly.GetExecutingAssembly().GetManifestResourceStream("Services.WCF.ServiceBehavior.SoapHeader.xsd"),
                new ValidationEventHandler (SoapHeaderWsdlExport.ValidationCallBack));

            // Create the HeaderMessage to add to wsdl:message AND to refer to from wsdl:operation
           
System.Web.Services.Description.Message headerMessage = CreateHeaderMessage();

           
foreach (WsdlDescription wsdl in exporter.GeneratedWsdlDocuments)
           
{
                // Add the schema of the CustomSoapHeader to the types AND add the namespace to the list of namespaces
               
wsdl.Types.Schemas.Add(customSoapHeaderSchema);
               
wsdl.Namespaces.Add("sh", SoapHeaderNames.SoapHeaderNamespace);

                // The actual adding of the message to the list of messages
               
wsdl.Messages.Add(headerMessage);
           
}

           
addHeaderToOperations(headerMessage, context);
       
}

        private static System.Web.Services.Description.Message CreateHeaderMessage()
       
{
            // Create Message
           
System.Web.Services.Description.Message headerMessage = new System.Web.Services.Description.Message();

            // Set the name of the header message
           
headerMessage.Name = SoapHeaderNames.SoapHeaderName;

            // Create the messagepart and add to the header message
           
MessagePart part = new MessagePart();
           
part.Name = "Header";
           
part.Element = new XmlQualifiedName(SoapHeaderNames.SoapHeaderName, SoapHeaderNames.SoapHeaderNamespace);
           
headerMessage.Parts.Add(part);

            return headerMessage;
       
}

        private static void addHeaderToOperations(System.Web.Services.Description.Message headerMessage, WsdlEndpointConversionContext context)
       
{
            // Create a XmlQualifiedName based on the header message, this will be used for binding the header message and the SoapHeaderBinding
           
XmlQualifiedName header = new XmlQualifiedName(headerMessage.Name, headerMessage.ServiceDescription.TargetNamespace);

           
foreach (OperationBinding operation in context.WsdlBinding.Operations)
           
{
                // Add the SoapHeaderBinding to the MessageBinding
               
ExportMessageHeaderBinding(operation.Input, context, header, false);
               
ExportMessageHeaderBinding(operation.Output, context, header, false);
           
}
       
}

        private static void ExportMessageHeaderBinding(MessageBinding messageBinding, WsdlEndpointConversionContext context, XmlQualifiedName header, bool isEncoded)
       
{
            // For brevity, assume Soap12HeaderBinding for Soap 1.2
           
SoapHeaderBinding binding = new Soap12HeaderBinding();
           
binding.Part = "Header";
           
binding.Message = header;
           
binding.Use = isEncoded ? SoapBindingUse.Encoded : SoapBindingUse.Literal;

           
messageBinding.Extensions.Add(binding);
       
}

        private static void ValidationCallBack(object sender, ValidationEventArgs args)
       
{
            if (args.Severity == XmlSeverityType.Warning)
                Console.WriteLine("\tWarning: Matching schema not found. No validation occurred." + args.Message);
            else
               
Console.WriteLine("\tValidation error: " + args.Message);

        }

   
}
}

In the addHeaderToOperations method there are two calls to the ExportMessageHeaderBinding method.  The second call passes the operation.Output parameter which will pass the header back to the calling application with the response message.  This also also means that the method signature at the client will be to pass in the header object by Ref.  Since we needed the client to pass the header data into BizTalk we didn't need to echo the header back to the client so we deleted this line.  If you want to echo it back then keep this line (as shown in the code above).

Also, in the code above, in the ExportEndpoint and CreateHeaderMessage methods there was another custom class called SoapHeaderNames.  This class contained the values that we wanted to place in the custom header.  By creating a class for this data we could limit the location of this information to one location.  The code for the SoapHeaderNames class looks like this:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Services.WCF.ServiceBehavior
{
    public static class SoapHeaderNames
   
{
        public const String SoapHeaderName = "SoapHeader";

        public const String AppName = "App";
        public const String UserName = "User";

        public const String SoapHeaderNamespace = http://servicebehavior.mycompany.com;
   
}
}

Way back at the top, in the ApplyDispatchBehavior method of the SoapHeaderEndpointBehavior class we have a custom object called SoapHeaderMessageInspector.  Therefore, let's add another class for the SoapHeaderMessageInspector.  There are two methods that we must implement on the IDispatchMessageInspector and they are the AfterReceiveRequest and the BeforeReceiveRequest.  Since we are interested in applying the headers in the dynamic WSDL we will only need code in the BeforeReceiveRequest.  The code will look like this:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel.Dispatcher;
using System.ServiceModel;
using System.Xml;

namespace Services.WCF.ServiceBehavior
{
    class SoapHeaderMessageInspector: IDispatchMessageInspector
   
{

        #region IDispatchMessageInspector Members

            public object AfterReceiveRequest(ref System.ServiceModel.Channels.Message request, System.ServiceModel.IClientChannel channel, System.ServiceModel.InstanceContext instanceContext)
           
{
                return null;
           
}

            public void BeforeSendReply(ref System.ServiceModel.Channels.Message reply, object correlationState)
           
{
                // Look for my custom header in the request
                Int32 headerPosition = OperationContext.Current.IncomingMessageHeaders.FindHeader(SoapHeaderNames.SoapHeaderName, SoapHeaderNames.SoapHeaderNamespace);

                // Get an XmlDictionaryReader to read the header content
               
XmlDictionaryReader reader = OperationContext.Current.IncomingMessageHeaders.GetReaderAtHeader(headerPosition);

                // Read through its static method ReadHeader
               
SoapHeader header = SoapHeader.ReadHeader(reader);

                if (header != null)
               
{
                    // Add the header from the request
                   
reply.Headers.Add(header);
               
}
           
}

       
#endregion
   
}
}

This code grabs the header section and will inject the header elements.  In the BeforeSendReply method you will see we are using a SoapHeader object.  This object contains the properties and methods to deal with the elements that we will be reading and writing to the header.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.ServiceModel.Channels;

namespace Services.WCF.ServiceBehavior
{
   
[Serializable]
    public class SoapHeader: MessageHeader
   
{
        private string _app;
        private string _user;

        public string App
       
{
            get
           
{
                return (this._app);
           
}
            set
           
{
               
this._app = value;
           
}
       
}

        public string User
       
{
            get
           
{
                return (this._user);
           
}
            set
           
{
                this._user = value;
           
}
       
}

        public SoapHeader()
       
{
       
}

        public SoapHeader(string app, string user)
       
{
            this._app = app;
            this._user = user;
        }

        public override string Name
       
{
            get { return (SoapHeaderNames.SoapHeaderName); }
        }

        public override string Namespace
       
{
            get { return (SoapHeaderNames.SoapHeaderNamespace); }
        }

         protected override void OnWriteHeaderContents(System.Xml.XmlDictionaryWriter writer, MessageVersion messageVersion)
       
{
            // Write the content of the header directly using the XmlDictionaryWriter
           
writer.WriteElementString(SoapHeaderNames.AppName, this.App);
           
writer.WriteElementString(SoapHeaderNames.UserName, this.User);
        }

        public static SoapHeader ReadHeader(XmlDictionaryReader reader)
       
{
            String app = null;
            String user = null;

            // Read the header content (key) using the XmlDictionaryReader
            if (reader.ReadToDescendant(SoapHeaderNames.AppName, SoapHeaderNames.SoapHeaderNamespace))
           
{
                app = reader.ReadElementString();
           
}

            if (reader.ReadToDescendant(SoapHeaderNames.UserName, SoapHeaderNames.SoapHeaderNamespace))
           
{
                user = reader.ReadElementString();
           
}

            if (!String.IsNullOrEmpty(app) && !String.IsNullOrEmpty(user))
           
{
                return new SoapHeader(app, user);
           
}
            else
           
{
                return null;
           
}
       
}

   
}
}

There is also a schema, called SoapHeader.xsd, that needs to be added to the project as well.  This schema defines the message contract for the header and is referenced and used in the ExportEndpoint method of the SoapHeaderWsdlExport class and looks like this:

<?xml version="1.0" encoding="utf-16"?>
<xs:schema xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:b="http://schemas.microsoft.com/BizTalk/2003" xmlns="http://servicebehavior.mycompany.com" attributeFormDefault="unqualified" elementFormDefault="qualified" targetNamespace="http://servicebehavior.mycompany.com" xmlns:xs="http://www.w3.org/2001/XMLSchema">
   
<xs:element name="SoapHeader">
       
<xs:complexType>
           
<xs:sequence>
               
<xs:element name="App" type="xs:string" />
               
<xs:element name="User" type="xs:string" />
            </xs:sequence>
        </xs:complexType>
    </xs:element>
</xs:schema>

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. 

There is an easy way to add entries by using the SvcConfigEditor.exe tool.  This tool is part of the Windows SDK and, if installed, can be found in the \Program Files\Microsoft SDKs\Windows\v6.0A\Bin directory. 

Once this utility is open you can click on the File ->Open->Config File menu item.  Open the machine.config file.  At the bottom of the tree view on the left side you will see an Advanced folder.  Expand that node and expand the the Extensions folder.  Click on the the 'behavior element extensions' node.  At the bottom right, click on the new button.  This will bring up the Extension Configuration Element Editor dialog box.  Enter the name you wish to give to your extension and then click on the ellipses next to type.  This will bring up the Type Browser dialog box.  Browse to your component (also note that you can select assemblies already placed in the GAC).  Once selected, your fully qualified assembly name will be entered.  Select Save under the File menu.  You are now ready to start using the new behavior.

Up to this point everything we have done has been specifically WCF functionality.  The next paragraph will outline how we can utilized this behavior in a BizTalk WCF endpoint. 

Create a WCF endpoint in BizTalk (if you need more information on creating a WCF endpoint check out the docs on MSDN (Insert Link)).  One thing to keep in mind is that when you create a WCF end point in BizTalk using one of the standard bindings you will not have the option to specify a behavior.  In order to specify a behavior you need to specify either the WCF-Custom or WCF-CustomIsolated binding.  Once you select the binding type, click on the Configure button and the Transport Properties dialog will appear.  Select the Behavior tab and then right click on the the Endpoint Behavior node.  Once the popup menu appears, select Add extension.  Select your behavior from the Select Behavior Extensions dialog box and click OK.  Enter the rest of the specific information you need for the end point and click OK to save your endpoint.

We have now done everything that is needed to create a custom behavior including the ability to link into the dynamic WSDL creation process at run time, register the behavior and finally to use the behavior.

Now, when you create a client against the end point you will see that there will be two parameters for the web method call.  The first will be the custom header and the second will be the message body.  When we look in the object browser for the header object we will see that the two items appear that we defined in our behavior. 

As I said at the beginning of this post, this will be a three part series.  What we have not covered is the ability to accept these header values from the client and promote or write the values to the context (part 2) and we have not covered the ability to create a behavior that exposes the properties through configuration to let you dynamically, per end point, set the header items (part 3).

Posted by skaufman | 2 Comments
Filed under: , ,

Pro BizTalk 2009 Book

I have just submitted my chapters for the new Pro BizTalk 2009 book from APress.  You can also find information about the book on Amazon.

I am co-authoring along with George Dunphy, Harold Campos, Peter Kelcey, Sergei Moukhnitski and David Peterson.

The book will be available this summer.

Posted by skaufman | 2 Comments
Filed under:

I will be speaking at TechEd 2009

I will be speaking at TechEd 2009 in Los Angeles.

I will be presenting two sessions and doing demos at a third.

My first presentation will be on 'Application Lifecycle Management Experience for BizTalk Server 2009 Developers'.

My second presentation will  be a 'Deep Dive with Microsoft BizTalk Server 2009 Development Platform'.

The demo will be done with Danny Garber and will demo an application featuring the new capabilities of BizTalk Server 2009, Windows Azure Service Platform, the Live Framework, .NET Services, the new ESB Guidance v2.0 and the new Managed Services Engine (MSE) 7.0

If you are going to be there stop by and say hello.

Posted by skaufman | 1 Comments
Filed under: ,

Twin Cities Connected Systems User Group - March 19th, 2009

If you are in Minneapolis on Thursday March 19th please join us for the Twin Cities Connected Systems User Group Meeting.

The meeting takes place at 8000 Norman Center Drive, Bloomington, MN 55437 (the building directly east of the Microsoft office) from 6:00 to 7:30

Steve Cavanagh and Balaji Thiagarajan will be delivering a presentation titled 'Business Rule Execution via Managed Stored Procedures: A Data-centric Approach'.

Posted by skaufman | 1 Comments

Twin Cities BizTalk User Group Changes

The Twin Cities BizTalk User Group is changing to become the Twin Cities Connected Systems User Group!

 

We are changing to encompass a broader set of technologies and ensure that we are keeping up with the current set of technologies and patterns.

The Twin Cities Connected Systems User Group sets out to build a community of developers and architects interested in Microsoft middle tier technologies to aid in the development of the members and evangelize these technologies within the Twin Cities.

The middle tier technologies cover products as well as patterns. The partial list of products that fall into our community include BizTalk, Dublin, Oslo, MSE, WCF, WF, MSMQ and Azure.

The patterns typically cover Business to Business (B2B), Application to Application (A2A), Enterprise Application Integration (EAI), EDI, Business Process Management (BPM), Services Oriented Architecture (SOA) and cloud computing.

Posted by skaufman | 1 Comments
Filed under: ,

BizTalk Hotrod Article Published

The latest edition of the BizTalk Hotrod magazine was just released. 

They are currently going through a membership update and if you did not get an e-mail letting you know the latest version was released then subscribe by sending an e-mail to the editor.

You can download the latest edition at http://biztalkhotrod.com/Documents/BizTalk%20HotRod%20Magazine%20Q1%202009.pdf

Be sure to check out my article titled: Application Servers: BizTalk vs. Dublin

Posted by skaufman | 0 Comments
Filed under:

Mapping a Repeating Source to a Delimited List

My last couple of projects have been very interesting, challenging and a lot of fun.  My current project is no exception.  I have the pleasure of working with a great group of people (I've got to give a shout out to C-Mappity (Casey), v-slice (Dan), Sniff Packy Packet (Rich) and 10 Bit (pronounced 2 bit) (Mike) - you know who you are).  Anyways, this a great group and we are working on a fun project to create their services architecture which will be the framework for their ESB.

During our redesign and re-platform of  some of their current services we have come up on some interesting scenarios.  One that I think many people may run into is the need to take a source xml document that has a repeating list and map it to a destination that requires that repeating list to be formatted into a single delimited string.

The picture below shows what the Mapper looks like to get this working along with the structure of the source and destination schemas. 

If we take a look at a sample source xml we will get a better understanding of the format of the repeated list.

<SearchByCodeList>
   
<FirstName>Jim</FirstName>
   
<LastName>Anderson</LastName>
   
<NetworkCodeList>
       
<NetworkCode>44</NetworkCode>
       
<NetworkCode>64</NetworkCode>
       
<NetworkCode>49</NetworkCode>
    </NetworkCodeList>
</SearchByCodeList>

and lets take a look at the output before we get into the mapping functionality

<ProviderSearchCanonical>
    <FirstName>Jim</FirstName>
    <LastName>Anderson</LastName>
    <NetworkCodeList>44|64|49</NetworkCodeList>
</ProviderSearchCanonical>

So, how do we do this in the Mapper?

We need to use the looping functoid and connect it to the NetworkCode node.  If our source xml had each NetworkCode node surrounded by opening and closing NetworkCodeList nodes then the looping functoid would need to be connected to the NetworkCodeList node.

Since we are using the looping functoid we will get an output record for each of the NetworkCode elements.  The first output would include the first value, the next loop would include the first value and the delimiter and the second value and so on.  This is not what we want.  We really want the last iteration of the loop to be output and none of the previous iterations.  This is what the script functoid at the bottom of the map does. 

The code returns a true when we want the output to be sent to the output message and false all other times.  The value of this functoid is sent to the Value Mapping functoid as the first parameter (which is the logical true or false that is required).  The code for the script functoid looks like:

public bool ShouldThereBeOutput( string inputIgnored, int totalRecords )
{
    if(sbCount.Length == (totalRecords))
   
{
        return true;
   
}
    else
   
{
        return false;
   
}
}

The totalRecs parameter is the value from the Record Count functoid (which is used in both Script functoids).  The inputIgnored parameter is a little trick.  We needed to make sure that the map processed the ShouldThereBeOutput method after the other Script functoid.  By drawing a line from the first Script functoid to this one we can ensure that it will be called afterwards.

Now lets look at the functionality in the first Script functoid.  The first thing that is in the code are two StringBuilder objects. They appear above the method declaration which will make them global in scope. 

System.Text.StringBuilder NCLState = null;
System.Text.StringBuilder sbCount = new System.Text.StringBuilder();

The first is our state object which will hold the delimited string.  The second is sbCount which holds the current iteration through the loop.  There is an Iteration functoid but that only keeps track of the iterations on the destination side.  We need the iteration that we are on based on the source.

The code below shows the method in the first Script functoid.  The bottom else statement is run for our first time through the loop.  It adds the value of the first node and appends a character to sbCount.  You can add any single character to sbCount.  The length property will tell us which loop we are currently in.  The embedded else statement is fired for every subsequent loop iteration and will prepend the delimiter.  Finally we check if the length of the sbCount object is equal to the value past in from the <<itermation>> functoid.  If it is then we are all done looping and need to return the final delimited string.  The code for this method looks like:

public string DetermineCodeList( string codeList, int totalRecords )
{
    if(NCLState == null)
   
{
        NCLState = new System.Text.StringBuilder();
   
}

    if (sbCount.Length > 0)
        if (sbCount.Length == totalRecords)
            {return NCLState.ToString();}
        else
       
{
            NCLState.Append("|" + codeList);
            sbCount.Append("x");
            return NCLState.ToString();
       
}
    else
   
{
        NCLState.Append(codeList);
        sbCount.Append("x");
        return NCLState.ToString();
   
}
}

Once the map is executed we will end up with our delimited list.

Posted by skaufman | 0 Comments
Filed under:

Twin Cities BizTalk User Group Meeting - January 15th, 2009


If you are in Minneapolis on Thursday January 15th please join us for the next Twin Cities BizTalk User Group Meeting.

The meeting takes place at the Microsoft office in Bloomington from 6:00pm to 7:30pm. 

Shawn Doty from RBA Consulting will be delivering a presentation on Exploring BizTalk 2009.

Posted by skaufman | 0 Comments
Filed under: ,

I will be speaking at the Microsoft SOA and BPM Conference

 

I will be speaking at the 2009 Microsoft SOA and BPM Conference in Redmond, WA.

I have two presentations for this conference. 

For my first presentation, I will be co-presenting on 'Application Lifecycle Management Experience for BizTalk Server 2009 Developers' with Vinay Ahuja and Manoj Agarwal.

For my second presentation, I will be co-presenting on 'Implementing an Internet Service Bus with Agent Design Patterns on a Windows Azure Services Platform' with Danny Garber.

If you are going to be there stop by and say hello.

The following week I will be presenting two topics at our internal conference so it is shaping up to be a very busy new year.
Posted by skaufman | 0 Comments
Filed under: ,

Multi-Value Key for a Dictionary

I have a situation in which I need to cache lookup data from a database tables that will be used in the BizTalk Mapper.  The part that makes it interesting is that it is not just key value pairs that I need to cache.  I need to return a value for a 4 part key.  The key needs to be the context (domain), the effective start and end date and the source key. 

The .NET framework does not contain an object that will allow you to have a key containing more than one part. 

So, how do we make this happen?  The first thing that we need to do is to create a class that represents the values that will be the key.  The second is to implement the IEqualityComparer<T> interface.

So, lets create the class that we will use as the key in the dictionary.

public class LookupCacheKey
{
    private string _sourceContext;
    private string _sourceKey;
    private DateTime _effectiveDate;
    private DateTime _effectiveStartDate;
    private DateTime _effectiveEndDate;
   
    public LookupCacheKey()
    {
    }

    public LookupCacheKey (string sourceContext, string sourceKey, DateTime effectiveDate)
    {
        this._sourceContext = sourceContext;
        this._sourceKey = sourceKey;
        this._effectiveDate = effectiveDate;
    }
}

In addition to this code, I also have a method (directly after the LookupCacheKey method) that sets the values in this class.  The setValues method takes all of the values that we received from the database query to create the collection.  The database contains values for the lookup data's valid use dates through a start and end date field.  The value that comes from our application will have a date that we need to ensure falls between the start and end date or we can't use the value (look at the Equals method below).  In our implementation, if we have a date and doesn't have a valid return value then we throw an exception.  So, the code for our setValues method looks like:

     public setValues string sourceContext, string sourceKey, DateTime effectiveStartDate, DateTime effectiveEndDate)
     {
        this._sourceContext = sourceContext;
        this._sourceKey = sourceKey;
        this._effectiveStartDate = effectiveStartDate;
        this._effectiveEndDate = effectiveEndDate;
     }

Now what I have is a class that can be instantiated but I still need to add the IEqualityComparer interface.

The IEqualityComparer has two methods that you need to implement.  They are the Equals and GetHashCode methods. 

The Equals method provides the functionality necessary to compare the two object instances (comparing the values within the instance).  The method returns true if the classes are equal.

The GetHashCode method provides a hash code for object.  This is used within the dictionary to divide the dictionary items in the buckets - subgroups to provide the speed that we expect from the dictionary object.  For more information on creating good hash keys and more information on the performance and implementation options check out this on MSDN.

To implement the IEqualityComparer interface for our LookupCacheKey class we will add an additional class called EqualityComparer.  This implementation the EqualityComparer class will placed inside the LookupCacheKey class so that we have access to the private variables in the LookupCacheKey class from within the EqualityComparer class.  This class will implement the Equals method to compare each of the 4 variables to let the dictionary object know if there is a match.

public class LookupCacheKey
{
    private string _sourceContext;
    private string _sourceKey;
    private DateTime _effectiveStartDate;
    private DateTime _effectiveEndDate;
   
    public LookupCacheKey()
    {
    }

    public LookupCacheKey (......)
    {
        ........
    }

    public class EqualityComparer: IEqualityComparer<LookupCacheKey>
    {
         public bool Equals(LookupCacheKey lc1, LookupCacheKey lc2)
         {
            return lc1._sourceContext == lc2._sourceContext &&
                   lc1._sourceKey == lc2._sourceKey &&
                   lc1._effectiveDate >= lc2._effectiveStartDate &&
                   lc1._effectiveDate <= lc2._effectiveEndDate;
         }
        
         public int GetHashCode(LookupCacheKey lc)
         {
            return 0;
         }
    }
}

We now have a complete custom key class for the dictionary.  The dictionary does not care what the key contains, only that it can compare the key values.  Lets create a dictionary object that uses the class we just created.

public class BizTalkCacheHelper
{
    private static Dictionary<LookupCacheKey, string> LookUpCacheDictionary =
                   new Dictionary<LookupCacheKey, string>(new LookupCacheKey.EqualityComparer());

    .....

}

One thing that will be evident very quickly is that if you don't create an instance of the EqualityComparer then the dictionary will use its default method of comparing objects - which will not match even when the keys are the same.

Now we have two more steps to use the Dictionary.  We need to populate the Dictionary and then we need to call the the class to get our value.

To populate the Dictionary object we used the SQL Dependency functionality.  The BizTalkCacheHelper class also included a function to populate the Dictionary through a DataReader (along with the setValues method).  After the initial data load, the function then sets up the SQL Dependency subscription.  Take a look at the Query Notifications page on MSDN for samples and requirements.  So, once we loaded the Dictionary we then needed to create the SQLDependency object and set the OnChange event so that we could listen to the event which would tell us when a change occurred in the database.  In the OnChange event handler, we then repopulated the Dictionary object.  One thing to note about the SQL Dependency functionality is that you create a subscription based on a Select query and you get one notification per change.  What that means is that you need to setup your subscription again after each notification (which you can see at the bottom of the PopulateDictionary method).

The code within the BizTalkCacheHelper function looks like:

private void PopulateDictionary()
{
    SqlConnection conn = null;
    SqlCommand comm = null;
    SqlCommand commDependency = null;

    conn = new SqlConnection(connString);
    conn.Open();

    comm = new SqlCommand();

    ......
    SqlDataReader dataReader = comm.ExecuteReader();

    while (dataReader.Read())
   
{
        LookupCacheKey key = new LookupCacheKey();

        key.setValues(......);

        string value = dataReader["TargetValue"].ToString();

        LookUpCacheDictionary.Add(key, value);
   
}

    dataReader.Close();

    //Now we set up the SQL Dependency Functionality
    commDependency = new SqlCommand();
    commDependency.Connection = conn;
    commDependency.CommandText = "............";
    commDependency.Notification = null;
    SqlDependency dependency = new SqlDependency(commDependency);
    dependency.OnChange += new OnChangeEventHandler(dependency_OnChange);
    commDependency.ExecuteNonQuery();
}

and the OnChange event handler receives the event and calls the PopulateDictionary method to reload the cache as well as to setup the next subscriptions.  It looks like this.

private void dependency_OnChange(object sender, SqlNotificationEventArgs e)
{
    if (e.Info != SqlNotificationInfo.Invalid)
   
{
        PopulateDictionary();
   
}
}

Now we have a cache component that is populated with data and is setup to receive and handle notification whenever data in the database changes.  Lastly, we just need to call into the component and get back our value for the key parameters we pass it.  To do this we will add a method like the following:

public string GetValue(string sourceContextName, string domainName, string sourceKey, string effectiveDate)
{
    string lookupValue = string.Empty;
    try
    {
        LookupCacheKey lookupKey = new LookupCacheKey(sourceContextName, domainName, sourceKey, DateTime.Parse(effectiveDate));

        if (LookUpCacheDictionary.ContainsKey(lookupKey))
        {
            string lookupValue = LookUpCacheDictionary[lookupKey].TargetValue.ToString();
        }
    }

    .........
    if(lookupValue == string.Empty)
    {
        //throw exception
    }

    return lookupValue;
}

Now we can call directly from the Mapper (or any front end) into the GetValue function and we will finally have the value that is needed from the cached component.

 

Posted by skaufman | 0 Comments
Filed under: ,

More Information on Processing MSMQ Messages in FIFO Order

In two of my previous posts  I talked about processing MSMQ messages in FIFO order with WCF.

The way that WCF, along with the MSMQ binding, works there could be a couple of situations in which messages may actually not process in FIFO order. 

The most prevalent is a rollback.  When a message is rolled back the process is asynchronous and thus end up not only having that message take time to get back to the head of the queue but may also lead to messages being processed while the rolled back message is in flight. 

However, not all is lost.  By adding the changes below you can further ensure that you can make WCF behave like a pure System.Messaging coded system and still get your messages in FIFO order.

So, add

<transactedBatching maxBatchSize="1"/>

as an endpoint behavior and setting

ReleaseServiceInstanceOnTransactionComplete = false

on the Service Behavior (along with the InstanceContextMode and the ConcurrencyMode)

Posted by skaufman | 0 Comments
Filed under: , ,

More Information on UpdateGrams

In previous blog posts I have mentioned this large project that I am working on.  This project has been a great project for finding lots of material to blog about.  What I have not mentioned so far are the people that I am working with. 

This project is a joint effort between Microsoft Consulting Services and Microsoft Global Services India (MGSI).  There have been great contributions by all of the off-shore team (unfortunately, far to many to list) and I would recommend them anytime as an off-shore group if you are looking to implement an off-shore model. 

I have been very fortunate to be working closely with a small tight knit sub team.  This team is made of up Vishal Agrawal, Ashish Mehndi, Ashish Shukla and Benny Matthew.

Why do I bring this up?  Well, in the last blog post I talked about changing the config file so that we could validate our canonical messages.  Once we did that we quickly found out that all of our interchanges that utilized a SQL UpdateGram broke.  They broke because the map that generates the UpdateGram uses a character to represent the parent/child relationship.  This character gets mapped to an element that is an Integer type - which is were it no longer conforms to the schema.  We did this because when the UpdateGram was sent through the Send Port the SQL Adapter uses SQLXML which takes that character data and replaces it with the value of the parents primary key.

So, the first thing that we did was to modify the map to use a numeric value (yes, numeric values can be used) and we decided that we were going to use the following scheme so that we would not overlap with real data.   We used a script functoid with the following code

System.Int32.MinValue.ToString() + the value from the iteration functoid.

Why do I bring up the issue with overlapping data?  Well, that brings us to the second interesting (and important) fact about using the UpdateGram. 

How does the SQLXml functionality work internally when replacing the child key with the value from the parent?  Well, it takes the identifier (lets say that we were still using a character and that the character was Y) and does a string replace.  The string replace however is done across all of the data in the child record and not just the child foreign key column.  We found very quickly that all of our columns that contained a Y were now replaced with the value from the parent key relationship - definitely not what we wanted!  This only happens if the data in the field is only a Y.  This does not happen if there is a Y in the middle of a word.

So, if you are still going to use character data to identify your relationships in the UpdateGram make sure that you take this into account.

Posted by skaufman | 0 Comments
Filed under:

Validating Schemas During Runtime

I recently came across an interesting scenario.  In my application, we are transforming multiple incoming messages to a a canonical format.  We then take the output and place it in the MessageBox for some other downstream subscriber to process. 

The Mapper did the transformation and created the output, however, based on the processing that we were doing during the mapping we found that some of the output messages we created did not always get created so that they would pass validation against the canonical schema.  Since we were doing our processing without the use of an Orchestration we did not have the ability to call a pipeline component to do the validation.   In addition, since the canonical message was used as input to the next set of processes we couldn't use a pipeline on the send side either.

What we needed was the ability to validate against the output schema without requiring an Orchestration.

By adding the following items to the BizTalk config file on all of our servers we were able to achieve this behavior.

 <configSections>
    <section
        name="xlangs"
        type="Microsoft.XLANGs.BizTalk.CrossProcess.XmlSerializationConfigurationSectionHandler, Microsoft.XLANGs.BizTalk.CrossProcess" />
</configSections>

<xlangs>
    <Configuration>
        <Debugging
            ValidateSchemas="true"
            ExtendedLogging="false"
        />
    </Configuration>
</xlangs>

Once you make this change make sure that you test all of the messages that you are processing in your environment.  There is a good chance that you could end up generating suspended messages for interchanges that were just working.

An prime example is if you are using SQL UpdateGrams as outlined in this previous post.

In my next post I will talk about how to get around this issue and another very curious and frustrating UpdateGram behavior.

Posted by skaufman | 0 Comments
Filed under:
More Posts Next page »
 
Page view tracker