RenderPartial to String in ASP.NET MVC Beta

November 16th, 2008 by kevin

Update! RenderPartial to String has become much easier in ASP.NET MVC, see the new post here!

We usually don’t post much about hardcore programming-related things here, but this is an exception to the rule.

We’re primarily a Ruby on Rails shop, but we still do ASP.NET development fairly regularly. Our overall interest in ASP.NET was waning until the new ASP.NET MVC framework was released. ASP.NET MVC brings a number of the concepts we love from Rails into the ASP.NET arena.

However, one limitation we’ve come across with ASP.NET MVC is the lack of ability to render a partial to a string. This is really handy if you’re doing Ajax things; it also happens to be one of the things we really love about Rails. Additionally, it’s a feature others have been wanting, too, look at this here and here.

Well, the good news is I have a solution for at least some of you out there. I’ve cobbled together a few concepts from various sources and forum posts to make it all happen. Here is how you can render a view into a string.

First, you need to include these files in your project, both are from the MvcContrib project.

Download BlockRender.cs – from MvcContrib

using System;
using System.IO;
using System.Web;
using System.Collections.Generic;

namespace MvcContrib.UI
{
    /// <summary>Renders an Action delegate and captures all output to a string. </summary>
    public class BlockRenderer
    {
        private readonly HttpContextBase _httpContext;

        public BlockRenderer(HttpContextBase httpContext)
        {
            _httpContext = httpContext;
        }

        public partial class HttpResponse
        {
            public bool UsingHttpWriter { get { return true; } }
        }

        /// <summary>Renders the action and returns a string.</summary>
        /// <param name="viewRenderer">The delegate to render.</param>
        /// <returns>The rendered text.</returns>
        public string Capture(Action viewRenderer)
        {
            HttpResponseBase resp = _httpContext.Response;
            Stream originalFilter = null;
            CapturingResponseFilter innerFilter;
            string capturedHtml = "";

            if (viewRenderer != null)
            {
                try
                {
                    resp.Flush();
                    originalFilter = resp.Filter;
                    innerFilter = new CapturingResponseFilter(resp.Filter);
                    resp.Filter = innerFilter;
                    viewRenderer();

                    resp.Flush();
                    capturedHtml = innerFilter.GetContents(resp.ContentEncoding);
                }
                finally
                {
                    if (originalFilter != null)
                    {
                        resp.Filter = originalFilter;
                    }
                }
            }
            return capturedHtml;
        }
    }
}

Download CapturingResponseFilter.cs – from MvcContrib

using System.IO;
using System.Text;

namespace MvcContrib.UI
{
    public class CapturingResponseFilter : Stream
    {
        private Stream _sink;
        private MemoryStream mem;

        public CapturingResponseFilter(Stream sink)
        {
            _sink = sink;
            mem = new MemoryStream();
        }

        // The following members of Stream must be overriden.
        public override bool CanRead
        {
            get { return true; }
        }

        public override bool CanSeek
        {
            get { return false; }
        }

        public override bool CanWrite
        {
            get { return false; }
        }

        public override long Length
        {
            get { return 0; }
        }

        public override long Position { get; set; }

        public override long Seek(long offset, SeekOrigin direction)
        {
            return 0;
        }

        public override void SetLength(long length)
        {
            _sink.SetLength(length);
        }

        public override void Close()
        {
            _sink.Close();
            mem.Close();
        }

        public override void Flush()
        {
            _sink.Flush();
        }

        public override int Read(byte[] buffer, int offset, int count)
        {
            return _sink.Read(buffer, offset, count);
        }

        // Override the Write method to filter Response to a file. 
        public override void Write(byte[] buffer, int offset, int count)
        {
            //Here we will not write to the sink b/c we want to capture

            //Write out the response to the file.
            mem.Write(buffer, 0, count);
        }

        public string GetContents(Encoding enc)
        {
            var buffer = new byte[mem.Length];
            mem.Position = 0;
            mem.Read(buffer, 0, buffer.Length);
            return enc.GetString(buffer, 0, buffer.Length);

        }
    }
}

With the above two files included in your project, you’re almost ready to go. Now you just need to include a method that makes all the magic happen (more easily). As seen in the example below, define the RenderPartialToString() and then call it from your controller:

