Using a Model for Partial Views in a Layout page - asp.net

I have a very basic Layout file:
<!DOCTYPE html>
<html lang="en">
<head></head>
<body>
#{Html.RenderPartial("/Views/Shared/_Menu.cshtml");}
#{Html.RenderPartial("/Views/Shared/_Header.cshtml");}
#{Html.RenderPartial("/Views/Shared/_Footer.cshtml");}
</body>
</html>
My problem is that menu, header and footer require a Model to load some sort of dynamic data which is common for all Views.
I can solve it by changing RenderPartial to RenderAction, example:
Layout:
#{Html.RenderAction("GetFooter", "Layout");}
Controller:
[ChildActionOnly]
public PartialViewResult GetFooter()
{
FooterViewModel footerViewModel = new FooterViewModel
{
// do whatever code is required
};
return PartialView("/Views/Shared/_Footer.cshtml", footerViewModel);
}
But I was reading that RenderAction is a big no-no as it performs an entire cycle of MVC so what options do I have?

You could send info using ViewData['footerViewModel'] to renderpartial
controller
public ActionResult Index()
{
var footerViewModel = new FooterViewModel
...
ViewData["footerViewModel"] = footerViewModel;
return View();
}
view
<ul>
#{var m = ViewData["footerViewModel"] as FooterViewModel}
<li>
#m.XX
</li>
</ul>
ViewData transfers data from the Controller to View, not vice-versa.
ViewData is derived from ViewDataDictionary which is a dictionary type.
ViewData's life only lasts during current http request. ViewData values will be cleared if redirection occurs.
ViewData value must be type cast before use.
ViewBag internally inserts data into ViewData dictionary. So the key of ViewData and property of ViewBag must NOT match.
source

Related

Asp.net mvc4 Partial view with model in Layout

I am trying to create the menus in my _Layout page using a partial view called _Menus which reads from a Json file on my server. But I can't figure out how to call a partial view with a model from the layout page.
Here is the _Menu.cshtml page, which is in the Shared folder:
#model IEnumerable<LangSite_151209.Models.MenuItem>
<div id="menu" class="largescreen_show smallscreen_hide" data-display="flex">
<div id="menu_left" class="menu_item">
#foreach (var mainMenuItem in Model)
{
// A bunch of stuff with the model that draws the menu
}
</div>
</div>
Here is how I call it in the _Layout page:
<!DOCTYPE html>
<html lang="en">
<head>
<!-- Meta and script stuff -->
</head>
<body style="overflow:hidden;">
<div id="fg">
<div id="mobile_wrapper">
#Html.Partial("../Shared/_Menu")
</div>
</div>
<!-- A bunch of footer stuff that's irrelevant here -->
</html>
I have tried returning the partial view with a SharedController that opens the Json file and turns it into a model for the partial, as follows:
public class SharedController : Controller
{
// GET: Shared
[ChildActionOnly]
public ActionResult _Menu()
{
string filePath = HostingEnvironment.MapPath(#"~/App_Data/MenuItems.json");
StreamReader sr = new StreamReader(filePath);
string JsonString = sr.ReadToEnd();
JsonSerializerSettings settings = new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.All
};
var menuItems = JsonConvert.DeserializeObject<List<MenuItem>>(JsonString, settings);
return View(menuItems.ToList());
}
}
But when I try this I get a NullReferenceException on the Model: "Object reference not set to an instance of an object." Apparently you can't pass a model to a partial that way. I know that the code that reads the Json object works because when I use it the same code on a regular (non-partial) page it passes the model correctly and the script draws the menu.
Normally if I want to pass a model to a partial I just put the model in the main page and pass it that way. But I don't know how to put a model in the _Layout. Is that how I should to it? Or is there a better way?
The line #Html.Partial("../Shared/_Menu") will not execute your _Menu action method. Your _Menu.cshtml partial view is strongly typed to a list of MenuItem's. So either you should explicitly pass it when calling the Html.Partial method or your the main view ( which is calling this partial) should be also typed to the same collection.
You should use Html.Action method instead of Html.Partial.
#Html.Action("_Menu", "Shared")
This will execute the _Menu action method and pass the needed data (list of MenuItem) to the corresponding partial view.

