Calling a BizTalk Transform (Map) from .Net Code

Now that BizTalk 2010 beta is out, you can use BizTalk maps in Workflow Foundation through the Mapper activity. (Installation of the WCF LOB Adapter SDK is required.) I was curious to see if there is a simple way to do the same, i.e. map from data contract to data contract, from pure .Net code (without the workflow context). It turned out to be relatively simple. I modified Paolo Salvatori’s How To Boost Message Transformations Using the XslCompiledTransform class removing BizTalk message parameters as follows:

public class XslCompiledTransformHelper<TransformType, InputDataContractType, OutputDataContractType> where TransformType : Microsoft.XLANGs.BaseTypes.TransformBase
{
    #region Private Constants
    private const int DefaultBufferSize = 10240; //10 KB
    private const int DefaultThresholdSize = 1048576; //1 MB
    #endregion

    #region Private Static Fields
    private static Dictionary<string, MapInfo> mapDictionary;
    private static DataContractSerializer inputDCSerializer;
    private static XmlSerializer inputXmlSerializer;
    private static DataContractSerializer outputDCSerializer;
    private static XmlSerializer outputXmlSerializer;
    #endregion

    #region Static Constructor
    static XslCompiledTransformHelper()
    {
        mapDictionary = new Dictionary<string, MapInfo>();

        if (UseXmlSerializer(typeof(InputDataContractType)))
            inputXmlSerializer = new XmlSerializer(typeof(InputDataContractType));
        else
            inputDCSerializer = new DataContractSerializer(typeof(InputDataContractType));
        if (UseXmlSerializer(typeof(OutputDataContractType)))
            outputXmlSerializer = new XmlSerializer(typeof(OutputDataContractType));
        else
            outputDCSerializer = new DataContractSerializer(typeof(OutputDataContractType));
    }
    #endregion

    #region Public Static Methods
    public static OutputDataContractType Transform(InputDataContractType input)
    {
        return Transform(input, false, DefaultBufferSize, DefaultThresholdSize);
    }

    public static OutputDataContractType Transform(InputDataContractType input, bool debug)
    {
        return Transform(input, debug, DefaultBufferSize, DefaultThresholdSize);
    }

    public static OutputDataContractType Transform(InputDataContractType input, bool debug, int bufferSize, int thresholdSize)
    {
        try
        {
            return Deserialize(Transform(Serialize(input), debug, bufferSize, thresholdSize));
        }
        catch (Exception ex)
        {
            ExceptionHelper.HandleException("XslCompiledTransformHelper", ex);
            TraceHelper.WriteLineIf(debug, ex.Message, EventLogEntryType.Error);
            throw;
        }
    }

    public static Stream Transform(Stream stream)
    {
        return Transform(stream, false, DefaultBufferSize, DefaultThresholdSize);
    }

    public static Stream Transform(Stream stream, bool debug)
    {
        return Transform(stream, debug, DefaultBufferSize, DefaultThresholdSize);
    }

    public static Stream Transform(Stream stream, bool debug, int bufferSize, int thresholdSize)
    {
        try
        {
            MapInfo mapInfo = GetMapInfo(typeof(TransformType).FullName, debug);
            if (mapInfo != null)
            {
                XmlTextReader xmlTextReader = null;

                try
                {
                    VirtualStream virtualStream = new VirtualStream(bufferSize, thresholdSize);
                    xmlTextReader = new XmlTextReader(stream);
                    mapInfo.Xsl.Transform(xmlTextReader, mapInfo.Arguments, virtualStream);
                    virtualStream.Seek(0, SeekOrigin.Begin);
                    return virtualStream;
                }
                finally
                {
                    if (xmlTextReader != null)
                    {
                        xmlTextReader.Close();
                    }
                }
            }
        }
        catch (Exception ex)
        {
            ExceptionHelper.HandleException("XslCompiledTransformHelper", ex);
            TraceHelper.WriteLineIf(debug, ex.Message, EventLogEntryType.Error);
            throw;
        }
        return null;
    }
    #endregion

    #region Private Static Methods

    private static MapInfo GetMapInfo(string mapFullyQualifiedName, bool debug)
    {
        MapInfo mapInfo = null;
        lock (mapDictionary)
        {
            if (!mapDictionary.ContainsKey(mapFullyQualifiedName))
            {
                TransformType transformBase = Activator.CreateInstance<TransformType>();
                if (transformBase != null)
                {
                    XslCompiledTransform map = new XslCompiledTransform(debug);
                    using (StringReader stringReader = new StringReader(transformBase.XmlContent))
                    {
                        XmlTextReader xmlTextReader = null;

                        try
                        {
                            xmlTextReader = new XmlTextReader(stringReader);
                            XsltSettings settings = new XsltSettings(true, true);
                            map.Load(xmlTextReader, settings, new XmlUrlResolver());
                            mapInfo = new MapInfo(map, transformBase.TransformArgs);
                            mapDictionary[mapFullyQualifiedName] = mapInfo;
                        }
                        finally
                        {
                            if (xmlTextReader != null)
                            {
                                xmlTextReader.Close();
                            }
                        }
                    }
                }
            }
            else
            {
                mapInfo = mapDictionary[mapFullyQualifiedName];
            }
        }
        return mapInfo;
    }

    private static Stream Serialize(InputDataContractType input)
    {
        VirtualStream stream = new VirtualStream();
        if (inputDCSerializer != null)
        {
            inputDCSerializer.WriteObject(stream, input);
        }
        else
        {
            inputXmlSerializer.Serialize(stream, input);
        }
        stream.Seek(0, SeekOrigin.Begin);
        return stream;
    }

    private static OutputDataContractType Deserialize(Stream stream)
    {
        if (outputDCSerializer != null)
        {
            XmlReader reader = XmlReader.Create(stream);
            return (OutputDataContractType)outputDCSerializer.ReadObject(reader);
        }
        else
            return (OutputDataContractType)outputXmlSerializer.Deserialize(stream);
    }

    private static bool UseXmlSerializer(Type type)
    {
        do
        {
            object[] customAttributes = type.GetCustomAttributes(typeof(XmlTypeAttribute), true);
            if ((customAttributes != null) && (customAttributes.Length > 0))
            {
                return true;
            }
            customAttributes = type.GetCustomAttributes(typeof(XmlRootAttribute), true);
            if ((customAttributes != null) && (customAttributes.Length > 0))
            {
                return true;
            }
            if (type.IsArray)
            {
                type = type.GetElementType();
            }
            else
            {
                type = null;
            }
        }
        while (type != null);
        return false;
    }

    #endregion
}

I tested this helper class using the following contracts:

[DataContract(Namespace="urn:Namespace1")]
public class Person1
{
    [DataMember]
    public string Name { get; set; }
    [DataMember]
    public string HomePhone { get; set; }
    [DataMember]
    public string WorkPhone { get; set; }
    [DataMember]
    public string Address { get; set; }
    [DataMember]
    public string Email { get; set; }

    public override string ToString()
    {
        return string.Format("Name: {0}rnE-mail: {1}", Name, Email);
    }
}

[DataContract(Namespace = "urn:Namespace2")]
public class Person2
{
    [DataMember]
    public string Name { get; set; }
    [DataMember]
    public string HomePhone { get; set; }
    [DataMember]
    public string WorkPhone { get; set; }
    [DataMember]
    public string Address { get; set; }
    [DataMember]
    public string Email { get; set; }

    public override string ToString()
    {
        return string.Format("Name: {0}rnE-mail: {1}", Name, Email);
    }
}

