Apr 182012


The ESB Toolkit 2.1 could be better

  • Compared to version 2.0, it appears to be compiled for .Net 4 (that is it), however all of the projects are still set to .Net 3.5
  • It used to be the ESB Guidance 1.0 so you can ‘extend’ it. However, there is no source code to do that anymore. If I wanted to change some behavior of a pipeline, I am screwed, I can download 1.0 and take a look at how it is done (or I can reverse engineer the current pipelines), but without doing a ton of work, it is not very extendable.
  • Calling into support is nearly a waste of time, okay: more than nearly.


There are a few deficiencies I have found with ESB 2.1:

  • The itinerary designer can query various databases (management db, rules engine db) to allow drop downs in the property window to choose things that were deployed. EXCEPT: orchestrations. For this, you have to go edit a configuration file, and restart visual studio (or if you really have some stones: unload a section in SSO make additions, and then reload it, using a nearly undocumented command line)
  • The entire design of the ESB solution assumes that each service you call responds with all of the data that you need to go to the next service that will be called.
    • WHAT?! First of all, a lot of services out there only return the value it needs to return, not the original payload coupled with the new value.
    • Many, if not all services can’t be re-engineered to change the behavior, as a lot of them are managed by an outside vendor.
  • The ESB Portal has to run in compatibility mode, even in IE8.
  • If you want to re-submit messages back into the message box, all of the context properties are GONE. Essentially, resubmittal is A TOTAL WASTE.

So, I have made a few changes to accomplish what I think are a couple fundamental changes to make the ESB toolkit usable.

First: I changed the behavior of the resubmittal process: here is the code I added to the portal:

(notice the source that is provided is for which version?)

