Friday, June 24, 2011

OOB BizTalk FTP Adapter Alternative

We tend to get naive when working with BizTalk and its many adapters.  I, for one, admit to operating under such soundless circumstances.  My latest issue has been with the out of the box FTP Adapter Microsoft so graciously included.  My objective was to generate an application that would connect to two separate FTP servers and apply the same file to both.  It just so happens that one of the vendors in this instance, was uncooperative or unable to supply a test account. So, I decided to test my process with the more cooperative of the two vendors and configured my send ports to push both files towards that vendor.

All worked great, and I ran and reran my process dozens of times over.  I passed development, alpha, beta and UAT testing until finally pushing my application to production.  That's when the wheels fell off the wagon.  After I reconfigured my send port to send my files to both vendors, within an hour of post-deployment testing, I soon discovered that the FTP adapter was failing for one of my send ports.  Upon review of the event logs, I noticed that my uncooperative vendor was refusing connection after the initial FTP call.

Digging a little deeper, I turned on the FTP logs made available by the FTP Adapter and found that the Adapter never sends the QUIT command to sever the connection until the host instance was shut down or restarted.  This was insane, I thought.  I tried a number of solutions such as applying an "After Put" command in the FTP Adapter. Sadly these solutions did not work effectively without throwing exceptions and cluttering up BizTalk with failed orchestrations.

So, I eventually gave in and decided to leverage a dynamic port and incorporate C# into the project.  The code declared a new class that would perform folder monitoring based on a value specified from the BizTalk configuration file.  My monitor would listen to the folder and wait for a new file or file modification to occur like such:

public void initiateFolderMonitor()
{
string fullFilePath = ConfigurationManager.AppSettings["FolderPath"];

folderMonitor.Path = fullFilePath;
UploadFilePath = fullFilePath;
UploadFile = ConfigurationManager.AppSettings["FileName"];
FTPAddress = ConfigurationManager.AppSettings["FTPAddress"];
FTPUser = ConfigurationManager.AppSettings["FTPUser"];
FTPPassword = ConfigurationManager.AppSettings["FTPPassword"];
FailureCount = 0;

folderMonitor.NotifyFilter = System.IO.NotifyFilters.FileName;

folderMonitor.NotifyFilter = folderMonitor.NotifyFilter | System.IO.NotifyFilters.Attributes;

folderMonitor.Created += new FileSystemEventHandler(monitoringEventRaised);
folderMonitor.Changed += new FileSystemEventHandler(monitoringEventRaised);


 try
 {
   folderMonitor.EnableRaisingEvents = true;
 }
 catch (ArgumentException)
 {
   stopActivityMonitoring();
 }
}

public void stopActivityMonitoring()
{
 if (FailureCount == 0)
   retryFileUpload();

 if (folderMonitor.EnableRaisingEvents)
   folderMonitor.EnableRaisingEvents = false;

 folderMonitor.Dispose();
 folderMonitor = null;
}

private void monitoringEventRaised(object sender, System.IO.FileSystemEventArgs e)
{
 switch (e.ChangeType)
 {
   case WatcherChangeTypes.Changed:
   case WatcherChangeTypes.Created:
      if (e.Name == UploadFile)
      {
         FailureCount += 1;

         if (FtpUloadFile())
         {
            stopActivityMonitoring();
            removeFile();
         }
         else
         {
            retryFileUpload();
         }
      }
      break;
   default:
      break;
 }
}

private void retryFileUpload()
{
  Thread.Sleep(10000);

  if (FailureCount > 3)
  {
     stopActivityMonitoring();
     return;
  } 
  else if (FtpUloadFile())
  {
     stopActivityMonitoring();
     removeFile();
     return;
  }

  FailureCount += 1;

  retryFileUpload();
}


And naturally, I have code to perform the actual FTP upload like such:
private bool FtpUloadFile()
{
   bool ftpReturn = false;

   try
   {
     System.Net.FtpWebRequest ftpRequest = (System.Net.FtpWebRequest)System.Net.WebRequest.Create(string.Format("ftp://{0}/{1}",
       FTPAddress, 
       UploadFile));
     ftpRequest.Method = System.Net.WebRequestMethods.Ftp.UploadFile;
     ftpRequest.Credentials = new System.Net.NetworkCredential(FTPUser, FTPPassword);

     System.IO.StreamReader streamFR = new System.IO.StreamReader(string.Format(@"{0}\{1}", 
       UploadFilePath, 
       UploadFile));
     byte[] filecontents = Encoding.UTF8.GetBytes(streamFR.ReadToEnd());
     streamFR.Close();
     ftpRequest.ContentLength = filecontents.Length;

     System.IO.Stream requestStr = ftpRequest.GetRequestStream();
     requestStr.Write(filecontents, 0, filecontents.Length);
     requestStr.Close();

     System.Net.FtpWebResponse resp = (System.Net.FtpWebResponse)ftpRequest.GetResponse();

     switch (resp.StatusCode)
     {
       case System.Net.FtpStatusCode.ClosingData:
          ftpReturn = true;
          break;
       default:
          ftpReturn = false;
          break;
     }

     resp.Close();
   }
   catch (Exception) { }

   return ftpReturn;
}

So, the above code gets initiated from my orchestration before the send shape. You have to declare a variable to hold the new class. Then, within an expression shape, you set the Folder monitoring in motion like so:
//Initiate Orchestration variable
monitorObj = new my.Helpers.MonitoringClass();
monitorObj.initiateFolderMonitor();

//Initiate the dynamic Send port
DynamicSendFilePort(Microsoft.XLANGs.BaseTypes.Address) = @"file://" + ConfigurationManager.AppSettings["FolderPath"] + @"\" + ConfigurationManager.AppSettings["FileName"];


Finally, in the orchestration, you can either call the disable monitoring method, or include code in your class to timeout the monitoring piece to release those system resources.