문제

개요

VBA (Applications for Applications for Applications for Applications)로 작성된 Microsoft Word 추가 기능이 있으며 문서와 모든 관련 내용 (임베디드 미디어)을 ZIP 아카이브로 압축합니다. Zip Archive를 작성한 후 파일을 바이트 배열로 바꾸고 ASMX 웹 서비스에 게시합니다. 이것은 대부분 작동합니다.

문제

내가 가진 주요 문제는 큰 파일을 웹 사이트로 전송하는 것입니다. 약 40MB의 파일을 성공적으로 업로드 할 수는 있지만 140MB (타임 아웃/일반 실패)는 아닙니다.

보조 문제는 zip 아카이브가 너무 커지면 클라이언트 시스템에서 메모리가 부족하여 VBScript Word Add-In에 바이트 배열을 구축하면 실패 할 수 있다는 것입니다.

잠재적 솔루션

다음 옵션을 고려하고 있으며 옵션 또는 기타 제안에 대한 피드백을 찾고 있습니다.

옵션 1

클라이언트 (MS Word VBA)에서 파일 스트림을 열고 한 번에 하나의 "청크"를 읽고 서버의 파일에 "청크"를 조립하는 ASMX 웹 서비스로 전송합니다.

이는 응용 프로그램에 추가 종속성 또는 구성 요소를 추가하지 않으면 기존 기능 만 수정하는 이점이 있습니다. (이 솔루션은 다양한 서버 환경에서 작동하고 비교적 쉽게 설정하기 때문에 종속성이 적습니다.)

의문:

  • 이 작업 또는 권장 기술을 수행하는 예가 있습니까 (VBA의 클라이언트 또는 C#/vb.net의 웹 서비스)?

옵션 2

WCF가 "청킹"또는 스트리밍 데이터를 통해 대형 파일을 전송하는 문제에 대한 솔루션을 제공 할 수 있다는 것을 알고 있습니다. 그러나 저는 WCF에 익숙하지 않으며 VBA의 WCF 서비스와 통신 할 수있는 것이 정확히 무엇인지 확실하지 않습니다. 이것은 다른 종속성을 추가하는 단점이 있습니다 (.NET 3.0). 그러나 WCF를 사용하는 것이 확실히 더 나은 솔루션이라면 그 종속성을 취하지 않을 수 있습니다.

질문:

  • WCF는 이러한 특성의 큰 파일 전송을 확실하게 지원합니까? 그렇다면 이것이 무엇을 포함합니까? 리소스 나 예제가 있습니까?
  • VBA에서 WCF 서비스를 호출 할 수 있습니까? 예가 있습니까?
도움이 되었습니까?

해결책

원래 질문에서 참조 된 옵션 1을 구현하게되었습니다.

나는 VBA의 파일을 "청크"하고 각 "청크"를 웹 서비스로 전송합니다. 나는 여기에있는 코드에 대한 솔루션의 VBA 부분을 기반으로합니다. 진행 상황 알림으로 청크로 큰 파일을 복사하십시오. 그러나 파일 시스템에 복사하는 대신 서버로 보냅니다.

코드 : VBA 토지

파일 청크를 생성하는 (Fugly) 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 웹 서비스

이제 서버 측에서 웹 서비스 방법은 몇 가지 중요한 매개 변수를 수용합니다.

  1. String filename : 파일 이름 (각 청크는 동일한 파일 이름을 가지고 있음)
  2. 바이트 [] zipbytes : 각 청크의 내용
  3. int index : 인덱스 (파일 시스템에서 고유 한 부분 파일을 제공하기 위해 파일 이름과 함께 사용)
  4. BOOL ISLASTCHUNK : 이것은 "내가 끝났습니다 - 계속해서 모든"청크 "를 병합하고 자신의 후에 청소하십시오"깃발입니다.

Int Index 및 Bool IslastChunk. VBA 세계에서 제공되는 이러한 맥락에서, 나는이 파일 청크를 저장 한 다음 islastChunk 플래그가 사실 일 때 그것들을 결합 할만 큼 충분히 알고 있습니다.

   /// <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);
            }
        }
    }
}

다른 팁

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 전화에 관해서는, 저는 해당 분야의 전문가가 아니므로 정보가 없습니다.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top