Question

Présentation

J'ai un complément Microsoft Word, écrit en VBA (Visual Basic pour Applications), qui compresse un document et tout le contenu associé (média intégré) dans une archive zip. Après avoir créé l’archive zip, il transforme le fichier en tableau d’octets et le publie sur un service Web ASMX. Cela fonctionne principalement.

Problèmes

Mon problème principal est le transfert de gros fichiers sur le site Web. Je peux télécharger avec succès un fichier d'environ 40 Mo, mais pas un fichier de 140 Mo (délai d'expiration / échec général).

Un problème secondaire est que la construction du tableau d'octets dans le complément VBScript Word peut échouer en raison d'un manque de mémoire sur la machine client si l'archive zip est trop volumineuse.

Solutions potentielles

J'envisage les options suivantes et je recherche des commentaires sur ces options ou toute autre suggestion.

Première option

Ouverture d’un flux de fichiers sur le client (MS Word VBA) et lecture d’un "chunk" à la fois et en transmettant au service Web ASMX qui assemble le "chunks" dans un fichier sur le serveur.

Cela présente l'avantage de ne pas ajouter de dépendances ou de composants supplémentaires à l'application. Je ne ferais que modifier les fonctionnalités existantes. (Moins de dépendances est préférable, car cette solution devrait fonctionner dans divers environnements de serveurs et être relativement facile à configurer.)

Question:

  • Existe-t-il des exemples de ce type ou des techniques recommandées (sur le client sous VBA ou sur le service Web sous C # / VB.NET)?

Deuxième option

Je comprends que WCF peut fournir une solution au problème du transfert de fichiers volumineux par "chunking". ou la diffusion de données. Cependant, je ne connais pas très bien WCF et je ne sais pas de quoi il est capable ni si je peux communiquer avec un service WCF de VBA. Cela a l'inconvénient d'ajouter une autre dépendance (.NET 3.0). Mais si utiliser WCF est définitivement une meilleure solution, cela ne me dérange pas de prendre cette dépendance.

Questions:

  • La WCF prend-elle en charge de manière fiable les transferts de fichiers volumineux de cette nature? Si oui, qu'est-ce que cela implique? Des ressources ou des exemples?
  • Pouvez-vous appeler un service WCF à partir de VBA? Des exemples?
Était-ce utile?

La solution

J'ai fini par implémenter l'option 1 référencée dans la question d'origine.

I "chunk" le fichier dans VBA et transférez chaque "chunk" sur le service Web. J'ai basé la partie VBA de la solution sur le code trouvé ici: Copier un fichier volumineux par Chunk with Progress Notification . Au lieu de copier sur le système de fichiers, toutefois, je l’envoie au serveur.

Le code: VBA Land

Voici le code (fugly) VBA qui crée les fragments de fichier:

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

Voici la méthode VBA référencée qui télécharge les fragments sur le serveur:

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

Le code: service Web ASMX C #

Désormais, côté serveur, la méthode de service Web accepte quelques paramètres importants.

  1. string fileName: Le nom du fichier (chaque morceau a le même fichier nom)
  2. octet [] zipBytes: le contenu de chaque morceau
  3. int index: l'index (utilisé en conjonction avec nomfichier fournir unique partiel commandé fichiers sur le système de fichiers)
  4. bool isLastChunk: Ceci est le "j'ai terminé" allez-y et fusionnez tous les "morceaux" " et nettoyer après vous-même " drapeau.

int index et bool isLastChunk. Avec ce contexte fourni par le monde VBA, j'en sais suffisamment pour enregistrer chacun de ces fragments de fichier, puis les combiner lorsque le drapeau isLastChunk est vrai.

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

Voici le code C # qui écrit chaque bloc entrant dans le système de fichiers:

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

Voici le code C # permettant de fusionner tous les fichiers 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);
            }
        }
    }
}

Autres conseils

J'ai transmis des fichiers volumineux comme celui-ci en utilisant l'encodage MTOM.

Plus d'informations sur MTOM ici: http://msdn.microsoft.com /en-us/library/aa395209.aspx

Vous pouvez télécharger un exemple de MTOM ici: http://msdn.microsoft .com / fr-us / library / ms751514.aspx

Consultez le livre de Bustamante sur WCF si vous souhaitez en savoir plus sur MTOM.

En ce qui concerne l’appel VBA, je ne suis pas un expert dans ce domaine et je n’ai donc aucune information à ce sujet.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top