How ASP.NET MVC _layout variables work? - c#

When I create a new ASP.NET 4.5 web application MVC in Visual Studio, it starts with a introduction template.
The relavant parts are:
Views/Home/About.cshtml
#{
ViewBag.Title = "About";
}
<h2>#ViewBag.Title.</h2>
...
Views/Shared/_Layout.cshtml
<!DOCTYPE html>
<html>
<head>
<title>#ViewBag.Title - My ASP.NET Application</title>
...
#RenderBody()
...
Views/_ViewStart.cshtml
#{
Layout = "~/Views/Shared/_Layout.cshtml";
}
If I understand right, and assuming the About page is opened in the browser:
The _ViewStart.cshtml is called first, and it says there is a layout _Layout.cshtml to be called.
It renders the _Layout.cshtml, until the call #RenderBody() is reached, on that point he will render the About.cshtml. When that ends, he will render the rest of the _Layout.cshtml.
But here is my doubt, if the _Layout.cshtml starts first, how does it print on the <title> the variable #ViewBag.Title, which is assigned only in About.cshtml?

When you hit a URL an Action is called on a controller. The view is a result of this, so you don't call views directly (my guess is your coming from a webforms background where you call an aspx page, MVC uses a different model that doesn't rely on physical files). The action then specifies which view to render (and passes it the model). This view then specifies a layout to use when rendering the view.
So the control mechanism is inverted compared to what your used to.
URL (via routing) specifies a controller and an action -> the action says render me using this view -> the view then says render me using this layout. So the hierachy is:
Controller
Action
View
Layout
So to answer your specific questions:
But here is my doubt, if the _Layout.cshtml starts first, how does it
print on the <title> the variable #ViewBag.Title, which is assigned
only in About.cshtml?
The layout isn't called first, the view specifies a layout that should be used to render itself.

It doesn't. The View is rendered before the layout.

Related

Static HTML page in ASP.NET MVC that does not use Layout

I have an entire Web Application built using ASP.NET MVC and have a _ViewStart.cshtml page that specifies a Layout.
This works great for my entire site. However, I have a single prototype static HTML page that I just need to dump into a directory.
I copied the HTML into a CSHTML file and fronted it with a controller. The problem is that when I go to this page, it is using the Layout.
How can I configure it so that I can just serve this page up as static, standalone content without the Layout from _ViewStart?
By default, all views will use the layout from ~/Views/Shared as it is specified in the _Viewstart.cshtml file. Every time a view is executed, the code inside the _Viewstart.cshtml will be executed which sets the layout for the view.
If you do not want to execute/include the layout for a specific view, you can explicitly set layout as null on a view. Add the below code to the view.
#{
Layout = null;
}
Keep in mind that, even though it is static html in your cshtml file, user will not/should not directly access this view (like a normal html page/htm page). It has to be routed via an action method which returns this cshtml view.
Another option is to use the PartialView method instead of View method. When using the PartialView method to render a view, the framework do not run _ViewStart.cshtml, hence you get the same result.
public ActionResult About()
{
return PartialView();
}
PartialView is really handy when you want to render the markup for parts of your page (Ex : content of for a modal dialog etc)
In your static view page set layout = null.
Like:
#{Layout = null;}

What is the execution order of an MVC Razor view/layout

