Set ViewData inside a RenderPartial - asp.net-mvc

I had previously been using TempData to set things like a "body css class", which then pages and partials could override.
I've now moved over to ViewData after realising that TempData uses sessions, however setting a ViewDataDictionary's value inside the partial basically gets ignored when it gets back up to the page - and never propagates up the layout hierarchy.
I've tried calling "RenderPartial" from inside my Page, and using the override which allows me to specify the ViewData to pass over:
Layout:
Page:
#{
var cssClass = (ViewData["something"] != null) ? ViewData["something"].ToString() : "";
}
<body class="#cssClass">
Page:
#{
ViewData["something"] = "blah";
Html.RenderPartial("MyPartial", ViewData)
}
Partial:
#{
ViewData["something"] += " blah";
}
When I debug inside my Layout, I can see that ViewData["something"] is "blah" - the partial didn't set it correctly.
When I was using TempData this would work ok. I don't really want to go back to using TempData because of ASP session locking and its effect on concurrent requests.
Has anybody got this to work before? Am I overlooking something?
Thanks

So this is a one way propagation as you've discovered if you want to set data in partial and return to parent view you can use the HttpContext which is not very cool but it works:
Parent:
#{
HttpContext.Current.Items["Something"] = "blah";
Html.RenderPartial("_Partial");
}
#HttpContext.Current.Items["Something"];
Partial:
#{
HttpContext.Current.Items["Something"] = "somethingelse";
}
Outputs "somethingelse" in the parent.
Alternatively and the way it's typically done, if you're bypassing TempData, is via the parent model or a temp model:
Model:
public class MyTempModel
{
public string Something { get; set; }
}
Parent:
#{
var tmod = new MyTemModel()
{
Something = "blah"
};
Html.RenderPartial("_Partial", tmod);
}
#tmod.Something;
Partial:
#model MyTempModel
#{
tMod.Something = "somethingelse";
}
Outputs "somethingelse" in the parent.

Each view has its own ViewData. By default, Razor fills views further down the hierarchy with the ViewData of their parents, but this is one-way. For example, if you do something like:
SomeView.cshmtl
#{ ViewData["foo"] = "bar"; }
#Html.Partial("_SomePartial")
SomePartial.cshtml
#ViewData["foo"]
The result of the partial will be "bar" as you'd expect. But, if you did something like:
SomeView.cshtml
#Html.Partial("_SomePartial")
#ViewData["foo"]
_SomePartial.cshtml
#{ ViewData["foo"] = "bar"; }
Nothing would be printed, as ViewData["foo"] doesn't exist in the view data for the parent view, only in the view data for the partial.

Related

How can I use Html.RenderPartial to get value

I have this My view
#model WebSite.Models.mymodelModel
#{ ViewBag.Title = "AudioPlayer2"; }
<div>
#{Html.RenderAction("AudioPlayer", "mycontroller", new { storyId = 3027, className = "" });}
</div>
this is my complete view and
this method in my controller
public PartialViewResult Audioplayer2()
{
return PartialView();
}
after that it calls another method in controller which name is Audioplayer
but the probelm is I set StoryId static in
#{Html.RenderAction("AudioPlayer", "mycontroller", new { storyId = 3027, className = "" });}
but the correct way to get the storyId is from #model.str.StoryId but
The #model comes null all the time. So, how can I use Html.RenderPartial to get the #model.str.storyId which I can get from controller as
List<str> rtn = new List<str>();
rtn = GetLatestEpisodes(DateTime.Now.AddMonths(-3).ToString(), DateTime.Now.ToString(), 0, 1);
storyId is the rtn.stroryId
but until this moment I can't get it
You should access your model in the razor view with Model variable and not model (Note the capital M, it makes a difference) like so: Model.storyId
Also ensure in your controller you are passing the variable rtn to the view like so: return View(rtn);

ASP.NET MVC - Execute controller action without redirecting

