Asp.Net MVC2 RenderAction changes page mime type?
Asked Answered
M

6

14

It appears that calling Html.RenderAction in Asp.Net MVC2 apps can alter the mime type of the page if the child action's type is different than the parent action's.

The code below (testing in MVC2 RTM), which seems sensible to me, will return a result of type application/json when calling Home/Index. Instead of dispylaying the page, the browser will barf and ask you if you want to download it.

My question: Am I missing something? Is this a bug? If so, what's the best workaround?

controller:

public class HomeController : Controller
{
    public ActionResult Index()
    {
        ViewData[ "Message" ] = "Welcome to ASP.NET MVC!";

        return View();
    }

    [ChildActionOnly]
    public JsonResult States()
    {
        string[] states = new[] { "AK", "AL", "AR", "AZ", };

        return Json(states, JsonRequestBehavior.AllowGet);
    }
}

view:

<h2><%= Html.Encode(ViewData["Message"]) %></h2>
<p>
    To learn more about ASP.NET MVC visit <a href="http://asp.net/mvc" title="ASP.NET MVC Website">http://asp.net/mvc</a>.
</p>
<script>
  var states = <% Html.RenderAction("States"); %>;
</script>
Module answered 15/3, 2010 at 15:25 Comment(0)
L
8

It's not a bug. The JsonResult type is supposed to set the result to JSON, because that's usually what you want.

You don't really want a JSON result here, you want a JSON string. So why not just write that?

[NonAction]
public string States()
{
    string[] states = new[] { "AK", "AL", "AR", "AZ", };

    return new JavaScriptSerializer().Serialize(states);
}
Lemuellemuela answered 15/3, 2010 at 15:47 Comment(3)
this will work... it just doesn't seem like a child action should be able to change the ContentType of the whole page.Module
Note that JavaScriptSerializer().Serialize does not serialize embedded quotes correctly, unlike the Json method. You'll need something like: Func<string, string> safeForJson = (s) => { return s.NotNull().Replace("\"", "\\\""); };Sensate
Sorry, NotNull method is mine but just makes sure the input string is not null (natch).Sensate
D
9

I Consider this a bug. If this is a child action being rendered, why it would change the parent action response? The same happens with Html.Action, which renders it into a string. My workaround is doing:

Html.ViewContext.HttpContext.Response.ContentType = "text/html";

after calling Html.Action. I suppose someone could write a wrapper Html Helper extension, something like:

var aux = Html.ViewContext.HttpContext.Response.ContentType;
Html.Action(....); // or Html.RenderAction(...)
Html.ViewContext.HttpContext.Response.ContentType = aux;
Driskell answered 16/11, 2010 at 19:19 Comment(0)
L
8

It's not a bug. The JsonResult type is supposed to set the result to JSON, because that's usually what you want.

You don't really want a JSON result here, you want a JSON string. So why not just write that?

[NonAction]
public string States()
{
    string[] states = new[] { "AK", "AL", "AR", "AZ", };

    return new JavaScriptSerializer().Serialize(states);
}
Lemuellemuela answered 15/3, 2010 at 15:47 Comment(3)
this will work... it just doesn't seem like a child action should be able to change the ContentType of the whole page.Module
Note that JavaScriptSerializer().Serialize does not serialize embedded quotes correctly, unlike the Json method. You'll need something like: Func<string, string> safeForJson = (s) => { return s.NotNull().Replace("\"", "\\\""); };Sensate
Sorry, NotNull method is mine but just makes sure the input string is not null (natch).Sensate
T
4

You're not missing something (unless I am too) and I think this is a bug. I have the same issue in ASP.NET MVC3.

We have a controller action which returns content from a simple content managment system. The CMS allows the user to define the content type of what is returned (for example text/plain or text/xml).

The controller action is either called directly, or called as a child action to allow a view to contain content managed elements.

If a piece of content is created with a content type of "text/plain", and this is embedded on an ASP.NET MVC view, the content type of the parent is overridden and the browser displays HTML.

Gabe, I think you've hit the nail on the head in that there does not appear to be a scenario where the child action overriding the parent is a desirable outcome.

My solution is to branch on ControllerContext.IsChildAction and construct my own return object, but this in my opinion is something that should be handled by the framework.

I'm sure you're aware of this, but in your case I would suggest explicitly setting JsonResult.ContentType to the content type of the parent.

Timorous answered 14/10, 2011 at 15:41 Comment(0)
M
3

This can be solved by explicitly forcing the mime type "back" to text/html:

return Json(states, "text/html", JsonRequestBehavior.AllowGet);

It doesn't seem like this should be necessary, though.

Module answered 15/3, 2010 at 15:47 Comment(2)
That doesn't so much solve the issue as hide it. JsonResult still changes ContentType here; it just changes it to what you (currently!) expect it to be.Lemuellemuela
and for me it just returns a text file with the Json result on it... Is it just me or is this garbage incredibly buggy... Who wrote this crap anyway?Suricate
F
1

Like Craig Stuntz said the content type is supposed to change.

A better approach would be calling that action with AJAX and then assigning the returned object to the states variable in the JavaScript code.

Footbridge answered 15/3, 2010 at 16:0 Comment(2)
Can you suggest a scenario in which this behavior (child action changing page's ContentType) is actually what you want to happen?Module
@Gabe Moothart, I can't think of a scenario where you'd want that to happen. But the thing is, the problem is not the change of the ContentType. The problem is calling a Json Result action while rendering an HTML view. JsonResult sets the request objects ContentType property to application/json like it's supposed to. But unfortunately, since you call that from an HTML view, the original response's type changes as you see.Birth
C
1

I had the problem today. The reason was I need to reuse an existing child action to populate some json data on the page so that unnecessary ajax requests can be avoided.

Based on Jamie and Niv's idea, I created following helper method.

public static MvcHtmlString ChildAction( this HtmlHelper htmlHelper, ActionResult result )
{
   var aux = htmlHelper.ViewContext.HttpContext.Response.ContentType;
   var actionResult = htmlHelper.Action( result );
   htmlHelper.ViewContext.HttpContext.Response.ContentType = aux;
   return actionResult;
}

Call Html.ChildAction instead of Html.Action when you need to use result of child action that returns json data.

Chessa answered 22/5, 2013 at 7:31 Comment(1)
Thanks, I like this workaround, it seems to be the cleanest. I would just rename the extension to for example "MimePreserveChildAction", so it's clear to other developers what the purpose of this method is.Schrick

© 2022 - 2024 — McMap. All rights reserved.