Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Dynamically Defined Content Sections in Razor Views

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.

like image 343
DrewG Avatar asked Jun 14 '11 17:06

DrewG


People also ask

What is@ section in Razor?

@section is for defining a content are override from a shared view. Basically, it is a way for you to adjust your shared view (similar to a Master Page in Web Forms).

Which keyword of Razor syntax is used to render the View Content in the layout page as body Content?

If you're from Web Forms world, the easiest way to think of RenderBody is it's like the ContentPlaceHolder server control. The RenderBody method indicates where view templates that are based on this master layout file should “fill in” the body content.

What are razor views in MVC?

Razor is a markup syntax that lets you embed server-based code into web pages using C# and VB.Net. It is not a programming language. It is a server side markup language. Razor has no ties to ASP.NET MVC because Razor is a general-purpose templating engine. You can use it anywhere to generate output like HTML.

What is razor view in asp net core?

Razor markup is code that interacts with HTML markup to produce a webpage that's sent to the client. In ASP.NET Core MVC, views are .cshtml files that use the C# programming language in Razor markup.


1 Answers

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);
        }
    });
}
like image 148
SLaks Avatar answered Sep 18 '22 22:09

SLaks