I have my _Layout.cshtml file that uses the ViewBag model to render some dynamic content.
I understand ViewBag can be populated in the controller and accessed in the view and/or layout page.
My question is, if my Layout page is using #ViewBag.SiteName, I want to avoid having to set this variable in each controller before I return the view. Is there a way to set this variable on a global level? Or how else should I pass this data to the layout page?
If you set anything in ViewBag - this happens after the Layout has been rendered -
You've missed the boat.
As others have mentioned, you can create a "helper" controller:
public class LayoutController : BaseController
{
[ChildActionOnly]
public ActionResult SiteName()
{
return new ContentResult {Content = "Site name goes here"};
}
}
Then, in your layout:
#{Html.Action("SiteName", "Layout")}
Related
I have a _Layout.cshtml files as a partial view as header on each main view.
I would like to define a Select element on the _Layout and pass some data to the partial view using ViewBag so that the data is populated on the view and can later be submitted.
My questions are:
Where is the ActionResult function defined that contains and defines the data in ViewBag?
What do I do if I want to submit a form on the partial view? Where and which action should be defined/used to accept the HttpPost command?
Thanks!
What I suggest is to make a base controller class.
Inherit all your controllers from it.
The code to render the data for the layout, can lie in it's constructor, or some other common function that all your controllers can use as children of this base class.
public class BaseController : System.Web.Mvc.Controller
{
public BaseController()
{
// This code will run for all your controllers
ViewBag.MyData = "SomeData";
}
}
About your question:
What do I do if I want to submit a form on the partial view? Where and which action should be defined/used to accept the HttpPost command?
You can just put the controller name on your form:
#using (Html.BeginForm("ActionName", "Controller"))
There are possibly a few misunderstandings about how _layout.cshmtl and partial views work:
_layout.cshtml is not a partial view. It is the layout template used by all your pages. It is kind of the "outer" of the page. It is automatically applied (except if you set Layout = null). A partial view in turn is the "inner" of the page. You call it explicitly from your page using #Html.Partial.
Even though your page is rendered by multiple views - the actual view, the layout, maybe some partial views - it is still the result of a single controller action. (Except if you use #Html.Action for rendering partial "actions"). Also, the page rendered is a single HTML page, that is, any forms on the page are simply HTML forms.
Therefore, the answer to "where is the ActionResult function defined" is: In the action that you want your page to be rendered for.*
The answer to "Which action should be used to accept the HttpPost command" is the same as if the form was on your page: You can define an arbitrary action on an arbitrary controller for receiving the form. You just need to refer to that action when you render the form:
#using (Html.BeginForm("action", "controller")) { ... }
*) If you want to prevent having to build the select list in each and every controller action that relies on _layout, you could conceivably use #Html.Action. That is, you define a "partial action" which is nothing else than a controller action that returns a PartialView() and a partial view to render the model from that action. Then you can use that partial action to build the select list.
However having read some news about ASP.NET vNext, partial actions seem not to be liked to much by the community and in vNext there will be another way to achieve the same.
Still if you want to go this way this enables you to separate the logic for your dropdown (language? user menu?) from your other actions and views:
class UserController
{
PartialViewResult UserMenuDropdown()
{
return PartialView(BuildUserMenuFrom(.....));
}
[Post]
ActionResult PostUserMenu()
{
// do whatever you want once the form is posted
}
}
In your _layout you call the partial action:
#Html.Action("UserMenuDropdown", "User")
And in the view for UserMenuDropdown you render the form:
#using (Html.BeginForm("PostUserMenu"))
{
#Html.DropDownListFor(m => m.UserMenuSelectList)
}
This way your dropdown list becomes a "first class member", with its own controller action, main view, and model. You don't need a ViewBag for this, and you don't have to build the select list in each and every controller action.
I have a MVC application that exist at the moment using a _MainLayoutPage for its Master Page.
I want to create another Master Page for a different purpose. I will be creating a new controller as well.
How can I do this?
The simplest way is in your Action Method, set a Viewbag property for your Layout
public ActionResult Index()
{
ViewBag.Layout= "~/Views/Shared/layout2.cshtml";
In your View, set the layout property
#{
Layout = #ViewBag.Layout;
}
In _ViewStart.cshtml, put this:
#{
try {
Layout = "~/Views/" + ViewContext.RouteData.Values["controller"] + "/_Layout.cshtml";
}
catch {
Layout = "~/Views/Shared/_Layout.cshtml";
}
}
And then you can put a controller specific _Layout.cshtml in your controller folders, like
~/Views/User/_Layout.cshtml for a controller named UserController
~/Views/Account/_Layout.cshtml for a controller named AccountController
And because of the try/catch, it will fall back to the '~/Views/Shared/_Layout.cshtml' layout if one is not defined for a specific controller.
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
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
I am migrating a web site to a new one using ASP .NET MVC2.
In the original site, master page has code-behind to check a query string parameter value. Depending on this value, code-behind dynamically modify some CSS property to hide / display master page elements.
As MVC2 has no code-behind because we are supposed to perform everything in the controllers, how should I proceed in this case ?
I see this : asp.net mvc modifying master file from a view
It partially answers my needs but the query string processing is common to all pages. How can I move this processing in a common code section ?
Regards.
A helper method looks like a good place:
public static class HtmlHelperExtensions
{
public static string GetCss(this HtmlHelper htmlHelper)
{
// read some request parameter
// here you also have access to route data so the
// parameter could be part of your custom routes as well
var foo = htmlHelper.ViewContext.HttpContext.Request["foo"];
// based on the value of this parameter
// return the appropriate CSS class
return (foo == "bar") ? "barClass" : "fooClass";
}
}
And somewhere in your master page:
<body class="<%= Html.GetCss() %>">
Or if you are always going to apply it to the body tag only it might be more appropriate to do this in order to reduce the tag soup:
public static class HtmlHelperExtensions
{
public static MvcHtmlString StartBody(this HtmlHelper htmlHelper)
{
var body = new TagBuilder("body");
var foo = htmlHelper.ViewContext.HttpContext.Request["foo"];
var bodyClass = (foo == "bar") ? "barClass" : "fooClass";
body.AddCssClass(bodyClass);
return MvcHtmlString.Create(body.ToString(TagRenderMode.StartTag));
}
}
and in your master page at the place of the body tag:
<%= Html.StartBody() %>
I can think of two solutions to this:
Derive your controllers from one controller base and set the ViewData parameter there depending on posted Form values
Don't use ViewData at all, but simply look for the form value in the view (using HttpContext.Current)
The second method violates the MVC pattern. IMO it is still acceptable in some scenarios, for example I am using this approach to highlight the currently selected item in a navigation menu.