Render View Inline

on Wednesday, May 6, 2009

For one reason or another I wanted to render a view within a controller in order to add the html output to a JSON reply. This isn’t the way ASP.NET MVC was designed, but it is a useful feature to add in.

Because ASP.NET wasn’t designed to run views from Controllers and manipulate/reuse the result after processing was finished, the ViewResult.ExecuteResult method does not return any handle to the html string produced.

You can get around this, by replacing the underlying HtmlTextWriter class which the ViewResult uses with a StringBuilder. Once ExecuteResult is finished, the properly formatted html will be available through the StringBuilder.

Here is a method which swaps in the StringBuilder and executes the View:

public static StringBuilder ExecuteResult( ViewResult view, ControllerContext context )
{
var responseWrapper = context.HttpContext.Response;
var response = ReflectionUtil.GetPrivateVariable( responseWrapper, "_httpResponse" );
var originalWriter = ReflectionUtil.GetPrivateVariable( response, "_writer" );

var builder = new StringBuilder( 1000 );
var writer = new HtmlTextWriter( new StringWriter( builder ) );

ReflectionUtil.SetPrivateVariable( response, "_writer", writer );
view.ExecuteResult( context );
ReflectionUtil.SetPrivateVariable( response, "_writer", originalWriter );

return builder;
}

ReflectionUtil.SetPrivateMethod is a wrapper for some of the common reflection usages (just to save a couple of keystrokes):

/// <summary>
/// Utility containing short-cut methods for the System.Reflection namespace.
/// </summary>
public class ReflectionUtil
{

/// <summary>
/// Changes the objects internal value.
/// </summary>
/// <param name="obj">Object to alter</param>
/// <param name="variableName">Private variable name</param>
/// <param name="newValue">Value to change the variable to.</param>
public static void SetPrivateVariable(
object obj,
string variableName,
object newValue
) {
SetInvoke(
obj,
variableName,
newValue,
BindingFlags.SetField | BindingFlags.NonPublic | BindingFlags.Instance
);
}

/// <summary>
/// Retrieves a private variables value using reflection.
/// </summary>
/// <param name="obj">Object to retrieve the value from.</param>
/// <param name="variableName">The variable to read.</param>
/// <returns>The variable's contents as an object.</returns>
public static object GetPrivateVariable(
object obj,
string variableName
) {
FieldInfo info = obj.GetType().GetField(
variableName,
BindingFlags.GetField | BindingFlags.NonPublic | BindingFlags.Instance
);

return info.GetValue( obj );
}

/// <summary>
/// Changes the objects internal value.
/// </summary>
/// <param name="obj">Object to alter</param>
/// <param name="variableName">Private variable name</param>
/// <param name="newValue">Value to change the variable to.</param>
public static void SetPrivateProperty(
object obj,
string variableName,
object newValue
) {
SetInvoke(
obj,
variableName,
newValue,
BindingFlags.SetProperty | BindingFlags.NonPublic | BindingFlags.Instance
);
}

/// <summary>
/// Changes the objects internal value.
/// </summary>
/// <param name="obj">Object to alter</param>
/// <param name="variableName">Private variable name</param>
/// <param name="newValue">Value to change the variable to.</param>
/// <param name="bindingFlags">The binding flags to look through.</param>
public static void SetInvoke(
object obj,
string variableName,
object newValue,
BindingFlags bindingFlags
) {
var t = obj.GetType();
var lastException = new Exception( "NULL" );

while( t != null ) {
try {
t.InvokeMember( variableName, bindingFlags, null, obj, new [] { newValue } );
return;
} catch( Exception e ) {
lastException = e;
t = t.BaseType;
}
}

if( lastException.Message != "NULL" ) throw lastException;
}

}

UPDATE: I ran into a posting by Mike Hadlow which was trying the same thing, but ran into a snag with the Response Headers being marked as written after ExecuteResult completed. He also has a way to get around the problem.


Technorati Tags: ,,


Creative Commons License
This site uses Alex Gorbatchev's SyntaxHighlighter, and hosted by herdingcode.com's Jon Galloway.