Okay, I admit it, I have never spent the time to learn the intricacies of Enterprise Single Sign On. I guess I had a hard time seeing the value when the only description I heard for quite a long time is that it is where BizTalk stores credentials.
I finally sat down and created a sample application that I could see would be useful.
I want to create a generic pipeline that interrogates the target namespace of the xml document in the decode stage, and applies a map. I actually have this requirement where I need to change the document into a different xml document that I can actually apply an envelope schema to.
I have always seen tutorials about how easy it is to create an envelope schema, reference a document schema and viola, BizTalk is splitting documents like crazy! I watched the podcasts, seen the demos eleventeen times and so I was tasked with doing it. I thought, hey, this is going to be easy!
Obvious, that this table would increase as more and more documents started to be processed by this splitting component.
The first thing I did was download the Enterprise Single Sign On application to allow you to put key value pairs in the SSO Database. You can download the SSO Configuration Application MMC Snap-In. When you install it, it creates a new menu in your Start Menu. You have to register it, but then you can start placing key value pairs.
I created an application called DecodeTransformations. and here is what my dictionary looks like:
So now I have to take the existing code and have it use ESSO to resolve the xslt instead of hard coding it in the properties of the pipeline.
As part of the download, Microsoft in their kindness, included sample code on how to retrieve values. You have to supply two values:
I included the class and referenced it in the primary class. Instead of giving you bits and pieces, I am going to tell you what lines I modified, and then show the resulting code (so you can use it yourself).
1: using System;
2: using System.IO;
3: using System.Xml;
4: using System.Xml.Xsl;
5: using System.Xml.Linq;
6: using System.ComponentModel;
7: using System.Collections;
8: using Microsoft.BizTalk.Message.Interop;
9: using Microsoft.BizTalk.Component.Interop;
10: using Microsoft.Win32;
11: using SSO.Utility;
12:
13: namespace Microsoft.BizTalk.SDKSamples.Pipelines.XslTransformComponent
14: {
15: /// <summary>
16: /// Implements a pipeline component that applies Xsl Transformations to XML messages
17: /// </summary>
18: /// <remarks>
19: /// XslTransformer class implements pipeline components that can be used in send pipelines
20: /// to convert XML messages to HTML format for sending using SMTP transport. Component can
21: /// be placed only in Encoding stage of send pipeline
22: /// </remarks>
23: [ComponentCategory(CategoryTypes.CATID_PipelineComponent)]
24: [ComponentCategory(CategoryTypes.CATID_Decoder)]
25: [System.Runtime.InteropServices.Guid("FA7F9C55-6E8E-4855-8DAC-FA1BC8A499E2")]
26: public class XslTransformer : Microsoft.BizTalk.Component.Interop.IBaseComponent,
27: Microsoft.BizTalk.Component.Interop.IComponent,
28: Microsoft.BizTalk.Component.Interop.IPersistPropertyBag,
29: Microsoft.BizTalk.Component.Interop.IComponentUI
30: {
31: #region IBaseComponent
32: /// <summary>
33: /// Name of the component.
34: /// </summary>
35: [Browsable(false)]
36: public string Name
37: {
38: get { return "XSL Transform Component"; }
39: }
40: /// <summary>
41: /// Version of the component.
42: /// </summary>
43: [Browsable(false)]
44: public string Version
45: {
46: get { return "1.0"; }
47: }
48: /// <summary>
49: /// Description of the component.
50: /// </summary>
51: [Browsable(false)]
52: public string Description
53: {
54: get { return "XSL Transform Pipeline Component"; }
55: }
56: #endregion
57: #region IComponent
58: /// <summary>
59: /// Implements IComponent.Execute method.
60: /// </summary>
61: /// <param name="pc">Pipeline context</param>
62: /// <param name="inmsg">Input message.</param>
63: /// <returns>Converted to HTML input message.</returns>
64: /// <remarks>
65: /// IComponent.Execute method is used to convert XML messages
66: /// to HTML messages using provided Xslt file.
67: /// It also sets the content type of the message part to be "text/html"
68: /// which is necessary for client mail applications to correctly render
69: /// the message
70: /// </remarks>
71: public IBaseMessage Execute(IPipelineContext pc, IBaseMessage inmsg)
72: {
73: inmsg.BodyPart.Data = TransformMessage(inmsg.BodyPart.Data);
74: inmsg.BodyPart.ContentType = "text/html";
75: return inmsg;
76: }
77: #endregion
78: #region Helper function
79: /// <summary>
80: /// Transforms XML message in input stream to xml message
81: /// </summary>
82: /// <param name="stm">Stream with input XML message</param>
83: /// <returns>Stream with output xml message</returns>
84: private Stream TransformMessage(Stream stm)
85: {
86: MemoryStream ms = null;
87: string validXsltPath = null;
88: try
89: {
90: //Load Xml stream in XmlDocument.
91: XmlDocument doc = new XmlDocument();
92: doc.Load(stm);
93: // Get the target name space
94: string tns = ReturnTargetNamespace(doc);
95: string xsltPath = SSOClientHelper.Read(this.SSOApplication, tns);
96: // Get the full path to the Xslt file
97: validXsltPath = GetValidXsltPath(xsltPath);
98: //Create memory stream to hold transformed data.
99: ms = new MemoryStream();
100: // Load transform
101: XslTransform transform = new XslTransform();
102: transform.Load(validXsltPath);
103: //Preform transform
104: transform.Transform(doc, null, ms, null);
105: ms.Seek(0, SeekOrigin.Begin);
106: }
107: catch(Exception e)
108: {
109: System.Diagnostics.Trace.WriteLine(e.Message);
110: System.Diagnostics.Trace.WriteLine(e.StackTrace);
111: throw e;
112: }
113: return ms;
114: }
115: /// <summary>
116: /// Get a valid full path to a Xslt file
117: /// </summary>
118: /// <param name="path">Path provided by user in Pipeline Designer</param>
119: /// <returns>The full path</returns>
120: /// <remarks>
121: /// If user provides absolute path then it is used as long as the file can be opened there
122: /// If user provides just a name of file or relative path then we try to open a file in
123: /// [Install foder]\Pipeline Components
124: /// </remarks>
125: private string GetValidXsltPath(string path)
126: {
127: string validPath = path;
128: if (!System.IO.File.Exists(path))
129: {
130: RegistryKey rk = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\BizTalk Server\3.0");
131: string InstallPath = string.Empty;
132:
133: if (null != rk)
134: InstallPath = (String)rk.GetValue("InstallPath");
135:
136: validPath = InstallPath + @"Pipeline Components\" + path;
137: if (!System.IO.File.Exists(validPath))
138: {
139: throw new ArgumentException("The XSL transformation file " + path + " can not be found");
140: }
141: }
142: return validPath;
143: }
144: private string GetXSLPathFromSSO(string Application,string MessageType)
145: {
146: string MapName = SSOClientHelper.Read(Application, MessageType);
147: return MapName;
148: }
149: #endregion
150: #region IPersistPropertyBag
151: private string _SSOApplication;
152: public string SSOApplication
153: {
154: get { return _SSOApplication; }
155: set { _SSOApplication = value; }
156: }
157: /// <summary>
158: /// Gets class ID of component for usage from unmanaged code.
159: /// </summary>
160: /// <param name="classid">Class ID of the component.</param>
161: public void GetClassID(out Guid classid)
162: {
163: classid = new System.Guid("FA7F9C55-6E8E-4855-8DAC-FA1BC8A499E2");
164: }
165: /// <summary>
166: /// Not implemented.
167: /// </summary>
168: public void InitNew()
169: {
170: }
171: public void Load(Microsoft.BizTalk.Component.Interop.IPropertyBag pb, Int32 errlog)
172: {
173: string val = (string)ReadPropertyBag(pb, "SSOApplication");
174: if (val != null) SSOApplication = val;
175: }
176: /// <summary>
177: /// Saves the current component configuration into the property bag.
178: /// </summary>
179: /// <param name="pb">Configuration property bag.</param>
180: /// <param name="fClearDirty">Not used.</param>
181: /// <param name="fSaveAllProperties">Not used.</param>
182: public void Save(Microsoft.BizTalk.Component.Interop.IPropertyBag pb, Boolean fClearDirty, Boolean fSaveAllProperties)
183: {
184: object val = (object) SSOApplication;
185: WritePropertyBag(pb, "SSOApplication", val);
186: }
187: /// <summary>
188: /// Reads property value from property bag.
189: /// </summary>
190: /// <param name="pb">Property bag.</param>
191: /// <param name="propName">Name of property.</param>
192: /// <returns>Value of the property.</returns>
193: private static object ReadPropertyBag(Microsoft.BizTalk.Component.Interop.IPropertyBag pb, string propName)
194: {
195: object val = null;
196: try
197: {
198: pb.Read(propName,out val,0);
199: }
200: catch(System.ArgumentException)
201: {
202: return val;
203: }
204: catch(Exception ex)
205: {
206: throw new ApplicationException( ex.Message);
207: }
208: return val;
209: }
210: private static void WritePropertyBag(Microsoft.BizTalk.Component.Interop.IPropertyBag pb, string propName, object val)
211: {
212: try
213: {
214: pb.Write(propName, ref val);
215: }
216: catch(Exception ex)
217: {
218: throw new ApplicationException( ex.Message);
219: }
220: }
221: #endregion
222: #region IComponentUI
223: /// <summary>
224: /// Component icon to use in BizTalk Editor.
225: /// </summary>
226: [Browsable(false)]
227: public IntPtr Icon
228: {
229: get { return IntPtr.Zero; }
230: }
231: /// <summary>
232: /// The Validate method is called by the BizTalk Editor during the build
233: /// of a BizTalk project.
234: /// </summary>
235: /// <param name="obj">Project system.</param>
236: /// <returns>
237: /// A list of error and/or warning messages encounter during validation
238: /// of this component.
239: /// </returns>
240: public IEnumerator Validate(object projectSystem)
241: {
242: if (projectSystem==null)
243: throw new System.ArgumentNullException("No project system");
244: IEnumerator enumerator = null;
245: ArrayList strList = new ArrayList();
246: try
247: {
248: //GetValidXsltPath(SSOApplication);
249: }
250: catch(Exception e)
251: {
252: strList.Add(e.Message);
253: enumerator = strList.GetEnumerator();
254: }
255: return enumerator;
256: }
257: private string ReturnTargetNamespace(XmlDocument xdoc)
258: {
259: XmlNodeReader xmlndRdr = new XmlNodeReader(xdoc);
260: string TargetNamespace = XElement.Load(xmlndRdr).GetDefaultNamespace().ToString();
261: TargetNamespace = TargetNamespace + "#" + xdoc.DocumentElement.Name;
262: return TargetNamespace;
263: }
264: #endregion
265: }
266: }
267:
Once that is completed, simply place the dll in the pipeline components folder.
I created the pipeline that has the XSLTransform pipeline component and the XML Disassembler pipeline component and it works like a charm!
Now I can use the same component in multiple locations and can receive multiple messages in the same location and based on the application deployed to SSO, it will resolve where the xsl lives and transform it. Unlike the ESB dispatcher pipeline component, I actually don’t even need to deploy the map, I simply need to place it somewhere and I am ready to go.
Here is what the pipeline looks like