2

概要

ドキュメントとそれに関連するすべてのコンテンツ (埋め込みメディア) を zip アーカイブに圧縮する、VBA (Visual Basic for Applications) で記述された Microsoft Word アドインがあります。zip アーカイブを作成した後、ファイルをバイト配列に変換し、ASMX Web サービスにポストします。これはほとんど機能します。

問題

私が抱えている主な問題は、大きなファイルを Web サイトに転送することです。約 40MB のファイルは正常にアップロードできますが、140MB のファイルはアップロードできません (タイムアウト/一般的な失敗)。

2 つ目の問題は、zip アーカイブが大きすぎる場合、VBScript Word アドインでバイト配列を構築すると、クライアント マシンのメモリが不足して失敗する可能性があることです。

考えられる解決策

次のオプションを検討しており、いずれかのオプションまたはその他の提案に関するフィードバックを探しています。

オプション 1

クライアント (MS Word VBA) でファイル ストリームを開き、一度に 1 つの「チャンク」を読み取り、「チャンク」をサーバー上のファイルにアセンブルする ASMX Web サービスに送信します。

これには、アプリケーションに追加の依存関係やコンポーネントを追加しないという利点があります。既存の機能を変更するだけです。(このソリューションはさまざまなサーバー環境で機能し、セットアップが比較的簡単であるため、依存関係が少ないほど優れています。)