/// Static Method to render string - put somewhere of your choosing
public static string RenderPartialToString(string userControl, object viewData, ControllerContext controllerContext)
{
    HtmlHelper h = new HtmlHelper(new ViewContext(controllerContext, new WebFormView("omg"), null, null), new ViewPage());
    var blockRenderer = new BlockRenderer(controllerContext.HttpContext);

    string s = blockRenderer.Capture(
        () => RenderPartialExtensions.RenderPartial(h, userControl, viewData)
    );

    return s;
}

/// Your Controller method...  
public ActionResult MakeStringForMe()
{
    var objectViewData = new objectViewData { SomeString = "Dude", SomeNumber = 1 };

    string s = RenderPartialToString("~/Views/Controls/UserControl.ascx", objectViewData, this.ControllerContext);

    View();
}

With a little bit of luck and good fortune, it should work and you’ll be rendering partials to strings like a machine!

How It All Works

Basically, it’s using the Response object to render a usercontrol and capture the output into a string. Then, it filters that rendered content out of the response with some fancy trickery. All in all, it’s definitely a hack, but it gets the job done—at least until the Microsoft team can address this issue at its core (hint hint).

A Few Limitiations and/or Questions

I read somewhere that this might not work if you need to retrieve things out of the session. I have not verified this.

Additionally, I have no idea what effects using this approach will have on the large-scale performance of an app. It hasn’t caused any problems for the application I’m using it on.

Lastly, you cannot specify the ContentType on the response. If you do, you’ll get an exception:

Server cannot set content type after HTTP headers have been sent

This seems to be a limitation that results from rendering a usercontrol. This will be problematic if you want your controller to return data with a ContenType of, say, “text/javascript.” I ran into this problem while building a Rails-like RJS framework (see below), but managed to deal with it fairly elegantly.

Rails-like RJS for ASP.NET MVC

In line with the ability to RenderPartial to string, I’ve also put together a Rails-like RJS framework for ASP.NET MVC. Simply put, RJS is a framework that renders and builds dynamic javascript on the server. Similar to Rails’ RJS, it’s specific to the Prototype framework – sorry JQuery guys (for what it’s worth, it could probably be easily ported to JQuery!).

Here’s a quick snippet of what this allows you to do from your Controller:

// Example Usage in your Controller:
public ActionResult HideElement(string elementId)
{
    RjsResult r = new RjsResult();
    r.Effect(elementId, RjsResult.EffectTypes.Hide);

    return r;
}

// A more complicated but powerful example of returning the contents of two UserControls and inserting them into two different <div>'s
public ActionResult InsertUserControl()
{
    ObjectViewData viewData = new ObjectViewData { SomeString = "dude", SomeNumber = 1 };

    var r = new RjsResult();
    r.Insert("div_one", RjsResult.Positions.Top, "~/Views/Controls/UserControl1.ascx", ObjectViewData, ControllerContext);
    r.Insert("div_two", RjsResult.Positions.Bottom, "~/Views/Controls/UserControl2.ascx", ObjectViewData, ControllerContext);

    return r;
}

In both of the above examples, I’m calling a few different methods on the RjsResult object. Behind the scenes, the RjsResult object is building up a string of javascript commands that make all the magic happen in the user’s browser. If you’re still confused, I suggest you read this page from the Rails documentation, which might explain the concept better.

With that said, here is the code for my ASP.NET MVC RJS framework!

Download RjsResult.cs

namespace System.Web.Mvc
{
    using System;
    using System.Text;
    using System.Web;
    using System.Collections.Generic;
    using System.Security.Policy;
    using System.Web.Script.Serialization;
    using MvcContrib.UI;
    using System.Web.Mvc.Html;

    [AspNetHostingPermission(System.Security.Permissions.SecurityAction.LinkDemand, Level = AspNetHostingPermissionLevel.Minimal)]
    [AspNetHostingPermission(System.Security.Permissions.SecurityAction.InheritanceDemand, Level = AspNetHostingPermissionLevel.Minimal)]
    public class RjsResult : ActionResult
    {
        #region Constructors
        public RjsResult(bool setContentType)
        {
            Actions = new List<IRjsActionBase>();
            this.SetContentType = setContentType;
        }

        public RjsResult() : this(false)
        {
        }
        #endregion

        #region Private Vars
        private bool SetContentType { get; set; }

