Reputation: 345
I am trying to learn how to use with Microsoft's Open XML SDK. I followed their steps on how to create a Word document using a FileStream
and it worked perfectly. Now I want to create a Word document but only in memory, and wait for the user to specify whether they would like to save the file or not.
This document by Microsoft says how to deal with in-memory documents using MemoryStream
, however, the document is first loaded from an existing file and "dumped" into a MemorySteam
. What I want is to create a document entirely in memory (not based on a file in a drive). What I thought would achieve that was this code:
// This is almost the same as Microsoft's code except I don't
// dump any files into the MemoryStream
using (var mem = new MemoryStream())
{
using (var doc = WordprocessingDocument.Create(mem, WordprocessingDocumentType.Document, true))
{
doc.AddMainDocumentPart().Document = new Document();
var body = doc.MainDocumentPart.Document.AppendChild(new Body());
var paragraph = body.AppendChild(new Paragraph());
var run = paragraph.AppendChild(new Run());
run.AppendChild(new Text("Hello docx"));
using (var file = new FileStream(destination, FileMode.CreateNew))
{
mem.WriteTo(file);
}
}
}
But the result is a file that is 0KB and that can't be read by Word. At first I thought it was because of the size of the MemoryStream
so I provided it with an initial size of 1024 but the results were the same. On the other hand if I change the MemoryStream
for a FileStream
it works perfectly.
My question is whether what I want to do is possible, and if so, how? I guess it must be possible, just not how I'm doing it. If it isn't possible what alternative do I have?
Upvotes: 3
Views: 9249
Reputation: 31
I had the same problem, and thanks to Lance U. Matthews's response, I realized that I was never closing the MemoryStream, so here's my solution (I had to use aliases for my usings)
private MemoryStream GenerateWord(DataTable dt)
{
MemoryStream mStream = new MemoryStream();
// Create Document
OpenXMLPackaging.WordprocessingDocument wordDocument =
OpenXMLPackaging.WordprocessingDocument.Create(mStream, OpenXML.WordprocessingDocumentType.Document, true);
// Add a main document part.
OpenXMLPackaging.MainDocumentPart mainPart = wordDocument.AddMainDocumentPart();
mainPart.Document = new OpenXMLWordprocessing.Document();
OpenXMLWordprocessing.Body body = mainPart.Document.AppendChild(new OpenXMLWordprocessing.Body());
OpenXMLWordprocessing.Table table = new OpenXMLWordprocessing.Table();
body.AppendChild(table);
OpenXMLWordprocessing.TableRow tr = new OpenXMLWordprocessing.TableRow();
foreach (DataColumn c in dt.Columns)
{
tr.Append(new OpenXMLWordprocessing.TableCell(new OpenXMLWordprocessing.Paragraph(new OpenXMLWordprocessing.Run(new OpenXMLWordprocessing.Text(c.ColumnName.ToString())))));
}
table.Append(tr);
foreach (DataRow r in dt.Rows)
{
if (dt.Rows.Count > 0)
{
OpenXMLWordprocessing.TableRow dataRow = new OpenXMLWordprocessing.TableRow();
for (int h = 0; h < dt.Columns.Count; h++)
{
dataRow.Append(new OpenXMLWordprocessing.TableCell(new OpenXMLWordprocessing.Paragraph(new OpenXMLWordprocessing.Run(new OpenXMLWordprocessing.Text(r[h].ToString())))));
}
table.Append(dataRow);
}
}
mainPart.Document.Save();
wordDocument.Close();
mStream.Position = 0;
return mStream;
}
Upvotes: 2
Reputation: 345
There's a couple of things going on here:
First, unlike Microsoft's sample, I was nesting the using
block code that writes the file to disk inside the block that creates and modifies the file. The WordprocessingDocument
gets saved to the stream until it is disposed or when the Save()
method is called. The WordprocessingDocument
gets disposed automatically when reaching the end of it's using
block. If I had not nested the third using statement, thus reaching the end of the second using
statement before trying to save the file, I would have allowed the document to be written to the MemoryStream
- instead I was writing a still empty stream to disk (hence the 0KB file).
I suppose calling Save()
might have helped, but it is not supported by .Net core (which is what I'm using). You can check whether Save()
is supported on you system by checking CanSave
.
/// <summary>
/// Gets a value indicating whether saving the package is supported by calling <see cref="Save"/>. Some platforms (such as .NET Core), have limited support for saving.
/// If <c>false</c>, in order to save, the document and/or package needs to be fully closed and disposed and then reopened.
/// </summary>
public static bool CanSave { get; }
So the code ended up being almost identical to Microsoft's code except I don't read any files beforehand, rather I just begin with an empty MemoryStream
:
using (var mem = new MemoryStream())
{
using (var doc = WordprocessingDocument.Create(mem, WordprocessingDocumentType.Document, true))
{
doc.AddMainDocumentPart().Document = new Document();
var body = doc.MainDocumentPart.Document.AppendChild(new Body());
var paragraph = body.AppendChild(new Paragraph());
var run = paragraph.AppendChild(new Run());
run.AppendChild(new Text("Hello docx"));
}
using (var file = new FileStream(destination, FileMode.CreateNew))
{
mem.WriteTo(file);
}
}
Also you don't need to reopen the document before saving it, but if you do remember to use Open()
instead of Create()
because Create()
will empty the MemoryStream
and you'll also end with a 0KB file.
Upvotes: 5
Reputation: 16612
You're passing mem
to WordprocessingDocument.Create()
, which is creating the document from the (now-empty) MemoryStream
, however, I don't think that is associating the MemoryStream
as the backing store of the document. That is, mem
is only the input of the document, not the output as well. Therefore, when you call mem.WriteTo(file);
, mem
is still empty (the debugger would confirm this).
Then again, the linked document does say "you must supply a resizable memory stream to [Open()
]", which implies that the stream will be written to, so maybe mem
does become the backing store but nothing has been written to it yet because the AutoSave
property (for which you specified true
in Create()
) hasn't had a chance to take effect yet (emphasis mine)...
Gets a flag that indicates whether the parts should be saved when disposed.
I see that WordprocessingDocument
has a SaveAs()
method, and substituting that for the FileStream
in the original code...
using (var mem = new MemoryStream())
using (var doc = WordprocessingDocument.Create(mem, WordprocessingDocumentType.Document, true))
{
doc.AddMainDocumentPart().Document = new Document();
var body = doc.MainDocumentPart.Document.AppendChild(new Body());
var paragraph = body.AppendChild(new Paragraph());
var run = paragraph.AppendChild(new Run());
run.AppendChild(new Text("Hello docx"));
// Explicitly close the OpenXmlPackage returned by SaveAs() so destination doesn't stay locked
doc.SaveAs(destination).Close();
}
...produces the expected file for me. Interestingly, after the call to doc.SaveAs()
, and even if I insert a call to doc.Save()
, mem.Length
and mem.Position
are both still 0
, which does suggest that mem
is only used for initialization.
One other thing I would note is that the sample code is calling Open()
, whereas you are calling Create()
. The documentation is pretty sparse as far as how those two methods differ, but I would have suggested you try creating your document with Open()
instead...
using (MemoryStream mem = new MemoryStream())
using (WordprocessingDocument doc = WordprocessingDocument.Open(mem, true))
{
// ...
}
...however when I do that Open()
throws an exception, presumably because mem
has no data. So, it seems the names are somewhat self-explanatory in that Create()
initializes new document data whereas Open()
expects existing data. I did find that if I feed Create()
a MemoryStream
filled with random garbage...
using (var mem = new MemoryStream())
{
// Fill mem with garbage
byte[] buffer = new byte[1024];
new Random().NextBytes(buffer);
mem.Write(buffer, 0, buffer.Length);
mem.Position = 0;
using (var doc = WordprocessingDocument.Create(mem, WordprocessingDocumentType.Document, true))
{
// ...
}
}
...it still produces the exact same document XML as the first code snippet above, which makes me wonder why Create()
even needs an input Stream
at all.
Upvotes: 1