I then used the workflow designer to generate a transform class (deriving from Microsoft.XLANGs.BaseTypes.TransformBase) called PersonToPerson. The I used the folowing code lines to run the transformation:

Person1 person1 = new Person1() { … };
Person2 person2 = XslCompiledTransformHelper<PersonToPerson, Person1, Person2>.Transform(person1);

Implementing a Custom BizTalk Adapter as a Custom WCF Channel Part 3 – Testing

When testing your WCF Channel, it can be simpler and quicker to do it directly from Visual Studio without needing to add the assembly to the GAC, restart BizTalk and attach to BTSNTSvc.exe each time you changed the code.

Testing the send side is simple. Create a new test project and add the following interface and class:

[ServiceContract]
interface IBizTalk
{
    [OperationContract(Action="*", ReplyAction="*")]
    Message Request(Message message);
}

class TestClient : ClientBase<IBizTalk>, IBizTalk
{
    public Message Request(Message message)
    {
        return base.Channel.Request(message);
    }
}

Then, write a test method:

[TestMethod]
public void TestSend()
{
    byte[] buffer = UTF8Encoding.UTF8.GetBytes("Hello, world!");
    Message msg = Message.CreateMessage(MessageVersion.Default, "Dummy", buffer);
    TestClient testClient = new TestClient();
    Console.Out.WriteLine(testClient.Request(msg).ToString());
    testClient.Close();
}

You also need an application configuration file (app.config):

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.serviceModel>
    <client>
      <endpoint address="net.file://localhost/test" binding="customBinding" bindingConfiguration="customBinding" contract="WCFFileTransport.IBizTalk" name="WCFFileTransport" />
    </client>
    <bindings>
      <customBinding>
        <binding name="customBinding">
          <WCFFileAdapter fileName="E:BizTalkTestMessagesWCFFileAdapterOut%datetime%.txt" useXmlWrapper="true" />
        </binding>
      </customBinding>
    </bindings>
  </system.serviceModel>
</configuration>

After running the test method, you should have a text file in the out folder.

On the receive side, you can create a console application to host the service like this:

[ServiceContract]
public interface IBizTalk
{
    [OperationContract(IsOneWay = true, Action = "*")]
    void Receive(Message message);
}

class TestService : IBizTalk
{
    public void Receive(Message message)
    {
        Debug.WriteLine(message.ToString(), "Test");
    }
}

class Program
{
    static void Main(string[] args)
    {
        Debug.Listeners.Add(new TextWriterTraceListener(Console.Out));
        ServiceHost service = new ServiceHost(typeof(TestService));
        service.Open();
        Console.WriteLine("Press <ENTER> to terminate the service and exit.");
        Console.ReadLine();
        service.Close();
    }
}

You need the following application configuration (app.config):

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.serviceModel>
    <services>
      <service name="TestService.TestService">
        <endpoint address="net.file://localhost/test.svc" binding="customBinding"
          bindingConfiguration="customBinding" name="WCFFileTransport"
          contract="TestService.IBizTalk" />
      </service>
    </services>
    <bindings>
      <customBinding>
        <binding name="customBinding">
          <WCFFileAdapter fileName="E:BizTalkTestMessagesWCFFileAdapterIn*.xml" useXmlWrapper="true" />
        </binding>
      </customBinding>
    </bindings>
  </system.serviceModel>
</configuration>

Drop a file matching the file mask in the in folder, and you should have a message printed in the console.

If you like more control, you could use the following code instead:

private bool complete = false;

[TestMethod]
public void TestReceive()
{
    FileBindingElement bindingElement = new FileBindingElement();
    bindingElement.FileName = @"E:BizTalkTestMessagesWCFFileAdapterIn*.xml";
    bindingElement.UseXmlWrapper = true;
    BindingParameterCollection parms = new BindingParameterCollection();
    CustomBinding binding = new CustomBinding(new BindingElement[] { bindingElement });
    BindingContext context = new BindingContext(binding, parms);
    context.ListenUriBaseAddress = new Uri("net.file://localhost/test.svc");
    IChannelListener<IInputChannel> listener = bindingElement.BuildChannelListener<IInputChannel>(context);
    listener.Open();
    IInputChannel channel = listener.AcceptChannel();
    channel.Open();
    Message msg;
    IAsyncResult result = channel.BeginTryReceive(TimeSpan.FromSeconds(60.0), new AsyncCallback(Callback), new object());
    while (!complete)
        System.Threading.Thread.Sleep(1000);
    channel.EndTryReceive(result, out msg);
    Console.WriteLine(msg.ToString());
    Assert.IsNotNull(msg);
}

public void Callback(object state)
{
    complete = true;
}

Implementing a Custom BizTalk Adapter as a Custom WCF Channel Part 2 – Receive

(Please, first read part 1 if you haven’t already done so.)

The receive side is more complex than the send side. In short, the TransportBindingElement builds a ChannelListener, which in turns creates an InputChannel. You might think that the InputChannel (or ChannelListener) would call the framework when it has received a new message, but it is in part the other way around. Messages are put in a queue by the ChannelListener when they are received. The framework calls BeginTryReceive on the InputChannel, where the channel waits until a message is available in the queue, and when one becomes available it issues a call-back to the framework. The framework then calls EndTryReceive, which returns the message.

Class Interaction Sequence

Here is a sequence diagram illustrating the calls when the receive location is enabled:

image

When a file is received, the FileSystemWatcher notifies FileChannelListener which creates a message and enqueues it. FileReceiveChannel, which is waiting for a message to be available in the queue, is notified and issues a callback to notify the framework, which calls EndTryReceive to get the message:

image

When the receive location is disabled, the channel listener and channel are closed:

image

Code

Here is the code for the channel listener:

#region using
using System;
using System.IO;
using System.Xml;
using System.Diagnostics;
using System.Collections.Generic;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.Threading;
using Microsoft.ServiceModel.Samples;
#endregion

internal class FileChannelListener : ChannelListenerBase<IInputChannel>
{
    private string filename;
    private bool useXmlWrapper;
    private Uri uri;
    private InputQueue<IInputChannel> channelQueue;
    private FileReceiveChannel currentChannel;
    private FileSystemWatcher fsWatcher;

    internal FileChannelListener(FileBindingElement bindingElement, BindingContext context)
        : base(context.Binding)
    {
        Debug.WriteLine("Constructor called", GetType().FullName);
        Debug.WriteLine("Uri=" + context.ListenUriBaseAddress.ToString() + context.ListenUriRelativeAddress.ToString(), GetType().FullName);
        Debug.WriteLine("Filename=" + bindingElement.FileName, GetType().FullName);
        Debug.WriteLine("UseXmlWrapper=" + bindingElement.UseXmlWrapper, GetType().FullName);

        this.uri = new Uri(context.ListenUriBaseAddress, context.ListenUriRelativeAddress);
        this.filename = bindingElement.FileName;
        this.useXmlWrapper = bindingElement.UseXmlWrapper;
        this.channelQueue = new InputQueue<IInputChannel>();
        this.fsWatcher = new FileSystemWatcher();
    }

    #region CommunicationObject Members

