65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 Windows 服务和 BizTalk Server 传输超大文件

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.88/5 (5投票s)

2011年1月3日

CPOL

11分钟阅读

viewsIcon

36120

如何使用 Windows 服务和 BizTalk Server 传输超大文件

引言

在一个 BizTalk 项目中,我需要接收超大文件(最大 2GB),对其进行压缩,然后通过 FTP 传输到已知目的地。

我的第一个想法是编写一个自定义管道组件来接收文件并将其写入磁盘进行临时存储。管道会创建一个小的 XML 文件,其中包含大文件的路径。然后会从业务流程(或发送管道)中调用一个组件来执行传输过程,这样大文件就不会通过 BizTalk 消息框发送。我的经验是,对于高达 1GB 的文件,这种方法效果很好,但当文件更大时,如果同时接收多个文件,接收管道就会变得太慢。

这个项目的解决方案是创建一个 Windows 服务,其中包含一个 FileSystemWatcher 对象来监视新的大文件,并在创建新文件时创建一个包含大文件路径的小型 XML 文件。Windows 服务在启动时从 BizTalk 策略中读取有关发送方和接收方的信息。小型 XML 文件由 BizTalk 接收并传递给业务流程。业务流程读取大文件的路径,并等待大文件准备好传输。然后,它调用一个外部组件来压缩大文件并将其发送到 FTP 目的地。

在这个项目中,使用 BizTalk 的主要原因是集中所有消息以及日志记录和监控功能。此外,BizTalk 策略使得添加或删除发送方和接收方变得容易。(在这个项目中,有许多发送方,只有一个接收方。)还可以设置消息与业务流程中任何类型的收据之间的关联。如果接收系统没有返回任何收据,则可以根据执行传输过程的组件的返回值在业务流程中创建一种传输收据。

创建 BizTalk 策略

首先是创建和部署 BizTalk 策略,以便 Windows 服务可以读取必要的信息。本文不会详细介绍如何创建 BizTalk 策略,但我将简要解释我是如何做的。

首先,我创建了一个程序集,其中只包含一个简单的类,其中包含我想要的有关发送方和接收方的所有信息。该类如下所示:

using System;
using System.Collections.Generic;
using System.Text;

namespace LargeFileTransfer.LFTRulesHelper
{
    public class LFTRulesHelper
    {
        private string _partnerName;
        private string _ftpPath;
        private string _catalog;
        private string _userName;
        private string _userPassword;
        private string _largeFilePath;
        private string _allPartners;
        private string _xmlInfoOutputPath;

