How to Filter by Filename by Mask in BizTalk

Business Requirement:

Only send an email to the credit department if the filename contains the word “EXPORT” and and ends with .txt.

We cannot put *EXPORT*.txt in the Receive Location, because there are other files coming in with different patterns.

Question: How would you do that in BizTalk?

BizTalk has filters on the send ports. So in theory, you could filter on the filename. But guess what?
There is no operator for “contains”, or “matches”, or any RegEx (Regular Expressions).

The image below shows the field we would use: FILE.ReceivedFileName, but there is no operator that helps solve this problem.

My Solution:

On the ReceivePort, I created my own custom pipeline component, did the matching myself, and promoted fields if the results were true. The admin/developer can identify the name of the promoted field as one of the values in my Pipeline Component. He also provides the filemask he wants to match.

The receive port pipeline configuration is shown below.

I created a Property Schema that corresponds:

Then in the SendPort, I can test the promoted fields that were promoted by that Receive Pipeline component.

The pipeline component is too big to include here, but here are some key fragments.

First, I found the code below on StackOverflow to do file matching like DOS does:

<pre>
        private static bool FitsMask(string fileName, string fileMask)
        {
            string pattern =
                 '^' +
                 Regex.Escape(fileMask.Replace(".", "__DOT__")
                                 .Replace("*", "__STAR__")
                                 .Replace("?", "__QM__"))
                     .Replace("__DOT__", "[.]")
                     .Replace("__STAR__", ".*")
                     .Replace("__QM__", ".")
                 + '$';
            bool result = new Regex(pattern, RegexOptions.IgnoreCase).IsMatch(fileName);
            return result; 
        }
</pre>

setPromotedField is a common function that I could easily call over and over, as shown in the code bock following this one.

<pre>
/*
 * Neal Walters 
 * 05/19/2017 
 * 
 * The purpose of this is to allow for more complex file matching, 
 * and allow the SendPort to do a subscription when the file does NOT contain a given mask. 
 * For example, in BillTrust, there was need to write file to disk if filename 
 * contains "Export" but not "*.txt".
 * So this component is put in a pipeline that runs on the Receive, and sets 
 * a series of promoted fields FileMatchGroup## (01-07) 
 * that can be used as Filters in the SendPort. 
 * 
 * Each of the 7 fields come in a pair, e.g. 
 * FileMask01Matches and FileMask01Group 
 * The matches field will be something like *.txt, and when true, the value of 
 * FileMask01Group will be set in the promoted fields, otherwise it is created with a value of blank. 
 * 
 * For further flexibility, The FileMask01Matches can contain an array of Matches, such as: 
 * *.txt,*.csv,*.xls 
 * The program will split on the comma, and if any one of the masks is true, 
 * then the FileMask01Group will be set. 
 * 
 * 
 */

        private static void setPromotedField(string seqNum,
                                                string strMatches,
                                                string strMaskGroup,
                                                string argReceivedFileName,
                                                IBaseMessage pInMsg)
        {
            string promotedFieldNamespace = "https://MyCompany.Common.PropertySchemas.CommonPropertySchema";

            // See the comments at the top of this pipeline component to explain this logic. 

            if (strMatches != null && strMatches.Length > 0)
            {
                //Before doing the split/array, this was the logic: 
                //bool fitsMaskResult = FitsMask(argReceivedFileName, strMatches);

                // allow multiple patterns, comma separated 
                // Pipe symbol was used, because Windows files cannot have pipe symbol, 
                // and because comma looks a lot like a comma in the 
                // BizTalk Admin Console configuration screens 

                string[] matchPatterns = strMatches.Split('|');
                bool fitsMaskResult = false;
                bool foundAtLeastOneMatch = false; 
                string debugSummary = "";

                // Loop through each of the split items (each mask/pattern) 
                // If any one of them is true, we count it as a match. 
                foreach (string matchPattern in matchPatterns)
                {
                    fitsMaskResult = FitsMask(argReceivedFileName, matchPattern);
                    debugSummary = debugSummary + matchPattern + "=" + fitsMaskResult + ",";
                    if (fitsMaskResult)
                    {
                        foundAtLeastOneMatch = true; 
                    }
                }
                string strSetValue = "";
                if (foundAtLeastOneMatch)
                {
                    strSetValue = strMaskGroup;
                }

                // Example: FileMask##Group - we substituted the two char seqNum to get the field name 
                // Note: We create a promoted field even if the match is false; 
                // this is to ensure that we can test the field in subequent SendPort Filter statements. 
                string varName = "FileMask" + seqNum + "Group";
                pInMsg.Context.Promote(varName, promotedFieldNamespace, strSetValue);
            }

        }
</pre>

The above “setPromotedField” function is called like this:

<pre>
  string receivedFileName = pInMsg.Context.Read(
       "ReceivedFileName",
       "http://schemas.microsoft.com/BizTalk/2003/file-properties")
       .ToString();

  // Repeat exact same process for 7 propertyBag pairs of fields 

  setPromotedField("01", this.FileMask01Matches, this.FileMask01Group, receivedFileName, pInMsg);
  setPromotedField("02", this.FileMask02Matches, this.FileMask02Group, receivedFileName, pInMsg);
  setPromotedField("03", this.FileMask03Matches, this.FileMask03Group, receivedFileName, pInMsg);
  setPromotedField("04", this.FileMask04Matches, this.FileMask04Group, receivedFileName, pInMsg);
  setPromotedField("05", this.FileMask05Matches, this.FileMask05Group, receivedFileName, pInMsg);
  setPromotedField("06", this.FileMask06Matches, this.FileMask06Group, receivedFileName, pInMsg);
  setPromotedField("07", this.FileMask07Matches, this.FileMask07Group, receivedFileName, pInMsg);

</pre>

Uncategorized  

Leave a Reply