ASP.NET MVC 3 Partial View in layout page

I'm working on setting up a shared content (navigation) for an asp.net MVC layout page.
Here is my partial view "_LayoutPartial.cshtml" with code to pull navigation data from a model.
#model MyApp.Models.ViewModel.LayoutViewModel
<p>
#foreach (var item in Model.navHeader)
{
//Test dump of navigation data
#Html.Encode(item.Name);
#Html.Encode(item.URL);
}
</p>
Here is how the code for my controller "LayoutController.cs" looks like.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using MyApp.Models.ViewModel;
namespace MyApp.Controllers
{
public class LayoutController : Controller
{
//
// GET: /Layout/
LayoutViewModel layout = new LayoutViewModel();
public ActionResult Index()
{
return View(layout);
}
}
}
Here is the code for the "_Layout.cshtml" page. I'm attempting to call the partial view here using Html.RenderAction(Action,Controller) method.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
</head>
<body>
<p>
#{Html.RenderAction("Index","Layout");}
</p>
#RenderBody()
</body>
</html>
When the layout page executes the #{Html.RenderAction("Index","Layout");} line, it throws out an error message "Error executing child request for handler 'System.Web.Mvc.HttpHandlerUtil+ServerExecuteHttpHandlerAsyncWrapper'."
What am I missing friends? How can I call a partial view in a layout page?
Thank you all in advance!
Instead of:
public ActionResult Index()
{
return View(layout);
}
do:
public ActionResult Index()
{
return PartialView(layout);
}
If you don't do that when you return a normal view from your child action, this normal view attempts to include the Layout, which in turn attempts to render the child action, which in turn returns a view, which in turn includes the Layout, which in turn attempts to render the child action, ... and we end up with names like the one ported by this very same site.
Also in your partial you don't need to do double encoding. The # Razor function already does HTML encode:
#model MyApp.Models.ViewModel.LayoutViewModel
<p>
#foreach (var item in Model.navHeader)
{
#item.Name
#item.URL
}
</p>
First verify that your child view is inside the Shared directory
#Html.Partial("_LayoutPartial")
OR
#{Html.RenderAction("actionname", "controller name");}
And don't use #Html.Encode(), Razor is already doing for u. Just use
#item.Name
#item.URL
I have solved this error getting on Layout page
System.Web.Mvc.HttpHandlerUtil+ServerExecuteHttpHandlerAsyncWrapper
Important !
First create partial view inside shared folder
In Controller ,
public PartialViewResult Userdetails()
{
....
return PartialView("PartialViewName", obj);
}
In Layout Page,
#{Html.RenderAction("action","controller");}
I know this is an old question but I thought I would throw this in here. You can use either Html.Action or Html.RenderAction. They both technically do the same thing but depending on how much content you're returning back can have an impact on which one you should really use for best efficiency.
Both of the methods allow you to call into an action method from a view and output the results of the action in place within the view. The difference between the two is that Html.RenderAction will render the result directly to the Response (which is more efficient if the action returns a large amount of HTML) whereas Html.Action returns a string with the result.
Source

Why is my validation firing on the get request before the post in MVC3?

