MVC3 - RenderPartial inside RenderSection not working - asp.net-mvc

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);
}

Related

Trying To pass list of data in from controller to partial view in mvc

I am trying to pass list of data through viewbag from controller to partial view but getting error
in login form after submitting data taking it from formcollection through HttPost and once action complete it return to home page from there i am calling method Page_Init inside that in 'loadmessage' method i am trying to return list to a partial view "Header" based on condition.but not able to perform getting error
Home controller
[HttpPost]
public ActionResult Login(FormCollection form)
{
return View("Home");
}
in Home.cshtml
calling method page_init in controller
$.get("/Home/Page_Init",null, function (data) {
alert(data);
});
Home controller
public ActionResult Page_Init()
{
loadMessages();
return view("Home");
}
public ActionResult loadMessages()
{
List<MessageModel> lstMessages = new List<MessageModel>();
List<MessageModel> lstInfoMessages = new List<MessageModel>();
lstInfoMessages = lstMessages.Where(msg => msg.MESSAGE_TYPE.Equals(CommonConstants.SAFETY_MESSAGE_INFO, StringComparison.InvariantCultureIgnoreCase)).ToList<MessageModel>();
if (lstInfoMessages.Count > 0)
{
ViewBag.lstInfoMessages = 1;
ViewBag.lstInfoMessages1 = lstInfoMessages;
return PartialView("Header", lstInfoMessages);
}
}
also trying to go to partial view from Home view
#ViewBag.lstInfoMessages1
#if (ViewBag.lstInfoMessages == 1)
{
#Html.Partial("Header",ViewBag.lstInfoMessages1)
}
Expected that list of information should go to partial view and bind
Error:Not getting Exact syntax what to do and how to proceed the steps tried above throw error
#Html.Partial method does not accept the dynamic value – so we need to cast it to the actual type.
#model MessageModel //you need to give correct path of MessageModel
#ViewBag.lstInfoMessages1
#if (ViewBag.lstInfoMessages == 1)
{
#Html.Partial("Header", (List<MessageModel>)ViewBag.lstInfoMessages1)
}
In Header Partial view, you can retrieve list using #Model

Set ViewData inside a RenderPartial

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.

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>
}

Two partial views inside an MVC view

I have the following scenario, where I have one model (named Model A) in a view (View1).
This view initially loads a partial view (Partial View 1)
On button click of partial view, I am trying to pass the id generated to another partial view (Partial View 2).
But I am getting an error saying View 1 cannot be found, which loaded without any issues on first run.
If I remove the else statement, the page successfully reloads after submission.
Any tips on passing this model object successfully to the other view please.
I put id=1 and tested it and the same error occured.
I tried RenderAction, RenderPartial and all these failed
Page
#model MyModel
#{
if (ViewBag.Created ==0) {
#Html.Partial("CreateView1",Model);
}
else
{
{ Html.Action("Action2", "Area/Controller2", new { id = Model.Id }); }
}
}
Controller methods:
Controller 1:Entry point of view
[Route("{CreateView1}")]
public ActionResult Create() {
ViewBag.Created = 0;
return View(new MyModel());
}
[Route("{CreateView1}")]
[HttpPost]
public ActionResult Create(MyModel model) {
...........................
ViewBag.Created = 1;
}
Controller 2 which renders 2nd partial view:
public PartialViewResult Index(int createdId)
{
return PartialView(new List<Model2>());
}
Regarding View 1 cannot be found, is because the keyword return in your second Create action is missing. The button click submits the form to the Create method with [HttpPost] attribute and the end of the method, it needs a return View.
Reg Any tips on passing this model object successfully to the other view please, The return in the second Create method should be return View(model); and not 'return View(new MyModel);` as later on in the View you are going to use the Model.
Re I put id=1 and tested it and the same error occured., because runtime never reachs that point as the operation is being handed to '[HttpPost] Create' and it never get back to your Original Page.
There are other issues with your code as you are using different names in your code than what you mention in your description...
A simple solution is:
1- use the following return at the end of you [HttpPost]Create Action:
return RedirectToAction("Action2", "Area/Controller2", new { id = model.Id});
2- replace the following code in your initial page
if (ViewBag.Created ==0) {
#Html.Partial("CreateView1",Model);
}
else
{
{ Html.Action("Action2", "Area/Controller2", new { id = Model.Id }); }
}
with the following:
#Html.Partial("CreateView1",Model);
and remove anywhere you set ViewBag.Created = 0 or ViewBag.Created =1
I also assume the action action2 in controller Controller2 returns a valid Partial View.
Hope this help you get some idea to fix your code.
You may have omitted this for brevity, but you will want to return a viewresult at the end of your post action:
return View(new MyModel());
try this:
if (ViewBag.Created ==0) {
#Html.RenderPartial("CreateView1",Model);
}

Should RenderAction be used with forms?

My setup:
Have a view for a route like: /Pages/Details/2
The page details view has <% Html.RenderAction("CreatePageComment", "Comments"); %> to render a comment form
Comment form posts to Comments/CreatePageComment
/Comments/CreatePageComment returns RedirectToAction when a comment is created successfully
This all works nicely
My question:
If there is a validation error, how should I return to /Pages/Detail/1 and show the error in the comment form?
If I use RedirectToAction, it seems validation is tricky; should I even be using the Post-Redirect-Get pattern for validation errors, instead of just returning?
If I return View() it kicks me back to showing the CreateComment.aspx view (with validation, but just a form on a white page), not the /Pages/Details/2 route that called the RenderAction.
If the PRG pattern should be used, then I think I just need to learn how to do validation while using PRG. If not — and to me this seems better handled by returning View() — then I don't know how to get the user returned to the initial view, showing the form errors, while using RenderAction.
This feels like the game where you tap your head and rub your belly at the same time. I wasn't good at that one either. I'm new at MVC, so that's likely the problem here.
I believe the answer is to use TempData, for example:
In my view (/Steps/Details) I have:
<!-- List comments -->
<% Html.RenderAction("List", "Comments", new { id = Model.Step.Id }); %>
<!-- Create new comment -->
<% Html.RenderAction("Create", "Comments", new { id = Model.Step.Id }); %>
In my comments controller I have my POST method:
// POST: /Comments/Create
[HttpPost]
public ActionResult Create([Bind(Exclude = "Id, Timestamp, ByUserId, ForUserId")]Comment commentToCreate)
{
if (ModelState.IsValid)
{
//Insert functionality here
return RedirectToAction("Details", "Steps", new { id = commentToCreate.StepId });
}
//If validation error
else
{
//Store modelstate from tempdata
TempData.Add("ModelState", ModelState);
//Redirect to action (this is hardcoded for now)
return RedirectToAction("Details", "Steps", new { id = commentToCreate.StepId });
}
}
Also in the comments controller is my GET method:
//
// GET: /Comments/Create
public ActionResult Create(int id)
{
if (TempData.ContainsKey("ModelState"))
{
ModelStateDictionary externalModelState = (ModelStateDictionary)TempData["ModelState"];
foreach (KeyValuePair<string, ModelState> valuePair in externalModelState)
{
ModelState.Add(valuePair.Key, valuePair.Value);
}
}
return View(new Comment { StepId = id });
}
This works great for me, but I'd appreciate feedback on whether this is a good practice, etc.
Also, I noticed that MvcContrib has a ModelStateToTempData decoration that appears to do this, but in a cleaner way. I'm going to try that next.

Resources