I am trying to execute an action on a controller without redirecting to the associated view for that action. For a good example of what I am trying to achieve take a look at the music.xbox.com website. When you add a song to a selected playlist from a popup menu - the page just shows a notification without any redirect or refresh. how is this possible?
What I have is the following:
I have a _playlistPopupMenu partial view that renders the list of playlists as follows:
_PlaylistPopupMenu
#model List<OneMusic.Models.GetPlaylists_Result>
#if (Model.Count > 0)
{
<li style="height:2px" class="divider"></li>
foreach (var item in Model)
{
<li style="height:30px">#Html.DisplayFor(p => item.Name)
#Html.ActionLink(item.Name, "AddSong", "Playlist", new { playlistId = #item.PlaylistId, songId = 1 }, "")
</li>
}
}
The PlaylistController AddSong action is as follows:
public PartialViewResult AddSong(int? playlistId, int? songId)
{
if (ModelState.IsValid)
{
db.AddSongToPlaylist(playlistId, songId);
db.SaveChanges();
return PartialView("_AddToPlaylist", "");
}
return PartialView("_AddToPlaylist", "");
}
I am struggling with what to put in the _AddToPlaylist partial view which I think I need to be able to display a notification of some kind (Possiblly using PNotify add in for Bootstrap). MVC wants to always redirect to ../Playlist/AddSong?playlistId=1&songId=1
Any ideas on how to complete this last part of the problem would be great.
If you don't want "full page reloads" then you need to approach the problem slightly differently, using javascript to alter the page dynamically. A library such as JQuery might make manipulating the DOM a little easier.
Display the popup dynamically using javascript.
When the user hits OK/Submit on the popup, post the data back to the server using javascript, and have the controller you are posting to return some HTML.
Append the returned HTML block (partial view) to an existing div containing playlist tracks.
The most difficult part of this is the asynchronous post. Help with updating a div without reloading the whole page can be found in this question.
EDIT - Example
If you have a controller action (accepting POSTs) with the URL myapp.com/PlayList/AddSong/, then you'd set up JQuery to post to this URL. You'd also set up the data property with any form data which you'd like to post, in your case you'd add playistId and songId to the data property.
You'd then use the result of the AJAX query (HTML) and append it to the existing playlist HTML on the page. So assuming that you want to append the partial view's HTML to a div with ID playlistDiv, and assuming that your partial view returns HTML which is valid when appended to the existing playlist, then your javascript will look something like this:
var data = { playlistId: 1, songId: 1 };
$.ajax({
type: "POST",
url: 'http://myapp.com/PlayList/AddSong/',
data: data,
success: function(resultData) {
// take the result data and update the div
$("#playlistDiv").append(resultData.html)
},
dataType: dataType
});
Disclaimer: I can't guarantee that this code will work 100% (unless I write the program myself). There may be differences in the version of JQuery that you use, etc, but with a little tweaking it should achieve the desired result.
using System.Web.Mvc;
using System.Web.Mvc.Html;
public ActionResult Index()
{
HtmlHelper helper = new HtmlHelper(new ViewContext(ControllerContext, new WebFormView(ControllerContext, "Index"), new ViewDataDictionary(), new TempDataDictionary(), new System.IO.StringWriter()), new ViewPage());
helper.RenderAction("Index2");
return View();
}
public ActionResult Index2(/*your arg*/)
{
//your code
return new EmptyResult();
}
in your controller you must add bottom code:
public ActionResult Index(string msg)
{
if (Request.Url.ToString().Contains("yourNewExampleUrlWithOutRedirect.com"))
{
string html = "";
using (System.Net.WebClient client = new System.Net.WebClient())
{
client.Encoding = Encoding.UTF8;
html = client.DownloadString("https://NewExampleUrl.com/first/index?id=1");
}
Response.Write(html);
}
...
}
your view must be empty so you add bottom code
#{
ViewBag.Title = "sample title";
if (Request.Url.ToString().Contains("yourNewExampleUrlWithOutRedirect.com"))
{
Layout = null;
}else
{
Layout ="~/Views/Shared/_Layout.cshtml"
}
}
#if (Request.Url.ToString().Contains("yourNewExampleUrlWithOutRedirect.com")==false)
{
before view like :
<div>hello world</div>
}

MVC3 - RenderPartial inside RenderSection not working

