If you are interesting in using C# to Monitor Biztalk, instead of Powershell to Monitor BizTalk, or Powershell to Monitor Diskspace, or Powershell to Monitor EventLogs (and send out emails) then check out this blog and sample code:
https://prashantbiztalkblogs.wordpress.com/2017/08/30/monitoring-biztalk-resources-programmatically-using-c/

I didn’t run it, but it looks like a good sample to get started with.

 

I created the routine below based on a StackOverflow that gave me the idea. In BizTalk Orchestrations, we often email out error to a distribution list, and it’s a lot easier on the eye when the request and response XML messages are nicely formatted. “Pretty Print” is a term used by several utilities like XML Spy and the XML Tools PlugIn for NotePad++.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.IO; 

namespace MySite.Common.BizTalkUtil
{
    public static class XmlFormatter
    {
        // Neal Walters - added 09/11/2015 from 
        // http://stackoverflow.com/questions/203528/what-is-the-simplest-way-to-get-indented-xml-with-line-breaks-from-xmldocument
        public static string PrettyPrint(this XmlDocument doc)
        {
            var stringWriter = new StringWriter(new StringBuilder());
            var xmlTextWriter = new XmlTextWriter(stringWriter) { Formatting = Formatting.Indented };
            doc.Save(xmlTextWriter);
            return stringWriter.ToString();
        }
    }
}

Example Use:

//tempStrBuilder is an object of type StringBuilder 
tempStrBuilder.Append("There was a SOAP 1.1 Fault. Check service for errors." + System.Environment.NewLine);
tempStrBuilder.Append("===========================" + System.Environment.NewLine);
tempStrBuilder.Append("Inbound Request:" + System.Environment.NewLine);
tempStrBuilder.AppendLine(MySite.Common.BizTalkUtil.XmlFormatter.PrettyPrint(xmlDocReq));
tempStrBuilder.Append("===========================" + System.Environment.NewLine);
tempStrBuilder.Append("SOAP Fault:" + System.Environment.NewLine);
tempStrBuilder.Append(MySite.Common.BizTalkUtil.XmlFormatter.PrettyPrint(vSOAPErrorXML));
// Then the error can be include in the body of an email 

Pretty Print XML in NotePad++

If you just need to format XML on your PC, consider adding the XML PlugIn for NotePad++ (a great free editor).

NotePad++ Pretty Print for XML

NotePad++ Pretty Print for XML

CNLT-ALT-SHIFT-B is the short cut, if you have enough fingers!

To install the Plugin, open the Plugin Manager as shown below.  Scroll down to the XML Plugin, and simply install it, then it restarts NotePad++ and it should be there.  Only takes two minutes or less.

Pretty Print XML in XSLT or BizTalk Mapper

From the map grid, right-click properties. In the properties window, not the “Indent” property, which says “Specified additional white space to add whne outputing the result tree; the value must be “yes” or “no”.

Plugin Manager

Plugin Manager

Presumably, that would set the XSLT code as follows, but I haven’t proven that yet.

 <xsl:output method="xml" indent="yes"/>

Reference: XSLT Syntax for Indenting, i.e. Pretty Print

There was a typo below, the first time is using LINQ, the second one is using Directory.GetFiles (then getting the time on each file within a loop). Big Difference, right? The last one is 41 times slower. The difference was not really using LINQ, but using the DirectoryInfo.GetFiles instead of the combination of Directory.GetFiles and then using File.GetCreationTime inside the loop.

DirInfo_SpeedUp_Stats_3

I was using the third method, and was getting so bored and tired of waiting on it to run when dealing with a file with about 5000 files in it, out of which about 450 matched my file mask. I knew it shouldn’t take that long to enumerate through a directory. So I did some searching and found this question on Stackoverflow.

 

The code below will benchmark all three methods:
1) LINQ (which uses DirectoryInfo.GetFiles)
2) DirectoryInfo.GetFiles
3) Directory.GetFiles and File.GetCreationTime

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO; 