        public string PartnerName
        {
            get { return _ partnerName; }
            set { _ partnerName = value; }
        }
        // Make one property for each variable
}

PartnerName 是大文件发送方的名称。

FtpPathCatalogUserNameUserPassword 是用于连接到 FTP 服务器的传输过程组件使用的属性。用户详细信息可以加密以提高安全性。

LargeFilePath 是找到大文件的路径。它是一种接收位置。这个项目要求此路径中的最后一个目录(找到大文件的目录)与发送方的名称相同。例如,如果发送方的名称是“Norway”,那么大文件应该在类似“..\Norway\LargeFile.xml”的路径中拾取。

AllPartners 是所有发送方的逗号分隔列表。

XmlInfoOutputPath 是创建小型 XML 文件的路径。

此类用于构建 BizTalk 策略。它必须编译,并且在创建 BizTalk 策略之前,DLL 必须添加到 BizTalk 服务器的 GAC 中。

下一步是打开 BizTalk 规则作曲器并基于此类创建一个新的规则集。为每个伙伴创建一个规则,并将规则名称设置为与发送方名称相同。为所有需要的属性赋值。(有大量文章详细描述了如何执行此操作,因此我在此不再赘述。)还要创建一个名为“All”的规则,将 Partnername 设置为“All”,并在 AllPartners 属性中列出所有伙伴。保存并部署 BizTalk 策略,然后就可以使用了。

创建和设置 Windows 服务

我将解释 Windows 服务中的主要功能以及如何安装它。关于 Windows 服务的基础知识有很多文章,所以在此不再赘述。

首先,我在 Visual Studio 中创建了一个新的 Windows 服务项目,添加了引用并在主类中包含了以下命名空间。

using Microsoft.RuleEngine;

此命名空间提供访问 BizTalk 策略的功能。

using LargeFileTransfer.LFTRulesHelper;

在此命名空间中,我找到了上面创建的类。当从 BizTalk 策略获取信息时,我使用它。

using System.IO;

在此命名空间中,我找到了 FileSystemWatcher 类。

在这个 Windows 服务中,我实现了三个方法:OnStart(在 Windows 服务启动时运行)、OnChanged(由 FileSystemWatcher 使用)和 GetPartnername(简单地从 string 返回伙伴名称)。我将详细描述这些方法的工作原理。

这个 Windows 服务在启动时从 BizTalk 策略中读取所有发送方名称。然后它循环遍历所有发送方,并为每个发送方创建一个 FileSystemWatcher 来监视所有位置的新文件。

protected override void OnStart(string[] args)
{
    	Policy policy = new Policy("LargeFileTransfer.LFTRules");
         LargeFileTransfer.LFTRulesHelper.LFTRulesHelper myHelper = _
new LFTRulesHelper.LFTRulesHelper();

BizTalk 服务器上的 BizTalk 策略名称是 LargeFileTransfer.LFTRules。上面,我创建了一个这种类型的策略,以及一个名为 myHelperLFTRulesHelper 类的对象。

// Read all partner names from the BizTalk Policy
myHelper.PartnerName = "ALL";
object objMyHelper = new object();
objMyHelper = myHelper;
policy.Execute(objMyHelper);
_xmlInfoPath = myHelper.XmlInfoOutputPath;
string partners = myHelper.AllPartners;
string[] partnersArray = partners.Split(',');

在上面的代码中,我首先将 PartnerName 设置为“ALL”。PartnerName 属性与发送方相同,并且必须与上面提到的 BizTalk 规则名称相同。然后我创建 objMyHelper 并将其用作执行策略的参数。myHelper 现在与名为“ALL”的规则具有相同的信息。由于我使用此类创建 BizTalk 策略,因此此方法有效。在我的示例中,_xmlInfoPath(我创建小型 XML 文件的路径)对于所有发送方都是相同的。这是 Windows 服务类中的一个 private 变量。

一个名为 partnersArray 的数组包含所有发送方名称。在下面的代码中,我遍历所有这些发送方。对于每个发送方,我执行策略并获取有关发送方的所有信息。我还为每个发送方创建一个 FileSystemWatcher 对象,将其路径设置为该发送方的超大文件将创建的路径,并添加一个事件,以便在该路径中每次创建新的 XML 文件时触发该事件。

// Get a path for each partner and start waiting for new files in that path.
for (int i = 0; i < partnersArray.Length; i++)
{
    myHelper.PartnerName = partnersArray[i];
    policy.Execute(objMyHelper);
    System.IO.FileSystemWatcher watcher = new System.IO.FileSystemWatcher();
    watcher.Path = MyHelper.LargeFilePath;
    watcher.Filter = "*.xml";
    watcher.Created += new FileSystemEventHandler(OnChanged);
    watcher.EnableRaisingEvents = true;
}
}// End of OnStart

每次在路径 (LargeFilePath) 中创建新文件时,都会触发 OnChanged 方法。代码如下所示。此方法使用 FileStream 对象在从策略获得的路径 (_xmlInfoPath) 中创建一个新的 XML 文件。类型为 FileSystemEventArgs 的对象 e 包含来自 FileSystemWatcher 对象的信息。从这里,我获取 e.FullPath,它是 FileSystemWatcher 正在监视新文件的路径。如上所述,此项目要求存储大文件的目录名称必须与发送方的名称相同。我从 GetPartnerName 方法获取此名称。

接下来,我构建 xmlString。这将是 XML 文件。XML 文件需要与 BizTalk 服务器上的架构匹配才能在业务流程中工作。xmlString 包含有关架构命名空间和大文件路径的信息。它还包含一些附加信息。

xmlString 完成后,我将其转换为字节数组,并使用 FileStream 对象将其写入 XML 文件。XML 文件现在可以被 BizTalk 拾取了。

private void OnChanged(object sender, FileSystemEventArgs e)
{
     	FileStream fs = new FileStream(_xmlInfoPath + @"XmlInfo" + 
		Guid.NewGuid() + ".xml", FileMode.CreateNew);
     	string partnerName = GetPartnerName(e.FullPath);
      	string receiverName = "LargeFileReceiver";
      	string xmlString = "<xmlinfo xmlns="\"LargeFileTransfer.Schemas\"">" 
	+ "<msgid />" + Guid.NewGuid() + "</msgid />" + "<sender />" 
	+ partnerName + "</sender />" + "<receiver />" + receiverName 
	+ "</receiver />" + "<filepath />" + e.FullPath + "</filepath />" 
	+ "<msgdatetime />" + DateTime.Now.ToString() + "</msgdatetime />" 
	+ "</xmlinfo />";

	byte[] byteArray = ASCIIEncoding.UTF8.GetBytes(xmlString);
      	fs.Write(byteArray, 0, byteArray.Length);
      	fs.Close();
}

GetPartnerName 方法如下所示:

// Get partner name from a path. The partner name is the same 
// as the name of the inner catalog.
private string GetPartnerName(string fullPath)
{
	int i = fullPath.LastIndexOf("\\");
      	if (i == -1)
      		i = fullPath.LastIndexOf("//");

      	string tmpString = fullPath.Remove(i);

      	int j = tmpString.LastIndexOf("\\");
      	if (j == -1)
      		j = tmpString.LastIndexOf("//");
	string partnerName = tmpString.Substring(j + 1);
      	return partnerName;
}

我构建了这个项目并得到了一个 .exe 文件。我使用 InstallUtil.exe 将其安装为 Windows 服务,然后打开服务管理器并启动它。它运行良好。

要安装它,请运行此命令:

C:\Windows\Microsoft.NET\Framework\v2.0.50727\InstallUtil.exe 
Ex: InstallUtil.exe “C:\MyService.exe”

请记住,每次 BizTalk 策略发生更改时,都要重新启动 Windows 服务。

BizTalk 业务流程

需要将与 Windows 服务创建的 XML 文件匹配的架构部署到 BizTalk,以便业务流程可以接收 XML 消息。我的 XML 文件包含 MsgIdSenderReceiverFilePath 等字段。我提升了所有字段,以便业务流程可以从文件中获取所有值。这意味着还部署了一个属性架构。业务流程做的第一件事是从小型 XML 文件中读取大文件的路径。在业务流程中,我将此消息命名为 XmlInfoIn。由于字段 FilePath 已被提升,我可以在表达式形状中轻松地将值写入 string 变量,如下所示:

strFilePath = XmlInfoIn(LargeFileTransfer.Schemas.PropertySchema.FilePath)

业务流程接下来要做的是检查大文件是否已准备好传输。Windows 服务一在大文件中创建小 XML 文件 filesystem,但当业务流程接收到小文件时,大文件可能尚未完成。例如,它还没有从另一个目的地复制完成。在新的表达式形状中,我执行以下检查:

blnFileReady = LargeFileTransfer.LFTHelper.IsFileReady(strFilePath)

blnFileReady 是一个布尔值,如果文件已准备就绪,则为 true。我在一个程序集中构建了一些额外的代码,其中有一个名为 LFTHelper 的类。该类包含一些业务流程用于处理大文件传输的方法。该程序集安装在 GAC 中。其中一个方法是 IsFileReady,它只是检查文件是否已准备好传输。

public static bool IsFileReady(string filePath)
	{
            string readyString = "";
            try
            {
                // The file is ready if it can be renamed.
                File.Move(filePath, filePath + "Ready");
                readyString = "Ready";
                return true;
            }
            catch (Exception ex)
            {
                return false;
            }
            finally
            {
                if (readyString == "Ready")
                {
                    // Rename back to the original name if the file was renamed.
                    File.Move(filePath + "Ready", filePath);
                }
            }
        }

此方法只是尝试重命名大文件。如果文件可以重命名,则表示已准备就绪,该方法将其重命名回原始名称并返回 true。业务流程的下一步是一个循环形状,它会循环直到文件准备就绪,这意味着直到 blnFileReadytrue。此循环包含两个形状。第一个等待一分钟,第二个再次执行相同的检查以查看文件是否准备就绪。这样做使业务流程等待直到大文件准备就绪。当文件准备就绪时,业务流程会压缩文件,然后通过 FTP 传输。此功能也包含在 LFTHelper 类中。在新的表达式形状中,首先调用一个方法来压缩文件。

strZipFileName = LargeFileTransfer.LFTHelper.LFTHelper.ZipFile(strFilePath)

strZipFileName 现在包含压缩文件的路径。此文件现在是要传输的文件。传输通过调用另一个方法完成:

strStatus = LargeFileTransfer.LFTHelper.LFTHelper.TransferFile(strZipFileName)

strStatus 是一个 string,其中包含尝试通过 FTP 传输文件后的状态。zipFileMethod 使用 Ionic.Zip 库中的对象,因此必须引用此库。此库是免费的,可以从 http://dotnetzip.codeplex.com 下载。

public static string ZipFile(string filePath)
{
   using (FileStream fs = new FileStream(filePath, FileMode.Open))
   {
      string zippedFileName = filePath + ".zip";
      using (ZipOutputStream zipOutputStream = new ZipOutputStream(zippedFileName))
      {
         byte[] buffer = new Byte[1024];

         string fileName = GetFileName(filePath);
         zipOutputStream.PutNextEntry(fileName);

         int bytesRead = 1024;
         while (bytesRead != 0)
         {
            bytesRead = fs.Read(buffer, 0, buffer.Length);
            zipOutputStream.Write(buffer, 0, bytesRead);
         }
      }
      return zippedFileName;
   }
}

// Get the file name from a full path.
private static string GetFileName(string fullPath)
{
   int i = fullPath.LastIndexOf("\\");
   if (i == -1)
      i = fullPath.LastIndexOf("//");

   string fileName = fullPath.Substring(i + 1);
   return fileName;
}

压缩文件的文件名是超大文件名称加上“.zip”。它存储在与超大文件相同的位置。这是一个通过 FTP 传输压缩文件的简单方法:

public static string TransferFile(string filePath)
{
	string partnerName = GetPartnerName(filePath);
         LFTRulesHelper.LFTRulesHelper lftRulesHelper = new _
		LargeFileTransfewr.LFTRulesHelper.LFTRulesHelper();
         Policy policy = new policy("LargeFileTransfer.LFTRules");

         // Get all the details from the policy to transfer the file.
         lftRulesHelper.PartnerName = partnerName;
         object objLftHelper = new object();
         objLftHelper = lftRulesHelper;
         policy.Execute(objLftHelper);

         try
         {
         		// The FTP request
                	FtpWebRequest request = (FtpWebRequest)FtpWebRequest.Create _
		(lftRulesHelper.FtpPath + "/" + Path.GetFileName(filePath));
                	request.Method = WebRequestMethods.Ftp.UploadFile;
                	request.Credentials = new NetworkCredential _
		(lftRulesHelper.UserName, lftRulesHelper.UserPassword);
                	request.UsePassive = true;
                	request.UseBinary = true;
                	request.KeepAlive = false;

               	// Loading the file
                	FileStream stream = File.OpenRead(filePath);
                	byte[] buffer = new byte[stream.Length];
                	stream.Read(buffer, 0, buffer.Length);
                	stream.Close();

                	// Uploading the file
                	Stream reqStream = request.GetRequestStream();
                	reqStream.Write(buffer, 0, buffer.Length);
                	reqStream.Close();
                	return "OK";
      	}
         catch(Exception ex)
         {
          	return ex.Message;
         }
}

首先,该方法以与 Windows 服务相同的方式在策略中查找,以获取 FTP 连接的所有存储详细信息。在 try 块中,使用这些详细信息发出 FTP 请求。然后,将压缩文件加载到缓冲区中,并通过将数据从缓冲区写入 FTP 请求 stream 来传输文件。传输完成后,该方法返回“OK”。在业务流程中,strStatus string 现在包含“OK”或从 TransferFile 方法返回的错误消息。此业务流程做的最后一件事是通过发送形状将小型 XML 文件发送到磁盘进行日志记录。

完成项目

为了完成此项目,当使用它们的这些方法完成后,必须从磁盘中删除压缩文件和大文件。这可以通过例如向 LFTHelper 类添加 deleteFile 方法来轻松完成。此方法可以从业务流程中的表达式调用。

还可以轻松添加加密和签名方法,以使解决方案更安全。可以将证书信息添加到 BizTalk 策略并由方法读取。这些方法可以从调用 zip 方法的相同形状调用。

还可以将传输方法的状态写入从业务流程发送的 XML 文件中的字段。这可以通过在业务流程中以与 Windows 服务相同的方式构建新的 XML 消息,将状态添加到某个字段,并将此新的 XML 文件发送出业务流程而不是原始文件来完成。

© . All rights reserved.