Question

Again, asking MVC noob questions. Forgive my lack of experience.

I have a situation where I am using an MVC route to return a large XML file. Sometimes it can be very large. Currently, I'm using a StringBuilder to build the XML output I want, and then returning it like this:

var sb = new StringBuilder();
XmlObject.WriteXml(sb);
return Content(sb.ToString(), "text/xml", Encoding.UTF8);

What I'm running into is that (for various reasons) the XML blog can take quite a long time to generate.

Within the XmlObject.WriteXml() method are calls to tons of other little methods that output bits and pieces of XML as they are called, so I START building an XML string right away, it just takes a while to finish. Each of these methods accepts a StringBuilder as an argument, so can created one and then pass it all over the place, using sb.Append() within each little method to build the final XML blob.

OK, so what I'd LIKE to do is start returning something to the client as soon as the string begins to build. In Webforms, I would have replaced all the StringBuilder paramaters with HttpResponse and used HttpResponse.Write() instead of StringBuilder.Append(), in a fashion similar to this:

this.Response.BufferOutput = false;
XmlObject.WriteXml(Response);

Then as each little piece of XML got written to the Reponse, the text would be sent to the client.

The problem I'm having is that the ActionResult has to have a return statement. I don't know how to treat in a similar fasion using an MVC route and ActionResult. Perhaps I need to be using something other than an ActionResult?

Thanks everybody!

Was it helpful?

Solution

If it doesn't need to be the result of a MVC action, and you don't want to break testability/encapsulation there you can do it the way we always did it -- with a custom IHttpHandler that streams directly into the response.

UPDATE

Sample might not make sense -- you've got the concept in your question. Only twist would be to skip StringBuilder and use an XmlTextWriter set to stream to the Response.OutputStream to skip a middleman.

That said, now that I'm reading what you are doing, I'd personally take the "build offline and cache reasonably" angle rather than worrying about streaming it. Its probably easier to deal with canned than live.

OTHER TIPS

You can change your action method to return void, then write directly to the response.
(That is possible; I've tried it)

Building large amounts of XML with a StringBuilder can put a lot of pressure on your memory. If you can write the XML in depth-first order, use an XmlTextWriter.

Here's some sample code of writing potentially large amounts of XML to the output stream:

public ActionResult SiteMap()
{
    Response.ClearContent();
    Response.ContentType = "application/xml";
    Response.ContentEncoding = Encoding.UTF8;

    using (var context = new MyDataContext())
    {
        using (var xml = new XmlTextWriter(Response.Output))
        {
            xml.WriteStartElement("urlset",
                                  "http://www.sitemaps.org/schemas/sitemap/0.9");

            var url = new UrlHelper(Request.RequestContext);

            xml.WriteStartElement("url");
            xml.WriteElementString("loc", /* some URL */);
            xml.WriteEndElement();

            foreach (var blogPost in context.BlogPosts)
            {
                xml.WriteStartElement("url");
                xml.WriteElementString("loc", /* blog post URL */);
                xml.WriteEndElement();
            }

            xml.WriteEndElement();
            xml.Flush();
        }
    }

    return null;
}

This particular example is an abbreviated version of some code I use to write out the XML contents of a sitemap.xml document. In my case the document has about 30,000 URLs and is about 2.5MB in size.

public ActionResult Xml()
{
    string xmlString = "xml goes here...";
    return this.Content(xmlString, "text/xml", Encoding.UTF8);
}

May want to consider caching the ActionResult, if you don't want to re-generate it on each request. Look into the [OutputCache] attribute, you can cache it by parameter, for specified amount of time, etc.

I'm not sure what you're getting at, it sounds like you want to do some kind of asynchronous http request. Generally speaking, they aren't. The client sends an http request, the server generates and sends back a response.

You could always load the page first and display friendly content, then use AJAX to call an action method that does the dirty work.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top