    /// <summary>
    /// Open the listener for use. Start listening for new files.
    /// </summary>
    protected override void OnOpen(TimeSpan timeout)
    {
        Debug.WriteLine("OnOpen called", GetType().FullName + "(" + Thread.CurrentThread.ManagedThreadId + ")");
        // Check if it is a valid directory
        string dir = Path.GetDirectoryName(filename);
        if (!Directory.Exists(dir))
            throw new ApplicationException(string.Format("Directory {0} does not exist.", dir));
        // Check if there are files there already and if so, handle them.
        string filemask = Path.GetFileName(filename);
        string[] existingFiles = Directory.GetFiles(dir, filemask);
        foreach (string f in existingFiles)
        {
            OnReceive(this, new FileSystemEventArgs(WatcherChangeTypes.Created, Path.GetDirectoryName(f), Path.GetFileName(f)));
        }
        // Set up the file system watcher so that we get notified when new files arrive.
        fsWatcher.Path = dir;
        fsWatcher.Filter = filemask;
        fsWatcher.Created += new FileSystemEventHandler(this.OnReceive);
        fsWatcher.EnableRaisingEvents = true;
    }

    protected override IAsyncResult OnBeginOpen(TimeSpan timeout, AsyncCallback callback, object state)
    {
        Debug.WriteLine("OnBeginOpen called", GetType().FullName + "(" + Thread.CurrentThread.ManagedThreadId + ")");
        throw new NotImplementedException();
    }

    protected override void OnEndOpen(IAsyncResult result)
    {
        Debug.WriteLine("OnEndOpen called", GetType().FullName + "(" + Thread.CurrentThread.ManagedThreadId + ")");
        throw new NotImplementedException();
    }

    /// <summary>
    /// Shutdown gracefully
    /// </summary>
    protected override void OnClose(TimeSpan timeout)
    {
        Debug.WriteLine("OnClose called", GetType().FullName + "(" + Thread.CurrentThread.ManagedThreadId + ")");
        lock (this.ThisLock)
        {
            fsWatcher.EnableRaisingEvents = false;
            fsWatcher = null;
            this.channelQueue.Close();
        }
    }

    protected override IAsyncResult OnBeginClose(TimeSpan timeout, AsyncCallback callback, object state)
    {
        Debug.WriteLine("OnBeginClose called", GetType().FullName + "(" + Thread.CurrentThread.ManagedThreadId + ")");
        throw new NotImplementedException();
    }

    protected override void OnEndClose(IAsyncResult result)
    {
        Debug.WriteLine("OnEndClose called", GetType().FullName + "(" + Thread.CurrentThread.ManagedThreadId + ")");
        throw new NotImplementedException();
    }

    /// <summary>
    /// Shutdown ungracefully
    /// </summary>
    protected override void OnAbort()
    {
        Debug.WriteLine("OnAbort called", GetType().FullName + "(" + Thread.CurrentThread.ManagedThreadId + ")");
        // Abort can be called at anytime, so we can’t assume that we’ve been Opened successfully.
        lock (this.ThisLock)
        {
            if (fsWatcher != null)
            {
                fsWatcher.EnableRaisingEvents = false;
                fsWatcher = null;
            }
            this.channelQueue.Close();
        }
    }

    #endregion

    #region ChannelListenerBase Members

    //Synchronously returns a channel that is attached to this listener.
    protected override IInputChannel OnAcceptChannel(TimeSpan timeout)
    {
        Debug.WriteLine("OnAcceptChannel called", GetType().FullName + "(" + Thread.CurrentThread.ManagedThreadId + ")");

        if (!this.IsDisposed)
        {
            this.EnsureChannelAvailable();
        }

        IInputChannel channel;
        if (this.channelQueue.Dequeue(timeout, out channel))
        {
            return channel;
        }
        else
        {
            throw new TimeoutException();
        }
    }

    protected override IAsyncResult OnBeginAcceptChannel(TimeSpan timeout, AsyncCallback callback, object state)
    {
        Debug.WriteLine("OnBeginAcceptChannel called", GetType().FullName + "(" + Thread.CurrentThread.ManagedThreadId + ")");
        if (!this.IsDisposed)
        {
            this.EnsureChannelAvailable();
        }

        Debug.WriteLine("OnBeginAcceptChannel returning", GetType().FullName + "(" + Thread.CurrentThread.ManagedThreadId + ")");
        return this.channelQueue.BeginDequeue(timeout, callback, state);
    }

    protected override IInputChannel OnEndAcceptChannel(IAsyncResult result)
    {
        Debug.WriteLine("OnEndAcceptChannel called", GetType().FullName + "(" + Thread.CurrentThread.ManagedThreadId + ")");
        IInputChannel channel;
        if (this.channelQueue.EndDequeue(result, out channel))
        {
            return channel;
        }
        else
        {
            throw new TimeoutException();
        }
    }

    protected override bool OnWaitForChannel(TimeSpan timeout)
    {
        Debug.WriteLine("OnWaitForChannel called", GetType().FullName + "(" + Thread.CurrentThread.ManagedThreadId + ")");
        throw new NotImplementedException();
    }

    protected override IAsyncResult OnBeginWaitForChannel(TimeSpan timeout, AsyncCallback callback, object state)
    {
        Debug.WriteLine("OnBeginWaitForChannel called", GetType().FullName + "(" + Thread.CurrentThread.ManagedThreadId + ")");
        throw new NotImplementedException();
    }

    protected override bool OnEndWaitForChannel(IAsyncResult result)
    {
        Debug.WriteLine("OnEndWaitForChannel called", GetType().FullName + "(" + Thread.CurrentThread.ManagedThreadId + ")");
        throw new NotImplementedException();
    }
    public override Uri Uri
    {
        get
        {
            return this.uri;
        }
    }
    #endregion

    #region Private members

    /// <summary>
    /// Called when there is a new file to dispatch.
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private void OnReceive(object sender, FileSystemEventArgs e)
    {
        Debug.WriteLine("OnRecieve called. Filename=" + e.FullPath, GetType().FullName + "(" + Thread.CurrentThread.ManagedThreadId + ")");

        // We can be notified before the file is closed.
        // Therefore, try to rename the file in a loop until it succeeds.
        bool retry = true;
        int retryInterval = 1;
        do
        {
            try
            {
                File.Move(e.FullPath, e.FullPath + ".wip");
                retry = false;
            }
            catch (IOException ex)
            {
                Debug.WriteLine(retryInterval + " " + ex.Message, GetType().FullName + "(" + Thread.CurrentThread.ManagedThreadId + ")");
                if (retryInterval < 30000) // Give up after 30 s
                {
                    Thread.Sleep(retryInterval);
                    retryInterval = retryInterval * 2;
                }
                else
                    throw ex;
            }
        }
        while (retry);

        // Since we cannot close and delete the file until the stream has been read,
        // create a buffered copy of the message.
        byte[] buffer = File.ReadAllBytes(e.FullPath + ".wip");
        Message message;
        if (useXmlWrapper)
            message = Message.CreateMessage(MessageVersion.Default, "Dummy", buffer);
        else
            message = Message.CreateMessage(MessageVersion.Default, "Dummy", XmlReader.Create(new MemoryStream(buffer)));
        message.Headers.To = uri;
        Dispatch(message);
        File.Delete(e.FullPath + ".wip");

        Debug.WriteLine("OnRecieve returning", GetType().FullName + "(" + Thread.CurrentThread.ManagedThreadId + ")");
    }

    /// <summary>
    /// Dispatch an incoming message to the waiting channel.
    /// </summary>
    private void Dispatch(Message message)
    {
        FileReceiveChannel newChannel;
        bool channelCreated = CreateOrRetrieveChannel(out newChannel);
        newChannel.Dispatch(message);
        if (channelCreated)
        {
            //Hand the channel off to whomever is waiting for AcceptChannel()
            //to complete
            this.channelQueue.EnqueueAndDispatch(newChannel);
        }
    }