I have a razor layout like:
#using (var context = SetUpSomeContext()) {
<div>
Some content here
#RenderBody();
</div>
}
And a view like:
#{
Layout = "MyLayout.cshtml";
}
<div>#SomethingThatDependsOnContextBeingSetUp()</div>
When the view renders, SomethingThatDependsOnContextBeingSetUp executes before SetUpSomeContext and fails. This seems weird, because I would expect that not to execute until RenderBody is called in the layout. When I switch this to use a "PageContent" section instead of RenderBody, everything works as expected. Can anyone explain this behavior?
The Razor pipeline is:
First, Razor evaluates, if present, _ViewStart.cshtml that contains only Razor statements (C# or VB) for assign Layout or other initialization, it should never have html tags inside.
Then, it parse and evaluates the "View" cshtml file.
Then, it parse and evaluates, if present, the Layout, and when evaluates the #RenderBody method of the cshtml layout file, replaces it with the html script resulting from evaluation of "View" cshtml file.
Finally, it builds the html control graph objects of layout and view html files.
So, you cannot do depend any "Razor" objects of a view from layout operations, but rather you may put in _ViewStart.cshtml your initialization of objects visible to your view.
You may imagine cs(vb)html views as a static content loaded when Controller.View method is called.
At that point, the cshtml loaded content is parsed by Razor that evaluates the expressions (assign properties(as Layout), branchs, loops) and build a sort of binary tree or graph of "HtmlControls" objects into the ActionResult object returned by View method.
Next, ActionResult is rendered as html from Asp.Net and returned to the client as http response.
To do that, Razor parses cshtml files and carries out their code inside parts starting first from the "_ViewStart.cshtml" (also more of one if present in the sub folders chain related to the origin controller), then follows cshtml file loaded by conventions (name of view equals to the name of action in the path Views/[ControllerName]/), or by expressed view's name as parameter when calling View method, and finally, the eventual layout file linked to the view by Layout property.
Let me clarify this by investigating a situation,Assume that you have view like;
#renderSection("Header")
#using (var context = SetUpSomeContext()) {
<div>
Some content here
#RenderBody();
</div>
}
#renderSection("Footer")
And we are assuming that razor executes the page in the order you expect, what would happen if we declared our view like?
#{
Layout = null;
}
<div>#SomethingThatDependsOnContextBeingSetUp()</div>
Razor would not have any idea whether that view needs a layout page until executing #RenderBody().Also it would deduce that it rendered layout page for nothing and this would not be reasonable.So this is not what actually happens.
When request made it is so natural that Razor first executes body of your view.
If your view not specified layout like in my demo Razor only renders output of that page and stops there.If view has a layout specified like in your code
after executing the view , it passes control to layout page.(Layout page starts to render from top to bottom)So what is remaining for Layout page is only content placement.When it sees #RenderBody() it only places output of your already executed view.
For sections; they are not executed when your view body executed, after your view passes control to layout page,layout page explicitly invokes the execution of your sections in the order they are declared.
Also notice that you are specifying your page title in your view body and it is rendered in your layout title tag (ViewBag.Title).After executing view body all the variables which is declared in view body are available in layout page.
Sum:Rendering order is from top to bottom but execution order is different.
For your situation: "SomethingThatDependsOnContextBeingSetUp executes before SetUpSomeContext and fails". Like i said it is natural behaviour of Razor execution cycle, view body executed before layout page executed.When you make it section ; view body executed first but sections are not executed before layout page.View body passes control to layout page and Layout page starts to render from top to bottom and if it sees #RenderSection then invokes the execution of section.So in this case SetUpSomeContext is executed before SomethingThatDependsOnContextBeingSetUp executed.
The execution order is from innermost to outermost.
I would argue that using a 'context' the way you use it is not the best design - you should consider moving the setup to the controller / action filter and pass the data to the views in model.
If you require some logic on all your views, create a ViewModelBase that all your ViewModels inherit from.
Then in your Controller(Base) you can initialize the ViewModel.SharedContext and other properties.

Trying to use two layout pages on MVC4 and Razor