        private List<IRjsActionBase> Actions
        {
            get;
            set;
        }

        #endregion

        #region Rjs Actions
        public interface IRjsActionBase
        {
            string Render();
        }

        private class RjsUpdateAction : IRjsActionBase
        {
            public RjsUpdateAction(string element, string contents)
            {
                this.element = element;
                this.contents = contents;
            }

            public string element { get; set; }
            public string contents { get; set; }

            #region IRjsActionBase Members

            public string Render()
            {
                if (!contents.StartsWith("""))
                    contents = """ + contents + """;

                return string.Format("Element.update("{0}", {1});", element, contents);
            }

            #endregion
        }

        private class RjsInsertAction : IRjsActionBase
        {
            public RjsInsertAction(string element, Positions p, string contents)
            {
                this.element = element;
                this.contents = contents;
                this.position = p;
            }

            public string element { get; set; }
            public string contents { get; set; }
            public Positions position { get; set; }

            #region IRjsActionBase Members

            public string Render()
            {
                if (!contents.StartsWith("""))
                    contents = """ + contents + """;

                return string.Format("Element.insert("{0}", {{{1}: {2} }});", element, position, contents);
            }

            #endregion
        }

        private class RjsStringAction : IRjsActionBase
        {
            public string contents { get; set; }

            public RjsStringAction(string contents)
            {
                this.contents = contents;
            }

            #region IRjsActionBase Members

            public string Render()
            {
                return this.contents;
            }

            #endregion
        }

        private class RjsShowAction : IRjsActionBase
        {
            public RjsShowAction(string element)
            {
                this.element = element;
            }
            public string element { get; set; }

            #region IRjsActionBase Members

            public string Render()
            {
                return string.Format("$("{0}").show();", element);
            }

            #endregion
        }

        private class RjsAlertAction : IRjsActionBase
        {
            public RjsAlertAction(string message)
            {
                this.message = message;
            }
            public string message { get; set; }

            #region IRjsActionBase Members

            public string Render()
            {
                return string.Format("alert('{0}');", message);
            }

            #endregion
        }

        private class RjsHideAction : IRjsActionBase
        {
            public RjsHideAction(string element)
            {
                this.element = element;
            }
            public string element { get; set; }

            #region IRjsActionBase Members

            public string Render()
            {
                return string.Format("$("{0}").hide();", element);
            }

            #endregion
        }

        private class RjsRemoveAction : IRjsActionBase
        {
            public RjsRemoveAction(string element)
            {
                this.element = element;
            }
            public string element { get; set; }

            #region IRjsActionBase Members

            public string Render()
            {
                return string.Format("$("{0}").remove();", element);
            }

            #endregion
        }

        private class RjsEffectAction : IRjsActionBase
        {
            public RjsEffectAction(string element, EffectTypes effect, KeyValuePair<string, string>[] options)
            {
                this.options = new Dictionary<string, string>();

                if (options != null)
                {
                    foreach (KeyValuePair<string, string> pair in options)
                        this.options.Add(pair.Key, pair.Value);
                }

                this.element = element;
                this.effect = effect;
            }

            public string element { get; set; }
            public EffectTypes effect { get; set; }
            public Dictionary<string, string> options { get; set; }

            #region IRjsActionBase Members

            public string Render()
            {
                string ret = string.Format("new Effect.{0}("{1}"", effect.ToString(), element);
                ret += ", {";

                foreach (string key in options.Keys)
                {
                    ret += string.Format("{0}: {1}", key, options[key]);
                }
                ret += "});";

                return ret;
            }

            #endregion
        }

        /// <summary>
        /// Call me from your controller and pass me your ControllerContext and I'll render a UserControl for you and return the contents as a string!
        /// </summary>
        /// <param name="userControl"></param>
        /// <param name="viewData"></param>
        /// <param name="controllerContext"></param>
        /// <returns></returns>
        private string RenderPartialToString(string userControl, object viewData, ControllerContext controllerContext)
        {
            HtmlHelper h = new HtmlHelper(new ViewContext(controllerContext, new WebFormView("omg"), null, null), new ViewPage());
            var blockRenderer = new BlockRenderer(controllerContext.HttpContext);

            var r = new RjsResult();
            JavaScriptSerializer serializer = new JavaScriptSerializer();
            string s = blockRenderer.Capture(
                () => RenderPartialExtensions.RenderPartial(h, userControl, viewData)
            );

            return serializer.Serialize(s);
        }
        #endregion

