Modifying MVC 3 ViewBag in a partial view does not persist to the _Layout.cshtml
Asked Answered
F

10

44

I am using MVC 3 with the Razor view engine. I want to set some values in the ViewBag inside a Partial View and want retrieve those values in my _Layout.cshtml. For example, when you setup a default ASP.NET MVC 3 project you get a _Layout.cshtml file in the "/Views/Shared" folder. In that _Layout.cshtml the Page Title is set like this:

<title>@ViewBag.PageTitle</title>

Then in "/Views/Home/About.cshtml" view the contents of the ViewBag are modified:

@{
    ViewBag.Title = "About Us";
}

This works fine. When the About view is rendered the page title is "About Us". So, now I want to render a Partial view inside my About view and I want to modify the ViewBag.Title inside my Partial view. ("/Views/Shared/SomePartial.cshtml")

@Html.Partial("SomePartial")

In this Partial view I have this code:

@{
    ViewBag.Title = "About Us From The Partial View";
}

When I debug this code I see the ViewBag.Title get set to "About Us" and then in the Partial view I see it get reset to "About Us From The Partial View", but when the code hits the _Layout.cshtml it goes back to "About Us".

Does this mean that if the contents of the ViewBag are modified in a Partial view, those changes will not appear(be accessible) in the main view (About.cshtml) or the _Layout.cshtml?

Thanks in advance!

Faison answered 11/2, 2011 at 21:3 Comment(2)
See this solution for a per-request singleton as an alternative to the one listed above, which works really well: #195499Speak
Not 100% sure, but I'm guessing you can't change ViewBag variables because based on the MVC model in .Net, partials are more like smaller, reusable components rather than full blown views. Views can use partials to add (render) those components to themselves but not the other way around. Views can also pass ViewModels to partials to supply them with data they need to be rendered. I know this isn't very constructive, but my gut feeling is that if you're trying to change the ViewBag variables from a partial then you're probably doing something wrong.Intestinal
N
13

I also had this problem, and couldn't find any neat and obvious solution.

The solution I came up with was to implement an Html extension method that returns a 'PageData' class that you define, containing whatever data you need:

    [ThreadStatic]
    private static ControllerBase pageDataController;
    [ThreadStatic]
    private static PageData pageData;

    public static PageData GetPageData(this HtmlHelper html) {
        ControllerBase controller = html.ViewContext.Controller;
        while (controller.ControllerContext.IsChildAction) {
            controller = controller.ControllerContext.ParentActionViewContext.Controller;
        }
        if (pageDataController == controller) {
            return pageData;
        } else {
            pageDataController = controller;
            pageData = new PageData();
            return pageData;
        }
    }

It finds the top-level controller for the current request, and returns the same PageData object every time the method is called within the same HTTP request. It creates a new PageData object the first time it is called in a new HTTP request.

Nonbeliever answered 12/2, 2011 at 21:46 Comment(2)
I'm kind of surprised that ASP.NET MVC does not offer a better way to do this. All I can think of is that what we are trying to do breaks the MVC pattern, but we all know that in the real world this happens. I found a way to go around this requirement in my current project so I will not get a chance to try out your solution. But thank you for the input. I will mark it as the accepted answer and hopefully it will help someone else. -ThanksFaison
I've tried this and it doesn't work. I made a simple class called PageData (not shown in your answer) with 1 string property. If you set this property of PageData in your partial view, it is null in your _Layout because the _Layout accesses the property BEFORE it is set in the partial.Sickroom
I
34

If you pass the ViewBag into the partial's viewdatadictionary, then pull it out (and cast), you can do whatever you want and the reference is kept. The cool part is that since it's dynamic, you can even add properties and then they'll show up on the parent page's Viewbag.

Page:

//set the viewbag into the partial's view data
@{Html.RenderPartial("Elaborate", Model, new ViewDataDictionary { {"vb", ViewBag}});}

Partial:

@{
   var vb = ((dynamic)ViewData["vb"]);
   vb.TheTitle = "New values";
 }

Page

@ViewBag.TheTitle = "New value"
Ivette answered 2/3, 2011 at 15:55 Comment(2)
Alternatively, you can add it to the Page's view bag directly. i.e. in Page "ViewBag.PageBag = ViewBag;" then in the partial "ViewBag.PageBag.Title = "New value""Flatus
This is the only thing i found to work for when i need to set a page title in my partial view for display in my layout head tag.Holocaine
N
13

I also had this problem, and couldn't find any neat and obvious solution.