namespace DirGetFilesSpeedTest
{
    class Program
    {
        static void Main(string[] args)
        {

            DateTime somedate = DateTime.Now.Subtract(new TimeSpan(24, 0, 0));
            string folderName = @"\\MyServer\Biztalk_Messages\Archive\856_EDI";

            DateTime startDateTime = DateTime.Now; 
            Console.WriteLine("Start LINQ query at " + DateTime.Now);
            DirectoryInfo dirInfo = new DirectoryInfo(folderName); 
            int countMatch = 0;
            var filterFiles = from file in dirInfo.GetFiles()
                              where file.CreationTime &gt; somedate
                              select file;
            DateTime stopDateTime = DateTime.Now; 
            TimeSpan tsElapsed  = stopDateTime.Subtract(startDateTime);
            foreach (var x in filterFiles)
            {
                countMatch++; 
            }


            Console.WriteLine("CountMatching Files=" + countMatch);
            Console.WriteLine("Stop LINQ query at " + stopDateTime + " Duration = " + tsElapsed.TotalSeconds);

            startDateTime = DateTime.Now;
            FileInfo[] fileInfoArray = dirInfo.GetFiles("*.*");
            countMatch = 0;
            foreach (FileInfo fileInfo in fileInfoArray)
            {
                DateTime fileDateTime = fileInfo.CreationTime; 
                TimeSpan ts1 = DateTime.Now.Subtract(fileDateTime);
                if (ts1.TotalSeconds &lt; 24 * 60 * 60)
                {
                    countMatch++;
                }
            }

            Console.WriteLine("CountMatching Files=" + countMatch);
            Console.WriteLine("Stop DirInfo loop  " + stopDateTime + " Duration = " + tsElapsed.TotalSeconds);

            
            startDateTime = DateTime.Now;
            string[] filenameArray = Directory.GetFiles(folderName, "*.*");
            countMatch = 0; 
            foreach (string filename in filenameArray)
            {
                DateTime fileDateTime = File.GetCreationTime(filename);
                TimeSpan ts1 = DateTime.Now.Subtract(fileDateTime);
                if (ts1.TotalSeconds &lt; 24 * 60 * 60)
                {
                    countMatch++; 
                }
            }


            Console.WriteLine("CountMatching Files=" + countMatch);
            stopDateTime = DateTime.Now;
            tsElapsed = stopDateTime.Subtract(startDateTime);
            Console.WriteLine("Stop LINQ query at " + stopDateTime + " Duration = " + tsElapsed.TotalSeconds);
             


            Console.WriteLine("\n\nPress enter to end:"); 
            Console.ReadLine(); 

        }
    }
}

I got this error: ORA-01843 Oracle date format picture ends before converting entire input string

The variable fileDateTime was a defined as a normal C# DateTime.

Code Failed with ORA=01843

                    commSQL.CommandText = "UPDATE MY_TAB " + 
                        " SET DATE_FILE_PROCESSED = TO_DATE('" + fileDateTime + "', 'MM/DD/YYYY HH:MI:SS')" +  
                        " WHERE DATE_FILE_PROCESSED IS NULL AND CUST_PO_NO = '" + PONum + "'";  

Above generated this SQL Update – Notice the “AM” on the end of the date time:

UPDATE MY_TAB  SET DATE_FILE_PROCESSED = TO_DATE('9/17/2015 8:01:51 AM', 'MM/DD/YYYY HH:MI:SS') 
WHERE DATE_FILE_PROCESSED IS NULL AND CUST_PO_NO = '118686'

Corrected Code Works

Note that I changed Oracle date from HH to HH24.

                    commSQL.CommandText = "UPDATE MY_TAB " + 
                        " SET DATE_FILE_PROCESSED = TO_DATE('" + fileDateTime.ToString("MM/dd/yyyy HH:mm:ss") + "', 'MM/DD/YYYY HH24:MI:SS')" +  
                        " WHERE DATE_FILE_PROCESSED IS NULL AND CUST_PO_NO = '" + PONum + "'";  

UPDATE MY_TAB  SET DATE_FILE_PROCESSED = TO_DATE('09/17/2015 08:01:53', 'MM/DD/YYYY HH24:MI:SS') 
WHERE DATE_FILE_PROCESSED IS NULL AND CUST_PO_NO = '118690'

References: Microsoft Dater Formats   Oracle TO_DATE function

 

 

I’m in the process of writing a C# program that will validate field sizes (and maybe data types) against a SQL table. I need to traverse through each element and get the element value, the next step will be to validate it each element value.

The code below will “walk the tree” structure of the XML. Attributes are ignored in this program. The variable “indent” is used to indent each level of the XML as it is being processed. This program has a method TraverXmlNode that recursively calls itself to read the XML through unlimited levels of depth.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;

