Wednesday, April 28, 2010

Route and Map a Single Message to Multiple Recipients Using an Itinerary Routing Slip

 

I have been working with the ESB toolkit and following MSDN Social. If you have worked through some of the Development Activities you may have come across How to: Route a Single Message to Multiple Recipients Using an Itinerary Routing Slip.

You may have thought what I thought.. what if I need to use a different transform for each recipient? I would suggest you read the above development activity and understand it, then come back to the blog.

To summarize :

  • A message comes in
  • A Message Extender extends the onramp
  • The Message Extender has more than one resolver
  • The toolkit will send the message to each resolver.

image

If you select one of the Resolvers you will see that you can specify a transform type. For a route message extender this has no affect. The message will NOT be transformed.

image

In order for the message to be transformed you need to create your own Custom Itinerary Messaging Service this new service will need to route and transform. You may think this sounds hard, but really.. it’s easy.

I set about creating this, I had reflector open on one window and Visual Studio open on the other. I wanted to reuse the existing ESB code has much as possible (without copy and pasting code blocks). The ESB Assembly Microsoft.Practices.ESB.Itinerary.Services.dll contains the class for each Service, these classes have public methods for transform and route. So all we need to do is call these.

The is the code :

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.IO;
using System.Globalization;
using Microsoft.Practices.ESB.Exception.Management;
using Microsoft.Practices.ESB.Resolver;
using Microsoft.Practices.ESB.Itinerary.Services;
using Microsoft.BizTalk.Message.Interop;
using Microsoft.BizTalk.Streaming;

namespace Diagonal.ESB.Services
{
public class TransformAndRouteService : IMessagingService
{
public IBaseMessage Execute(IPipelineContext context, IBaseMessage msg, string resolverString, IItineraryStep step)
{
if (context == null)
throw new ArgumentNullException("context");
if (msg == null)
throw new ArgumentNullException("msg");
if (string.IsNullOrEmpty(resolverString))
throw new ArgumentException("Properties.Resources.ArgumentStringRequired", "resolverString");
try
{

msg = route(context, msg, resolverString);
msg = transform(context, msg, resolverString);
return msg;

}
catch (System.Exception ex)
{
EventLogger.Write(MethodInfo.GetCurrentMethod(), ex);
throw;
}
}

private static IBaseMessage transform(IPipelineContext context, IBaseMessage msg, string resolverString)
{
// Get settings for transform to see if we need to call it
ResolverInfo info = ResolverMgr.GetResolverInfo(ResolutionType.Transform, resolverString);

if (info.Success)
{
// if we dont find transform info, we dont mind
Dictionary<string, string> resolverDictionary = ResolverMgr.Resolve(info, msg, context);

string transformMap;
resolverDictionary.TryGetValue("Resolver.TransformType", out transformMap);
if ((!string.IsNullOrEmpty(transformMap)) && (!transformMap.Equals("(None)")))
{
IBaseMessage transformedmessage;
Stream safeDataStream = new ReadOnlySeekableStream(msg.BodyPart.GetOriginalDataStream());

// move the stream pointer back to the start; could theoretically throw an exception if BizTalk
// Server were to return a non-seekable clone of the original stream within GetSafeDataStream
safeDataStream.Position = 0L;

msg.BodyPart.Data = safeDataStream;

// Backup the stream
MemoryStream ms = new MemoryStream();
byte[] respBuffer = new byte[1024];

int bytesRead = safeDataStream.Read(respBuffer, 0,
respBuffer.Length);

while (bytesRead > 0)
{
ms.Write(respBuffer, 0, bytesRead);
bytesRead = safeDataStream.Read(respBuffer, 0,
respBuffer.Length);
}

msg.BodyPart.Data.Position = 0;
TransformationService transform = new TransformationService();
transformedmessage = transform.ExecuteTransform(context, msg, resolverDictionary["Resolver.TransformType"], false);

// restore the stream
ms.Position = 0L;
msg.BodyPart.Data = ms;

return transformedmessage; // Pass back the transformed message
}
}
// not getting transformed.
return msg; // Pass back non transformed message
}

private static IBaseMessage route(IPipelineContext context, IBaseMessage msg, string resolverString)
{
// Pass to routeservice
RoutingService routeService = new RoutingService();
msg = routeService.ExecuteRoute(context, msg, resolverString);
return msg;
}

public string Name
{
get {
System.Diagnostics.Trace.WriteLine("custom code 2");
return "Diagonal.ESB.Services.TransformAndRoute";
}
}

public bool ShouldAdvanceStep(IItineraryStep step, Microsoft.BizTalk.Message.Interop.IBaseMessage msg)
{
return true;
}

public bool SupportsDisassemble
{
get { return true; }
}

}
}


You will need edit the esb.config to allow you to use your new service, add



      <itineraryService 
id="A5414011-7D60-4670-8AB5-9B6F14C4F0FF"
name="Diagonal.ESB.Services.TransformAndRoute"
type="Diagonal.ESB.Services.TransformAndRouteService, Diagonal.ESB.Services, Version=1.0.0.0, Culture=neutral, PublicKeyToken=bf0fd96c065177b4"
scope="Messaging"
stage="AllReceive"/>



to itineraryServices node.



Once this is in place you can select TransformAndRoute from the list of ServiceNames. When you use this Service the MAP specified by Transform Type will be used to transform the message. You can set this statically , BRE etc. Note if you don’t select a MAP it won’t crash.



I will put the code on codeplex when time permits.



More ESB blogs to follow ;


7 comments:

  1. Hi,
    I have tried this code, But shows errors, that "The type or namespace name could not be found"
    It seems assembly references are missing.

    The following dlls are not there in my machine

    using Microsoft.BizTalk.Message.Interop;
    using Microsoft.BizTalk.Streaming;

    Even not in Windows/assembly

    please guide.

    ReplyDelete
  2. If I have three send pipelines for 'Message One', 'Message Two' and 'Message Three', which pipeline should be selected as a send pipeline for a dynamic 'Send Port' from Admin console?

    ReplyDelete
  3. Anonymous,

    They are in the GAC on my build PC.

    BizTalkPP, Do you need a different pipeline for each message? You may be able to set a 'prop' and put a filter on the send port.

    PS

    I have just returned from my hols so I apologise for the slow reply.

    ReplyDelete
  4. C:\Windows\assembly\GAC_MSIL\Microsoft.BizTalk.Streaming\

    ReplyDelete
  5. I get this solution when you need to route a message to multiple locations. Is there a way to take inbound message, create multiple copies of it and use different itinerary with each copy of the message? I guess I can do it with the orchestration, but is there a way to do it with ESB toolkit.

    ReplyDelete
  6. Hi Steve,

    First resolver is executed properly and all fine. However the other two resolvers are not getting executed. Its always executing the first resolver only. Any thoughts about it?

    ReplyDelete
    Replies
    1. The current default pipelines use the ESB Dispatch which does not process multiple resolvers. There is not an out-of-box pipeline setup with this configuration, but this is very easy to put together. This can be built by creating a custom pipeline that includes the Itinerary Selector in the Decode stage and then the ESB Dispatch Disassembler in the Disassemble stage.

      Delete