The solution I came up with was to implement an Html extension method that returns a 'PageData' class that you define, containing whatever data you need:

    [ThreadStatic]
    private static ControllerBase pageDataController;
    [ThreadStatic]
    private static PageData pageData;

    public static PageData GetPageData(this HtmlHelper html) {
        ControllerBase controller = html.ViewContext.Controller;
        while (controller.ControllerContext.IsChildAction) {
            controller = controller.ControllerContext.ParentActionViewContext.Controller;
        }
        if (pageDataController == controller) {
            return pageData;
        } else {
            pageDataController = controller;
            pageData = new PageData();
            return pageData;
        }
    }

It finds the top-level controller for the current request, and returns the same PageData object every time the method is called within the same HTTP request. It creates a new PageData object the first time it is called in a new HTTP request.

Nonbeliever answered 12/2, 2011 at 21:46 Comment(2)
I'm kind of surprised that ASP.NET MVC does not offer a better way to do this. All I can think of is that what we are trying to do breaks the MVC pattern, but we all know that in the real world this happens. I found a way to go around this requirement in my current project so I will not get a chance to try out your solution. But thank you for the input. I will mark it as the accepted answer and hopefully it will help someone else. -ThanksFaison
I've tried this and it doesn't work. I made a simple class called PageData (not shown in your answer) with 1 string property. If you set this property of PageData in your partial view, it is null in your _Layout because the _Layout accesses the property BEFORE it is set in the partial.Sickroom
M
8

The partial view gets its own ViewBag.

You can get the page's ViewBag from ((WebViewPage) WebPageContext.Current.Page).ViewBag

Mimamsa answered 11/2, 2011 at 21:17 Comment(2)
That didn't work. I tried using that code to modify the ViewBag in the Partial view, but when I access the ViewBag in the _Layout.cshtml I still have the value that was set in the "About" view.Faison
This doesn't work (in MVC 4 at least). The partial view has a different WebPageContext.Current.Page object (it has one specific to the partial.Flatus
M
7

You can do this trick in your partial view to override the title in your _Layout.cshtml:

@{
    ViewBag.Title = "About Us From The Partial View";
}

......

<script type="text/javascript">
    document.title = "@ViewBag.Title";
</script>
Melancholia answered 14/11, 2012 at 19:40 Comment(1)
This does not work for SEO, since it needs to be in the rendered markupContusion
H
4

As others have pointed out Layout, Views and Partials get their own ViewBag. However, I was able to get it to work with the following: In the View or Partial.

@{ Html.ViewContext.ViewBag.Title = "Reusable Global Variable"; }

Then in _Layout

@Html.ViewContext.ViewBag.Title

By explicitly using the ViewContext, the views 're-use' the same ViewBag.

Hypothermal answered 12/6, 2012 at 0:30 Comment(0)
H
3

If anyone is still looking for a solution to this it appears that you can do it with TempData:

TempData["x"] = x;

TempData is persisted until it is read so you can just read it in your _Layout. You just have to be careful that you read everything so that it is cleared for the next request.

Halvorsen answered 9/5, 2017 at 11:51 Comment(0)
J
1

try @SLaks code with

(((WebViewPage) WebPageContext.Current.Page).ViewBag).PropertyName
Jame answered 7/6, 2011 at 3:13 Comment(0)
G
1

I've tried the following and it works:

In the (parent) view...

@Html.Partial("SomePartial", ViewData, null)

Note: ViewData is passed as the model argument, but you have to specify null for the viewData argument to use the correct overload. You can't use ViewBag because Html.Partial doesn't like dynamics.

Then , in the partial view...

@model ViewDataDictionary
@{
    Model["Title"] = "About us from the partial view";
}

Of course, if you need to use the model argument for a real model, you'll have to be more creative.

Gustafson answered 14/1, 2013 at 14:56 Comment(0)
N
-1

I encountered the same problem when I use mvc3, and I found that

this.ViewBag.ViewBag.PropertyName 

works in your custom control.

Nottage answered 27/10, 2011 at 1:0 Comment(0)
S
-1

I this is what page data is designed for. Pop this into your view.

@Page.somePropertyName = "Whatever you want";

And then access it in your layout view. Be sure to check that its not null first.

@{
    if(Page.somePropertyName != null)
    {
       //Do stuff
    }
}
Susannahsusanne answered 20/6, 2013 at 15:33 Comment(1)
Unfortunately @Page has the same problem as ViewData: changes made to it in the partial view don't persist down to the layout. See: #5127784Ethmoid

© 2022 - 2024 — McMap. All rights reserved.