Pregunta

Descripción general

Tengo un complemento de Microsoft Word, escrito en VBA (Visual Basic para aplicaciones), que comprime un documento y todos sus contenidos relacionados (medios incrustados) en un archivo zip. Después de crear el archivo zip, convierte el archivo en una matriz de bytes y lo publica en un servicio web ASMX. Esto funciona principalmente.

Problemas

El problema principal que tengo es transferir archivos grandes al sitio web. Puedo cargar con éxito un archivo de alrededor de 40 MB, pero no uno de 140 MB (tiempo de espera / falla general).

Un problema secundario es que la creación de la matriz de bytes en el complemento VBScript Word puede fallar al quedarse sin memoria en la máquina cliente si el archivo zip es demasiado grande.

Soluciones potenciales

Estoy considerando las siguientes opciones y estoy buscando comentarios sobre cualquiera de las opciones o cualquier otra sugerencia.

Opción uno

Abrir una secuencia de archivos en el cliente (MS Word VBA) y leer un " fragmento " a la vez y transmitiendo al servicio web ASMX que ensambla los "trozos" en un archivo en el servidor.

Esto tiene el beneficio de no agregar dependencias o componentes adicionales a la aplicación, solo estaría modificando la funcionalidad existente. (Menos dependencias es mejor ya que esta solución debería funcionar en una variedad de entornos de servidor y ser relativamente fácil de configurar).

Pregunta:

  • ¿Hay ejemplos de cómo hacer esto o alguna técnica recomendada (ya sea en el cliente en VBA o en el servicio web en C # / VB.NET)?

Opción dos

Entiendo que WCF puede proporcionar una solución al problema de la transferencia de archivos de gran tamaño mediante "fragmentación" o transmisión de datos. Sin embargo, no estoy muy familiarizado con WCF, y no estoy seguro de qué es exactamente capaz o si puedo comunicarme con un servicio WCF de VBA. Esto tiene el inconveniente de agregar otra dependencia (.NET 3.0). Pero si usar WCF es definitivamente una mejor solución, puede que no me importe tomar esa dependencia.

Preguntas:

  • ¿WCF admite de manera confiable transferencias de archivos grandes de esta naturaleza? Si es así, ¿qué implica esto? ¿Algún recurso o ejemplo?
  • ¿Puede llamar a un servicio WCF desde VBA? ¿Algún ejemplo?
¿Fue útil?

Solución

Terminé implementando la opción uno mencionada en la pregunta original.

I " trozo " el archivo en VBA y transfiera cada '' trozo '' al servicio web. Basé la parte VBA de la solución en el código que se encuentra aquí: Copiar archivo grande por trozo con progreso Notificación . Sin embargo, en lugar de copiarlo en el sistema de archivos, lo envío al servidor.

El Código: VBA Land

Aquí está el código (fugoso) de VBA que crea los fragmentos de archivo:

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

Aquí está el método VBA referenciado que carga los fragmentos al 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

El Código: Servicio web C # ASMX

Ahora, en el lado del servidor, el método del servicio web acepta algunos parámetros importantes.

  1. string fileName: el nombre de la archivo (cada fragmento tiene el mismo archivo nombre)
  2. byte [] zipBytes: el contenido de cada trozo
  3. int index: el índice (usado junto con fileName para proporcionar parcial ordenado único archivos en el sistema de archivos)
  4. bool isLastChunk: Este es el " he terminado - siga adelante y combine todos los "pedazos" y limpiar después de ti " bandera.

int index y bool isLastChunk. Con este contexto proporcionado por el mundo VBA, sé lo suficiente como para guardar cada uno de estos fragmentos de archivos y luego combinarlos cuando el indicador isLastChunk es verdadero.

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

Aquí está el código C # que escribe cada fragmento entrante en el sistema de archivos:

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

Aquí está el código C # para fusionar todos los archivos zip " fragmentos " ;:

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

Otros consejos

He transmitido archivos grandes como este usando la codificación MTOM.

Más información sobre MTOM aquí: http://msdn.microsoft.com /en-us/library/aa395209.aspx

Puede descargar una muestra de MTOM aquí: http://msdn.microsoft .com / es-us / library / ms751514.aspx

Consulte el libro de Bustamante sobre WCF si desea más información sobre MTOM.

En cuanto a la llamada de VBA, no soy un experto en esa área, por lo tanto, no tengo ninguna información al respecto.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top