I have a MVC3 view that enables the user to create a couple different things. Within the parent view the forms to do so are broken up via jquery ui tabs like the following:
<div id="tabs">
<ul>
<li>New Thing 1</li>
<li>Different New Thing</li>
</ul>
<div id="tabs-1">#Html.Action("CreateNewThing", "NewThingController")</div>
<div id="tabs-2">#Html.Action("CreateDifferentThing", "DifferentThing")</div>
<div></div>
</div>
<script type="text/javascript">
$(function () {
$("#tabs").tabs();
});
</script>
Within the partial view I have:
#model NewThingViewModel
#using (Html.BeginForm("CreateNewThing", "NewThingController", FormMethod.Post, new { id = "frmCreateNewThing" }))
{
...
with input fields, a submit button, etc. This seems to work well: it renders everything and posts just fine to the right controller action method.
However I'm now wiring in the validation and I've got an issue.
In the controller it is rendering the view like so:
public ActionResult CreateNewThing(NewThingViewModel model)
{
... initializing model fields, drop downs etc.
return PartialView("CreateNewThing", model);
}
I have a seperate post method like so:
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult CreateNewThing(NewThingViewModel newThingViewModel, FormCollection collection)
{
.....
}
Sample Model:
public class NewThingViewModel
{
[Required]
[StringLength(50)]
[Display(Name = "Display Name:")]
public string DisplayName { get; set; }
}
The trouble is, when the page first comes up the fields marked as [Required] through DataAnnotations in the model are showing up red as well as the validation summary showing them invalid when the page initially shows. I.E. it's acting like it's already been posted before the user gets to enter anything on the initial load or even put anything in the text boxes.
I know the first non-post CreateNewThing is firing because I can catch it in the debugger and I know the 2nd one does not on the initial load.
What would cause my validations to fire on the Get?
Is it due to the way Html.Action works and the fact that I'm rendering partial views onto another view?
I'm using UnobtrusiveJavaScriptEnabled and ClientValidationEnabled = true in web.config.
I can't find anyone else that has run into this particular problem. Every other example just seems to work, then again I don't find an example where the view is broken into three partials contained within jquery ui tabs.
How do I fix this?
Options:
Do I need to manually manipulate the Model.IsValid as a workaround?
Use a different mechanism to render the partial views on the parent view instead of Html.Action?
Use some javascript/jquery to catch the validation and stop it?
Don't have method parameters on your GET controller action. You can initialize an empty model and pass it to the view but you dont need a model to be passed into the method
You're passing in an "empty" model (which I assume has default values set for your required properties), when you should be passing in null.

How can I run code from my layout file?

I used the following tutorial to help me build an RSS Reader in my ASP.NET MVC3 Razor application:
http://weblogs.asp.net/jalpeshpvadgama/archive/2011/08/17/creating-basic-rss-reader-in-asp-net-mvc-3.aspx
However, unlike the tutorial example, I want the RSS feed to be displayed on every page, and have therefore added it to my layout file, /Views/Shared/_Layout.cshtml
I currently only have 2 views on my site, and to get the RSS Reader to work on both views I've got the following code in my HomeController:
public class HomeController : Controller
{
//
// GET: /Index/
public ActionResult Index()
{
return View(CT.Models.RssReader.GetRssFeed());
}
public ActionResult About()
{
return View(CT.Models.RssReader.GetRssFeed());
}
}
From my WebForms experience, I would simply add the RSS Reader code in my master page code behind, and it would automatically work on every page.
Is there a Controller for layout pages which allows me to do the same?
How can I get this to work on every call of the layout page, without having to return anything?
EDIT: Following #Sebastian's advice, I've now added this code to a Partial View, removed CT.Models.RssReader.GetRssFeed() from return View() and included this in my layout file:
#Html.Partial("_MyPartialView")
The code in this partial view is:
<ul>
#foreach (var item in Model)
{
<li>
#item.Title
</li>
}
</ul>
However, I'm not getting a runtime error:
Object reference not set to an instance of an object.
It's erroring on the line #foreach (var item in Model)
You have to create a partial view and add functionality there.
Then in your layout, render this partial.
EDIT
Is your partial view really a partial view? The reason I said that is because you have "_" in front of the name which suggests that it might be a layout (might just be a naming convention).
To fix object reference error, you have to add the #Model declaration on top of your partial view.
Hope it helps.
UPDATE
In order to use different model in partial view, you need to explicitly declare which model you are going to use on render partialmethod.
#{Html.RenderPartial("../YourFeed", Model.YourFeedModel);}
Let me know if that resolved your issue.
The new error you are having is due to you not passing a Model to the partial view. You can do this with the second argument of the Html.Partial function...
Html.Partial("ViewName", MyModel);
As I think you are trying to do this in a Layout page you could also consider using a static reference to get your RSS feed. So forget about needing to pass in a Model and in your partial have:
#foreach (var item in RssRepository.GetFeed())
{
<li>
#item.Title
</li>
}
this like to a class something like...
public static RssRepository
{
public static MyModel GetFeed()
{
return new MyModel();//<- return what you would normally pass as a Model for RSS feeds
}
}
Hope that all makes sense

Create controller for partial view in ASP.NET MVC

How can I create an individual controller and model for a partial view? I want to be able to place this partial view any where on the site so it needs it's own controller. I am current rendering the partial as so
#Html.Partial("_Testimonials")
Why not use Html.RenderAction()?
Then you could put the following into any controller (even creating a new controller for it):
[ChildActionOnly]
public ActionResult MyActionThatGeneratesAPartial(string parameter1)
{
var model = repository.GetThingByParameter(parameter1);
var partialViewModel = new PartialViewModel(model);
return PartialView(partialViewModel);
}
Then you could create a new partial view and have your PartialViewModel be what it inherits from.
For Razor, the code block in the view would look like this:
#{ Html.RenderAction("Index", "Home"); }
For the WebFormsViewEngine, it would look like this:
<% Html.RenderAction("Index", "Home"); %>
It does not need its own controller. You can use
#Html.Partial("../ControllerName/_Testimonials.cshtml")
This allows you to render the partial from any page. Just make sure the relative path is correct.
If it were me, I would simply create a new Controller with a Single Action and then use RenderAction in place of Partial:
// Assuming the controller is named NewController
#{Html.RenderAction("ActionName",
"New",
new { routeValueOne = "SomeValue" });
}
The most important thing is, the action created must return partial view, see below.
public ActionResult _YourPartialViewSection()
{
return PartialView();
}
You don't need a controller and when using .Net 5 (MVC 6) you can render the partial view async
#await Html.PartialAsync("_LoginPartial")
or
#{await Html.RenderPartialAsync("PartialName");}
or if you are using .net core 2.1 > you can just use:
<partial name="Shared/_ProductPartial.cshtml"
for="Product" />
Html.Action is a poorly designed technology.
Because in your page Controller you can't receive the results of computation in your Partial Controller. Data flow is only Page Controller => Partial Controller.
To be closer to WebForm UserControl (*.ascx) you need to:
Create a page Model and a Partial Model
Place your Partial Model as a property in your page Model
In page's View use Html.EditorFor(m => m.MyPartialModel)
Create an appropriate Partial View
Create a class very similar to that Child Action Controller described here in answers many times. But it will be just a class (inherited from Object rather than from Controller). Let's name it as MyControllerPartial. MyControllerPartial will know only about Partial Model.
Use your MyControllerPartial in your page controller. Pass model.MyPartialModel to MyControllerPartial
Take care about proper prefix in your MyControllerPartial. Fox example: ModelState.AddError("MyPartialModel." + "SomeFieldName", "Error")
In MyControllerPartial you can make validation and implement other logics related to this Partial Model
In this situation you can use it like:
public class MyController : Controller
{
....
public MyController()
{
MyChildController = new MyControllerPartial(this.ViewData);
}
[HttpPost]
public ActionResult Index(MyPageViewModel model)
{
...
int childResult = MyChildController.ProcessSomething(model.MyPartialModel);
...
}
}
P.S.
In step 3 you can use Html.Partial("PartialViewName", Model.MyPartialModel, <clone_ViewData_with_prefix_MyPartialModel>). For more details see ASP.NET MVC partial views: input name prefixes

Resources