Setting masterName on MVC view causes error when calling RenderAction

1.4k Views Asked by At

I have a Masterpage (site.master) that calls a View using RenderAction. At the moment the View returns "hello world".

site.master:

<%@ Master Language="C#" Inherits="System.Web.Mvc.ViewMasterPage" %>

<html>
<head id="Head1" runat="server">
   <title><asp:ContentPlaceHolder ID="ContentPlaceHolder1" runat="server" /></title>
   <asp:ContentPlaceHolder ID="ContentPlaceHolder2" runat="server" />
</head>

<body>
    <% Html.RenderAction("Test", "Charts"); %>

    <asp:ContentPlaceHolder ID="ContentPlaceHolder3" runat="server" >
      <p>site.master</p>
    </asp:ContentPlaceHolder>

    <asp:ContentPlaceHolder ID="ContentPlaceHolder4" runat="server" />  
</body>
</html>

Test.aspx:

<%@ Page Language="C#" Inherits="System.Web.Mvc.ViewPage<dynamic>" %>

hello world!

ChartsController.cs:

public ActionResult Test()
{
    return View();
}

If I update the View to pass in the name of the Masterpage explicitly I get an error when I call RenderAction.

ChartsController.cs:

public ActionResult Test()
{
    return View(null, "site");
}

Error:

Content controls have to be top-level controls in a content page or a nested master page that references a master page.

Stack Trace:

[HttpException (0x80004005): Content controls have to be top-level controls in a content page or a nested master page that references a master page.]
   System.Web.UI.MasterPage.CreateMaster(TemplateControl owner, HttpContext context, VirtualPath masterPageFile, IDictionary contentTemplateCollection) +8857854
   System.Web.UI.Page.get_Master() +54
   System.Web.UI.Page.ApplyMasterPage() +15
   System.Web.UI.Page.PerformPreInit() +45
   System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint) +328

How do I go about setting the master page that I want the view to use? Ultimately I will be setting the Masterpage dynamically using a custom ViewEngine (by overriding VirtualPathProviderViewEngine.FindView).

if ( String.IsNullOrEmpty(masterName) ){ masterName = "site"; }

When I set the masterName property in my ViewEngine and then call RenderAction from site.master I get the same error as when I set the masterName property in the Action.

I am using:
Visual Studio 2010
MVC 3
IIS Express

edited: added full site.master markup

2

There are 2 best solutions below

0
On BEST ANSWER

I have come up with a solution and at least a partial answer/understanding to the cause of my issue.

If I am correct the issue is that I as trying to set the Masterpage on a view that didn't have/need a Masterpage. I think that the result was that I was setting a path to a Masterpage and even though that Masterpage exists the View was not expecting it and through an error.

When I updated the View to use a Masterpage I was able to pass the name of the Masterpage directly to the View without an error.

Test.aspx:

<%@ Page Language="C#" MasterPageFile="~/Views/Shared/Charts.Master" Inherits="System.Web.Mvc.ViewPage<dynamic>" %>

<asp:Content ID="Content1" ContentPlaceHolderID="ContentPlaceHolder1" runat="server">
    hello world!
</asp:Content>

The way that I resolved this in my custom ViewEngine was to check if the current View is a ChildAction.

if (String.IsNullOrEmpty(masterName) && !controllerContext.IsChildAction)
{
    masterName = "site";
}
0
On

Subclass ViewPage<T> and override the OnPreInit() event. In your override,

protected override void OnPreInit(EventArgs e)
{
    this.MasterLocation = GetMasterLocation();
    base.OnPreInit(e);
}

The GetMasterLocation() method should get the filename of the view (beginning with a "~/").

The error comes from the CreateMaster method in MasterPage, the code that throws it is:

if (masterPageFile == null)
{
    if ((contentTemplateCollection != null) && (contentTemplateCollection.Count > 0))
    {
        throw new HttpException(SR.GetString("Content_only_allowed_in_content_page"));
    }
    return null;
}

So, the MasterPage doesn't exist and content template collection has templates, so the page throws an exception. Following the above instructions will programmatically set the masterpage's location (which gets processed into the virtual path which is the variable masterPageFile)