views:

39

answers:

2

So I'm rendering a partial view in memory in order to direct the XSL-FO data in the view to a PDF renderer.

The PDF renderer can take a string, XmlDocument or Stream as input.

Here is what I'd like to do: (Edited for clarity)

Solution taken from: http://thriftybliss.spaces.live.com/blog/cns!58DA805F37F31F20!170.entry?wa=wsignin1.0&sa=362921628

var viewPage = new ViewPage();
var viewData = new ViewDataDictionary(model);
viewPage.ViewData = viewData;

var control = viewPage.LoadControl(viewName);
viewPage.Controls.Add(control);

using (var inStream = new MemoryStream())
{
    using (var sw = new StreamWriter(inStream))
    {
        using (var tw = new HtmlTextWriter(sw))
        {
            viewPage.RenderControl(tw);
        }
        using (var outStream = new MemoryStream())
        {
            driver.Render(inStream, outStream);
            return outStream.ToArray();
        }
    }
}

Here's what works, but what I'd rather not do:

var sb = new StringBuilder();

using (var sw = new StringWriter(sb))
{
    using (var tw = new HtmlTextWriter(sw))
    {
        vp.RenderControl(tw);
    }
}

var xml = new XmlDocument();
xml.LoadXml(sb.ToString());

using (var ms = new MemoryStream())
{
    driver.Render(xml, ms);
    return ms.ToArray();
}

Unfortunately the HtmlTextWriter doesn't seem to want to write to the StreamWriter, or perhaps I just messed something else up.

If I replace StreamWriter with StringWriter it works fine, but I can't use StringWriter because when I call .ToString() I get all kinds of escape characters that mess up the XSL-FO and the renderer throws an "Illegal Characters" exception.

I got around this by creating an XmlDocument and loading the string into that, but it seems like a very roundabout way of doing things. I'd really just like to just capture the output from the HtmlTextWriter and feed it directly into the Renderer. Is there any way to do this?

Thanks!

A: 

I haven't tested this AT ALL... but it's a modified version of a RenderViewToString method that I made and use elsewhere:

    public static Stream RenderViewToStream<T>(string viewPath, T model, ControllerContext controllerContext)
    {
        var ms = new MemoryStream();
        using (var writer = new StreamWriter(ms))
        {
            var view = new WebFormView(viewPath);
            var vdd = new ViewDataDictionary<T>(model);
            var viewCxt = new ViewContext(controllerContext, view, vdd, new TempDataDictionary(), writer);
            viewCxt.View.Render(viewCxt, writer);
        }
        return ms;
    }
blesh
Terminal Frost
+1  A: 

When you say "HtmlTextWriter doesn't seem to want to write to the StreamWriter" does that mean that driver.Render can't get any bytes out of inStream? Two possibilities I can think of...

First is that HtmlTextWriter isn't flushing the stream after it's done writing, in which case you might need a sw.Flush() in there before you hand the inStream to driver.Render.

Second is that after writing to the MemoryStream, the location pointer will be at the end of the stream, and any attempts to read from it will yield zero bytes (since it's at the end).

In that case, setting inStream.Position to 0, or calling inStream.Seek(0, SeekOrigin.Begin) before calling driver.Render should fix it.

I've ran into the second situation a few times before, so that's my best guess.

Brad
Thanks! That was it. Stepping through with the debugger I didn't know why the inStream length was 0 after rendering the Control. After flushing the StreamWriter the inStream length read the proper length and setting the position to 0 before reading worked perfectly. Thanks again!
Terminal Frost