Domanda

Panoramica

Ho un componente aggiuntivo di Microsoft Word, scritto in VBA (Visual Basic, Applications Edition), che comprime un documento e tutto il relativo contenuto (supporto incorporato) in un archivio zip. Dopo aver creato l'archivio zip, trasforma il file in un array di byte e lo invia a un servizio Web ASMX. Funziona principalmente.

Pubblicazioni

Il problema principale che ho è il trasferimento di file di grandi dimensioni sul sito web. Posso caricare correttamente un file di circa 40 MB, ma non di 140 MB (timeout / errore generale).

Un problema secondario è che la creazione dell'array di byte nel componente aggiuntivo VBScript Word può fallire esaurendo la memoria sul computer client se l'archivio zip è troppo grande.

Soluzioni potenziali

Sto considerando le seguenti opzioni e cerco feedback su entrambe le opzioni o su altri suggerimenti.

Opzione 1

Apertura di un flusso di file sul client (MS Word VBA) e lettura di un "blocco" contemporaneamente e trasmettendo al servizio web ASMX che assembla i "pezzi" in un file sul server.

Questo ha il vantaggio di non aggiungere dipendenze o componenti aggiuntivi all'applicazione, vorrei solo modificare le funzionalità esistenti. (Meno dipendenze è meglio poiché questa soluzione dovrebbe funzionare in una varietà di ambienti server ed essere relativamente facile da configurare.)

Domanda:

  • Esistono esempi di questa o altre tecniche consigliate (sul client in VBA o nel servizio Web in C # / VB.NET)?

Opzione due

Comprendo che WCF potrebbe fornire una soluzione al problema del trasferimento di file di grandi dimensioni tramite "chunking" o streaming di dati. Tuttavia, non ho molta familiarità con WCF e non sono sicuro di cosa sia esattamente capace o se posso comunicare con un servizio WCF di VBA. Questo ha il rovescio della medaglia di aggiungere un'altra dipendenza (.NET 3.0). Ma se usare WCF è sicuramente una soluzione migliore, potrei non preoccuparmi di prendere quella dipendenza.

Domande:

  • WCF supporta in modo affidabile trasferimenti di file di grandi dimensioni di questo tipo? In tal caso, cosa comporta? Qualche risorsa o esempio?
  • Sei in grado di chiamare un servizio WCF da VBA? Qualche esempio?
È stato utile?

Soluzione

Ho finito per implementare l'opzione 1 a cui fa riferimento la domanda originale.

I "pezzo" " il file in VBA e trasferisci ogni "pezzo" " al servizio web. Ho basato la parte VBA della soluzione sul codice trovato qui: Copia file di grandi dimensioni con Chunk con Progress Notifica . Invece di copiarlo nel file system, tuttavia, lo invio al server.

Il codice: VBA Land

Ecco il codice (fugly) VBA che crea i blocchi di file:

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

Ecco il metodo VBA di riferimento che carica i blocchi sul server:

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

Il codice: servizio Web ASMX C #

Ora, sul lato server, il metodo del servizio Web accetta alcuni parametri importanti.

  1. string fileName: il nome di file (ogni blocco ha lo stesso file nome)
  2. byte [] zipBytes: il contenuto di ogni pezzo
  3. int index: l'indice (utilizzato insieme a fileName per fornire un unico ordine parziale file nel file system)
  4. bool isLastChunk: questo è il " ho finito - vai avanti e unisci tutti i "pezzi" " e ripulisci dopo te stesso " bandiera.

int index e bool isLastChunk. Con questo contesto fornito dal mondo VBA, conosco abbastanza per salvare ciascuno di questi blocchi di file e poi combinarli quando il flag isLastChunk è vero.

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

Ecco il codice C # che scrive ogni blocco in arrivo nel file system:

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

Ecco il codice C # per unire tutto il file zip " chunks " ;:

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

Altri suggerimenti

Ho trasmesso file di grandi dimensioni come questo usando la codifica MTOM.

Ulteriori informazioni su MTOM qui: http://msdn.microsoft.com /en-us/library/aa395209.aspx

Puoi scaricare un campione MTOM qui: http://msdn.microsoft .com / it-it / library / ms751514.aspx

Dai un'occhiata al libro di Bustamante su WCF se vuoi di più su MTOM.

Per quanto riguarda la chiamata VBA, non sono un esperto in quell'area quindi non ho alcuna informazione in merito.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top