//--------------------------------------------------------------------- // File: MessageViewer.aspx.cs // // Summary: This page displays the details of a message. It renders the message body // if it is an XML or flat file. Otherwise if allows the message // body to be downloaded. // //--------------------------------------------------------------------- // This file is part of the Microsoft ESB Guidance for BizTalk // // Copyright (c) Microsoft Corporation. All rights reserved. // // This source code is intended only as a supplement to Microsoft BizTalk // Server 2006 R2 release and/or on-line documentation. See these other // materials for detailed information regarding Microsoft code samples. // // THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY // KIND, WHETHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR // PURPOSE. //---------------------------------------------------------------------- using System; using System.Data; using System.Configuration; using System.Collections; using System.Web; using System.Web.Security; using System.Web.UI; using System.Web.UI.WebControls; using System.Web.UI.WebControls.WebParts; using System.Web.UI.HtmlControls; using System.ComponentModel; using System.Net; using System.IO; using System.Text; using System.Xml; using System.ServiceModel; using ESB.BAM.Service.Implementation; using ESB.Exceptions.Service.Implementation; using System.Collections.Generic; namespace Microsoft.Practices.ESB.Portal { public partial class MessageViewer : System.Web.UI.UserControl { #region Properties /// <summary> /// True if the message is text based, false otherwise. /// </summary> private bool MessageIsText { get { return (((Label)this.MessageView.FindControl("contentTypeLabel")).Text.Contains("text") || ((Label)this.MessageView.FindControl("contentTypeLabel")).Text.Equals("application/octet-stream")); } } /// <summary> /// Indicates whether a resubmission attempt has been made on the message. /// </summary> private bool _resubmitAttempted; private bool ResubmitAttempted { get { return _resubmitAttempted; } set { _resubmitAttempted = value; } } /// <summary> /// Indicates whether the message has been successfully resubmitted. /// </summary> private bool _resubmitSuccessful; private bool ResubmitSuccessful { get { return _resubmitSuccessful; } set { _resubmitSuccessful = value; } } /// <summary> /// Indicated whether the control is allowed to go into edit mode. /// </summary> private bool _editModeAvailable; private bool EditModeAvailable { get { return _editModeAvailable; } set { _editModeAvailable = value; } } /// <summary> /// Get the selected receive location from the 'receiveLocationList' DropDownlist. /// </summary> private string SelectedReceiveLocation { get { if (((DropDownList)this.MessageView.FindControl("receiveLocationList")).SelectedItem.Text == "Submit to Routing URL") { return "RoutingURL"; } else { return ((DropDownList)this.MessageView.FindControl("receiveLocationList")).SelectedValue; } } } /// <summary> /// Gets the message content type (MIME type). /// </summary> private string MessageContentType { get { return ((Label)this.MessageView.FindControl("contentTypeLabel")).Text; } } /// <summary> /// This property indicates whether or not the page is in the mode to /// edit and resubmit the message. This is determined by the esb:Mode /// querystring parameter containing the value: "Edit". /// </summary> private bool _editMode; protected bool EditMode { set { _editMode = value; } get { return _editMode; } } #endregion protected void Page_PreRender(object sender, EventArgs e) { SetControlVisibility(); SetControlEditability(); RenderMessageBody(); if (!IsPostBack) { PopulateReceiveLocationList(); } } private void BindGrid() { IExceptionService client = null; try { client = PortalHelper.GetExceptionService(); List<usp_select_ContextPropertiesResult> contextProperties = client.GetContextProperties(new Guid(Request.QueryString["esb:MessageID" ])); contextPropertiesView.DataSource = contextProperties; contextPropertiesView.DataBind(); } context; } protected void Page_Load(object sender, EventArgs e) { if (!IsPostBack) { IExceptionService client = null; try { client = PortalHelper.GetExceptionService(); List<usp_select_MessagesResult> messages = client.GetMessages(Request.QueryString["esb:FaultID"], Request.QueryString["esb:MessageID"]); MessageView.DataSource = messages; MessageView.DataBind(); BindGrid(); } catch (Exception ex) { throw ex; } finally { if (client != null) { ((IDisposable)client).Dispose( ); } } } this.EditMode = (Request.QueryString["esb:Mode"] == "Edit"); this.EditModeAvailable = (((HtmlInputHidden)this.MessageView.FindControl("resubmitSuccessful")).Value == "True" || !this.MessageIsText || this.EditMode) ? false : true; this.ResubmitAttempted = (((HtmlInputHidden)this.MessageView.FindControl("resubmitAttempted")).Value == "True"); this.ResubmitSuccessful = (((HtmlInputHidden)this.MessageView.FindControl("resubmitSuccessful")).Value == "True"); if (this.ResubmitSuccessful) { ((Label)this.MessageView.FindControl("resubmissionLabel")).Text = "<img src='../images/severityinformation.gif' style='vertical-align:middle;'/>&nbsp;This message has been successfully resubmitted."; ((Label)this.MessageView.FindControl("resubmissionLabel")).ForeColor = System.Drawing.Color.Black; } else if (this.ResubmitAttempted) { ((Label)this.MessageView.FindControl("resubmissionLabel")).Text = "<img src='../images/severitycritical.gif' style='vertical-align:middle;'/>&nbsp;A message resubmission was attempted but failed."; ((Label)this.MessageView.FindControl("resubmissionLabel")).ForeColor = System.Drawing.Color.Red; } } /// <summary> /// Calls the resubmit helper class to transmit the message back to BizTalk. Logs the result to the portal audit log. /// </summary> protected void resubmitButton_Click(object sender, EventArgs e) { //Resubmit the message to the selected receive location via HTTP string messageID = ((Label)this.MessageView.FindControl("messageIDLabel")).Text; string statusCode; string statusMessage; bool result = ResubmitMessage(((DropDownList)this.MessageView.FindControl("receiveLocationList")).SelectedValue, out statusCode, out statusMessage); this.ResubmitAttempted = true; if (result == false) { ((Label)this.MessageView.FindControl("resubmissionLabel")).Text = "<img src='../images/severitycritical.gif' style='vertical-align:middle;'/>&nbsp;The resubmission failed: " + statusCode + " - " + statusMessage; ((Label)this.MessageView.FindControl("resubmissionLabel")).ForeColor = System.Drawing.Color.Red; ((Label)this.MessageView.FindControl("resubmissionLabel")).Visible = true; } else { this.ResubmitSuccessful = true; this.EditMode = false; this.EditModeAvailable = false; ((Label)this.MessageView.FindControl("resubmissionLabel")).Text = "<img src='../images/severityinformation.gif' style='vertical-align:middle;'/>&nbsp;This message has been successfully resubmitted."; ((Label)this.MessageView.FindControl("resubmissionLabel")).ForeColor = System.Drawing.Color.Black; ((Label)this.MessageView.FindControl("resubmissionLabel")).Visible = true; } //Update the resubmission status in the Message table UpdateMessageResubmissionStatus(result); //Write an audit log event for the message resubmission AuditMessageResubmission(result, statusCode, statusMessage, ((DropDownList)this.MessageView.FindControl("receiveLocationList")).SelectedValue); } /// <summary> /// Resubmits the message body to the URL selected in the resubmit location DropDownList. /// </summary> /// <returns>True if the message was successfully resubmitted, false otherwise.</returns> private bool ResubmitMessage(string resubmitUrl, out string responseCode, out string responseMessage) { bool result = false; // returned. if (resubmitUrl.ToUpper().Equals("WCF ONRAMP")) { // resubmit message and capture the result System.Xml.XmlDocument doc = new System.Xml.XmlDocument(); doc.LoadXml(((TextBox)this.MessageView.FindControl("messageBodyBox")).Text); result = MessageResubmitter.ResubmitWCF(doc); if (result == true) { responseCode = "202"; responseMessage = "Successfully sumbitted to WCF OnRamp"; } else { responseCode = "500"; responseMessage = "Failure submitting to WCF OnRamp"; } } else if (resubmitUrl.ToUpper().Equals("SOAP ONRAMP")) { // resubmit message and capture the result System.Xml.XmlDocument doc = new System.Xml.XmlDocument(); doc.LoadXml(((TextBox)this.MessageView.FindControl("messageBodyBox")).Text); result = MessageResubmitter.ResubmitSOAP(doc); if (result == true) { responseCode = "202"; responseMessage = "Successfully sumbitted to SOAP OnRamp"; } else { responseCode = "500"; responseMessage = "Failure submitting to SOAP OnRamp"; } } else { HttpStatusCode statusCode; string MessageToSubmit = getCompleteContent(((TextBox)this.MessageView.FindControl("messageBodyBox" )).Text); //check to see if we need to set a proxy using (Stream messageReader = this.GetMessageDataStream(MessageToSubmit)) { HttpWebRequest HttpWRequest = (HttpWebRequest)WebRequest.Create(resubmitUrl); // TODO: (jsk) put settings in web.config // set the name of the user agent. This is the client name that is passed to IIS HttpWRequest.Headers.Set("Pragma", "no-cache"); HttpWRequest.UserAgent = Page.User.Identity.Name; HttpWRequest.KeepAlive = true; //this is the default HttpWRequest.Timeout = 300000; HttpWRequest.Method = "POST"; HttpWRequest.ContentType = MessageContentType; HttpWRequest.Credentials = CredentialCache.DefaultCredentials; // resubmit message and capture the result statusCode = MessageResubmitter.ResubmitHTTP(HttpWRequest, messageReader); //Anything between 200 and 299 is considered success result = ((int)statusCode > 199 && (int)statusCode < 301); responseCode = ((int)statusCode).ToString(); responseMessage = Enum.GetName(typeof(HttpStatusCode), statusCode); } } return result; } private string getCompleteContent(string body) { string UberDocument = "<ns1:Message xmlns:ns1=\"http://Message\">"; UberDocument += SerializeContext(); UberDocument += "<ns1:Body>" + body + "</ns1:Body></ns1:Message>"; return UberDocument; } /// <summary> /// Extracts and returns a Stream from the given string. /// </summary> /// <param name="messageDataString">The string to extract into a stream.</param> /// <returns>A stream from the given string.</returns> private Stream GetMessageDataStream(string messageDataString) { MemoryStream ms = new MemoryStream(ASCIIEncoding.Default.GetBytes(messageDataString)); return ms; } /// <summary> /// Updates the ResubmitAttempted and ResubmitSuccessful fields on the Message table /// in the database. /// </summary> /// <param name="success">True if the message was successfully resubmitted, false otherwise.</param> private void UpdateMessageResubmissionStatus(bool success) { IExceptionService client = null; try { client = PortalHelper.GetExceptionService(); client.InsertMessage(((Label)this.MessageView.FindControl("messageIDLabel")).Text, true, success); } catch (Exception ex) { throw ex; } finally { if (client != null) { ((IDisposable)client).Dispose( ); } } //DataSets.Faults.MessagesDataTable messageTable = new DataSets.Faults.MessagesDataTable(); ////The only values that are used by this call are MessageID, ResubmissionAttempted, and ResubmissionSuccessful. ////The 2nd new Guid is just there because null is not accepted. //messageTable.AddMessagesRow(((Label)this.MessageView.FindControl("messageIDLabel")).Text, null, null, null, null, null, null, null, true, success, DateTime.Now); //MessagesTableAdapter messageAdapter = new MessagesTableAdapter(); //messageAdapter.Update(messageTable); } /// <summary> /// Writes the failed or successful resubmission event to the audit log. /// </summary> /// <param name="success">True if the message was successfully resubmitted, false otherwise.</param> private void AuditMessageResubmission(bool success, string resubmitCode, string resubmitMessage, string resubmitUrl) { IExceptionService client = null; try { client = PortalHelper.GetExceptionService(); AuditLog auditLog = new AuditLog(); auditLog.ActionName = success ? "SuccessfulResubmit" : "UnsuccessfulResubmit"; auditLog.AuditDate = DateTime.UtcNow; auditLog.AuditUserName = Page.User.Identity.Name; auditLog.MessageID = new Guid(((Label)this.MessageView.FindControl("messageIDLabel")).Text); auditLog.ResubmitURL = resubmitUrl; auditLog.ResubmitCode = resubmitCode; auditLog.ResubmitMessage = resubmitMessage; auditLog.MessageData = ((TextBox)this.MessageView.FindControl("messageBodyBox")).Text; //client.InsertAuditLog(auditLog); } catch (Exception ex) { throw ex; } finally { if (client != null) { ((IDisposable)client).Dispose( ); } } ////Insert audit information //AuditLogTableAdapter auditLogTA = new AuditLogTableAdapter(); //DataSets.AuditLog.AuditLogDataTable auditLogTable = new DataSets.AuditLog.AuditLogDataTable(); //DataSets.AuditLog.AuditLogRow auditRow = auditLogTable.NewAuditLogRow(); //auditRow.ActionName = success ? "SuccessfulResubmit" : "UnsuccessfulResubmit"; //auditRow.AuditDate = DateTime.UtcNow; //auditRow.AuditUserName = Page.User.Identity.Name; //auditRow.MessageID = new Guid(((Label)this.MessageView.FindControl("messageIDLabel")).Text); //auditRow.ResubmitURL = resubmitUrl; //auditRow.ResubmitCode = resubmitCode; //auditRow.ResubmitMessage = resubmitMessage; ////auditRow.ContentType = (((Label)this.MessageView.FindControl("contentTypeLabel")).Text); //if (success) //{ // auditRow.MessageData = ((TextBox)this.MessageView.FindControl("messageBodyBox")).Text; //} //auditLogTable.AddAuditLogRow(auditRow); //auditLogTA.Update(auditLogTable); } /// <summary> /// Populates the receive location selection list with the WCF OnRamp, the SOAP OnRamp, and/or all HTTP receive locations on the BizTalk group. Flat File resubmission is limited to HTTP receive /// locations only, while XML document resubmission can be to WCF, SOAP, or HTTP. /// </summary> private void PopulateReceiveLocationList() { BizTalkOperationsService.Operations queryService = Microsoft.Practices.ESB.Portal.BizTalkOperationsService.Operations.CreateInstance(); BizTalkOperationsService.BTSysStatus sysStatus = queryService.ReceiveLocations(string.Empty); //Loop through the receive locations and extract the HTTP and WCF HTTP receive locations. int i = 0; ArrayList httpRcvLocs = new ArrayList(); foreach (BizTalkOperationsService.BTReceiveLocation rcvLoc in sysStatus.ReceiveLocations) { if (rcvLoc.Handler.ProtocolName.ToUpper().Equals("HTTP")) { rcvLoc.Address = "http://" + rcvLoc.Handler.Host.HostInstances[0].ServerName + rcvLoc.Address; httpRcvLocs.Add(rcvLoc); i++; } } DropDownList receiveLocList = ((DropDownList)this.MessageView.FindControl("receiveLocationList")); //Add a default selection to the applications list if (String.IsNullOrEmpty(((Label)this.MessageView.FindControl("routingURLLabel")).Text)) { receiveLocList.Items.Add(new ListItem("Select a receive location.", null)); } else { receiveLocList.Items.Add(new ListItem("Submit to Routing URL", ((Label)this.MessageView.FindControl("routingURLLabel")).Text)); } //Add the WCF and SOAP OnRamps if the message is not a flat file if (((Label)this.MessageView.FindControl("contentTypeLabel")).Text.ToUpper() != "TEXT/PLAIN") { receiveLocList.Items.Add(new ListItem("WCF OnRamp", "WCF OnRamp")); //URL will be retrieved from web.config receiveLocList.Items.Add(new ListItem("SOAP OnRamp", "SOAP OnRamp")); //URL will be retrieved from web.config } //Populate the receive locations list with the receive locations from BizTalk receiveLocList.AppendDataBoundItems = true; receiveLocList.DataSource = httpRcvLocs; receiveLocList.DataTextField = "Name"; receiveLocList.DataValueField = "Address"; receiveLocList.DataBind(); } /// <summary> /// This method shows or hides the correct control to display the message body. When the message /// body is text based, it is rendered to the screen in a TextBox. When the message body is binary /// a link is displayed to download it. /// </summary> private void RenderMessageBody() { //If the message content type is text if (this.MessageIsText) { //Make the message body download link invisible. ((TextBox)this.MessageView.FindControl("messageBodyBox")).Visible = true; } } /// <summary> /// Reads the mode from the querystring, and shows/hides the appropriate buttons. /// By default the Edit button is shown, if the mode = Edit, the Resubmit and Cancel buttons /// are shown. /// </summary> private void SetControlVisibility() { ((HtmlGenericControl)this.MessageView.FindControl("editButton")).Visible = this.EditModeAvailable; ((HtmlAnchor)this.MessageView.FindControl("messageBodyAnchor")).Visible = this.EditModeAvailable; ((LinkButton)this.MessageView.FindControl("resubmitButton")).Visible = this.EditMode; ((HtmlGenericControl)this.MessageView.FindControl("cancelButton")).Visible = this.EditMode; ((DropDownList)this.MessageView.FindControl("receiveLocationList")).Visible = this.EditMode; ((Label)this.MessageView.FindControl("resubmissionLabel")).Visible = this.ResubmitAttempted; } /// <summary> /// Sets the editability of controls based on whether the page is in edit mode. /// </summary> private void SetControlEditability() { TextBox messageBodyBox = this.MessageView.FindControl("messageBodyBox") as TextBox; messageBodyBox.ReadOnly = !this.EditMode; if (!this.EditMode) { messageBodyBox.BackColor = System.Drawing.Color.WhiteSmoke; } else { messageBodyBox.BackColor = System.Drawing.Color.White; } } public string GetCrumbedString(object t, int crumbAt) { string text = t.ToString(); if (text.Length > crumbAt) { //return "<span title='" + text + "'>" + HttpUtility.HtmlEncode( PortalHelper.CrumbString(text, crumbAt)) + "</span>"; return HttpUtility.HtmlEncode(PortalHelper.CrumbString(text, crumbAt)); } else { return text; } } protected void labelHeaderCaption_PreRender(object sender, EventArgs e) { Label me = (Label)sender; if (Request.QueryString["esb:Mode"] == "Edit") { me.CssClass = "iconMessageEdit"; } else { me.CssClass = "iconMessage"; } } protected void contextPropertiesView_RowDataBound(object sender, GridViewRowEventArgs e) { if (e.Row.RowType == DataControlRowType.DataRow) { Label name = e.Row.FindControl("lblName") as Label; if (name != null && name.Text.Equals("ItineraryHeader", StringComparison.InvariantCultureIgnoreCase)) { Label content = e.Row.FindControl("lblValueOriginal") as Label; if (content != null && !string.IsNullOrEmpty(content.Text)) { string contentData = HttpUtility.HtmlDecode(content.Text); XmlDocument xml = new XmlDocument(); xml.LoadXml(contentData); XmlNodeList nodeList = xml.GetElementsByTagName("Itinerary"); if (nodeList.Count > 0) { XmlNode node = nodeList[0]; if (node.Attributes["uuid"] != null) { string itineraryUuid = node.Attributes["uuid"].Value; HyperLink lnk = e.Row.FindControl("hlnkItineraries") as HyperLink; if (lnk != null) { lnk.Visible = true; lnk.NavigateUrl = "Itineraries.aspx?uuid=" + itineraryUuid; } } } } } } } protected void contextPropertiesView_PageIndexChanging(object sender, GridViewPageEventArgs e) { contextPropertiesView.PageIndex = e.NewPageIndex; BindGrid(); } } }

