كيفية نقل ملف كبير من وظيفة MS Word الإضافية (VBA) إلى خادم الويب؟

StackOverflow https://stackoverflow.com/questions/803294

سؤال

ملخص

لدي وظيفة إضافية لـ Microsoft Word، مكتوبة بلغة VBA (Visual Basic for Applications)، والتي تعمل على ضغط المستند وجميع محتوياته ذات الصلة (الوسائط المضمنة) في أرشيف مضغوط.بعد إنشاء أرشيف مضغوط، يقوم بعد ذلك بتحويل الملف إلى مصفوفة بايت ونشره على خدمة ويب ASMX.هذا يعمل في الغالب.

مشاكل

المشكلة الرئيسية التي أواجهها هي نقل الملفات الكبيرة إلى موقع الويب.يمكنني بنجاح تحميل ملف يبلغ حجمه حوالي 40 ميجابايت، ولكن ليس ملفًا يبلغ حجمه 140 ميجابايت (مهلة/فشل عام).

هناك مشكلة ثانوية وهي أن إنشاء مصفوفة البايت في وظيفة VBScript Word الإضافية يمكن أن يفشل بسبب نفاد الذاكرة على الجهاز العميل إذا كان الأرشيف المضغوط كبيرًا جدًا.

الحلول الممكنة

أنا أفكر في الخيارات التالية وأبحث عن تعليقات حول أي من الخيارين أو أي اقتراحات أخرى.

خيار واحد

فتح دفق ملف على العميل (MS Word VBA) وقراءة "قطعة" واحدة في كل مرة وإرسالها إلى خدمة الويب ASMX التي تجمع "القطع" في ملف على الخادم.

يتمتع هذا بميزة عدم إضافة أي تبعيات أو مكونات إضافية إلى التطبيق، وسأقوم فقط بتعديل الوظائف الحالية.(يعد تقليل التبعيات أمرًا أفضل نظرًا لأن هذا الحل يجب أن يعمل في مجموعة متنوعة من بيئات الخادم ويكون من السهل نسبيًا إعداده.)

سؤال:

  • هل هناك أمثلة للقيام بذلك أو أي تقنيات موصى بها (إما على العميل في VBA أو في خدمة الويب في C#/VB.NET)؟

الخيار الثاني

أتفهم أن WCF قد يوفر حلاً لمشكلة نقل الملفات الكبيرة عن طريق "تقطيع" البيانات أو دفقها.ومع ذلك، فأنا لست على دراية بـ WCF، ولست متأكدًا مما هو قادر عليه بالضبط أو إذا كان بإمكاني التواصل مع خدمة WCF من VBA.وهذا له الجانب السلبي المتمثل في إضافة تبعية أخرى (.NET 3.0).ولكن إذا كان استخدام WCF هو بالتأكيد حل أفضل، فقد لا أمانع في أخذ هذه التبعية.

أسئلة:

  • هل يدعم WCF بشكل موثوق عمليات نقل الملفات الكبيرة من هذا النوع؟إذا كان الأمر كذلك، ما الذي ينطوي عليه هذا؟أي موارد أو أمثلة؟
  • هل يمكنك الاتصال بخدمة WCF من VBA؟أي أمثلة؟
هل كانت مفيدة؟

المحلول

انتهى بي الأمر إلى تنفيذ الخيار الأول المشار إليه في السؤال الأصلي.

أقوم "بتقطيع" الملف في VBA ونقل كل "قطعة" إلى خدمة الويب.لقد قمت ببناء جزء VBA من الحل على الكود الموجود هنا: انسخ ملفًا كبيرًا بواسطة Chunk مع إشعار التقدم.وبدلاً من النسخ إلى نظام الملفات، أقوم بإرساله إلى الخادم.

الرمز:أرض VBA

إليك رمز 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. اسم ملف السلسلة:اسم الملف (كل قطعة لها نفس اسم الملف)
  2. البايت [] الرمز البريدي البايت:محتويات كل قطعة
  3. مؤشر كثافة العمليات:الفهرس (المستخدم بالاقتران مع اسم الملف لتوفير ملفات جزئية فريدة من نوعها على نظام الملفات)
  4. المنطق هو LastChunk:هذا هو "لقد انتهيت - المضي قدما ودمج جميع" القطع "وتنظيف بعد نفسك".

مؤشر int والمنطق هوLastChunk.مع هذا السياق المقدم من عالم 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);
        }
    }
}

إليك رمز 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

راجع كتاب بوستامانتي عن WCF إذا كنت تريد المزيد عن MTOM.

أما بالنسبة لمكالمة VBA، فأنا لست خبيرًا في هذا المجال، وبالتالي ليس لدي أي معلومات بخصوصها.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top