    /// <summary>
    /// Guarantees that channel is attached to this listener.
    /// </summary>
    private void EnsureChannelAvailable()
    {
        FileReceiveChannel newChannel;
        bool channelCreated = CreateOrRetrieveChannel(out newChannel);

        if (channelCreated)
        {
            this.channelQueue.EnqueueAndDispatch(newChannel);
        }
    }

    /// <summary>
    /// If there is a channel attached to this listener, returns it.
    /// Othwerwise, creates a new channel.
    /// </summary>
    /// <param name="newChannel"></param>
    /// <returns></returns>
    private bool CreateOrRetrieveChannel(out FileReceiveChannel newChannel)
    {
        bool channelCreated = false;
        if ((newChannel = currentChannel) == null)
        {
            newChannel = new FileReceiveChannel(this);
            newChannel.Closed += new EventHandler(this.OnChannelClosed);
            currentChannel = newChannel;
            channelCreated = true;
        }

        return channelCreated;
    }

    /// <summary>
    /// Called when the channel has been closed.
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="args"></param>
    private void OnChannelClosed(object sender, EventArgs args)
    {
        FileReceiveChannel channel = (FileReceiveChannel)sender;
        if (channel == this.currentChannel)
        {
            this.currentChannel = null;
        }
    }

    #endregion
}

And here is the code for the input channel:

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.ServiceModel;
using System.ServiceModel.Channels;
using Microsoft.ServiceModel.Samples;
using System.Threading;

internal class FileReceiveChannel : ChannelBase, IInputChannel
{
    private InputQueue<Message> messageQueue;

    internal FileReceiveChannel(FileChannelListener listener)
        : base(listener)
    {
        Debug.WriteLine("Constructor called", GetType().FullName + "(" + Thread.CurrentThread.ManagedThreadId + ")");
        this.messageQueue = new InputQueue<Message>();
    }

    #region CommunicationObject members

    protected override void OnOpen(TimeSpan timeout)
    {
        Debug.WriteLine("OnOpen called", GetType().FullName + "(" + Thread.CurrentThread.ManagedThreadId + ")");
    }

    protected override IAsyncResult OnBeginOpen(TimeSpan timeout, AsyncCallback callback, object state)
    {
        Debug.WriteLine("OnBeginOpen called", GetType().FullName + "(" + Thread.CurrentThread.ManagedThreadId + ")");
        throw new NotImplementedException();
    }

    protected override void OnEndOpen(IAsyncResult result)
    {
        Debug.WriteLine("OnEndOpen called", GetType().FullName + "(" + Thread.CurrentThread.ManagedThreadId + ")");
        throw new NotImplementedException();
    }

    //Closes the channel gracefully during normal conditions.
    protected override void OnClose(TimeSpan timeout)
    {
        Debug.WriteLine("OnClose called", GetType().FullName + "(" + Thread.CurrentThread.ManagedThreadId + ")");
        this.messageQueue.Close();
    }

    //Closes the channel ungracefully during error conditions.
    protected override void OnAbort()
    {
        Debug.WriteLine("OnAbort called", GetType().FullName + "(" + Thread.CurrentThread.ManagedThreadId + ")");
        this.messageQueue.Close();
    }

    protected override IAsyncResult OnBeginClose(TimeSpan timeout, AsyncCallback callback, object state)
    {
        Debug.WriteLine("OnBeginClose called", GetType().FullName + "(" + Thread.CurrentThread.ManagedThreadId + ")");
        throw new NotImplementedException();
    }

    protected override void OnEndClose(IAsyncResult result)
    {
        Debug.WriteLine("OnEndClose called", GetType().FullName + "(" + Thread.CurrentThread.ManagedThreadId + ")");
        throw new NotImplementedException();
    }

    #endregion

    #region IInputChannel members

    public EndpointAddress LocalAddress
    {
        get
        {
            throw new NotImplementedException();
        }
    }

    public Message Receive()
    {
        Debug.WriteLine("Receive called", GetType().FullName + "(" + Thread.CurrentThread.ManagedThreadId + ")");
        throw new NotImplementedException();
    }

    public Message Receive(TimeSpan timeout)
    {
        Debug.WriteLine("Receive(timeout) called", GetType().FullName + "(" + Thread.CurrentThread.ManagedThreadId + ")");
        throw new NotImplementedException();
    }

    public IAsyncResult BeginReceive(AsyncCallback callback, object state)
    {
        Debug.WriteLine("BeginReceive called", GetType().FullName + "(" + Thread.CurrentThread.ManagedThreadId + ")");
        throw new NotImplementedException();
    }

    public IAsyncResult BeginReceive(TimeSpan timeout, AsyncCallback callback, object state)
    {
        Debug.WriteLine("BeginReceive(timeout) called", GetType().FullName + "(" + Thread.CurrentThread.ManagedThreadId + ")");
        throw new NotImplementedException();
    }

    public Message EndReceive(IAsyncResult result)
    {
        Debug.WriteLine("EndReceive called", GetType().FullName + "(" + Thread.CurrentThread.ManagedThreadId + ")");
        throw new NotImplementedException();
    }

    public bool TryReceive(TimeSpan timeout, out Message message)
    {
        Debug.WriteLine("TryReceive called", GetType().FullName + "(" + Thread.CurrentThread.ManagedThreadId + ")");
        return this.messageQueue.Dequeue(timeout, out message);
    }

    public IAsyncResult BeginTryReceive(TimeSpan timeout, AsyncCallback callback, object state)
    {
        Debug.WriteLine("BeginTryReceive called", GetType().FullName + "(" + Thread.CurrentThread.ManagedThreadId + ")");
        IAsyncResult result = this.messageQueue.BeginDequeue(timeout, callback, state);
        Debug.WriteLine("BeginTryReceive returning", GetType().FullName + "(" + Thread.CurrentThread.ManagedThreadId + ")");
        return result;
    }

    public bool EndTryReceive(IAsyncResult result, out Message message)
    {
        Debug.WriteLine("EndTryReceive called", GetType().FullName + "(" + Thread.CurrentThread.ManagedThreadId + ")");
        bool ok = this.messageQueue.EndDequeue(result, out message);
        Debug.WriteLine("EndTryReceive returning", GetType().FullName + "(" + Thread.CurrentThread.ManagedThreadId + ")");
        return ok;
    }

    public bool WaitForMessage(TimeSpan timeout)
    {
        Debug.WriteLine("WaitForMessage called", GetType().FullName + "(" + Thread.CurrentThread.ManagedThreadId + ")");
        throw new NotImplementedException();
    }

    public IAsyncResult BeginWaitForMessage(TimeSpan timeout, AsyncCallback callback, object state)
    {
        Debug.WriteLine("BeginWaitForMessage called", GetType().FullName + "(" + Thread.CurrentThread.ManagedThreadId + ")");
        throw new NotImplementedException();
    }

    public bool EndWaitForMessage(IAsyncResult result)
    {
        Debug.WriteLine("EndWaitForMessage called", GetType().FullName + "(" + Thread.CurrentThread.ManagedThreadId + ")");
        throw new NotImplementedException();
    }

    #endregion

    #region Private methods

