Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ActionResult to String

I know there are a lot of questions similar, but I didn't get the answer I wanted in any of them. I want to convert, as it is, an ActionResult to String, by doing something like that:

public ActionResult ProductDetail(int id)
{
    // ... lots of operations that include usage of ViewBag and a Model ...
    return View(product);
}

To show the product page to the client, but also I want to be able to send it by JSON, like:

public JsonResult ProductDetailJSON(int id)
{
    // ... lots of operations that include usage of ViewBag and a Model ...
    return Json(new {
            html = this.ProductDetail(id).ToString(),
            name = ""
            // ... more properties
        }, JsonRequestBehavior.AllowGet);
}

So I will invoke the Controller Action and get its response (a ActionResult, that is actually a ViewResult) and turn it into a String, but having it created using all the business code of the controller method (including, specially, ViewBag usage).

EDIT Clarification about why I want to to do it that way

I need to be able to present the product in the browser, so I create my Controller Action called ProductDetail, and works perfectly fine.

After that comes the need to have an API that gives, for a given product a JSON object with some meta-data (like sold units, name, etc.) and the View. One option was to send a link to the view and that's all... but the view MUST be inlined as a client requirement.

To avoid code repetition and to keep it clean, I'd like to invoke the method ProductDetail, and capture the HTML Raw response to put it inside the JSON response as an String.

I've seen others rendering the View with methods like this one:

public string RenderRazorViewToString(string viewName, object model)
{
    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);
        viewResult.ViewEngine.ReleaseView(ControllerContext, viewResult.View);
        return sw.GetStringBuilder().ToString();
    }
}

But that way does not execute the controller logic.

like image 343
Vicenç Gascó Avatar asked Feb 12 '23 21:02

Vicenç Gascó


1 Answers

Here is a working solution.

Create new classes:

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();
        }
    }
}

in your json method do this:

public JsonResult ProductDetailJSON(int id)
{
    // ... lots of operations that include usage of ViewBag and a Model ...
    return Json(new {
            // ControllerContext - modify it so it accepts Id
            html = View("ProductDetail").Capture(ControllerContext),
            name = ""
            // ... more properties
        }, JsonRequestBehavior.AllowGet);
}
like image 163
aleha Avatar answered Feb 15 '23 11:02

aleha