none
Reading Values from SSO into BizTalk Custom XSLT

    Question

  • Hi All
    I am using BDTF for my application deployment .

    Now there is a scenario where in I have a map that uses Custom XSLT . There are fields/variables in tht XSLT for which  I would like to read the value from SSO , by referring to SSOSettingsFileReader.dll that comes along with BDTF.
    I know I can use it in the scripting functoid with external assembly reference option but I want that in the custom  XSLT. I have already tried this link

    http://www.tfabraham.com/BTDFDocs/V5_5/ 

    also if you use any other functoid in the map , the XSL will generate the script at end of the xsl but in case of scripting functoid all you get is 

    <xsl:variable name="var:v1" select="ScriptNS0:Read(string(TagName/text()))" />

    and nothing else and the following name space 

    xmlns:ScriptNS0="http://schemas.microsoft.com/BizTalk/2003/ScriptNS0">

    in short no reference to the DLL and the static method inside the dll .

    Please advise. Thanks in advance 


    Regards Gaurav


    • Edited by Gaurav Sood Thursday, May 4, 2017 8:03 PM edited the link
    Thursday, May 4, 2017 8:02 PM

Answers

  • Hi 

    I was able to make things work by using the code that comes with SSO MMC Snap in and then calling the function inside the custom XSLT using the ExternalAssembly.xml . The process for that is listed here 

    https://code.msdn.microsoft.com/windowsdesktop/calling-an-external-c74ce8dc 

    and download  MMC Snap in 

    https://www.microsoft.com/en-in/download/details.aspx?id=14524

    But after you use this Snap in there will be issues of NOT able to create the keys , so download the Fix from 

    https://gallery.technet.microsoft.com/BizTalk-Server-2016-Fix-12784c1f

    (Unzip the downloaded file, overwrite the “SSOMMCSnapIn.dll” file to "C:\Program Files (x86)\Microsoft Services\SSO Application Configuration\" folder (or the folder where you have installed the snap-in from the setup file downloaded from Microsoft))

    use the function in Custom XLST as 

    <xsl:variable name="var:YourVariableName" select="ScriptNS0:Read(&quot;YourSSOApplicationName&quot; , &quot;YourApplicationKey&quot;)" />

    The mistake I was doing was in the XSLT was to view the name spaces in one window I changed the xsl:stylesheet tag  to the below code

    <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
    xmlns:msxsl="urn:schemas-microsoft-com:xslt" 
    xmlns:var="http://schemas.microsoft.com/BizTalk/2003/var" 
    exclude-result-prefixes="msxsl var s0 userCSharp ScriptNS0" 
    version="1.0" 
    xmlns:ns0="http://www.myNamespace.com/XXX/MsgRequest"
     xmlns:s0="http://www.MyNameSpace.com/XXX/TagList" 
    xmlns:ScriptNS0="http://schemas.microsoft.com/BizTalk/2003/ScriptNS0" 
    xmlns:userCSharp="http://schemas.microsoft.com/BizTalk/2003/userCSharp">

    INSTEAD of 

    <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt" xmlns:var="http://schemas.microsoft.com/BizTalk/2003/var" exclude-result-prefixes="msxsl var s0 userCSharp ScriptNS0" version="1.0" xmlns:ns0="http://www.myNameSpace.com/XXX/MsgRequest" xmlns:s0="http://www.myNameSpace.com/XXX/TagList" xmlns:ScriptNS0="http://schemas.microsoft.com/BizTalk/2003/ScriptNS0" xmlns:userCSharp="http://schemas.microsoft.com/BizTalk/2003/userCSharp">

    Doing so it introduced new line characters which didnt allow the compiler to read the namespaces . Hence always keep the xsl:stylesheet in one row. 

    I would like to thank all of you for your time and inputs however. 

    Regards

    Gaurav


    Regards Gaurav


    • Edited by Gaurav Sood Thursday, May 11, 2017 8:21 AM changed
    • Marked as answer by Gaurav Sood Thursday, May 11, 2017 8:22 AM
    Thursday, May 11, 2017 8:20 AM

All replies


  • I know I can use it in the scripting functoid with external assembly reference option but I want that in the custom  XSLT. I have already tried this link

    http://www.tfabraham.com/BTDFDocs/V5_5/ 

    also if you use any other functoid in the map , the XSL will generate the script at end of the xsl but in case of scripting functoid all you get is 


    Regards Gaurav


    Using .net helpers via an external assembly reference is always a better as it prevents memory leaks during map execution. 

    If you have a custom xslt, its better to have the custom extension and call the helper method rather than doing it inline.


    Pi_xel_xar

    Blog: My Blog

    BizTalkApplicationDeploymentTool: BizTalk Application Deployment Tool/

    Friday, May 5, 2017 6:15 AM
    Answerer
  • Hi 

    Thank you for your reply. As suggested I have used the custom extension but some how I get the error in that as well. I have implemented exactly the same approach listed at the following link 

    https://code.msdn.microsoft.com/windowsdesktop/calling-an-external-c74ce8dc

     BUT I am NOT using INLINE XSLT within the scripting functiod instead custom XSLT . This is the error I get 

    sometimes this 

     Error details: Exception from HRESULT: 0x80131942 

    OR 

    Error details: The Messaging Engine failed while executing the inbound map for the message coming from source URL:"C:\Gaurav\BatchIN\*.xml" with the Message Type "http://www.XXXX.com/YYYY/TagList#AllTags". Details:"An error occurred during a call to extension function 'Read'. See InnerException for a complete description of the error."

    I am unable to fine any innerException details anywhere . Suggestions please.

    Regards

    Gaurav



    Regards Gaurav

    Friday, May 5, 2017 3:16 PM
  • Hi Garuav,

    In the same link the Author also faced the same issue and he suggested two solution for this issue. Have you tried to implement those solutions ?

    you can do one more thing :

    first complete your map without using SSO , just assign blank value in respective node and then after this map execution in the orchestration you can use SSO to read the value then assign to respective node.

    Here i am assuming that you need to assign some value in any node , not aware about your exact req.


    Regards

    Abhay Giri

    Mark this reply as answered or vote if this help you. 

    Saturday, May 6, 2017 5:44 PM
  • Make sure you also apply the Custom Extension XML for the reference to the classes
    Any helper classes has to be strong named and added to the GAC
    https://msdn.microsoft.com/en-us/library/aa560154.aspx

    hth /Peter


    • Edited by Peter Lykkegaard Sunday, May 7, 2017 5:37 PM Added link to MSDN article
    Sunday, May 7, 2017 7:32 AM
  • Hi

    THanks for your reply . The issue was not related to the workarounds listed on the link since I am using BTS 2016. It was because I did not pass the Key name correctly as a string. 

    But now there is a new problem. Things WORKED ! BUT ONLY FOR FIRST TIME . Now whatever value I am passing , it only returns the OLD CACHED values . No idea how to solve that .

    1. I have deleted the SSO application and tested by passing the correct key name - it fails 

    2. I have then deleted the valid Key name from the SSO , it fails again 

    3. I recreated the SSO application with the new updated values to the keys , but Then again , It gave the same OLD value which is no where present in the SSO 

    4. Thinking that I might have hardcoded something in custom XSLT , I created a new Map and using the scripting functoid and used the external assembly refernce and the fucntion name and passed the parameters , I still get the old values . :(

    5. I have tried restarting the host instances as well as the ENTSSO service but to no good . 

    Please help me on how to resolve this caching issue. 

    Regards

    Gaurav 


    Regards Gaurav

    Monday, May 8, 2017 6:17 AM
  • Host instance restart should reflect any changes.

    You should restart the instances, your orch is running on if the map is used with orch else the port instances if map runs on port.

    Try a Stop and Start aswell.


    Pi_xel_xar

    Blog: My Blog

    BizTalkApplicationDeploymentTool: BizTalk Application Deployment Tool/

    Monday, May 8, 2017 7:49 AM
    Answerer
  • Hi 

    I am not using the orchestration . Its an inbound map on receive port. 

    Also I am confused too why restarting the host instances along with the ENTSSO service isnt reflecting the changes in the cache ?

    Regards

    Gaurav


    Regards Gaurav

    Monday, May 8, 2017 8:47 AM
  • Hi,

    1. We need to use the .net code for fetching value from the BTDF from the SSOSettings files. The sso application config code wont work 

    using System;
    using System.Collections;
    using System.Diagnostics;
    using System.Runtime.InteropServices;
    using System.IO;
    using System.Text;
    using System.Xml;
    using System.Xml.Serialization;
    using System.Timers;
    using Microsoft.BizTalk.SSOClient.Interop;
    
    namespace SSOSettingsFileManager
    {
    	public class SSOSettingsFileReader
    	{
    		private static Hashtable _hashOfHashes = new Hashtable();
    		public static bool ExceptionOnNull = true;
          private static DateTime _timeOfLastRefresh;
          private const int RefreshIntervalSecs = 60;
    
          static SSOSettingsFileReader()
          {
             _timeOfLastRefresh = DateTime.Now;
          }
          /// <summary>
          /// Will clear all the caches after the refresh interval has expired.
          /// It is assumed that a lock has been acquired prior to calling this function.
          /// </summary>
          private static void ClearAllCachesIfRefreshIntervalExpired()
          {
             // First get the elapsed time in seconds.
             TimeSpan elapsed = DateTime.Now - _timeOfLastRefresh;
    
             if (elapsed.TotalSeconds >= RefreshIntervalSecs)
             {
                _hashOfHashes.Clear();
                _timeOfLastRefresh = DateTime.Now;
             }
          }
     
    		[Obsolete]
    		public static string ReadValue(string affiliateApplication, string valueName)
    		{
    			return ReadString(affiliateApplication,valueName);
    		}
    
    		public static string ReadString(string affiliateApplication, string valueName)
    		{
    			Hashtable ht = Read(affiliateApplication);
    			string val = (string)ht[valueName];
    			
    			if(val == null && ExceptionOnNull)
    			{
    				throw(new ArgumentException(
    					string.Format("Requested SSO value {0} is not available within affiliate app {1}",
    					valueName==null?"empty":valueName,affiliateApplication),"valueName"));
    			}
    
    			return val;
    		}
    
    		public static int ReadInt32(string affiliateApplication, string valueName)
    		{
    			return System.Convert.ToInt32(ReadString(affiliateApplication, valueName));
    		}
    
    		public static void ClearCache(string affiliateApplication)
    		{
    			Hashtable ht = (Hashtable)_hashOfHashes[affiliateApplication];
    			if(ht != null)
    			{
                lock (_hashOfHashes)
                {
                   _hashOfHashes[affiliateApplication] = null;
                }
    			}
    		}
    
    		public static Hashtable Read(string affiliateApplication)
    		{
             Hashtable htToReturn = null;
    
    			lock(_hashOfHashes)
    			{
                // Clear all caches if the refresh interval has expired.
                ClearAllCachesIfRefreshIntervalExpired();
    
                // Check to see if the applicatin is in the table.
                htToReturn = (Hashtable)_hashOfHashes[affiliateApplication];
                if (htToReturn == null)
    			   {
    				   // Not in table.  Read it in from SSO.
    				   object propertyValue;
    				   ISSOConfigStore configStore = (ISSOConfigStore)new SSOConfigStore();
                       SSOPropertyBag bag = new SSOPropertyBag();
    				   configStore.GetConfigInfo(affiliateApplication,SSOHelper.InfoIdentifier, SSOFlag.SSO_FLAG_RUNTIME, bag);
    				   bag.Read(SSOHelper.PropName, out propertyValue, 0);
    
    				   // Now we have what should be a settings file in hand as a string...
    				   // Need to deserialize it...
    				   settings appSettings = null;
    				   XmlSerializer serializer = new XmlSerializer(typeof(settings));	
    				   StringReader stringReader = new StringReader((string)propertyValue);
    				   appSettings = (settings)serializer.Deserialize(stringReader);
    
    				   // Get a hashtable from the property collection, cache it, and return it.
                   htToReturn = new Hashtable();
                   _hashOfHashes[affiliateApplication] = htToReturn;
    				   foreach(property prop in appSettings.propertyCollection)
    				   {
                      htToReturn[prop.name] = prop.Value;
    				   }
    				}
    			}
             return htToReturn;
    		}
    
    		public static void Update(string affiliateApplication, Hashtable ht)
    		{
    			settings inSettings = new settings();
    			foreach(object key in ht.Keys)
    			{
    				SSOSettingsFileManager.property prop = new SSOSettingsFileManager.property();
    				prop.name = key.ToString();
    				prop.Value = (string)ht[key];
    				inSettings.propertyCollection.Add(prop);
    			}
    
    			StringBuilder sb = new StringBuilder();
    			StringWriter writer = new StringWriter(sb);
    			XmlSerializer serializer = new XmlSerializer(typeof(settings));	
    			serializer.Serialize(writer,inSettings);
    
    			SSOHelper helper = new SSOHelper();
    			SSOPropertyBag bag = new SSOPropertyBag();
    			object o = sb.ToString();
    			bag.Write(SSOHelper.PropName,ref o);
    				
    			if(helper.AppExists(affiliateApplication))
    			{
    				helper.SaveConfigInfo(bag, affiliateApplication);			
    			}
    
    			ClearCache(affiliateApplication);
    		}
    	}
    
    	/// <summary>
    	/// Helper component for SSO interactions
    	/// </summary>
    	internal class SSOHelper
    	{
    		public static string PropName = "AppConfig";
    		public static string InfoIdentifier = "{56D74464-67EA-464d-A9D4-3EBBA4090010}";
    
    		/// <summary>
    		/// Saves the config info.
    		/// </summary>
    		public void SaveConfigInfo(SSOPropertyBag bag, string affiliateApplication)
    		{
    			ISSOConfigStore configStore = (ISSOConfigStore)new SSOConfigStore();
    			configStore.SetConfigInfo(affiliateApplication, SSOHelper.InfoIdentifier, bag); 
    		}
    
    		/// <summary>
    		/// Deletes the config info.
    		/// </summary>
    		public void DeleteConfigInfo(string affiliateApplication)
    		{
    			ISSOConfigStore configStore = (ISSOConfigStore)new SSOConfigStore();
    			configStore.DeleteConfigInfo(affiliateApplication, SSOHelper.InfoIdentifier);
    		}
    
    		public void DeleteApp(string appName)
    		{
    			if(AppExists(appName))
    			{
    				ISSOAdmin ssoa = GetAdmin();
    				ssoa.DeleteApplication(appName);
    			}
    		}
    	
    		// Borrowed largely from Flanders' SSOAppConfigHelper object.
    		public void CreateApp(string appName, string userGroup, string adminGroup)
    		{
    			ISSOAdmin ssoa = GetAdmin();
    			int fields = 2;
    			ssoa.CreateApplication(appName,appName + " Configuration Data","someone@microsoft.com",userGroup,adminGroup,SSOFlag.SSO_FLAG_APP_CONFIG_STORE|SSOFlag.SSO_FLAG_APP_ALLOW_LOCAL,fields);
    			ssoa.CreateFieldInfo(appName,"someone@microsoft.com",SSOFlag.SSO_FLAG_FIELD_INFO_SYNC);
    			ssoa.CreateFieldInfo(appName,SSOHelper.PropName,SSOFlag.SSO_FLAG_FIELD_INFO_SYNC);
    			ssoa.UpdateApplication(appName,null,null,null,null,SSOFlag.SSO_FLAG_ENABLED,SSOFlag.SSO_FLAG_ENABLED);
    		}
    
    		// Borrowed from Flanders' SSOAppConfigHelper object.
    		public bool AppExists(string appName)
    		{
    			ISSOAdmin ssoa = GetAdmin();
    			string description;
    			string contact;
    			string grpName;
    			string grpAdmin;
    			int flags;
    			int fields;
    			bool ret=false;
    			try
    			{
    				ssoa.GetApplicationInfo(appName,out description,out contact,out grpName,out grpAdmin,out flags, out fields);
    				ret=true;
    			}
    			catch{}
    			finally
    			{
    				if(ssoa!=null)
    					Marshal.ReleaseComObject(ssoa);
    			}
    			return ret;
    		}
    
    		// Borrowed from Flanders' SSOAppConfigHelper object.
    		ISSOAdmin GetAdmin()
    		{
    			SSOAdmin ssoadmin  = new SSOAdmin();
    			ISSOAdmin ssoa = (ISSOAdmin)ssoadmin;
    			return ssoa;
    		}
    
    	}
    }
    
    /// <summary>
    /// This class is simply a forwarder (with a few extra convenience methods) that lives outside the
    /// namespace so as to shorten up the syntax in the orchestration expression editor.
    /// </summary>
    public class SSOSettingsFileReader
    {
    	public static string ReadString(string affiliateApplication, string valueName)
    	{
    		return SSOSettingsFileManager.SSOSettingsFileReader.ReadString(affiliateApplication, valueName);
    	}
    
    	public static int ReadInt32(string affiliateApplication, string valueName)
    	{
    		return SSOSettingsFileManager.SSOSettingsFileReader.ReadInt32(affiliateApplication, valueName);
    	}
    
    	public static void ClearCache(string affiliateApplication)
    	{
    		SSOSettingsFileManager.SSOSettingsFileReader.ClearCache(affiliateApplication);
    	}
    
    	public static Hashtable Read(string affiliateApplication)
    	{
    		return SSOSettingsFileManager.SSOSettingsFileReader.Read(affiliateApplication);
    	}
    
    }

    2. Whenever u are changing the sso value  we need to change the value in the BTDF under your project in c drive and then urProjectPath\Framework\DeployTools\SSOFileImport open that exe and load your application

    using SSOSettingsFileReader.ReadValue("Applicationname","Key") you can fetch value from sso.


    Regards, Aboorva Raja R Please remember to mark the replies as answers if they help and unmark them if they provide no help.


    Tuesday, May 9, 2017 8:04 PM
  • I am using the helper class that comes along with SSO MMC Snap in released  by Microsoft now. BDTF has some issues it seems with caching. 

    BUT yes some how it hasnt solved my problem , I need to figure out whats causing the issue again . 

    Regards

    Gaurav


    Regards Gaurav

    Wednesday, May 10, 2017 8:30 AM
  • The helper class u need to change as i attached, the helper class comes along with SSO MMC snap in will not work

    Regards, Aboorva Raja R Please remember to mark the replies as answers if they help and unmark them if they provide no help.

    Wednesday, May 10, 2017 9:12 AM
  • The helper class u need to change as i attached, the helper class comes along with SSO MMC snap in will not work

    Regards, Aboorva Raja R Please remember to mark the replies as answers if they help and unmark them if they provide no help.

    Hi Aboorva 

    On the contrary SSO Snap in code works fine when I tested it against a console application .

    The issue is even after updated the ExternalAssembly.xml file to use the newly built DLL with SSO Snap in helper class , the solution still says that it cannot find the function 'ReadString' which is a function SSOSettingsFileReader  . I have checked my xslt code and I am using ScriptNS0:Read  which is a function in the helper class that came along with the SSO Snap in . 

    I am still looking through what is wrong in the code . 

    PS: all host instances restarted , ENTSSO restarted.

    Reagrds

    Gaurav


    Regards Gaurav

    Wednesday, May 10, 2017 10:09 AM
  • Hi 

    I was able to make things work by using the code that comes with SSO MMC Snap in and then calling the function inside the custom XSLT using the ExternalAssembly.xml . The process for that is listed here 

    https://code.msdn.microsoft.com/windowsdesktop/calling-an-external-c74ce8dc 

    and download  MMC Snap in 

    https://www.microsoft.com/en-in/download/details.aspx?id=14524

    But after you use this Snap in there will be issues of NOT able to create the keys , so download the Fix from 

    https://gallery.technet.microsoft.com/BizTalk-Server-2016-Fix-12784c1f

    (Unzip the downloaded file, overwrite the “SSOMMCSnapIn.dll” file to "C:\Program Files (x86)\Microsoft Services\SSO Application Configuration\" folder (or the folder where you have installed the snap-in from the setup file downloaded from Microsoft))

    use the function in Custom XLST as 

    <xsl:variable name="var:YourVariableName" select="ScriptNS0:Read(&quot;YourSSOApplicationName&quot; , &quot;YourApplicationKey&quot;)" />

    The mistake I was doing was in the XSLT was to view the name spaces in one window I changed the xsl:stylesheet tag  to the below code

    <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
    xmlns:msxsl="urn:schemas-microsoft-com:xslt" 
    xmlns:var="http://schemas.microsoft.com/BizTalk/2003/var" 
    exclude-result-prefixes="msxsl var s0 userCSharp ScriptNS0" 
    version="1.0" 
    xmlns:ns0="http://www.myNamespace.com/XXX/MsgRequest"
     xmlns:s0="http://www.MyNameSpace.com/XXX/TagList" 
    xmlns:ScriptNS0="http://schemas.microsoft.com/BizTalk/2003/ScriptNS0" 
    xmlns:userCSharp="http://schemas.microsoft.com/BizTalk/2003/userCSharp">

    INSTEAD of 

    <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt" xmlns:var="http://schemas.microsoft.com/BizTalk/2003/var" exclude-result-prefixes="msxsl var s0 userCSharp ScriptNS0" version="1.0" xmlns:ns0="http://www.myNameSpace.com/XXX/MsgRequest" xmlns:s0="http://www.myNameSpace.com/XXX/TagList" xmlns:ScriptNS0="http://schemas.microsoft.com/BizTalk/2003/ScriptNS0" xmlns:userCSharp="http://schemas.microsoft.com/BizTalk/2003/userCSharp">

    Doing so it introduced new line characters which didnt allow the compiler to read the namespaces . Hence always keep the xsl:stylesheet in one row. 

    I would like to thank all of you for your time and inputs however. 

    Regards

    Gaurav


    Regards Gaurav


    • Edited by Gaurav Sood Thursday, May 11, 2017 8:21 AM changed
    • Marked as answer by Gaurav Sood Thursday, May 11, 2017 8:22 AM
    Thursday, May 11, 2017 8:20 AM
  • Werid with the line breaks between namespaces issue
    Done it a few times without problems

    Thanks for the heads up  /Peter

    Thursday, May 11, 2017 8:43 AM