    //Hands the message off to other components higher up the
    //channel stack that have previously called BeginReceive()
    //and are waiting for messages to arrive on this channel.
    internal void Dispatch(Message message)
    {
        Debug.WriteLine("Dispatch called", GetType().FullName + "(" + Thread.CurrentThread.ManagedThreadId + ")");
        this.messageQueue.EnqueueAndDispatch(message);
        Debug.WriteLine("Dispatch returning", GetType().FullName + "(" + Thread.CurrentThread.ManagedThreadId + ")");
    }

    #endregion
}

Configure BizTalk

Create a new receive location, and choose transport type WCF-Custom. Press the Configure button. On the General tab, type an address, e.g. net.file://localhost/test.svc.

On the Binding tab, choose customBinding. Then remove existing binding elements and insert WCFFileAdapter. Specify the input file mask.

image

Testing

In part 3 of this series, I’m going to demonstrate how to test the channels without BizTalk.

Implementing a Custom BizTalk Adapter as a Custom WCF Channel Part 1 – Send

This is a walkthrough of the steps needed to create a custom WCF channel that can be used from BizTalk.

As you probably know, you can also create a “native” BizTalk adapter using the BizTalk adapter framework (and wizard), but with the WCF interoperability in BizTalk Server 2006 R2 and BizTalk Server 2009, you can implement a WCF channel instead, which makes the adapter available for other .NET applications.

Also, there is a WCF LOB Adapter SDK (documentation) that is more appropriate if you want to integrate with a line of business system. The custom channel method described here is more appropriate if you want to support transport protocols not natively supported by BizTalk.

The transport implemented in this example is a fairly easy one, namely file. (The send channel writes to files and the receive channel reads files.) In order to make this work, I have studied the Transport- UDP sample and even stolen some code from it. I have also used a lot of trial and error.

Create the Project

First, create a new Windows class library project. Call it e.g. WCFFileTransport. Then, add references to System.ServiceModel and System.Configuration.

image

image

Implement the channel factory and the send channel

The send side is fairly straight forward. You need two classes, one that inherits from ChannelFactoryBase and one that inherits form ChannelBase.

Create a class called FileChannelFactory that inherits from ChannelFactoryBase<IRequestChannel>. (I have read that you cannot use IOutputChannel with BizTalk, but I haven’t verified that.) The FileChannelFactory constructor, which is called by FileBindingElement (which is described later), has a filename parameter. The framework will first call OnOpen and then OnCreateChannel. OnCreateChannel creates the channel passing the filename as a parameter.

class FileChannelFactory : ChannelFactoryBase<IRequestChannel>
//class FileChannelFactory : ChannelFactoryBase<IOutputChannel>
{
    private string fileName;
    private bool useXmlWrapper;

    public FileChannelFactory(string fileName, bool useXmlWrapper, BindingContext context)
        : base(context.Binding)
    {
        this.fileName = fileName;
        this.useXmlWrapper = useXmlWrapper;
    }

    protected override IRequestChannel OnCreateChannel(System.ServiceModel.EndpointAddress address, Uri via)
    //protected override IOutputChannel OnCreateChannel(System.ServiceModel.EndpointAddress address, Uri via)
    {
        Debug.WriteLine("OnCreateChannel called", this.GetType().FullName);
        return new FileSendChannel(this, fileName, useXmlWrapper);
    }

    protected override void OnOpen(TimeSpan timeout)
    {
        Debug.WriteLine("OnOpen called with timeout " + timeout.ToString(), this.GetType().FullName);
    }

    protected override IAsyncResult OnBeginOpen(TimeSpan timeout, AsyncCallback callback, object state)
    {
        Debug.WriteLine("OnBeginOpen called", this.GetType().FullName);
        throw new NotImplementedException();
    }

    protected override void OnEndOpen(IAsyncResult result)
    {
        Debug.WriteLine("OnEndOpen called", this.GetType().FullName);
        throw new NotImplementedException();
    }
}

Now, create a class implementing the actual send channel. The following methods are called in the following sequence: OnOpen, BeginRequest, EndRequest, OnClose.

