Thursday, 11 November 2010

Render any ASP.NET MVC ActionResult to a string

I often see questions on the net about how to render a view to a string so it can be used somewhere.

My approach allows doing it without thinking about all the boilerplate code. Additionally not only the ViewResult can be rendered into a string but just about any type of the result. Here is example on how to return a JSON including the result of the view as additional information:

 

// Controller Action:
public JsonResult DoSomething() {
    var viewString = View("TheViewToRender").Capture(ControllerContext);
    return new JsonResult {
        JsonRequestBehavior = JsonRequestBehavior.AllowGet,
        Data = new {
            time = DateTime.Now,
            html = viewString
        }
    };
}

 

This can be done with 2 simple utility classes below. Just include them somewhere into your project.

 

    public class ResponseCapture : IDisposable {
        private readonly HttpResponseBase response;
        private readonly TextWriter originalWriter;
        private StringWriter localWriter;
        public ResponseCapture(HttpResponseBase response) {
            this.response = response;
            originalWriter = response.Output;
            localWriter = new StringWriter();
            response.Output = localWriter;
        }
        public override string ToString() {
            localWriter.Flush();
            return localWriter.ToString();
        }
        public void Dispose() {
            if (localWriter != null) {
                localWriter.Dispose();
                localWriter = null;
                response.Output = originalWriter;
            }
        }
    }
    public static class ActionResultExtensions {
        public static string Capture(this ActionResult result, ControllerContext controllerContext) {
            using (var it = new ResponseCapture(controllerContext.RequestContext.HttpContext.Response)) {
                result.ExecuteResult(controllerContext);
                return it.ToString();
            }
        }
    }

Enjoy and let me know if it works for you.

13 comments:

  1. This is a cleaner solution that others out there. Does anyone have an example if the Action also uses parameters?

    ReplyDelete
  2. You can write:

    View("ViewName").With("name", "Dima").With("likes", "Ruby").Capture(ControllerContext)


    with this simple extension method:

    public static ViewResult With(this ViewResult vr, string key, object value) {
    vr[key] = value;
    return vr;
    }

    ReplyDelete
    Replies
    1. Well actually I can't .
      Visual Studio says
      Cannot apply indexing with [] to an expression of type 'System.Web.Mvc.ViewResult'

      Delete
    2. Oops
      this is is how it works in MVC3.

      public static ViewResult With(this ViewResult vr, string key, object value)
      {
      vr.ViewData[key] = value;
      return vr;
      }

      Does someone know how to do handle this with the ViewBag instead of ViewData?

      Delete
  3. Hello
    I have a question. How can I do it in .NET Framework 3.5? in thi, Output property doesn't have a setter method.
    Could you help me please?

    ReplyDelete
  4. For the .NET 3.5 it will be needed to substitute the HttpContext on the controller and involves a bit more code, but still is possible. Have a look for an example

    ReplyDelete
  5. Thanks a lot, this helped!

    ReplyDelete
  6. It almost works..
    If my view contains "@Model" on the first line. This line will be also in the output.

    "@Model x.y" will be in the string as "x.y x.y"

    any ideas ?

    ReplyDelete
  7. Email me a sample project and I will have a look. Hard to say anything without seeing it.

    ReplyDelete
  8. After very long looking at the code, I noticed that I typed "@Model". it should be "@model".
    I didn't know model is case-sensitive.

    ReplyDelete
  9. Does this work with Razor?

    Compiler Error Message: CS1973: 'System.Web.Mvc.HtmlHelper' has no applicable method named 'TextBox' but appears to have an extension method by that name. Extension methods cannot be dynamically dispatched. Consider casting the dynamic arguments or calling the extension method without the extension method syntax.

    ReplyDelete
  10. Never mind. I found the problem. I miss the @model.

    ReplyDelete
  11. This is great stuff! YMMD!
    But does anyone have an idea, how I can apply this to calling ActionMethods on another controller?

    ReplyDelete