質問:

  • これを行う例または推奨される手法はありますか (VBA のクライアントまたは C#/VB.NET の Web サービスのいずれか)?

オプション 2

WCF は、データを「チャンク」またはストリーミングすることによって大きなファイルを転送する問題を解決できる可能性があることを理解しています。しかし、私は WCF にあまり詳しくなく、正確に何ができるのか、VBA から WCF サービスと通信できるのかどうかもわかりません。これには、別の依存関係 (.NET 3.0) を追加するという欠点があります。しかし、WCF を使用することが間違いなく優れたソリューションである場合、その依存関係を気にする必要はありません。

質問:

  • WCF は、この性質の大きなファイル転送を確実にサポートしていますか? もしそうなら、これには何が関係していますか?リソースや例はありますか?
  • VBA から WCF サービスを呼び出すことはできますか? 例はありますか?
4

2 に答える 2

5

元の質問で参照されているオプション1を実装することになりました。

VBA でファイルを「チャンク」し、各「チャンク」を Web サービスに転送します。ここにあるコードに基づいて、ソリューションの VBA 部分を作成しました: Copy Large File by Chunk with Progress Notification。ただし、ファイル システムにコピーする代わりに、サーバーに送信します。

コード: VBA ランド

ファイルチャンクを作成する(醜い)VBAコードは次のとおりです。

Function CopyFileByChunk(fileName As String, sSource As String) As Boolean

   Dim FileSize As Long, OddSize As Long, SoFar As Long
   Dim Buffer() As Byte, f1 As Integer, ChunkSize As Long

   On Error GoTo CopyFileByChunk_Error

   f1 = FreeFile: Open sSource For Binary Access Read As #f1
   FileSize = LOF(f1)
   If FileSize = 0 Then GoTo Exit_CopyFileByChunk ' -- done!

   ChunkSize = 5505024 '5.25MB
   OddSize = FileSize Mod ChunkSize

   Dim index As Integer
   index = 0

   If OddSize Then
      ReDim Buffer(1 To OddSize)
      Get #f1, , Buffer

      index = index + 1
      SoFar = OddSize

      If UploadFileViaWebService(Buffer, fileName, index, SoFar = FileSize) Then
            g_frmProgress.lblProgress = "Percent uploaded: " & Format(SoFar / FileSize, "0.0%")
            Debug.Print SoFar, Format(SoFar / FileSize, "0.0%")
            DoEvents
         Else
            GoTo CopyFileByChunk_Error
         End If
   End If

   If ChunkSize Then
      ReDim Buffer(1 To ChunkSize)
      Do While SoFar < FileSize
         Get #f1, , Buffer

         index = index + 1
         SoFar = SoFar + ChunkSize

         If UploadFileViaWebService(Buffer, fileName, index, SoFar = FileSize) Then
            g_frmProgress.lblProgress = "Percent uploaded: " & Format(SoFar / FileSize, "0.0%")
            Debug.Print SoFar, Format(SoFar / FileSize, "0.0%")
            DoEvents
         Else
            GoTo CopyFileByChunk_Error
         End If
      Loop
   End If

   CopyFileByChunk = True

Exit_CopyFileByChunk:
   Close #f1
   Exit Function

CopyFileByChunk_Error:
   CopyFileByChunk = False
   Resume Exit_CopyFileByChunk
End Function

チャンクをサーバーにアップロードする、参照されている VBA メソッドは次のとおりです。

Public Function UploadFileViaWebService(dataChunk() As Byte, fileName As String, index As Integer, lastChunk As Boolean) As Boolean

    On Error GoTo ErrHand
    Dim blnResult As Boolean
    blnResult = False

        'mdlConvert.SetProgressInfo "Connecting to the web server:" & vbNewLine & _
            DQUOT & server_title() & DQUOT
        If InternetAttemptConnect(0) = 0 Then
            On Error Resume Next

            Dim strSoapAction As String
            Dim strXml As String
            strXml = "<?xml version=""1.0"" encoding=""utf-8""?>" & _
            "<soap:Envelope xmlns:xsi=""http://www.w3.org/2001/XMLSchema-instance"" xmlns:xsd=""http://www.w3.org/2001/XMLSchema"" xmlns:soap=""http://schemas.xmlsoap.org/soap/envelope/"">" & _
            "<soap:Body>" & _
            "<UploadZipFile xmlns=""http://something.com/"">" & _
            "<zipBytes></zipBytes>" & _
            "<index>" & index & "</index>" & _
            "<isLastChunk>" & IIf(lastChunk, 1, 0) & "</isLastChunk>" & _
            "</UploadZipFile>" & _
            "</soap:Body>" & _
            "</soap:Envelope>"

            Dim objXmlhttp As Object
            Dim objDom As Object
            Set objXmlhttp = New MSXML2.xmlhttp

            ' Load XML
            Set objDom = CreateObject("MSXML2.DOMDocument")
            objDom.LoadXML strXml

            'insert data chunk into XML doc
            objDom.SelectSingleNode("//zipBytes").dataType = "bin.base64"
            objDom.SelectSingleNode("//zipBytes").nodeTypedValue = dataChunk

            ' Open the webservice
            objXmlhttp.Open "POST", webServiceUrl, False

            ' Create headings
            strSoapAction = "http://something.com/UploadZipFile"
            objXmlhttp.setRequestHeader "Content-Type", "text/xml; charset=utf-8"
            objXmlhttp.setRequestHeader "SOAPAction", strSoapAction

            ' Send XML command
            objXmlhttp.send objDom.XML

            ' Get all response text from webservice
            Dim strRet
            strRet = objXmlhttp.responseText

            ' Close object
            Set objXmlhttp = Nothing
            Set objDom = Nothing

            'get the error if any
            Set objDom = CreateObject("MSXML2.DOMDocument")
            objDom.LoadXML strRet
            Dim isSoapResponse As Boolean
            isSoapResponse = Not (objDom.SelectSingleNode("//soap:Envelope") Is Nothing)
            Dim error As String
            If Not isSoapResponse Then
                error = "Woops"
            Else
                error = objDom.SelectSingleNode("//soap:Envelope/soap:Body/soap:Fault/faultstring").text
            End If
            If error <> "" Then
                ShowServerError error, True
                blnResult = False
            Else
                Err.Clear 'clear the error caused in the XPath query above
                blnResult = True
            End If
            'close dom object
            Set objDom = Nothing


         Else
             GetErrorInfo "UploadFileViaWebService:InternetCheckConnection"
        End If

ErrHand:
    If Err.Number <> 0 Then
        ShowError Err, "UploadFileViaWebService"
        blnResult = False
    End If

    UploadFileViaWebService = blnResult
End Function

コード: C# ASMX Web サービス

サーバー側では、Web サービス メソッドがいくつかの重要なパラメーターを受け取ります。

  1. string fileName: ファイルの名前 (各チャンクは同じファイル名です)
  2. byte[] zipBytes: 各チャンクの内容
  3. int index: インデックス (fileName と組み合わせて使用​​され、ファイル システム上で一意の順序付けられた部分ファイルを提供します)
  4. bool isLastChunk: これは「完了です。先に進んで、すべての「チャンク」をマージし、自分でクリーンアップしてください」フラグです。

int index と bool isLastChunk. VBA の世界から提供されたこのコンテキストを使用して、これらのファイル チャンクをそれぞれ保存し、isLastChunk フラグが true の場合にそれらを結合する方法を十分に理解しています。

   /// <summary>
    /// Accepts a chunk of a zip file.  Once all chunks have been received,  combines the chunks into a zip file that is processed.
    /// </summary>
    /// <param name="fileName">Name of the file.</param>
    /// <param name="zipBytes">The collection of bytes in this chunk.</param>
    /// <param name="index">The index of this chunk.</param>
    /// <param name="isLastChunk">if set to <c>true</c> this is the last chunk.</param>
    /// <returns>Whether the file was successfully read and parsed</returns>
    /// <exception cref="ParserException">An error occurred while trying to upload your file. The details have been written to the system log.</exception>
    [WebMethod]
    public bool UploadZipFile(string fileName, byte[] zipBytes, int index, bool isLastChunk)
    {
        try
        {
            const string ModuleRootUrl = "/Somewhere/";
            string folderName = HostingEnvironment.MapPath("~" + ModuleRootUrl);
            string fullDirectoryName = Path.Combine(folderName, Path.GetFileNameWithoutExtension(fileName));

            try
            {
                if (!Directory.Exists(fullDirectoryName))
                {
                    Directory.CreateDirectory(fullDirectoryName);
                }

                string pathAndFileName = Path.Combine(fullDirectoryName, AddIndexToFileName(fileName, index));
                using (var stream = new MemoryStream(zipBytes))
                {
                    WriteStreamToFile(stream, pathAndFileName);
                }

                if (isLastChunk)
                {
                    try
                    {
                        MergeFiles(fullDirectoryName, fileName, index);

                        // file transfer is done.
                        // extract the zip file
                        // and do whatever you need to do with its contents
                        // we'll assume that it works - but your "parsing" should return true or false
                        return true;
                    }
                    finally
                    {
                        DeleteDirectoryAndAllContents(fullDirectoryName);
                    }
                }
            }
            catch
            {
                DeleteDirectoryAndAllContents(fullDirectoryName);
                throw;
            }
        }
        return false;
    }

各受信チャンクをファイル システムに書き込む C# コードを次に示します。

/// <summary>
/// Writes the contents of the given <paramref name="stream"/> into a file at <paramref name="newFilePath"/>.
/// </summary>
/// <param name="stream">The stream to write to the given file</param>
/// <param name="newFilePath">The full path to the new file which should contain the contents of the <paramref name="stream"/></param>
public static void WriteStreamToFile(Stream stream, string newFilePath)
{
    using (FileStream fs = File.OpenWrite(newFilePath))
    {
        const int BlockSize = 1024;
        var buffer = new byte[BlockSize];
        int numBytes;
        while ((numBytes = stream.Read(buffer, 0, BlockSize)) > 0)
        {
            fs.Write(buffer, 0, numBytes);
        }
    }
}

すべての zip ファイルの「チャンク」をマージする C# コードを次に示します。

/// <summary>
/// Merges each file chunk into one complete zip archive.
/// </summary>
/// <param name="directoryPath">The full path to the directory.</param>
/// <param name="fileName">Name of the file.</param>
/// <param name="finalChunkIndex">The index of the last file chunk.</param>
private static void MergeFiles(string directoryPath, string fileName, int finalChunkIndex)
{
    var fullNewFilePath = Path.Combine(directoryPath, fileName);

    using (var newFileStream = File.Create(fullNewFilePath))
    {
        for (int i = 1; i <= finalChunkIndex; i++)
        {
            using (var chunkFileStream = new FileStream(AddIndexToFileName(fullNewFilePath, i), FileMode.Open))
            {
                var buffer = new byte[chunkFileStream.Length];
                chunkFileStream.Read(buffer, 0, (int)chunkFileStream.Length);
                newFileStream.Write(buffer, 0, (int)chunkFileStream.Length);
            }
        }
    }
}
于 2009-12-05T22:56:19.257 に答える
0

MTOMエンコーディングを使用してこのような大きなファイルを送信しました。

MTOMの詳細については、こちらをご覧ください:http: //msdn.microsoft.com/en-us/library/aa395209.aspx

MTOMサンプルはここからダウンロードできます:http://msdn.microsoft.com/en-us/library/ms751514.aspx

MTOMについて詳しく知りたい場合は、WCFに関するBustamanteの本を確認してください。

VBAの呼び出しについては、私はその分野の専門家ではないため、それに関する情報はありません。

于 2009-05-04T22:02:12.093 に答える