When I use #RenderBody on a view (not principal), I receive this message Error: The file "~/Views/Shared/_Sistema.cshtml" cannot be requested directly because it calls the "RenderBody" method.
I do not understand it as I am a novice in MVC.
What do I can do?
Thanks!
If you are using the Renderbody in _Sistema.cshtml file, then make it as a Layout page.
And add another partial page named like MyPartial.cshtml with Layout name as _Sistema.cshtml.
Renderbody is supposed to be in the master page only. i.e., Layout page.
So your _Sistema.cshtml page should only contains the following:
#RenderBody()
#RenderSection("scripts", required: false) #*----Optional---*#
Then your your new partial page MyPartial.cshtml should contain the following:
#{
Layout = "~/_Sistema.cshtml";
}
Then use your partial page in your view as follows:
#Html.Partial("MyPartial")
Hope it helps.
RenderBody is for masters only. This method renders markup of content pages that does not belong to any particular section. If your view calls RenderBody, two cases are possible:
Either this is a mistake and this view should not call it.
Or this view is a master, and you should instead use some other views inheriting layout from this master.
you just need to interact with _ViewStart.cshtml
and use if condition to specify the share mater page for each group of users.
for example user is admin then user _Layout.cshtm other wise use _Layout2.cshtml
use following code :
#{
if(User.Identity.Name.ToLower()=="admin") {
Layout = "~/Views/Shared/_Layout2.cshtml";
}
else {
Layout = "~/Views/Shared/_Layout.cshtml";
}
}
In my experience, I was struggling with this same issue for days. After further investigation, I found that when starting a new Umbraco site, I had the option to select templates. That resolved the RenderBody issue. When creating the layout page without the templates it doesn't see the Master page as the layout, hence not being able to call the RenderBody method. Using the templates automatically sets the Master page as the Layout allowing you to call the RenderBody. I hope this helps.

Including JavaScript at bottom of page, from Partial Views

