Pergunta

Visão geral

Eu tenho um Microsoft Word Add-In, escrito em VBA (Visual Basic for Applications), que comprime um documento e tudo isso de conteúdos relacionados (mídia incorporada) em um arquivo zip. Depois de criar o arquivo zip-lo, em seguida, transforma o arquivo em um array de bytes e mensagens-lo para um serviço de web ASMX. Este principalmente funciona.

Temas

O principal problema que tenho é a transferência de arquivos grandes para o web site. Eu posso enviar com sucesso um arquivo que é de cerca de 40MB, mas não aquele que é 140MB (timeout / falha geral).

A questão secundária é que a construção da matriz de bytes no VBScript suplemento do Word pode falhar por falta de memória na máquina do cliente se o arquivo zip é muito grande.

Soluções Potenciais

Eu estou considerando as seguintes opções e estou procurando feedback sobre qualquer opção ou quaisquer outras sugestões.

Uma opção

A abertura de um fluxo de arquivo no cliente (MS Word VBA) e leitura de um "pedaço" de cada vez e transmitindo ao serviço web ASMX que reúne os "pedaços" em um arquivo no servidor.

Isto tem a vantagem de não adicionar quaisquer dependências ou componentes adicionais para o aplicativo, eu só seria modificar a funcionalidade existente. (Dependências Menos é melhor, esta solução deve funcionar em uma variedade de ambientes de servidor e ser relativamente fácil de configurar.)

Pergunta:

  • Existem exemplos de fazer isso ou quaisquer técnicas recomendadas (quer no cliente em VBA ou no serviço web em C # / VB.NET)?

Opção Dois

Eu entendo WCF pode fornecer uma solução para a questão da transferência de grandes arquivos por "chunking" ou streaming de dados. No entanto, eu não estou muito familiarizado com o WCF, e não estou certo o que exatamente ele é capaz de ou se eu pode se comunicar com um serviço WCF de VBA. Isto tem a desvantagem de adicionar uma outra dependência (.NET 3.0). Mas se estiver usando WCF é definitivamente uma solução melhor que pode não importa de tomar essa dependência.

Perguntas:

  • O WCF suportar de forma confiável grandes transferências dessa natureza de arquivo? Se assim for, o que isso implica? Quaisquer recursos ou exemplos?
  • Você é capaz de chamar um serviço WCF do VBA? Alguns exemplos?
Foi útil?

Solução

acabei implementando uma opção de referência na pergunta original.

I "pedaço" o arquivo no VBA e transferir cada "pedaço" para o serviço web. Baseei a parte VBA da solução no código encontrado aqui: Copiar arquivos grandes pela Chunk com Progress notificação. Em vez de copiar o sistema de arquivos, no entanto, eu enviá-lo para o servidor.

O Código: VBA Terra

Aqui está o (fugly) código VBA que cria os pedaços de arquivos:

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

Aqui está o método VBA referenciada que carrega os pedaços para o servidor:

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

O Código: C # ASMX Web Service

Agora, no lado do servidor, o método de serviço web aceita alguns parâmetros importantes.

  1. fileName string: O nome do arquivo (cada pedaço tem o mesmo arquivo nome)
  2. byte [] zipBytes: O conteúdo de cada pedaço
  3. int index: O índice (Usado em conjunto com nomeArquivo para fornecer único parcial ordenada arquivos no sistema de arquivos)
  4. bool isLastChunk: Este é o "Eu sou feito - vá em frente e juntar todas as "pedaços" e limpar depois de ti" bandeira.

index int e bool isLastChunk. Com este contexto fornecido a partir do mundo VBA, eu sei o suficiente para salvar cada um desses pedaços de arquivo e, em seguida, combiná-los quando a bandeira isLastChunk é verdade.

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

Aqui está o código C # que escreve cada bloco de entrada do sistema de arquivos:

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

Aqui está o código C # para mesclar todas do arquivo zip "pedaços":

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

Outras dicas

Eu tenho transmitido arquivos grandes como esta usando a codificação de MTOM.

Mais informações sobre MTOM aqui: http://msdn.microsoft.com /en-us/library/aa395209.aspx

Você pode baixar uma amostra MTOM aqui: http://msdn.microsoft .com / en-us / library / ms751514.aspx

Confira o livro de Bustamante em WCF se você quiser saber mais sobre MTOM.

Como para a chamada VBA, eu não sou um especialista nessa área, portanto, eu não tenho qualquer informação no que diz respeito a ele.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top