DocumentFormat.OpenXml: возможно ли сохранить текстовый документ в Memory Stream?

Я работаю над функцией, которая позволяет мне создать простой текстовый документ из строки. Я использую DocumentFormat.OpenXml Version="2.20.0" для создания документа Word. Я не понимаю, почему я не могу сохранить документ Word в потоке памяти, тогда как я могу сохранить документ Word в файле.

  public Task<byte[]> ConvertToWordAsync(string text)
    {
        if (text.IsNullOrEmpty())
            return Task.FromResult(Array.Empty<byte>());

        using var memoryStream = new MemoryStream();
        using var wordDocument = WordprocessingDocument.Create(memoryStream, WordprocessingDocumentType.Document);
        
        MainDocumentPart mainPart = wordDocument.AddMainDocumentPart();
        mainPart.Document = new Document
        {
            Body = new Body()
        };
        Body body = mainPart.Document.Body;
        Paragraph paragraph = new Paragraph();
        Run run = new Run();
        Text bodyText = new Text(text);
        run.Append(bodyText);
        paragraph.Append(run);
        body.Append(paragraph);

        wordDocument.Save();
        
        return Task.FromResult(memoryStream.ToArray());
    }

Когда я вызываю эту функцию, поток памяти всегда пуст. если я изменюсь

using var wordDocument = WordprocessingDocument.Create(memoryStream, WordprocessingDocumentType.Document);

К

using var wordDocument = WordprocessingDocument.Create("C:\\Workspace\\65.docx", WordprocessingDocumentType.Document);

Я могу открыть файл word.

Я не понимаю, почему я не могу сохранить тот же файл слова в поток памяти. Есть ли у вас какие-либо идеи о решении этой проблемы?

Тело (содержимое) HTTP-сообщения должно соответствовать Content-Type (см.: geeksforgeeks.org/http-headers-content-type/… ). Также вложения могут быть MIME, которые начинаются с новой строки, содержащей два тире (см.: learn.microsoft.com/en-us/previous-versions/office/developer‌​/…). HTTP-сообщение должно быть в формате HTML, если это текст, или использовать GZIP (сжатие), который преобразует двоичный файл в текст, или использовать строку Base64.

jdweng 14.04.2023 14:47

Я ожидаю, что блок автоматического использования будет проблемой здесь. Блок использования для wordDocument должен заканчиваться до return Task.FromResult(memoryStream.ToArray()); или, если вам не нравится этот вызов wordDocument.Close();, до получения массива из потока памяти.

rene 14.04.2023 15:12

@jdweng вторая половина вашего комментария, как обычно, не имеет смысла, но, похоже, она вообще не относится к этому вопросу? Здесь никто не говорит о HTTP.

CodeCaster 14.04.2023 15:15

@rene Вы совершенно правы, спасибо. Мне нужно было использовать try/finally и вызвать wordDocument.Dispose(). Теперь работает нормально :)

Alexandre Sobral Martins 14.04.2023 15:54

@CodeCaster Я пытался, но это не решило проблему. Спасибо за помощь в любом случае :)

Alexandre Sobral Martins 14.04.2023 15:57

Да, я удалил этот комментарий, перемотка MemoryStream не требуется, когда вы вызываете для него ToArray(), только при передаче его другому коду, который читает его как поток.

CodeCaster 14.04.2023 16:02
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
6
85
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

тл;др Не позволяйте компилятору угадывать, где заканчивается блок using, когда вы полагаетесь на вызов Dispose.

using var Foo = new Foo(); Foo.Whatever(); по-прежнему генерирует этот код при компиляции (несущественные детали опущены, см.: Каково использование «using» в C#?):

   var Foo = new Foo();
   try
   {
      Foo.Whatever();
   }
   finally
   {
      Foo.Dispose();
   }

Вызов Dispose здесь актуален.

В этом (детали опущены) коде:

public Task<byte[]> ConvertToWordAsync(string text)
{
    using var memoryStream = new MemoryStream();
    using var wordDocument = WordprocessingDocument.Create(memoryStream, WordprocessingDocumentType.Document);
    
    // details omitted for brevity

    wordDocument.Save();
    
    return Task.FromResult(memoryStream.ToArray());
}

компилятор сгенерировал:

public Task<byte[]> ConvertToWordAsync(string text)
{
    var memoryStream = new MemoryStream();
    try 
    {
       var wordDocument = WordprocessingDocument.Create(memoryStream, WordprocessingDocumentType.Document);
       try
       {
          // details omitted for brevity

           wordDocument.Save();
    
           return Task.FromResult(memoryStream.ToArray());
       }
       finally
       {
          wordDocument.Dispose();
       } 
    }
    finally
    {
       memoryStream.Dispose();
    }
}

Проблема здесь в том, что WordprocessingDocument необходимо записать в свой Stream полный Zip-архив с несколькими файлами, чтобы создать действительный файловый контейнер OpenXml. Это произойдет только тогда, когда больше не ожидается вызовов Save(). Это происходит либо при вызове Close(), либо при вызове его метода Dispose().
До этого поток в лучшем случае неполный.

Из-за того, что компилятор выдал блоки finally с вызовами методов Dispose, memoryStream даже не был близок к завершению, когда был вызван ToArray(). Это было, когда ваш метод вернулся, но не осталось кода для захвата этих данных.

Вы говорите, что решили проблему, явно вызвав Dispose. Это работает. Или вернуться к нормальному синтаксису без сюрпризов:

using var memoryStream = new MemoryStream();
using(var wordDocument = WordprocessingDocument.Create(memoryStream, WordprocessingDocumentType.Document))
{

  // details omitted for brevity

  wordDocument.Save();
} // wordDocument.Dispose() called here
return Task.FromResult(memoryStream.ToArray());

Другие вопросы по теме