This creates a message that looks like this:

<ns1:Message xmlns:ns1="http://Message"> <ns1:Context> <ns1:Property Name="" Type="" Value="" /> <!--All of the context properties associated--> <!--Except those from the All.Exceptions--> <!--And the InboundTransportLocation (cause it is always the ESB Exception)--> <!--Along with any that has the error-report--> </ns1:Context> <ns1:Body> Content that is displayed in the text box </ns1:Body> </ns1:Message>

Well, the problem is that we don’t have a schema that looks like this message. I really just want to submit the content contained in the Body. So I created a decode pipeline component that access the SSO Configuration Store to promote certain properties of the message as it comes back into BizTalk.

using System; using System.IO; using System.Xml; using System.Xml.XPath; using System.ComponentModel; using System.Collections; using Microsoft.BizTalk.Message.Interop; using Microsoft.BizTalk.Component.Interop; using Microsoft.Win32; using System.Web; using System.Text; using SSO.Utility; using System.Diagnostics; namespace StottCreations.ContextReader { public class PromoteContext { /// <summary> /// Implements a pipeline component that takes unwraps the data from the body of the xml document /// and promotes the values in the content of the message to the context of the messaage payload. /// </summary> /// <remarks> /// </remarks> [ComponentCategory(CategoryTypes.CATID_PipelineComponent)] [ComponentCategory(CategoryTypes.CATID_Decoder)] [System.Runtime.InteropServices.Guid("FA7F9C55-6E8E-4855-8DAC-FA1BC8A499E2")] public class PromoteContextComponent : Microsoft.BizTalk.Component.Interop.IBaseComponent, Microsoft.BizTalk.Component.Interop.IComponent, Microsoft.BizTalk.Component.Interop.IPersistPropertyBag, Microsoft.BizTalk.Component.Interop.IComponentUI { #region Pipeline Properties private string ssoApplication = null; /// <summary> /// Application that contains all of the properties to be passed through the pipeline /// </summary> public string SSOApplication { get { return ssoApplication; } set { ssoApplication = value; } } #endregion #region IBaseComponent /// <summary> /// Name of the component. /// </summary> [Browsable(false)] public string Name { get { return "Context Promoter"; } } /// <summary> /// Version of the component. /// </summary> [Browsable(false)] public string Version { get { return "1.0"; } } /// <summary> /// Description of the component. /// </summary> [Browsable(false)] public string Description { get { return "Promotes context data from message and passes the data to Dissassembler"; } } #endregion #region IComponent public IBaseMessage Execute(IPipelineContext pc, IBaseMessage inmsg) { try { inmsg = Promote(inmsg); inmsg.BodyPart.Data = TransformMessage(inmsg.BodyPart.GetOriginalDataStream()); } catch (Exception ex) { EventLog.WriteEntry("Promote Pipeline Component", ex.Message, EventLogEntryType.Error); } return inmsg; } #endregion #region Helper function /// <summary> /// Extracts data from message /// </summary> /// <param name="stm">Stream with input XML message</param> /// <returns>Message</returns> public Stream TransformMessage(Stream stm) { Stream s = null; try { //Load Xml stream in XmlDocument. XmlDocument doc = new XmlDocument(); doc.Load(stm); XPathNavigator nav = doc.CreateNavigator(); XPathNodeIterator iter; // retrieve a node-set via XPath iter = nav.Select("/*[local-name()='Message' and namespace-uri()='http://Message']/*[local-name()='Body' and namespace-uri()='http://Message']"); // make sure a match was found while (iter.MoveNext()) { string data = iter.Current.InnerXml; s = new MemoryStream(ASCIIEncoding.Default.GetBytes(data)); } } catch (Exception e) { System.Diagnostics.Trace.WriteLine(e.Message); System.Diagnostics.Trace.WriteLine(e.StackTrace); throw e; } return s; } private IBaseMessage Promote(IBaseMessage inmsg) { string ErrorMsg = string.Empty; XmlDocument doc = new XmlDocument(); doc.Load(inmsg.BodyPart.GetOriginalDataStream()); XPathNavigator nav = doc.CreateNavigator(); XPathNodeIterator iter; // retrieve a node-set via XPath iter = nav.Select("/*[local-name()='Message' and namespace-uri()='http://Message']/*[local-name()='Context' and namespace-uri()='http://Message']/*[local-name()='Property' and namespace-uri()='http://Message']"); // make sure a match was found while (iter.MoveNext()) { //Check the SSO Application to see if we should be promoting it, otherwise skip it if (System.Convert.ToBoolean(SSOClientHelper.Read(SSOApplication, HttpUtility.HtmlDecode(iter.Current.GetAttribute("Name", ""))))) { try { string Name = HttpUtility.HtmlDecode(iter.Current.GetAttribute("Name", "")); string Type = HttpUtility.HtmlDecode(iter.Current.GetAttribute("Type", "")); string Value = HttpUtility.HtmlDecode(iter.Current.GetAttribute("Value", "")); inmsg.Context.Promote(Name, Type, Value); } catch (Exception) { //We could not promote this particular property, let's report it and go on ErrorMsg += "The " + SSOApplication + " SSO Application was attempting to promote " + HttpUtility.HtmlDecode(iter.Current.GetAttribute("Name", "")) + ", but it it can't be promoted, please remove it./r/n"; } } } if (ErrorMsg.Length > 0) EventLog.WriteEntry("Promote Pipeline Component", ErrorMsg.Substring(0,2048), EventLogEntryType.Warning); return inmsg; } #endregion #region IPersistPropertyBag /// <summary> /// Gets class ID of component for usage from unmanaged code. /// </summary> /// <param name="classid">Class ID of the component.</param> public void GetClassID(out Guid classid) { classid = new System.Guid("DA7F9C55-6E8E-4855-8DAC-FA1BC8A499E2"); } /// <summary> /// Not implemented. /// </summary> public void InitNew() { } public void Load(Microsoft.BizTalk.Component.Interop.IPropertyBag pb, Int32 errlog) { string val = (string)ReadPropertyBag(pb, "SSOApplication"); if (val != null) ssoApplication = val; } /// <summary> /// Saves the current component configuration into the property bag. /// </summary> /// <param name="pb">Configuration property bag.</param> /// <param name="fClearDirty">Not used.</param> /// <param name="fSaveAllProperties">Not used.</param> public void Save(Microsoft.BizTalk.Component.Interop.IPropertyBag pb, Boolean fClearDirty, Boolean fSaveAllProperties) { object val = (object)ssoApplication; WritePropertyBag(pb, "SSOApplication", val); } /// <summary> /// Reads property value from property bag. /// </summary> /// <param name="pb">Property bag.</param> /// <param name="propName">Name of property.</param> /// <returns>Value of the property.</returns> private static object ReadPropertyBag(Microsoft.BizTalk.Component.Interop.IPropertyBag pb, string propName) { object val = null; try { pb.Read(propName, out val, 0); } catch (System.ArgumentException) { return val; } catch (Exception ex) { throw new ApplicationException(ex.Message); } return val; } private static void WritePropertyBag(Microsoft.BizTalk.Component.Interop.IPropertyBag pb, string propName, object val) { try { pb.Write(propName, ref val); } catch (Exception ex) { throw new ApplicationException(ex.Message); } } #endregion #region IComponentUI /// <summary> /// Component icon to use in BizTalk Editor. /// </summary> [Browsable(false)] public IntPtr Icon { get { return IntPtr.Zero; } } /// <summary> /// The Validate method is called by the BizTalk Editor during the build /// of a BizTalk project. /// </summary> /// <param name="obj">Project system.</param> /// <returns> /// A list of error and/or warning messages encounter during validation /// of this component. /// </returns> public IEnumerator Validate(object projectSystem) { if (projectSystem == null) throw new System.ArgumentNullException("No project system"); IEnumerator enumerator = null; ArrayList strList = new ArrayList(); try { } catch (Exception e) { strList.Add(e.Message); enumerator = strList.GetEnumerator(); } return enumerator; } #endregion } } }

So in the pipeline that I have used to submit xml data back to the message box looks like this:


Within the SSO Configuration, I can promote as many (or few) properties as I need. Here is the only things I wanted to promote:


This means that it will take all of the properties, and only promote ReceivedFileName and OrderedDelivery.

There are certain properties that are not promotable, and as such, it will not blow up, but simply write an entry into the event log and continue on. You should really remove it from the SSO Configuration Store.

To address the need to carry data between one service call to the next, I had to modify the MessageEnrichment orchestration.

I modified it in these steps:

  1. I went through and created proper labels (NotOneSingleWordThatMakesItReallyHardToRead)
  2. Then I went through and created message names that made sense to an ordinary human
  3. I added a separate resolver in the middle so that there was a completely different step to determine the destination, instead of setting the map AND the destination in the resolver
  4. I took the copy context from the original message to the response of the service call. (Why in the world would you do that anyway? The response has a different target name space than the original message.)

This is what the new orchestration looks like:


Which means that I have Itineraries that look like this:


So if I need to call another service, I simply need to add the same Orchestration Extender, define the next three resolvers:

  1. Map that creates the request to the service
  2. Defines the endpoint configuration for the service
  3. The map the enhances the canonical message

I really means that it REALLY isn’t ESB though, it is a series of orchestrations chained together that call services.

Not cool…