I'm working on an MVC3/Razor page, and in my _layout I have
#RenderSection("relatedBooksContainer", false)
In another page I use that section with:
#section relatedBooksContainer
{
#{ Html.RenderPartial("~/Views/Shared/Bookshelf.cshtml", Model.Books);}
}
This doesn't work. From what I've read, RenderSection will only ever go one layer deep - it has no concept of the Html.RenderPartial in the related section and will just return a blank area. The workaround I read at http://forums.asp.net/t/1590688.aspx/2/10 is to use RenderPage and commit the returned HTML to a string, then outout that string in the render section...which works! That is, until I pass a model to the partial page, then it throws an error saying:
The model item passed into the
dictionary is of type
'TheBookshelf.ViewModels.BookshelfViewModel',
but this dictionary requires a model
item of type
'System.Collections.Generic.List`1[TheBookshelf.EntityModel.Book]'.
Anyone have any idea why this might be happening? Are there any other ways to achieve this?
Try the #Html.Partial instead
#section relatedBooksContainer
{
#{ Html.Partial("~/Views/Shared/Bookshelf.cshtml", Model.Books);}
}
The error message is regarding the type and return type of the Bookshelf from the model.
public IEnumerable<Book> Bookshelf()
{
var q = from book in bookshelf
select book;
IEnumerable<Book> myBooks = q.ToList<Book>();
return myBooks;
}
did the solution in the provided link work for you?
I couldn't get it to work. That is I couldn't get ViewData["MainView"] to pass the data from Layout.cshtml to partialview. This aparently is a feature as every view is supposed to have it own ViewData obj. It seems ViewData is not global like I have thought. So what I get in ViewData["MainView"] from Layout in my partial view is null......I eventually found a work around for this and was able to pass the page reference from Layout to Partialview via a #Html.Action call from Layout -> Controller -> PartialView. I was able to get my partialview to access and write to the correct rendersection. However I want to call the same partialview many times in my Layout.cshtml. A subsequent call to the same Partialview again in the Layout, does not work, as the reference to layout has changed since the first call and rendersection update. So the code looks like this:
Layout.cshtml:
#RenderSection("Top", false)
#Html.Action("Load", "Home", new { viewname = "_testPartialView", pageref = this })
#Html.Action("Load", "Home", new { viewname = "_testPartialView", pageref = this })
Partial View:
#Model Models.testModel
#Model.Content
#{
var md = (System.Web.Mvc.WebViewPage)#Model.pageRef;
#*This check fails in subsequent loads as we get null*#
if(md.IsSectionDefined("Footer")) {
md.RenderSection("Footer");
}
else {
md.DefineSection("Footer", () => { md.WriteLiteral("<div>My Contents</div>"); });
}
}
}
controller:
public ActionResult Load(string viewname, System.Web.Mvc.WebViewPage pageRef)
{
var model = new Models.testModel { Content = new HtmlString("time " + i++.ToString()), pageRef = pageRef };
return PartialView(viewname, model);
}

Pro Asp.net mvc 2: Problem in example

I am trying to recreate an example from Chapter-5 of book Pro Asp.net MVC2. But as soon as I add Menu code server stops working. Any Problem with the code?
public class NavController : Controller
{
private IProductRepository productsRepository;
public NavController(IProductRepository productsRepository)
{
this.productsRepository = productsRepository;
}
public ViewResult Menu()
{
Func<string, NavLink> makeLink = categoryName => new NavLink
{
Text = categoryName ?? "Home",
RouteValues = new RouteValueDictionary( new {
controller = "Products", action = "List", category = categoryName, page = 1
})
};
List<NavLink> navLinks = new List<NavLink>();
navLinks.Add(makeLink(null));
var categories = productsRepository.Products.Select(x => x.Category);
foreach (string categoryName in categories.Distinct().OrderBy(x => x))
navLinks.Add(makeLink(categoryName));
return View(navLinks);
}
}
Menu.cshtml
#model IEnumerable<SStore.WebUI.Models.NavLink>
#foreach (var link in Model)
{
Html.RouteLink(link.Text, link.RouteValues);
}
If I remove this line from my master page then server works
#{
Html.RenderAction("Menu", "Nav");
}
otherwise getting this error
Html.RenderAction("Menu", "Nav");: That's horrible recursion: Nav/Menu which renders Nav/Menu which renders Nav/Menu, ..., until you run out of stack and your web server blows :-)
When you render a child action like this ensure it has no master or the master's gonna rerender it again and again and again, .... So modify this view (~/Views/Nav/Menu.cshtml) like this:
#model IEnumerable<SStore.WebUI.Models.NavLink>
#{
Layout = null;
}
#foreach (var link in Model)
{
Html.RouteLink(link.Text, link.RouteValues);
}
Let me explain:
The example you saw in the book was using the WebForms view engine. In this view engine you have .aspx (views) and .ascx (partials). I suppose that in the book they were using Menu.ascx which by default has no master because it is a partial.
In Razor there is no longer such distinction. You simply have views: .cshtml pages. It is up to you to control whether they have a master or not. There are different ways. One is what I showed previously, another is to return PartialView(navLinks) inside the child action.

