2
votes

I'm just looking for a nice way how to return JSON object from server that contains html of partial view and some additional data if needed.

I'm using approach by Tim Scott to render partialview as string (with some modifications - made it to be viewenginescollection aware, made it to understand difference between view and partial view, put it into StringResult class and wrapped around with controller extension method).

Here's what causes trouble:

public static string RenderViewToString(ControllerContext controllerContext,
            IView view, ViewDataDictionary viewData, TempDataDictionary tempData)
        {
            Stream filter = null;
            ViewPage viewPage =
               new ViewPage
               {
                   ViewContext = new ViewContext
                       (controllerContext, view, viewData, tempData)
               };
            //Right, create our view

            //Get the response context, flush it and get the response filter.
            var response = viewPage.ViewContext.HttpContext.Response;
            response.Flush();
            var oldFilter = response.Filter;

            try {
                //Put a new filter into the response
                filter = new MemoryStream();
                response.Filter = filter;

                //Now render the view into the memorystream and flush the response
                viewPage.ViewContext.View.Render(viewPage.ViewContext, viewPage.ViewContext.HttpContext.Response.Output);
                response.Flush();

                //Now read the rendered view.
                filter.Position = 0;
                var reader = new StreamReader(filter, response.ContentEncoding);
                return reader.ReadToEnd();
            }
            finally {
                //Clean up.
                if (filter != null) {
                    filter.Dispose();
                }

                //Now replace the response filter
                response.Filter = oldFilter;
            }
        }

usage looks like this:

var v = this.ViewResultToString(PartialView("_Foo", foo));
return Json(new {Html = Server.HtmlEncode(v), Bar = foo.Bar});

However - this throws an exception:

Server cannot set content type after HTTP headers have been sent.

Here's a stack trace:

[HttpException (0x80004005): Server cannot set content type after HTTP headers have been sent.] System.Web.HttpResponse.set_ContentType(String value) +8760264 System.Web.HttpResponseWrapper.set_ContentType(String value) +11 System.Web.Mvc.JsonResult.ExecuteResult(ControllerContext context) +131 System.Web.Mvc.ControllerActionInvoker.InvokeActionResult(ControllerContext controllerContext, ActionResult actionResult) +10 System.Web.Mvc.<>c__DisplayClass11.b__e() +20 System.Web.Mvc.ControllerActionInvoker.InvokeActionResultFilter(IResultFilter filter, ResultExecutingContext preContext, Func1 continuation) +255 System.Web.Mvc.<>c__DisplayClass13.<InvokeActionResultWithFilters>b__10() +20 System.Web.Mvc.ControllerActionInvoker.InvokeActionResultWithFilters(ControllerContext controllerContext, IList1 filters, ActionResult actionResult) +179 System.Web.Mvc.ControllerActionInvoker.InvokeAction(ControllerContext controllerContext, String actionName) +395 System.Web.Mvc.Controller.ExecuteCore() +123 System.Web.Mvc.ControllerBase.Execute(RequestContext requestContext) +23 Company.UI.Project.Controllers.Base.BaseController.Execute(RequestContext requestContext) in c:\Project\Controllers\Base\BaseController.cs:109 System.Web.Mvc.ControllerBase.System.Web.Mvc.IController.Execute(RequestContext requestContext) +7 System.Web.Mvc.MvcHandler.ProcessRequest(HttpContextBase httpContext) +144 System.Web.Mvc.MvcHandler.ProcessRequest(HttpContext httpContext) +54 System.Web.Mvc.MvcHandler.System.Web.IHttpHandler.ProcessRequest(HttpContext httpContext) +7 System.Web.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute() +181 System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously) +75

Thing is - whenever i call RenderViewToString, an exception will be thrown if I'm going to return any other ActionResult that's not ContentResult.

So - is there another nice way how to serialize anonymous object into Json without 3rd party tools
OR
how to make this approach to work (how to kill that damn exception,
what exactly forces those headers to be sent)?

2
I think MVC caches output, so you might call Response.Clear() and even clear the Response.Headers too after you get that string in memory.Josh Pearce
@Josh maybe I'm doing something wrong - but it didn't help.Arnis Lapsa

2 Answers

2
votes

Got it working using this.

This was the problem:

In the first case, intercepting the output to HttpResponse using a "capturing filter" forces you to flush the output before the whole view is rendered and, since the original HttpResponse object is used, doesn't allow you to change content encoding, mime type or add headers after the partial view has been rendered.

Solution - not to use 'flushing' technique to render partial view.

1
votes

Seems to me like your RenderViewToString should be creating a ViewUserControl rather than a ViewPage.

Do note that what you're doing will make the OnResultExecuting on any Action filters happen after the view is rendered!