RenderPartialViewToString and async methods in ASP.NET MVC 5
Methods like RenderPartialViewToString
can be extremely useful when trying to render HTML partial views in JSON results. However, they don't play well with async
methods.
My reasoning is it has something to do with the thread-marshaling-black-magic ASP.NET seems to be doing with it comes to anything async
in ASP.NET.
Anyhow, here's what a typical RenderPartialViewToString
method looks like, taken from one of our project:
public string RenderPartialViewToString(string viewName, object model)
{
if (string.IsNullOrEmpty(viewName))
viewName = ControllerContext.RouteData.GetRequiredString("action");
ViewData.Model = model;
using (var sw = new StringWriter())
{
var viewResult = ViewEngines.Engines.FindPartialView(ControllerContext, viewName);
var viewContext = new ViewContext(ControllerContext, viewResult.View, ViewData, TempData, sw);
viewResult.View.Render(viewContext, sw);
return sw.GetStringBuilder().ToString();
}
}
No problem here per say, but when it's used to feed a JSON result inside an async
method, everything is done from within the controller action. That means, the view is actually rendering from within the action too, but on what thread? What worker? What context? Only ASP.NET's guts seem to hold the answer to that.
public async Task<ActionResult> MyAction()
{
await Something();
return Json(new { Html = RenderPartialViewToString("MyView") });
}
One way around the problem is simply to defer the execution of the RenderPartialViewToString
in an ActionResult
:
public class DeferredResult : ActionResult
{
private readonly Func<ActionResult> Func;
public DeferredResult(Func<ActionResult> func)
{
Func = func;
}
public override void ExecuteResult(ControllerContext context)
{
Func().ExecuteResult(context);
}
}
We can then simply use it like so:
public async Task<ActionResult> MyAction()
{
await Something();
return new DeferredResult(() => Json(new { Html = RenderPartialViewToString("MyView") }));
}
We can make it even slicker if you have access to a base controller class:
public class MyBaseController : Controller
{
protected ActionResult Deferred(Func<ActionResult> func)
{
return new DeferredResult(func);
}
}
The final result:
public async Task<ActionResult> MyAction()
{
await Something();
return Deferred(() => Json(new { Html = RenderPartialViewToString("MyView") }));
}
No new comments are allowed on this post.
Comments
No comments yet. Be the first!