I'm sure there are a variety of ways to do this. I manually render the view from the controller, and then pass the rendered view back as part of my JSON response.
This preserves the responsibilities of each entity. Views are still located using the view engine and they can be reused. The controller knows little or nothing about the view beyond its name and model type.
Manual Rendering
public static class RenderHelper
{
public static string PartialView( Controller controller, string viewName, object model )
{
controller.ViewData.Model = model;
using( var sw = new StringWriter() )
{
var viewResult = ViewEngines.Engines.FindPartialView( controller.ControllerContext, viewName );
var viewContext = new ViewContext( controller.ControllerContext, viewResult.View, controller.ViewData, controller.TempData, sw );
viewResult.View.Render( viewContext, sw );
viewResult.ViewEngine.ReleaseView( controller.ControllerContext, viewResult.View );
return sw.ToString();
}
}
}
In your action method:
object model = null; // whatever you want
var obj = new {
someOtherProperty = "hello",
view = RenderHelper.PartialView( this, "_PartialName", model )
};
return Json( obj );
Note that I'm returning an anonymous type. You can return any (serializable) type you want, as long as it has a string property for the rendered view.
Testing
Testing an action that uses manual rendering requires a slight modification. This is due to rendering the view a bit earlier than it would be rendered in the MVC pipeline.
Manual Rendering
- Enter action method
- Render view explicitly <-- this will make it difficult to test the calling action
- Exit action method
Automatic Rendering
- Enter action method
- Create a view result
- Exit action method
- Process view result (thus rendering the view)
In other words, our manual rendering process kicks off a variety of other operations that make it difficult to test (such as interacting with the build manager to compile the view).
Assuming you wish to test the action method and not the actual contents of the view, you can check whether or not the code is executing in a hosted environment.
public static string PartialView( Controller controller, string viewName, object model )
{
// returns false from a VS 2013 unit test, true from IIS
if( !HostingEnvironment.IsHosted )
{
// return whatever you want here
return string.Empty;
}
// continue as usual
}
Checking HostingEnvironment.IsHosted
is inexpensive (under the hood, it is simply a null check).