class FileSendChannel : ChannelBase, IRequestChannel
//class FileSendChannel : ChannelBase, IOutputChannel
{
    private string fileName;
    private bool useXmlWrapper;

    delegate Message RequestAsyncDelegate(Message message);
    private RequestAsyncDelegate requestAsyncDelegate;

    public FileSendChannel(ChannelManagerBase channelManager, string fileName, bool useXmlWrapper)
        : base(channelManager)
    {
        this.fileName = fileName;
        this.useXmlWrapper = useXmlWrapper;
        this.requestAsyncDelegate = new RequestAsyncDelegate(Request);
    }

    #region IRequestChannel Members

    public System.ServiceModel.EndpointAddress RemoteAddress
    {
        get { throw new NotImplementedException(); }
    }

    public Uri Via
    {
        get { throw new NotImplementedException(); }
    }

    public Message Request(Message message)
    {
        // Replace special tokens (macros) in the file name.
        string fileName = this.fileName.Replace("%datetime%", DateTime.Now.ToString("yyyy-MM-dd hhmmss.ffffff"));
        // Create the file and write to it.
        FileStream stream = new FileStream(fileName, FileMode.Create);
        if (useXmlWrapper)
        {
            byte[] buffer = message.GetBody<byte[]>();
            stream.Write(buffer, 0, buffer.Length);
            stream.Close();
        }
        else
        {
            XmlDictionaryWriter xdw = XmlDictionaryWriter.CreateTextWriter(stream);
            //XmlDictionaryWriter xdw = XmlDictionaryWriter.CreateBinaryWriter(stream);
            message.WriteBodyContents(xdw);
            xdw.Close();
        }
        // Return an empty message
        return Message.CreateMessage(MessageVersion.Default, "Dummy");
    }

    public Message Request(Message message, TimeSpan timeout)
    {
        // This implementation does not handle timeout.
        return Request(message);
    }

    public IAsyncResult BeginRequest(Message message, TimeSpan timeout, AsyncCallback callback, object state)
    {
        Debug.WriteLine("BeginRequest called with timeout " + timeout.ToString(), this.GetType().FullName);
        // This implementation does not handle timeout.
        return BeginRequest(message, callback, state);
    }

    public IAsyncResult BeginRequest(Message message, AsyncCallback callback, object state)
    {
        Debug.WriteLine("BeginRequest called", this.GetType().FullName);
        return requestAsyncDelegate.BeginInvoke(message, callback, state);
    }

    public Message EndRequest(IAsyncResult result)
    {
        Debug.WriteLine("EndRequest called", this.GetType().FullName);
        return requestAsyncDelegate.EndInvoke(result);
    }

    #endregion

    #region ChannelBase abstract members

    protected override void OnOpen(TimeSpan timeout)
    {
        Debug.WriteLine("OnOpen called with timeout " + timeout.ToString(), this.GetType().FullName);
    }

    protected override IAsyncResult OnBeginOpen(TimeSpan timeout, AsyncCallback callback, object state)
    {
        Debug.WriteLine("OnBeingOpen called with timeout " + timeout.ToString(), this.GetType().FullName);
        throw new NotImplementedException();
    }

    protected override void OnEndOpen(IAsyncResult result)
    {
        Debug.WriteLine("OnEndOpen called", this.GetType().FullName);
    }

    protected override void OnClose(TimeSpan timeout)
    {
        Debug.WriteLine("OnClose called", this.GetType().FullName);
    }

    protected override IAsyncResult OnBeginClose(TimeSpan timeout, AsyncCallback callback, object state)
    {
        Debug.WriteLine("OnBeginClose called", this.GetType().FullName);
        throw new NotImplementedException();
    }

    protected override void OnEndClose(IAsyncResult result)
    {
        Debug.WriteLine("OnEndClose called", this.GetType().FullName);
    }

    protected override void OnAbort()
    {
        Debug.WriteLine("OnAbort called", this.GetType().FullName);
    }

    #endregion

Implement Configuration Classes

The first class to create is FileBindingElement, which inherits from TransportBindingElement. FileBindingElement contains configuration properties, in this case FileName and UseXmlWrapper (more on that later) , and builds a channel factory (BuildChannelFactory), which in turns creates the send channel.

public class FileBindingElement : TransportBindingElement
{
    public string FileName { get; set; }
    public bool UseXmlWrapper { get; set; }

    public FileBindingElement()
    {
        this.FileName = FileDefaults.FileName;
        this.UseXmlWrapper = FileDefaults.UseXmlWrapper;
    }

    public override bool CanBuildChannelFactory<TChannel>(BindingContext context)
    {
        Debug.WriteLine("CanBuildChannelFactory: " + typeof(TChannel).FullName, this.GetType().FullName);
        return typeof(TChannel) == typeof(IRequestChannel);
        //return typeof(TChannel) == typeof(IOutputChannel);
    }

    public override IChannelFactory<TChannel> BuildChannelFactory<TChannel>(BindingContext context)
    {
        Debug.WriteLine("BuildChannelFactory called", this.GetType().FullName);
        return (IChannelFactory<TChannel>)new FileChannelFactory(FileName, UseXmlWrapper, context);
    }

    public override bool CanBuildChannelListener<TChannel>(BindingContext context)
    {
        Debug.WriteLine("CanBuildChannelListener called. Type of channel = " + typeof(TChannel).Name, this.GetType().FullName);
        //return typeof(TChannel) == typeof(IReplyChannel);
        return typeof(TChannel) == typeof(IInputChannel);
    }

    public override IChannelListener<TChannel> BuildChannelListener<TChannel>(BindingContext context)
    {
        Debug.WriteLine("BuildChannelListener called.", this.GetType().FullName);
        return (IChannelListener<TChannel>)new FileChannelListener(this, context);
    }

    public override string Scheme
    {
        get { return "net.file"; }
    }

    public override BindingElement Clone()
    {
        return new FileBindingElement(this);
    }

    private FileBindingElement(FileBindingElement elementToBeCloned)
        : base(elementToBeCloned)
    {
        this.FileName = elementToBeCloned.FileName;
        this.UseXmlWrapper = elementToBeCloned.UseXmlWrapper;
    }

CanBuildChannelFactory is called multiple times, but only returns true for the type of channel our channel factory (FileChannelFactory) can build, i.e. a channel that implements IRequestChannel.

BuildChannelFactory creates a new FileChannelFactory with the configured filename as a parameter.

Now, create a signing key and build the project.

To create the rest of the configuration classes, use ConfigurationCodeGenerator (under <samples installation folder>WCFTools):

samplesWCFWFCardSpaceWCFToolsConfigurationCodeGeneratorCSbinConfigurationCodeGenerator.exe /be:WCFFileTransport.FileBindingElement /dll:bindebugWCFFileTransport.dll

Add the output files (FileElement.cs, FileDefaults.cs, FileConfigurationSrings.cs, sampleConfig.xml) to the project and make the following modifications:

  • Rename FileElement to the better describing FileBindingElementExtensionElement. Don’t forget to change sampleConfig.xml!
  • This class inherits from a non-existing class BindingElementExtensionSection – this should be changed to BindingElementExtensionElement.
  • The type of the parameter of CopyFrom should be ServiceModelExtensionElement, not ServiceModelExtensionSection.
  • You can get rid of the ManualAddressing, MaxBufferPoolSize and MaxReceivedMessageSize properties.
  • In FileDefaults, set a sensible default for DefaultFileName and DefaultUseXmlWrapper. In FileBindingElement, use this default.

Rebuild and add the assembly to the GAC.

Configure BizTalk

Copy the bindingElementExtension from the generated sampleConfig.xml and paste it into machine.config (normally located in C:WindowsMicrosoft.NETFrameworkv2.0.xCONFIG). (I changed the name from “file” to “WCFFileAdapter” here.) If you had started BizTalk Administration already, you might need to close and restart it now.

Create a new send port, and choose transport type WCF-Custom. Press the Configure button. On the General tab, type an address, e.g. net.file://localhost/test and an action (any will do). If you forget to state an action you will get this error later:

System.ArgumentNullException: Value cannot be null.
Parameter name: key

On the Binding tab, choose customBinding. Then remove existing binding elements and insert WCFFileAdapter. Specify the output filename.

image

Create a receive port and file receive location, and a filter on the send port so that you can drop test messages that are sent to the new WCF channel.

Test with a simple XML message. It should work. You should get something similar to this in the debug log:

WCFFileTransport.FileBindingElement: CanBuildChannelFactory: System.ServiceModel.Channels.IDuplexChannel
WCFFileTransport.FileBindingElement: CanBuildChannelFactory: System.ServiceModel.Channels.IRequestChannel
WCFFileTransport.FileBindingElement: BuildChannelFactory called
WCFFileTransport.FileChannelFactory: OnOpen called with timeout 00:01:00
WCFFileTransport.FileChannelFactory: OnCreateChannel called
WCFFileTransport.FileSendChannel: OnOpen called with timeout 00:01:00
WCFFileTransport.FileSendChannel: BeginRequest called
WCFFileTransport.FileSendChannel: EndRequest called
WCFFileTransport.FileSendChannel: OnClose called

If you test with a binary file, or an ordinary text file, you must set useXmlWrapper to True as shown above. Otherwise you will get the following error:

A message sent to adapter "WCF-Custom" on send port "net.file" with URI "net.file://localhost/test" is suspended.
Error details: System.Xml.XmlException: Data at the root level is invalid. Line 1, position 1.

In other words, WCF expects that everything is XML. But you probably want to be able to use the file adapter with arbitrary files. That is why I have the useXmlWrapper configuration property. If it set to true, the file contents is wrapped in a binary XML element like this:

<base64Binary xmlns="http://schemas.microsoft.com/2003/10/Serialization/">…</base64Binary>

References

Extending the Channel Layer in Windows Communication Foundation

Transport/Certificate Authentication and Party Resolution

I recently worked on a solution where partners would call web services, and “transport security” (SSL) would be used for confidentiality and client authentication. In this solution, partner information, like agreement ID, should be looked up. Wouldn’t it be nice if data from the client certificate used for authentication could be used in party resolution? Yes, but I haven’t found a way to make it work.

If I look at the context properties written by the WCF adapter, there are no certificate related. (In ASP and ASP.NET, you can use the CERT_SUBJECT or CERT_SERIALNUMBER server variables.) In other words, it is not even possible to write a custom party resolution component to do the party resolution.

With the SOAP adapter, it is the same story, but there you could at least set up certificate to user mapping in IIS. If you do that, the Windows user ID ends up in the WindowsUser context property, and the built-in party resolution component works. This mapping technique doesn’t seem to work with the WCF adapter, though. The only workaround I can come up with is to use message security instead, where the signature and certificate is part of the SOAP header.

If anyone has another idea, I would be glad to hear it.

Accessing a BizTalk WCF Service over SSL with Client Certificate Authentication

Accessing a WCF Service published by BizTalk over SSL with client certificate authentication proved to be difficult. Here are some of the steps I had to do:

1. Publish the web service using the BizTalk WCF Service Publishing Wizard.

2. Add a service reference in the test client.

3. Change the receive location security mode to Transport and Transport client credential type to Certificate.

image

4. Generate a server certificate for IIS. Using IIS 7 Manager, I generated a self-signed certificate by selecting the server (top node), opening Server Certificates and then clicking on “Create a Self-Signed Certificate…”.

5. Create an https binding. (Select the web site and click on Bindings… on the right.)

6. Create a self-signed client certificate for testing. I used the following commands:

makecert -r -pe -n "CN=Henrik" -b 01/01/2007 -e 01/01/2010 -sky exchange Client.cer -sv Client.pvk
pvk2pfx.exe -pvk Client.pvk -spc Client.cer -pfx Client.pfx

7. Add Client.pfx to the Current User/Personal store and Client.cer to the Local Computer/Trusted People and Trusted Root Certificate Authorities store.

8. Select the application, double click SSL Settings. I used the following settings: Require SSL, require client certificates.

9. Obviously, the endpoint address in the client’s App.config must be changed from http://&#8230; to https://. But it must also be changed to use client certificates:

<bindings>
  <wsHttpBinding>
    <binding …
        <security mode="Transport">
          <transport clientCredentialType="Certificate" proxyCredentialType="None" realm="" />
          …

10. To choose authentication certificate, you can either add the following code:

myClient.ClientCredentials.ClientCertificate.SetCertificate(StoreLocation.CurrentUser, StoreName.My, X509FindType.FindBySerialNumber, …)

or use the following configuration:

<behaviors>
  <endpointBehaviors>
    <behavior name="ClientCertificateBehavior">
      <clientCredentials>
        <clientCertificate findValue="…" storeLocation="CurrentUser" storeName="My" x509FindType="FindBySerialNumber" />
      </clientCredentials>
    </behavior>
  </endpointBehaviors>
</behaviors>
<client>
  <endpoint … behaviorConfiguration="ClientCertificateBehavior" />
  …

11. When I ran the test client, I got an exception. Looking in the application event log, I had the following error: “…Could not find a base address that matches scheme http for the endpoint with binding MetadataExchangeHttpBinding. Registered base address schemes are [https]…”

12. To resolve this, I had to modify the service Web.config. First I commented out
<endpoint name="HttpMexEndpoint" address="mex" binding="mexHttpBinding" bindingConfiguration="" contract="IMetadataExchange" />
and uncommented
<endpoint name="HttpsMexEndpoint" address="mex" binding="mexHttpsBinding" bindingConfiguration="" contract="IMetadataExchange" />
It didn’t work. Now, I got “…The SSL settings for the service ‘None’ does not match those of the IIS ‘Ssl, SslNegotiateCert, SslRequireCert’…” After a great deal of binging, I found that the solution was to comment out both these endpoints. Not so obvious!

BizTalk and the WCF Channel for WebSphere MQ

This post is about how to (or not how to – more of that later) use the WCF Channel for WebSphere MQ with BizTalk.

WCF Channel for WebSphere MQ is part of BizTalk Adapters for Host Systems (BAHS) 2.0. (On MSDN subscriber downloads, it is listed under BizTalk Server 2009. In MSDN library, the product documentation is listed under Host Integration Server 2009.) During installation, check this component:

image

To be able to use the new WCF channel, a new channel binding must be defined in Machine.config (in C:WindowsMicrosoft.NETFrameworkv2.0.50727CONFIG). Under <system.serviceModel><extensions><bindingExtensions> add

<add name="mqChannelBinding" type="System.ServiceModel.Channels.WebSphereMQ.WebSphereMQBindingCollectionElement, System.ServiceModel.Channels.WebSphereMQ.Channel, Version=7.0.2300.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />

(For details, see How to Enable the WCF Extensibility Points with the WCF Adapters.)

To be able to test, I installed an evaluation copy of WebSphere MQ 7.0, which I downloaded from IBM. (Registration is necessary.) During installation, include MQ Explorer – it is handy for troubleshooting. During configuration, I used the following options:

image

The installation was performed on a Windows 2008 server called BTS2009. This installation created a queue manager called QM_BTS2009, a channel called S_BTS2009 and a listener on port 1414.

Next, create a send port in BizTalk, choose transport type WCF-Custom and click Configure. On the general tab, the address (URI) should be net.mqs://<queue manager name>/<queue name>, e.g. net.mqs://QM_BTS2009/default. On the Binding tab, select mqChannelBinding and input the appropriate configuration:

Key Value Example
connectionType BaseClient BaseClient
deadLetterQuee True True
leaveMQHeadersInMessage True True
mqcdChannelName <channel name> S_BTS2009
mqcdConnectionName <host>(<port>) BTS2009(1414)
mqcdTransportType TCP TCP

Or import this configuration file if you don’t want to type so much:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <enterpriseLibrary.ConfigurationSource selectedSource="ESB File Configuration Source" />
  <system.serviceModel>
    <client>
      <endpoint address="net.mqs://QM_BTS2009/default" behaviorConfiguration="EndpointBehavior" binding="mqChannelBinding" bindingConfiguration="SendSampleBinding" contract="BizTalk" name="ToMQSeries" />
    </client>
    <behaviors>
      <endpointBehaviors>
        <behavior name="EndpointBehavior" />
      </endpointBehaviors>
    </behaviors>
    <bindings>
      <mqChannelBinding>
        <binding name="SendSampleBinding" connectionType="BaseClient" leaveMQHeadersInMessage="True" deadLetterQueue="True" mqcdChannelName="S_BTS2009" mqcdTransportType="TCP" mqcdConnectionName="BTS2009(1414)" mqpmoSyncPoint="True" mqgmoSyncPoint="True">
          <readerQuotas maxDepth="10" maxArrayLength="1000" maxBytesPerRead="2000" />
        </binding>
      </mqChannelBinding>
    </bindings>
  </system.serviceModel>
</configuration>

Now send an XML document to this send port and check the result in WebSphere MQ Explorer. The queue depth should be 1.

So what about receiving a message from MQ? I tested that too, but had issues. Using the following configuration:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <enterpriseLibrary.ConfigurationSource selectedSource="ESB File Configuration Source" />
  <system.serviceModel>
    <services>
      <service behaviorConfiguration="SendReceiveSampleServiceBehaviour" name="BizTalk">
        <endpoint address="net.mqs://QM_BTS2009/default" behaviorConfiguration="EndpointBehavior" binding="mqChannelBinding" bindingConfiguration="SendReceiveSampleBinding" name="FromMQSeries2" contract="BizTalk" />
      </service>
    </services>
    <behaviors>
      <endpointBehaviors>
        <behavior name="EndpointBehavior" />
      </endpointBehaviors>
      <serviceBehaviors>
        <behavior name="SendReceiveSampleServiceBehaviour" />
      </serviceBehaviors>
    </behaviors>
    <bindings>
      <mqChannelBinding>
        <binding name="SendReceiveSampleBinding" connectionType="BaseClient" leaveMQHeadersInMessage="True" deadLetterQueue="True" mqcdChannelName="S_BTS2009" mqcdTransportType="TCP" mqcdConnectionName="BTS2009(1414)" mqpmoSyncPoint="True" mqgmoSyncPoint="True">
          <readerQuotas maxDepth="10" maxArrayLength="1000" maxBytesPerRead="2000" />
        </binding>
      </mqChannelBinding>
    </bindings>
  </system.serviceModel>
</configuration>

I got the following warning repeatedly in the application event log:

Event Type:    Warning
Event Source:    BizTalk Server 2009
Event Category:    (1)
Event ID:    5740
Date:        2009-08-25
Time:        15:24:07
User:        N/A
Computer:    BTS2009
Description:
The adapter "WCF-Custom" raised an error message. Details "System.ServiceModel.Channels.WebSphereMQ.WebSphereMQException: Unexpected error; webspheremqtransport.cpp#1206; ImqQueueManager::connect; completionCode = 2; reasonCode = 2058
   at System.ServiceModel.Channels.WebSphereMQ.AsyncResult.End[TAsyncResult](IAsyncResult result)
   at System.ServiceModel.Channels.WebSphereMQ.TryReceiveAsyncResult.End(IAsyncResult result, Message& message)
   at System.ServiceModel.Channels.WebSphereMQ.WebSphereMQInputChannel.EndTryReceive(IAsyncResult result, Message& message)
   at System.ServiceModel.Dispatcher.InputChannelBinder.EndTryReceive(IAsyncResult result, RequestContext& requestContext)
   at System.ServiceModel.Dispatcher.ErrorHandlingReceiver.EndTryReceive(IAsyncResult result, RequestContext& requestContext)".

The message was not read. The strange thing is that I could get it to work with the MQSC (client based) adapter. I could also run the BAHS SendReceiveSample .Net sample without errors.

Supposedly the cause is that BizTalk uses System.URI which appears to convert everything to lower case, and since MQ is case sensitive, it does not work. So until this bug is fixed, you have to use queue manager and queue names in lower case.

In my case, I created a new queue manager called qm_test with a listener listening on port 1415. I then created a queue called q_test and a channel called S_BTS2009 as before. I then adjusted the send port and receive location configurations. That worked!

Installing BizTalk ESB Toolkit 2.0

Microsoft BizTalk ESB Toolkit 2.0 (formerly known as ESB Guidance 2.0) is out. I installed it, together with the ESB Management Portal, today on a Windows Server 2008 32-bit virtual machine using the following procedure:

  1. Install BizTalk ESB Toolkit 2.0-<CPU>.msi.
  2. Import C:Program FilesMicrosoft BizTalk ESB Toolkit 2.0/Microsoft.Practices.ESB.CORE.msi into BizTalk Server using the BizTalk Server 2009 Administration Console and install the .msi file.
  3. Now, before you install the ESB Management Portal, you should follow the steps in Installing the BizTalk ESB Toolkit –> Configuring Services and Components in the help.
  4. Extract C:Program FilesMicrosoft BizTalk ESB Toolkit 2.0/ESBSource.zip to C:ProjectsMicrosoft.Practices.ESB.
  5. Navigate to C:ProjectsMicrosoft.Practices.ESBSamplesManagement PortalInstallScripts, and then start ManagementInstall.cmd.

When browsing the portal (http://localhost/ESB.Portal/Home/HomePage.aspx), I first got an exception. From the event log:

Event Type:    Warning
Event Source:    ASP.NET 2.0.50727.0
Event Category:    (3)
Event ID:    1309
Date:        2009-06-22
Time:        09:04:23
User:        N/A
Computer:    BTS2009.europe.corp.microsoft.com
Description:
Event code: 3005
Event message: An unhandled exception has occurred.
Event time: 2009-06-22 09:04:23
Event time (UTC): 2009-06-22 07:04:23
Event ID: 6a2cd3b312364acf8bda81406418cb91
Event sequence: 211
Event occurrence: 8
Event detail code: 0
Application information:
    Application domain: /LM/W3SVC/1/ROOT/ESB.Portal-1-128901266654457632
    Trust level: Full
    Application Virtual Path: /ESB.Portal
    Application Path: C:ProjectsMicrosoft.Practices.ESBSourceSamplesManagement PortalESB.Portal
    Machine name: BTS2009
Process information:
    Process ID: 4816
    Process name: w3wp.exe
    Account name: NT AUTHORITYNETWORK SERVICE
Exception information:
    Exception type: SqlException
    Exception message: A network-related or instance-specific error occurred while establishing a connection to SQL Server. The server was not found or was not accessible. Verify that the instance name is correct and that SQL Server is configured to allow remote connections. (provider: TCP Provider, error: 0 – No connection could be made because the target machine actively refused it.)
Request information:
    Request URL: http://localhost/ESB.Portal/Lists/RegistrySummaryContainer.aspx
    Request path: /ESB.Portal/Lists/RegistrySummaryContainer.aspx
    User host address: ::1
    User: BTS2009Administrator
    Is authenticated: True
    Authentication Type: Negotiate
    Thread account name: NT AUTHORITYNETWORK SERVICE
Thread information:
    Thread ID: 5
    Thread account name: NT AUTHORITYNETWORK SERVICE
    Is impersonating: False

I searched the web and found this blog post that provided the solution:

  1. Use SQL Server Configuration Manager to enable TCP/IP.
  2. Restart SQL Server.
  3. Restart IIS (by typing IISReset at a command prompt).

Connection String for DB2 on Windows

I had a lot of trouble with connecting to DB2 on Windows. I thought it was hard to figure out the right connection string, and there is also a bug that led me on the wrong track.

I had DB2 installed on the same virtual server as BizTalk, and used this connection string:

Provider=DB2OLEDB;Network Transport Library=TCPIP;Network Address=localhost;Network Port=50000;Initial Catalog=SAMPLE;Package Collection=db2admin;User ID=db2admin;Password=pass@word1;

I got the following error message:

Message: The network connection was terminated because the host failed to send any data.
Native error: –605
Source: Microsoft DB2 OLE DB Provider
SQLState: 08S01

This was regardless if I used the Data Access Tool or .net code. Using IBM’s OLE DB provider from .net code worked well:

Provider=IBMDADB2;Database=SAMPLE;Hostname=localhost;Protocol=TCPIP; Port=50000;Uid=db2admin;Pwd=pass@word1;

It turned out that you cannot use localhost with the Microsoft DB2 OLE DB provider! When I changed to 127.0.0.1 it worked. The real host name works as well.

Next problem was the parameters in the connection string. I wanted to use the sample database that I created using IBM’s setup program. I was logged in as Administrator during this installation, so the sample database objects were created in the Administrator schema. This configuration worked:

image

Note that you should specify the database in “Initial catalog” on in “Database name”.

Host Integration Server 2009 vs. BizTalk Server 2009 Adapters for Host Systems 2.0

I recently did a proof of concept where one part was connectivity with DB2. I had BizTalk Server 2009 developer edition installed, and thought that I would get the DB2 adapter by installing Host Integration Server 2009. Wrong! After a couple of reinstalls and troubleshooting I realized that HIS 2009 does not think BizTalk 2009 developer edition is BizTalk, and therefore the setup program hides the DB2 adapter selection. The developer version is not called Host Integration Server, but BizTalk Server 2009 Adapters for Host Systems 2.0!

From https://connect.microsoft.com/site/sitehome.aspx?SiteID=66&wa=wsignin1.0:

Microsoft BizTalk Adapters for Host System 2.0 Developer edition is available as supplemental software to the Microsoft BizTalk Server 2009 Developer edition. See the Microsoft BizTalk Server 2009 web site for information on purchasing Microsoft BizTalk Server 2009 Developer edition. Microsoft does not offer a Microsoft Host Integration Server 2009 Developer edition. Please utilize the BAHS 2.0 Developer edition.

On MSDN subscriber downloads, you find BizTalk Adapters for Host Systems 2.0 under BizTalk Server 2009.