Introduction
Mapping is one of the main activities in any integration solutions. We always use mapping to transform an input message to another message format and structure. It is very crucial to have a dynamic solution as much as we can to let our solution easy to update and deployment process smooth and not affecting the existing code.Where do we use mapping?
- We use mapping in inbound maps in receive port as shown in figure 1
Figure 1. Inbound Maps
- We can use mapping in outbound maps in send port as shown in figure 2
Figure 2. Outbound Maps
- We can use mapping in orchestration as shown in figure 3
Figure 3. Mapping in Orchestration
Problem
In orchestration we can see a big problem that if there is any a new map that we need to use in orchestration then we have to change orchestration which means to recompile and a redeploy our solution and that will affect the existing BizTalk solution process. On the other hand, in inbound and outbound maps if you imagine that we have dozens of maps, it will be headache for BizTalk administrator and developer to make sure that all maps are configured correctly and sometimes we need an operator user to update a BizTalk application configuration from BRE or a web portal because he is not convenient with BizTalk administration console and what if you have some cases that you want to map depending on context or content of that message in inbound and outbound maps without using orchestration.Solution
We can have a solution by applying a dynamic mapping resolver.What is dynamic map resolver?
It is a mechanism of dynamically(at run time) associate map name from existing repository like business rule engine or custom configuration database table to transformer based on an identifier contained in each source instance message.How to implement dynamic map resolver?
We need to call transformation code from .NET code in custom pipeline component. I used ILSpy to reflect the code of TransformStream method from Microsoft.Practices.ESB.Itinerary.Services assembly. This method takes as parameters a Stream containing the message to transform, a String containing the fully qualified name of a map deployed within BizTalk, a Boolean for validating map source with message type passed and reference to message type. The method returns a Stream containing the transformed document.I created a map helper that will be called from custom pipeline component to transform the stream of source to the expected destination stream.
01.
using
Microsoft.XLANGs.BaseTypes;
02.
using
Microsoft.XLANGs.RuntimeTypes;
03.
using
System;
04.
using
System.Globalization;
05.
using
System.IO;
06.
using
System.Xml.XPath;
07.
08.
namespace
TechNetWiki.DynamicMappingResolver.Library
09.
{
10.
public
class
MapHelper
11.
{
12.
public
static
Stream TransformStream(Stream stream,
string
mapName,
bool
validate,
ref
string
messageType)
13.
{
14.
Type
mapType = Type.GetType(mapName);
15.
if
(
null
== mapType)
16.
throw
new
Exception(
string
.Format(
"Map {0} is not exist."
, mapName));
17.
18.
TransformMetaData
metaData = TransformMetaData.For(mapType);
19.
20.
SchemaMetadata
schema = metaData.SourceSchemas[0];
21.
String
sourceMap = schema.SchemaName;
22.
23.
SchemaMetadata
targetSchema = metaData.TargetSchemas[0];
24.
25.
if
(validate)
26.
{
27.
if
(
string
.Compare(messageType, sourceMap,
false
, CultureInfo.CurrentCulture) != 0)
28.
throw
new
Exception(
string
.Format(
"Unable to execute map,'{0}'."
+
29.
"The
source document type,'{1}'does not match the maps's "
+
30.
"target
source document type,'{2}'"
, mapName,
31.
messageType,
32.
sourceMap));
33.
}
34.
messageType
= targetSchema.SchemaName;
35.
36.
XPathDocument
doc =
new
XPathDocument(stream);
37.
ITransform
transform = metaData.Transform;
38.
Stream
output =
new
MemoryStream();
39.
transform.Transform(doc,
metaData.ArgumentList, output,
null
);
40.
output.Flush();
41.
output.Seek(0,
SeekOrigin.Begin);
42.
return
output;
43.
}
44.
}
45.
}
Note that you have to add <BizTalkInstallationFolder>\Microsoft.XLANGs.BaseTypes.dll and <BizTalkInstallationFolder>\Microsoft.XLANGs.RuntimeTypes.dll
to your class library project to build map helper code.
Then we need to create a custom pipeline component by creating class library project that has a reference to the created project that including MapHelper code and add reference to these dlls <BizTalkInstallationFolder>\Microsoft.BizTalk.Pipeline.dll , <BizTalkInstallationFolder>\Microsoft.XLANGs.BaseTypes.dll.
We need to use any configuration repository to retrieve map information at run time like Business Rule Engine (BRE ) or custom configuration table in database . In this article, I am using Business Rule Engine to retrieve map information.
to your class library project to build map helper code.
Then we need to create a custom pipeline component by creating class library project that has a reference to the created project that including MapHelper code and add reference to these dlls <BizTalkInstallationFolder>\Microsoft.BizTalk.Pipeline.dll , <BizTalkInstallationFolder>\Microsoft.XLANGs.BaseTypes.dll.
We need to use any configuration repository to retrieve map information at run time like Business Rule Engine (BRE ) or custom configuration table in database . In this article, I am using Business Rule Engine to retrieve map information.
BRE implementation
I will retrieve mapping map name depending on the MessageType which represents the source message type.However, you can use any context information to retrieve map name or any content information of the message. I created schema for this purpose containing MessageType and MapStrongNameas shown in figure 4.
Figure 4. BRE Schema
Then I created BRE rules using Business Rule Composer as shown in figure 5.
Figure 5. Sample BRE Rule
Note: that the message type is formed as Namespace#SchemaRoot and map strong name is formed as MapName, assembly strong name.
You can use vocabulary to ease life of operator user too.
Dynamic map resolver in Inbound Maps
We need to create a custom pipeline component and adding this component to pre-assemble part in the receive pipelineso first we need to implement IComponent which contains Execute where we need to call TransformStream method as shown in the following code
01.
public
IBaseMessage Execute(IPipelineContext pContext, Microsoft.BizTalk.Message.Interop.IBaseMessage pInMsg)
02.
{
03.
string
messageType =
null
;
04.
string
messageSourceType = pInMsg.Context.Read(
"MessageType"
,
"http://schemas.microsoft.com/BizTalk/2003/system-properties"
).ToString();
05.
string
mapName = GetMapStrongName(messageSourceType);
06.
System.IO.Stream
originalStream = pInMsg.BodyPart.GetOriginalDataStream();
07.
pInMsg.BodyPart.Data
= MapHelper.TransformStream(originalStream, mapName,
false
,
ref
messageType);
08.
return
pInMsg;
09.
}
We need to redecorate custom pipeline class with CategoryTypes.CATID_Any to be allowed add it to Pre-assemble component in receive pipeline and validate component in send pipeline .
The complete code of custom pipeline as following
001.
using
Microsoft.BizTalk.Component.Interop;
002.
using
Microsoft.BizTalk.Message.Interop;
003.
using
Microsoft.RuleEngine;
004.
using
System;
005.
using
System.Collections;
006.
using
System.Collections.Generic;
007.
using
System.Runtime.InteropServices;
008.
using
System.Xml;
009.
using
TechNetWiki.DynamicMappingResolver.Library;
010.
011.
namespace
VeriCash.BizTalk.PipelineComponents
012.
{
013.
[ComponentCategory(CategoryTypes.CATID_Any)]
014.
[ComponentCategory(CategoryTypes.CATID_PipelineComponent)]
015.
[Guid(
"5153F77B-F6E3-4F6A-AB96-670D324C8F7A"
)]
016.
public
class
DynamicMapResolverComp : IBaseComponent, IComponent, IComponentUI
017.
{
018.
/// <summary>
019.
/// The
cached sources to cache values of sources schema message types
020.
/// </summary>
021.
private
static
Dictionary<
string
,
string
> cachedSources =
new
Dictionary<
string
,
string
>();
022.
023.
#region IBaseComponent
Members
024.
025.
public
string
Description
026.
{
027.
get
028.
{
029.
return
"Dynamic mapping resolver"
;
030.
}
031.
}
032.
033.
public
string
Name
034.
{
035.
get
036.
{
037.
return
"Dynamic Mapping Resolver"
;
038.
}
039.
}
040.
041.
public
string
Version
042.
{
043.
get
044.
{
045.
return
"1.0"
;
046.
}
047.
}
048.
049.
#endregion
050.
051.
#region IComponent
Members
052.
053.
public
IBaseMessage Execute(IPipelineContext pContext, Microsoft.BizTalk.Message.Interop.IBaseMessage pInMsg)
054.
{
055.
string
messageType =
null
;
056.
string
messageSourceType = pInMsg.Context.Read(
"MessageType"
,
"http://schemas.microsoft.com/BizTalk/2003/system-properties"
).ToString();
057.
string
mapName = GetMapStrongName(messageSourceType);
058.
System.IO.Stream
originalStream = pInMsg.BodyPart.GetOriginalDataStream();
059.
pInMsg.BodyPart.Data
= MapHelper.TransformStream(originalStream, mapName,
false
,
ref
messageType);
060.
return
pInMsg;
061.
}
062.
063.
#endregion
064.
065.
#region IComponentUI
Members
066.
public
IntPtr Icon
067.
{
068.
get
069.
{
070.
071.
return
System.IntPtr.Zero;
072.
}
073.
074.
}
075.
public
IEnumerator Validate(
object
obj)
076.
{
077.
return
null
;
078.
}
079.
#endregion
080.
081.
#region Helper
082.
083.
private
string
GetMapStrongName(
string
messageType)
084.
{
085.
string
mapStrongName =
string
.Empty;
086.
////create
an instance of the XML object
087.
XmlDocument
xmlDoc =
new
XmlDocument();
088.
xmlDoc.LoadXml(
string
.Format(@"<ns0:BRERoot
xmlns:ns0=
'http://TechNetWiki.DynamicMappingResolver.Schemas.BRESchema'
>
089.
<MessageType>{0}</MessageType>
090.
<MapStrongName></MapStrongName>
091.
</ns0:BRERoot>",
messageType));
092.
if
(cachedSources.ContainsKey(messageType))
093.
{
094.
mapStrongName
= cachedSources[messageType];
095.
}
096.
else
097.
{
098.
////Prepare
object to call BRE API
099.
TypedXmlDocument
typedXmlDocument =
new
TypedXmlDocument(
"TechNetWiki.DynamicMappingResolver.Schemas.BRESchema"
, xmlDoc);
100.
Microsoft.RuleEngine.Policy
policy =
new
Microsoft.RuleEngine.Policy(
"DynamicMappingResolverPolicy"
);
101.
policy.Execute(typedXmlDocument);
102.
XmlNode
messageTypeNode = typedXmlDocument.Document.SelectSingleNode(
"//MapStrongName"
);
103.
mapStrongName
= messageTypeNode.InnerText;
104.
policy.Dispose();
105.
////
Fail if message type is unknown
106.
if
(
string
.IsNullOrEmpty(mapStrongName))
107.
{
108.
throw
new
Exception(
string
.Format(
"Map for message type {0} is not exist"
, messageType));
109.
}
110.
111.
cachedSources.Add(messageType,
mapStrongName);
112.
}
113.
return
mapStrongName;
114.
}
115.
116.
#endregion
117.
}
118.
}
- Create a new BizTalk project and add assembly to <BizTalkInstallationFolder>\Pipeline Components
- Add new item and choose Receive Pipeline
- Right click to toolbox and choose dynamic mapping resolver as shown in figure 6
Figure 6. Adding Dynamic Mapping Resolver in ToolBox
- Drag XML dis-assembler to disassemble part and drag Dynamic Mapping Resolver to Validate part as shown in figure 7.
Figure 7. Final Receive Pipeline
Dynamic map resolver in Outbound Maps
- Add new item and select send pipeline.
- Drag Dynamic Mapping Resolver to Pre-Assemble part and Xml-Assembler to Assemble part
as shown in figure 8.
Figure 8. Final Send Pipeline
- Deploy BizTalk project
Note: that I am using xml instance for testing sample source code, if you want to use flat file then you can use Flat file dissembler and assembler.
Dynamic map resolver in Orchestration
- You can have a sample orchestration as shown in figure 9
Figure 9. Sample orchestration
We can use dynamic transformation by using XLANG which exposes a transform method that can be called from within a Message Assignment shape inside of a Construct Message shape. This is the same method that is called when a Transform shape is used.
1.
MapType=System.Type.GetType(msgBRE.MapStrongName);
2.
transform(msgDestination) = MapType(msgSource);
Message / Variable | Identifier | Type | Comments |
Message | msgBRE | TechNetWiki.DynamicMappingResolver.Schemas.BRESchema | The BRE instance message |
Message | msgSource | System.Xml.XmlDocument | The incoming instance message |
Message | msgDestination | System.Xml.XmlDocument | The outgoing instance message |
Variablut | MsgType | System.Type | Type of map |
Sample Code
You can find the source code belonging to this article on MSDN Code Gallery: BizTalk
Server: Dynamic Mapping ResolverConclusion
In this article I explained and answered the following questions:- What is dynamic mapping resolver?
- What are the problems that we can solve by using dynamic mapping resolver?
- How to implement dynamic mapping resolver in receive/send pipeline and orchestration?
See Also
Read suggested related topics:Another important place to find a huge amount of BizTalk related articles is the TechNet Wiki itself. The best entry point is BizTalk Server Resources on the TechNet Wiki.