        #region Fun Enums
        public enum Positions
        {
            Before = 1,
            After,
            Top,
            Bottom
        }

        public enum EffectTypes
        {
            Appear = 1,
            Fade,
            SlideDown,
            SlideUp,
            Shake,
            Highlight
        }
        #endregion

        #region Rjs Methods
        /// <summary>
        /// Renders a user control and inserts the contents into specified element
        /// </summary>
        /// <param name="element"></param>
        /// <param name="p"></param>
        /// <param name="userControl"></param>
        /// <param name="viewData"></param>
        /// <param name="controllerContext"></param>
        public void Insert(string element, Positions p, string userControl, object viewData, ControllerContext controllerContext)
        {
            Insert(element, p, RenderPartialToString(userControl, viewData, controllerContext) );
        }

        /// <summary>
        /// Inserts content into specific element
        /// </summary>
        /// <param name="element"></param>
        /// <param name="p"></param>
        /// <param name="contents"></param>
        public void Insert(string element, Positions p, string contents)
        {
            Actions.Add(new RjsInsertAction(element, p, contents));
        }

        /// <summary>
        /// Update a part of the page by rendering a user control
        /// </summary>
        /// <param name="element"></param>
        /// <param name="userControl"></param>
        /// <param name="viewData"></param>
        /// <param name="controllerContext"></param>
        public void Update(string element, string userControl, object viewData, ControllerContext controllerContext)
        {
            Update(element, RenderPartialToString(userControl, viewData, controllerContext));
        }

        public void Update(string element, string contents)
        {
            Actions.Add(new RjsUpdateAction(element, contents));
        }

        /// <summary>
        /// Alert some text
        /// </summary>
        /// <param name="message"></param>
        public void Alert(string message)
        {
            Actions.Add(new RjsAlertAction(message));
        }

        /// <summary>
        /// Render some javascript code.. whatever you want
        /// </summary>
        /// <param name="contents"></param>
        public void RenderString(string contents)
        {
            Actions.Add(new RjsStringAction(contents));
        }

        /// <summary>
        /// Shows an element
        /// </summary>
        /// <param name="element"></param>
        public void Show(string element)
        {
            Actions.Add(new RjsShowAction(element));
        }

        /// <summary>
        /// Hides an element
        /// </summary>
        /// <param name="element"></param>
        public void Hide(string element)
        {
            Actions.Add(new RjsHideAction(element));
        }

        /// <summary>
        /// Delets an element
        /// </summary>
        /// <param name="element"></param>
        public void Remove(string element)
        {
            Actions.Add(new RjsHideAction(element));
        }

        /// <summary>
        /// Calls an effect of type EffectTypes, also takes array of prototype options
        /// </summary>
        /// <param name="element"></param>
        /// <param name="effect"></param>
        /// <param name="options"></param>
        public void Effect(string element, EffectTypes effect, KeyValuePair<string, string>[] options)
        {
            Actions.Add(new RjsEffectAction(element, effect, options));
        }
        public void Effect(string element, EffectTypes effect)
        {
            Actions.Add(new RjsEffectAction(element, effect, null));
        }

        #endregion

        #region ActionResult Override
        public override void ExecuteResult(ControllerContext context)
        {
            if (context == null)
            {
                throw new ArgumentNullException("context");
            }

            HttpResponseBase response = context.HttpContext.Response;

            if (this.SetContentType)
                response.ContentType = "application/javascript";

            string result = string.Empty;

            foreach (IRjsActionBase action in Actions)
            {
                result += action.Render();
            }

            response.Write(result);
        }
#endregion
    }
}

One Major Gotcha

A trick to getting this to work with Prototype’s Ajax.Request is the ContentType of the returned data needs to be of type “text/javascript” in order for it to automatically be evaluated as javascript. However, you can get around this by forcing Prototype to evaluate the response data as javascript via the EvalJS: ‘force’ parameter.

So that’s it. A working way to RenderPartial to a string on the server-side and also the startings of a Rails-like RJS framework for ASP.NET MVC!

Thoughts, comments, patches or otherwise are appreciated!