namespace FieldSizeChecker 
{
    class Program
    {
        static void Main(string[] args)
        {
            string xmlFilename = "CanonicalSample1.xml";
            Console.WriteLine("\n\nStart XML Parser");
            ValidateXMLFieldValues(xmlFilename, objTable); 

            Console.WriteLine("\n\nPress enter to continue:");
            Console.ReadLine(); 

        }

        static void ValidateXMLFieldValues(string xmlFilename, SQLParserLibrary.Table objTable)
        {
            XmlDocument xmlDoc = new XmlDocument(); 
            xmlDoc.Load(xmlFilename);
            int depth = 0; 
            TraverseXMLNode(xmlDoc.DocumentElement, depth); 
        }

        
        static void TraverseXMLNode(XmlNode xmlNode, int depth)
        {
            //Loop through results
            depth = depth + 1;
            string indent = new String(' ', depth);
            if (xmlNode.NodeType == XmlNodeType.Element)
            {
                XmlElement element = (XmlElement)xmlNode;
                Console.WriteLine(indent + " elementName=" + element.Name + " Value=" + getElementTextValue(element))
                    ;
            }


            foreach (XmlNode childNode in xmlNode.ChildNodes)
            {
                TraverseXMLNode(childNode, depth);
            }

        }

        static string getElementTextValue(XmlElement element)
        {
            XmlNode firstChildNode = element.FirstChild; 
            if (firstChildNode != null)
            {
                return firstChildNode.Value;
            }
            else
            {
                return ""; 
            }
        }

    }

}

I created this code to generate a unique Nonce code. This relates back to this post about BizTalk calling a webservice with Basic Authenticiation.

Message Assignment is prior blog was updated to call C# business component:

xmlDocSoapHeader.LoadXml(
   "<headers>" + 
   "<wsse:Security mustUnderstand=\"1\" xmlns:wsse=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd\" xmlns:wsu=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd\">" + 
   "<wsse:UsernameToken wsu:Id=\"UsernameToken-E4E3CB1F68472DCF2914369675811859\">" +   
   "  <wsse:Username>" + strServiceBenchUserID + "</wsse:Username>" + 
   "  <wsse:Password Type=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText\">" + strServiceBenchPasswordFromSSO + "</wsse:Password>" + 
   "  <wsse:Nonce EncodingType=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary\">" + Asurion.BusinessCompnents.getNonce() + "</wsse:Nonce>" +
   "  <wsu:Created>" + System.DateTime.UtcNow.ToString("s") + "Z" + "</wsu:Created>" + 
   "</wsse:UsernameToken>" +
   "</wsse:Security>" + 
   "</headers>"  
);

msgListNewCallRequest(WCF.OutboundCustomHeaders) = xmlDocSoapHeader.OuterXml;

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Security.Cryptography;
using System.Threading;</code>

namespace Asurion
{
public static class BusinessCompnents
{

public static string getNonce()
{
// http://stackoverflow.com/questions/896901/wcf-adding-nonce-to-usernametoken
// Nonce explained here: http://weblog.west-wind.com/posts/2012/Nov/24/WCF-WSSecurity-and-WSE-Nonce-Authentication
// quote: Why is there a nonce? My first thought here was WTF? The username and password are there in clear text, what does the Nonce accomplish?
// The Nonce and created keys are are part of WSE Security specification and are meant to allow the server to detect and prevent replay attacks.
// The hashed nonce should be unique per request which the server can store and check for before running another request thus ensuring that
// a request is not replayed with exactly the same values.
//Random r = new Random();
Random r = RandomProvider.GetThreadRandom(); // better to get unique random number if called mulitple times
DateTime created = DateTime.Now;
string nonce = Convert.ToBase64String(Encoding.ASCII.GetBytes(SHA1Encrypt(created + r.Next().ToString())));
return nonce;
}

public static String SHA1Encrypt(String phrase)
{
UTF8Encoding encoder = new UTF8Encoding();
SHA1CryptoServiceProvider sha1Hasher = new SHA1CryptoServiceProvider();
byte[] hashedDataBytes = sha1Hasher.ComputeHash(encoder.GetBytes(phrase));
return ByteArrayToString(hashedDataBytes);
}

public static String ByteArrayToString(byte[] inputArray)
{
StringBuilder output = new StringBuilder("");
for (int i = 0; i &lt; inputArray.Length; i++)
{
output.Append(inputArray[i].ToString("X2"));
}
return output.ToString();
}

} // end class

public static class RandomProvider
{
// http://csharpindepth.com/Articles/Chapter12/Random.aspx
private static int seed = Environment.TickCount;

private static ThreadLocal randomWrapper = new ThreadLocal(() =&gt;
new Random(Interlocked.Increment(ref seed))
);

public static Random GetThreadRandom()
{
return randomWrapper.Value;
}

} // end class
}

