views:

314

answers:

4

I am really a newbie with streams, so I don't really know what I am doing here. :)

I have a XElement containing XML. I want it to return it as a file to the user.

  XElement xml = IndicesXMLGenerator.XML();

  //Code for creating a memorystream for returning to browser as file
   MemoryStream stream = new MemoryStream();
  XmlTextWriter writer = new XmlTextWriter(stream, System.Text.Encoding.Unicode);
  xml.Save(writer);
  writer.Close();

  //Code for direct saving to harddisk
  FileStream filestream = new FileStream(@"D:\indices.xml", FileMode.Create);
  XmlTextWriter writer2 = new XmlTextWriter(filestream, System.Text.Encoding.Unicode);
  xml.Save(writer2);
  writer2.Close();
  filestream.Close();

  //Return memorystream as fileresult
  return base.File(new MemoryStream(stream.GetBuffer()), "text/xml", "AlleIndices.xml");
    }

When I open the file that I got from my browser, it is totally mangled. like: �< ? X M L

When I change the encoding in the code to UTF8 it gives me a normal looking document, but at the end I get a lot of 0x0 characters that make the document invalid.

Strange thing is that the XML file that I saved directly to the harddisk from within the code is:

  • Perfectly fine in it's encoding
  • Doesn't contain any strange 0x0characters

So, what's going on here? Why can't I easily stream my XElement to the browser as file?

+1  A: 

Calling GetBuffer will return the stream's internal buffer, which will be larger than the actual data. (In case you write some more to the stream)

You need to replace the call to GetBuffer with ToArray(), which will copy the used portion of the buffer to a new array.


However, the best way to do this is return the original MemoryStream, but first set the Position property to 0, like this:

stream.Position = 0;
return File(stream, "text/xml", "AlleIndices.xml");
SLaks
I can't set position or seek, because stream is closed,probably due to closing of writer.However, calling ToArray() looks like a good solution!
Peterdk
You should `Flush()` the writer instead of calling `Close()`.
SLaks
+1  A: 
  1. You don't need to create a new MemoryStream, you can just reuse the same (you just need to Seek to the beginning of the stream)

  2. The XML appears mangled because the browser doesn't know that it's encoded as Unicode. You need to add a Content-Encoding header to the response to specify the encoding

Thomas Levesque
Memorystream is closed when the writer closes. So yes, a new memstream is needed.
Peterdk
good point, I missed that... my second still stands, though ;)
Thomas Levesque
A: 

The MemoryStream implements an array doubling technique so that it can resize efficiently as you add bytes. This means that the buffer is usually larger than the actual data.

There are a few solutions:

  1. Use ToArray instead of GetBuffer.
  2. Use the MemoryStream constructor that takes an offset and length.

For example:

new MemoryStream(stream.GetBuffer(), 0, (int)stream.Length)
binarycoder
A: 

You will be much happier in the long run if you write your code like this:

XElement xml = IndicesXMLGenerator.XML();
using (MemoryStream ms = new MemoryStream())
using (XmlWriter xw = XmlWriter.Create(ms))
{
   xml.Save(xw);
}
  1. Use XmlWriter.Create, not XmlTextWriter, and use XmlWriterSettings to control encoding and formatting.
  2. Use using blocks rather than explicitly calling Flush and Close - that way you don't have to explicitly call them.

That said, none of that's actually your problem, which as noted is the use of GetBuffer instead of ToArray. But as I said, you'll be happier in the long run.

Robert Rossney
Thanks, I know it's ugly code. :)
Peterdk