Asp.net-mvc – Dynamically Defined Content Sections in Razor Views

asp.net-mvcasp.net-mvc-3razor

I'm trying to implement a thought i had to allow user defined sections to be dynamically generated for my MVC 3 Razor site.

A template would look something like this

<div class="sidebar">
     @RenderSection("Sidebar", false)
</div>
<div class="content">
     @RenderSection("MainContent", false)
     @RenderBody()
</div>

Adding a view with the following code gives me the result I would expect

DefineSection("MainContent", () =>
{
    this.Write("Main Content");
});
DefineSection("Sidebar", () =>
{
    this.Write("Test Content");
});

Output:

<div class="sidebar">Test Content </div>
<div class="content">Main Content <p>Rendered body from view</p></div>

Looking at this it seemed easy enough to create a model
Dictionary<SectionName, Dictionary<ControlName, Model>>

var sectionControls = new Dictionary<string, Dictionary<string, dynamic>>();
        sectionControls.Add("MainContent", new Dictionary<string, dynamic>()
        { 
            {"_shoppingCart", cart}
        });
        sectionControls.Add("Sidebar", new Dictionary<string, dynamic>() 
        { 
            { "_headingImage", pageModel.HeadingImage },
            { "_sideNav", null }
        });
        pageModel.SectionControls = sectionControls;

So the above code declares two template sections ("MainContent" with a cart and a "Sidebar" with an image and a nav.

So now my view contains code to render the output like so

foreach(KeyValuePair<string,Dictionary<string,dynamic>> section in Model.SectionControls)
    {
        DefineSection(section.Key, () =>
        {
            foreach (KeyValuePair<string, dynamic> control in section.Value)
            {
                RenderPartialExtensions.RenderPartial(Html, control.Key, control.Value);
            }
        });
  }

Now when I run this code, both sections contain the same content! Stepping through the code shows the load path is as follows

Action Returns, Code above runs in View, LayoutTemlpate begins to load. when RenderSection is called for these two sections in the layout template, the view Runs again! What seems even stranger to me is that the end result is that the "HeadingImage" and "SideNav" end up in both the Sidebar and MainContent sections. The MainContent section does not contain the cart, it contains a duplicate of the sidebar section.

<div class="sidebar">
<h2><img alt=" " src="..."></h2>
..nav..
</div>
<div class="content">
<h2><img alt=" " src="..."></h2>
..nav..
<p>Rendered body from view</p>
</div>

Commenting out one of the two section definitions in the Controller causes the other one to be the only item (but it is still duplicated!)

Has anyone had this issue before or know what limitation could be causing this behavior?

Edit: Excellent. Thanks for the linkage as well! I'm hurting for the new version of resharper with razor support.

Best Answer

Your lambda expressions are sharing the same section variable.
When either lambda is called, the current value of the variable is the last section.

You need to declare a separate variable inside the loop.

foreach(KeyValuePair<string,Dictionary<string,dynamic>> dontUse in Model.SectionControls)
{
    var section = dontUse;

    DefineSection(section.Key, () =>
    {
        foreach (KeyValuePair<string, dynamic> control in section.Value)
        {
            RenderPartialExtensions.RenderPartial(Html, control.Key, control.Value);
        }
    });
}