This C# code fragment illustrates how to use the RegEx Captures collection within RegEx Groups.

I needed the ability to read all files in a directory, and build a CSV to cross reference the PO number to the EDI file it was in.

            //Pattern we want, but then escape all the ^ signs  "REF^BM^(\d*)~";
            string strRegExPattern856 = @"REF^BM^(\d*)~".Replace("^", @"\^");

public static void parseFile(string strDirectoryName, string filemask, string prefix, 
                             string filetype, string strRegExPattern, 
                             StreamWriter objFileSW, int maxDaysBack)
{
		string[] filenameArray = 
                  Directory.GetFiles(strDirectoryName, filemask, SearchOption.TopDirectoryOnly);

		foreach (string filename in filenameArray)
		{
			Console.WriteLine("Processing file=" + filename);
			string fileContents = File.ReadAllText(filename);

			foreach (Match m in Regex.Matches(fileContents, strRegExPattern))
			{
				//Console.WriteLine("'{0}' found at index {1}.",m.Value, m.Index);
				string showFilename = Path.GetFileName(filename);
				FileInfo fileInfo = new FileInfo(filename);
				TimeSpan daysBack = DateTime.Now.Subtract(fileInfo.LastWriteTime);
				
				if (daysBack.Days < maxDaysBack)
				{

					//Worthless Capture collection! 
					/*
					foreach (Capture capture in m.Captures)
					{
					   Console.WriteLine("Index={0}, Value={1}", capture.Index, capture.Value);
					}
					*/

					int loopCounter = 0;
					foreach (Group grp in m.Groups)
					{

						loopCounter++;
						if (loopCounter == 2)  // trying to avoid subscript error 
						{
							string PONum = grp.Captures[0].Value;
							Console.WriteLine(loopCounter + " " + showFilename + " " + PONum);
							string outline = prefix + "," +
                                                                filetype + "," + 
                                                                showFilename + "," + 
                                                                fileInfo.LastWriteTime +
                                                                "," + PONum;
							objFileSW.WriteLine(outline);
						}
					}
				}
				else
				{
					Console.WriteLine("Skipping file " + showFilename + " Created=" + 
                                                          fileInfo.CreationTime + 
                                                          " Over " + maxDaysBack + " Days Ago"); 
				}

			}  // end foreach 

	}
} 


Short blog today, below is working C# code I ran today to format and update a date in an Oracle database.
My first attempt using just DateTime.Now resulted in error “ORA-01861: literal does not match format string”.

<code>
commSQL.CommandText = "UPDATE PollingTable " +
" SET DATE_POLLED = '" + DateTime.Now.ToString("yyyy-MM-dd hh:mm:ss") + "'" + 
" WHERE ... ";
</code>

The BizTalk roundingfunctoid uses “Banker’s rounding” by default. That means if you have odd numbers round away from zero, and even numbers round towards zero.
For example, 1.5 rounds to 2, and 2.5 rounds to 2. This is the a “fair” way of rounding, so that neither party in a business transaction benefits. I’m currently working in the aviation fuel industry, and they always wanted to round “up”. In Microsoft terminology, this is called “away from zero”, because for negative numbers you are rounding up- or away from zero, to a bigger negative number.

First I implemented the first function below. It worked great when the data was clean. But if I had a field with a NULL value (i.e. the XML element was missing), the routine blew up with a nasty error.

I came up with the idea of the second routine, called “RoundAwayFromZeroSafe”. I have to pass more parameters to it, in order to get a useful error message written to the application event log.
If I wanted my code to be re-usable, these parms are needed to identify 1) Which map (I include the project name/map name here), 2) Field Name (for example, Net vs Gross, which field being rounded caused the problem), and 3) some identification number (in my case it was called TicketNum, although I should have gone with a more generic name for the parm). The first parm is the number to round, that comes from the left side of the map. Likewise, the TicketNum comes from the left side of the map. The other two fields are entered as constants into the Scripting Functoid (see picture below the code).

