Question

I have an ASP.NET MVC 4 site organized into multiple areas. Each area has a Views/Shared/_Layout.cshtml view which references a common shared layout. In the common layout, I have a sidebar which contains a list of items. I would like the ability to have a shared list of items that can be accessed by all of the _Layout.cshtml views in order to aggregate a set of links.

Area1/Views/Shared/_Layout.cshtml:

@{
   SidebarItems.Add("Area1 Item");
   Layout = "~/Views/Shared/_Layout.cshtml";
}

Views/Shared/_Layout.cshtml:

@{
   SidebarItems.Add("Common Item");
}
<ul>
@foreach (var item in SidebarItems)
{
   <li>@item</li>    @* List should contain two items: "Area1 Item", and "Common Item" *@
}
</ul>

I have tried two approaches:

  1. Create a custom WebViewPage<T> class for each area that inherits from a common custom WebViewPage<T> class and make the SidebarItems collection a property of the common base class. This does not work as it appears that Razor allocates a new WebPageView when moving between layouts.

  2. Create a static class with a static collection that each _Layout calls to add items. This successfully accumulates the list items, but, since it's a static class, its lifetime is tied to the Application Domain, which means that the sidebar accumulates items from every area that is visited across multiple requests, rather than being a per-request list.

I am considering using the HttpRequest.Items property, but that seems like it would be too short-lived -- the list of items does not change across requests and is completely determined by the Area's view that is displayed.

The other alternative is to push the rendering of the list into a section that is rendered in each Area's _Layout. This is less than ideal, as I would like to have a single point in the code that renders the list, but is doable.

Suggestions?

Was it helpful?

Solution

You can try using the ViewBag for it.

I have done a quick test by adding a property named Items to the ViewBag. This will be populated from every area (by adding its own items) and from the main layout by adding the common items. It will then be used to render the list of items from the main layout.

Area1\Views\Shared_Layout.cshtml

@{
    ViewBag.Title = "_Layout";
    Layout = "~/Views/Shared/_Layout.cshtml";

    if (ViewBag.Items == null){ViewBag.Items = new List<String>();}
    ViewBag.Items.Add("Area1 item");
}

<h2>Area1 Layout</h2>

@RenderBody()

Views\Shared_Layout.cshtml (part of it)

@{
    if (ViewBag.Items == null){ViewBag.Items = new List<String>();}
    ViewBag.Items.Add("Common Item");
}
<ul>
@foreach (var item in ViewBag.Items)
{
    <li>@item</li>    @* List should contain two items: "Area1 Item", and "Common Item" *@
}
</ul>

I don't like much how that code looks, as it is repeating quite a bit and spreading the usage of ViewBag.Items. It could be cleaner by using Html helpers for adding the items to the list and rendering the list. For example, you could create the following 2 Html helpers:

public static class HtmlHelpers
{
    public static void AddCommonListItems(this HtmlHelper helper, params string[] values)
    {
        if(helper.ViewContext.ViewBag.Items == null) helper.ViewContext.ViewBag.Items=new List<String>();
        helper.ViewContext.ViewBag.Items.AddRange(values);
    }

    public static MvcHtmlString CommonList(this HtmlHelper helper)
    {
        if (helper.ViewContext.ViewBag.Items == null)
            return new MvcHtmlString(new TagBuilder("ul").ToString());

        var itemsList = new TagBuilder("ul");
        foreach (var item in helper.ViewContext.ViewBag.Items)
        {
            var listItem = new TagBuilder("li");
            listItem.SetInnerText(item);
            itemsList.InnerHtml += listItem.ToString();
        }
        return new MvcHtmlString(itemsList.ToString());

    }
}

And then your views would look cleaner, as they will just use those helpers and avoid repeating the code:

Area1\Views\Shared_Layout.cshtml (using new Html helpers)

@{
    ViewBag.Title = "_Layout";
    Layout = "~/Views/Shared/_Layout.cshtml";

    Html.AddCommonListItems("Area1 item", "Area1 item 2");
}

<h2>Area1 Layout</h2>

@RenderBody()

Views\Shared_Layout.cshtml (part of it, using new Html helpers)

@{Html.AddCommonListItems("Common Item");}
@Html.CommonList()
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top