An easy way to set the active tab using controllers and a usercontrol in ASP.NET MVC?

How do I create tabbed navigation with the "Current" tab highlighted in the UI?
Before MVC I looked at the file path and figured out which tab was currrent. Now it's a lot easier, you can assign the current tab based on the current controller.
Check it out ...
Most of the work happens in the usercontrol.
public partial class AdminNavigation : ViewUserControl
{
/// <summary>
/// This hold a collection of controllers and their respective "tabs." Each Tab should have at least one controller in the collection.
/// </summary>
private readonly IDictionary<Type, string> dict = new Dictionary<Type, string>();
public AdminNavigation()
{
dict.Add(typeof(BrandController), "catalog");
dict.Add(typeof(CatalogController), "catalog");
dict.Add(typeof(GroupController), "catalog");
dict.Add(typeof(ItemController), "catalog");
dict.Add(typeof(ConfigurationController), "configuration");
dict.Add(typeof(CustomerController), "customer");
dict.Add(typeof(DashboardController), "dashboard");
dict.Add(typeof(OrderController), "order");
dict.Add(typeof(WebsiteController), "website");
}
protected string SetClass(string linkToCheck)
{
Type controller = ViewContext.Controller.GetType();
// We need to determine if the linkToCheck is equal to the current controller using dict as a Map
string dictValue;
dict.TryGetValue(controller, out dictValue);
if (dictValue == linkToCheck)
{
return "current";
}
return "";
}
}
Then in your .ascx part of the usercontol call into the SetClass method to check the link against the dict. Like so:
<li class="<%= SetClass("customer") %>"><%= Html.ActionLink<CustomerController>(c=>c.Index(),"Customers",new{#class="nav_customers"}) %></li>
All you need now is the CSS to highlight your current tab. There are a bunch of different ways to do this, but you can get started with some ideas here: http://webdeveloper.econsultant.com/css-menus-navigation-tabs/
Oh, and don't forget to put the usercontrol on your page (or MasterPage) ...
<% Html.RenderPartial("AdminNavigation"); %>
I wrote some simple helper classes to solve this problem. The solution looks att both which controller that is used as well as which action in the controller.
public static string ActiveTab(this HtmlHelper helper, string activeController, string[] activeActions, string cssClass)
{
string currentAction = helper.ViewContext.Controller.
ValueProvider.GetValue("action").RawValue.ToString();
string currentController = helper.ViewContext.Controller.
ValueProvider.GetValue("controller").RawValue.ToString();
string cssClassToUse = currentController == activeController &&
activeActions.Contains(currentAction)
? cssClass
: string.Empty;
return cssClassToUse;
}
You can the call this extension method with:
Html.ActiveTab("Home", new string[] {"Index", "Home"}, "active")
This will return "active" if we are on the HomeController in either the "Index" or the "Home" action. I also added some extra overloads to ActiveTab to make it easier to use, you can read the whole blog post on: http://www.tomasjansson.com/blog/2010/05/asp-net-mvc-helper-for-active-tab-in-tab-menu/
Hope this will help someone.
Regards,
--Tomas
One method I am using on a current project - this also helps for other page-specific CSS needs.
First, an HTML helper that returns a string that represents the current controller and action:
public static string BodyClass(RouteData data) {
return string.Format("{0}-{1}", data.Values["Controller"], data.Values["Action"]).ToLower();
}
Then, add a call to this helper in your master page:
<body class="<%=AppHelper.BodyClass(ViewContext.RouteData) %>">
...
</body>
Now, you can target specific pages with your CSS. To answer your exact question about navigation:
#primaryNavigation a { ... }
.home-index #primaryNavigation a#home { ... }
.home-about #primaryNavigation a#about { ... }
.home-contact #primaryNavigation a#contact { ... }
/* etc. */
MVC's default Site.css comes with a class named 'selectedLink' which should be used for this.
Add the following to your ul list in _Layout.cshtml:
#{
var controller = #HttpContext.Current.Request.RequestContext.RouteData.Values["controller"].ToString();
}
<ul id="menu">
<li>#Html.ActionLink("Home", "Index", "Home", null, new { #class = controller == "Home" ? "selectedLink" : "" })</li>
...
</ul>
I know this is not clean. But just a quick and dirty way to get things rolling without messing with partial views or any of that sort.

Resources