Let's say I have a javascript slide-show in a partial view...
_Slideshow.cshtml:
#{
ViewBag.Title = "Slide Show";
}
<div id="slides">
</div>
<script src="js/slides.min.jquery.js"></script>
<script type="text/javascript">
$(function(){
$('#slides').slides({
// slide show configuration...
});
});
</script>
But I want to be a good little web developer, and make sure all of my scripts go at the bottom of the page. So I'll make my *_Layout.cshtml* page look like this:
_Layout.cshtml:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>#ViewBag.Title</title>
<link href="#Url.Content("~/css/global.css")" rel="stylesheet" type="text/css" />
</head>
<body>
<div id="wrapper">
#RenderBody
</div>
<!-- Being a good little web developer, I include my scripts at the BOTTOM, yay!
-->
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js" type="text/javascript"></script>
</body>
</html>
But UH OH! What do I do now, because my slide show script ends up above my jQuery inclusion?! It wouldn't be a big deal if I wanted my slide show on every page, but I only want the slide show partial view to be rendered on a certain page, and.... I WANT MY SCRIPTS AT THE BOTTOM! What to do?
You could define a section in your layout page for the scripts like this:
<body>
<div id="wrapper">
#RenderBody
</div>
<!-- Being a good little web developer, I include my scripts at the BOTTOM, yay!
-->
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js" type="text/javascript"></script>
#RenderSection("myScripts")
</body>
Then on your pages you define what goes in that section:
#{
ViewBag.Title = "Slide Show";
}
<div id="slides">
</div>
#section myScripts { //put your scripts here
<script src="js/slides.min.jquery.js"></script>
<script type="text/javascript">
$(function(){
$('#slides').slides({
// slide show configuration...
});
});
</script>
}
Then when the page renders, it will take everything in your section and add it to where it is supposed to go on your layout page (in this case, at the bottom).
Note: The accepted solution won't work for partial views as the question asks for.
The Problem
In the normal flow, you can define the contents for a particular section from inside of the parent view on your ActionResult using a #section SectionName {} declaration. And when that view is finally inserted into its LayoutPage, it can call RenderSection to place those contents anywhere within the page, allowing you to define some inline JavaScript that can be rendered and parsed at the bottom of the page after any core libraries that it depends on like this:
The problem arises when you want to be able to reuse the full page view inside of a partial view. Perhaps you'd like to also re-use the view as a widget or dialog from inside of another page. In which case, the full Partial View is rendered in its entirety wherever you've placed the call to #Html.EditorFor or #Html.Partial inside of the Parent View like this:
According to the MSDN Docs on Layouts with Razor Syntax:
Sections defined in a view are available only in its immediate layout page.
Sections cannot be referenced from partials, view components, or other parts of the view system.
The body and all sections in a content page must all be rendered by the layout page
In that scenario, it becomes tricky to get the script defined into the partial view to the bottom of the page. Per the docs, you can only call RenderSection from the layout view and you cannot define the #section contents from inside of a partial view, so everything gets lumped into the same area and your script will be rendered, parsed, and run from the middle of your HTML page, instead of at the bottom, after any libraries it might depend on.
The Solution
For a full discussion of the many ways to inject sections from partial views into your page, I'd start with the following two questions on StackOverflow:
Injecting content into specific sections from a partial view with Razor View Engine
Using sections in Editor/Display templates
The varying solutions therein differ on support for nesting, ordering, multiple script support, different content types, calling syntax, and reusability. But however you slice it, pretty much any solution will have to accomplish two basic tasks:
Gradually build script objects onto your request from within any page, partial view, or template, probably leveraging some kind of HtmlHelper extension for reusability.
Render that script object onto your layout page. Since the layout page actually renders last, this is simply emitting the object we've been building onto the master page.
Here's a simple implementation by Darin Dimitrov
Add the Helper Extension Methods which will allow you to build arbitrary script objects into the ViewContent.HttpContext.Items collection and subsequently fetch and render them later.
Utilities.cs
public static class HtmlExtensions
{
public static MvcHtmlString Script(this HtmlHelper htmlHelper, Func<object, HelperResult> template)
{
htmlHelper.ViewContext.HttpContext.Items["_script_" + Guid.NewGuid()] = template;
return MvcHtmlString.Empty;
}
public static IHtmlString RenderScripts(this HtmlHelper htmlHelper)
{
foreach (object key in htmlHelper.ViewContext.HttpContext.Items.Keys)
{
if (key.ToString().StartsWith("_script_"))
{
var template = htmlHelper.ViewContext.HttpContext.Items[key] as Func<object, HelperResult>;
if (template != null)
{
htmlHelper.ViewContext.Writer.Write(template(null));
}
}
}
return MvcHtmlString.Empty;
}
}
Then you can use like this within your application
Build this script objects like this inside of your Partial View like this:
#Html.Script(
#<script>
$(function() {
$("##Html.IdFor(model => model.FirstName)").change(function() {
alert("New value is '" + this.value + "'");
});
})
</script>
)
And then render them anywhere within your LayoutPage like this:
#Scripts.Render("~/bundles/jquery")
#RenderSection("scripts", required: false)
#Html.RenderScripts()

Razor Layout doesn't work if file is called _ViewStart.cshtml

I have an MVC3 web app to which I want to start using whole Razor views.
Most of the site is working fine with a Site.Master so I've packaged up most of it into partial views and am trying to add a layout with the same content.
After reading Scott Gu's blog I've added a file called _ViewStart.cshtml in my ~/Views directory so that it'll be applied by default.
Unfortunately this gives me the error:
The name 'RenderBody' does not exist in the current context
If the file is called _viewstart.cshtml.
If it is called _layoutviewstart.cshtml it will work but I have to reference it directly in the view.
If it's called _billyviewstart.cshtml is works but again I have to reference it directly in the view.
View start
#{
Layout = "~/Views/Shared/_Layout.cshtml";
}
Layout.cshtml
<!DOCTYPE html>
<html>
<body>
#RenderBody()
</body>
</html>
This error will be raised when you include #RenderBody directive on a page with #page directive at the top. By adding #page you have directed ASP.Net Core that it is a page and not layout. Therefore, the compiler does not expect RenderBody to be included on the page.
To fix this error, remove #page directive from layout.

Categories

Resources