Why do I want to pass the ID/TicketNum?  Suppose you have a map of 1000 tickets/records.  How do you know which one has the bad data in it, unless you include the ID in the error?  It certainly saves a lot of time guessing or searching for the bad data to have it clearly labeled which “row” it is on.

        public static decimal RoundAwayFromZero(decimal numberToRound)
        {
            // BizTalk functoid uses MidpointRounding.ToEven (also called Banker's Rounding)
            // http://msdn.microsoft.com/en-us/library/system.midpointrounding%28v=vs.110%29.aspx 
            // BUt we want to always "round up", i.e. away from zero. 
            return Math.Round(numberToRound, 0, MidpointRounding.AwayFromZero);
        }

        public static decimal RoundAwayFromZeroSafe(string strNumberToRound, string strTicketNum, string strMapName, string strFieldName)
        {

            decimal decNumberToRound = 0; 
            if (decimal.TryParse(strNumberToRound, out decNumberToRound)) 
            {

                // BizTalk functoid uses MidpointRounding.ToEven (also called Banker's Rounding)
                // http://msdn.microsoft.com/en-us/library/system.midpointrounding%28v=vs.110%29.aspx 
                // BUt we want to always "round up", i.e. away from zero. 
                return Math.Round(decNumberToRound, 0, MidpointRounding.AwayFromZero);
            }
            else if (strNumberToRound == null) 
            {
                // same as below, but no need to write error message to EventLog 
                return 0;
            }
            else 
            {
                string errorMsg = "Non-Decimal passed to RoundAwayFromZeroSafe Ticket=" + strTicketNum +
                                    " MapName=" + strMapName + 
                                    " FieldName=" + strFieldName + 
                                    " Value Passed='" + strNumberToRound + "'";
                System.Diagnostics.EventLog.WriteEntry("Logger", errorMsg, System.Diagnostics.EventLogEntryType.Error);
                return 0;
            }
        }

This shows how I pass the constants to the above routine: RoundAwayFromZeroSafe

BizTalkRoundingFunctoidConfig

Here’s an example of the error written the log (before I started checking for nulls). One might debate whether a map should ever write to the log. In theory, these type of errors should be worked out during development and QA, so it should never happen in Production. We use “Logger” for our common Event-Log-Source, so any of our programs that write to the EventLog will be tagged with the source as “Logger”. NOTE: There are a few extra steps (documented elsewhere) to establish a new event log source on each system.

BizTalkRoundingFunctoidEventLogErrorExample

The actual message reads:

Non-Decimal passed to RoundAwayFromZeroSafe Ticket=QT.Epic.Export:EFSRExtractsGrouped_v1_0_To_EPIC_csv.btm MapName=SPMSP476723 FieldName=Gross Value Passed=”

(Oops – I just noticed that I have Ticket and Mapname backwards in the error message).

 

Microsoft References:
1) BizTalk Rounding Functoid
2) Microsoft Rounding AwayFromZero vs ToEven (Banker’s Rounding)

Below is a code fragment I use to extract a matching string using RegEx (C# Regular Expression). I put it in a class library, and make it available to BizTalk Orchestrations, Pipeline, and Map Scripting functoids that might need to call it.

//using System.Text.RegularExpressions;  <-- put this above your class, put the method below in some class library 

        public static string extractUsingRegEx(string text, string pattern)
        {
            string resultString = "";
            Regex r1 = new Regex(pattern);
            Match match = r1.Match(text);
            if (match.Success)
            {
                resultString = match.Groups[1].Value;
            }
            else
            {
                throw new System.ApplicationException
                    ("RegEx did not match: pattern=" + pattern + " text=" + text);
            }
            return resultString;
        }

Below is an example usage of the RegEx routine

            string searchText = "LastName=Walters FirstName=Neal State=Texas";
            string pattern = "FirstName=(.*?) "; 
            string result = extractUsingRegEx(searchText, pattern);
            Console.WriteLine("Result=" + result); 

The parentheses indicate the text to be captured and returned in the result string. Note that in RegEx, the ? mark is used as the non-greedy indicator.