Best Approach: Mutliple content place holders in MVC - asp.net

I am converting an ASP.Net web forms project to MVC3. The master page contains multiple content place holders at different locations.
I replaced the first content place holder with #RenderBody() but I am confused with what to do of the second one.
One approach might be to separate views and place a #Html.RenderAction() for each content place holder method. Is there a better way to do it?

Razor has got sections understanding in place of asp.net webforms ContentPlaceHolders. Take a look at this introductionary link.

You can use sections. For example, to have a section for scripts, in the head tag of the layout.cshtml, you can specifiy
<head>
<title>#ViewBag.Title</title>
<link href="#Url.Content("~/Content/Site.css")" rel="stylesheet" type="text/css" />
<script src="#Url.Content("~/Scripts/jquery-1.4.4.min.js")" type="text/javascript"></script>
#RenderSection("scripts", false);
</head>
Inside of any view, you can now add a scripts section to inject your scripts:
#section scripts{
<script src="#Url.Content("~/Scripts/myscript.js")" type="text/javascript"></script>
}
the "false" param tells MVC to render the section if exists on the child page or do nothing if no call

Related

Cannot load jquery before other script

I need to load JQuery before anything else, so I created this code inside _Layout:
<!DOCTYPE html>
<html>
<body class="sidebar-enable" data-keep-enlarged="true">
<div class="wrapper">
#RenderBody()
</div>
<script src="~/js/jquery.min.js"></script>
#RenderSection("DataTableScript", required: false)
</body>
</html>
I have a ViewComponent called ProductsViewComponent, I load it inside a View called Home in this way:
#await Component.InvokeAsync("Products", new { date = "2018-09-05" })
Inside the ViewComponent I need to load the DataTableScript (which requires JQuery for works properly), so I created this logic inside the Default.cshtml (which is the html of ProductsViewComponent):
#{
Layout = "/Views/Shared/_LayoutViewComponent.cshtml";
}
#section DataTableScript{
<script src="~/js/vendor/jquery.dataTables.js"></script>
<script src="~/js/vendor/dataTables.bootstrap4.js"></script>
<script src="~/js/vendor/dataTables.responsive.min.js"></script>
<script src="~/js/vendor/responsive.bootstrap4.min.js"></script>
<script src="~/js/vendor/dataTables.buttons.min.js"></script>
<script src="~/js/vendor/buttons.bootstrap4.min.js"></script>
<script src="~/js/vendor/buttons.html5.min.js"></script>
<script src="~/js/vendor/buttons.print.min.js"></script>
<script src="~/js/vendor/dataTables.keyTable.min.js"></script>
<script src="~/js/vendor/dataTables.select.min.js"></script>
<script src="~/js/dataTable.js"></script>
}
I have specified another Layout because the ViewComponent is not able to render a section (see this question for further information)
inside the _LayoutViewComponent I placed this code:
#RenderBody()
#RenderSection("DataTableScript")
Essentially RenderBody calls _Layout and then the DataTableScript are loaded. But when I start the application I get the DataTableScript loaded before of JQuery and this is really weird because in the _Layout I specified to load the DataTableScript after JQuery.
You can use a partial to render your script tags.
Views/Shared/_DataTableScriptsPartial.cshtml
<script src="~/js/vendor/jquery.dataTables.js"></script>
<script src="~/js/vendor/dataTables.bootstrap4.js"></script>
<script src="~/js/vendor/dataTables.responsive.min.js"></script>
<script src="~/js/vendor/responsive.bootstrap4.min.js"></script>
<script src="~/js/vendor/dataTables.buttons.min.js"></script>
<script src="~/js/vendor/buttons.bootstrap4.min.js"></script>
<script src="~/js/vendor/buttons.html5.min.js"></script>
<script src="~/js/vendor/buttons.print.min.js"></script>
<script src="~/js/vendor/dataTables.keyTable.min.js"></script>
<script src="~/js/vendor/dataTables.select.min.js"></script>
<script src="~/js/dataTable.js"></script>
In your _Layout.cshtml declare a scripts section.
<!DOCTYPE html>
<html>
<head>
#RenderSection("head", required: false)
</head>
<body class="sidebar-enable" data-keep-enlarged="true">
<div class="wrapper">
#RenderBody()
</div>
<script src="~/js/jquery.min.js"></script>
#RenderSection("scripts", required: false)
</body>
</html>
In the page that uses your ViewComponent render the partial in the scripts section.
Products.cshtml
#{
Layout = "_Layout";
}
#await Component.InvokeAsync("Products", new { date = "2018-09-05" })
#section scripts {
#await Html.PartialAsync("_DataTableScriptsPartial")
}
You cannot specify a layout for a view component view. It's essentially a partial view. By the time the view component is processed the main layout is already set in stone. You should use a script loading library to conditionally include your additional scripts, and then externalize the JavaScript that serves your view component and have it run after the associated scripts are finished loading.
There's various different libraries/techniques you can use for this: CommonJS, AMD, RequireJS, etc. You'll need to do some research and figure out which is the best fit for you. However, this is a transformative thing. It will change the entire way you handle scripts with your application and might require some major restructuring of anything existing, as a result.
The easier approach is to simply just include the script in your main layout. If your layout includes this view component, then it's always going to need the scripts anyways, so just go ahead make it static. It doesn't have a the warm fuzzies of a completely self-contained unit of functionality that you're wanting your view component to be, but to get there is going to likely require far more effort that is warranted or reasonable for this one particular scenario.
For what it's worth, you can optimize things a bit by front-loading the scripts using the <link rel="preload" href="..." as="script" /> tag. This would go in your head, and prompt supporting browsers to go ahead and load in the script, without actually running it (which is the part that blocks rendering). Then, by the time you actually include the script before the closing body tag, it's likely already good to go, and the script tag will simply prompt the browser to run it.
UPDATE
One further options is to use a client-side library that supports "components". This would be a replacement for your view component, not something you'd add in addition to. However, the benefit of a client-side component is that you can contain all the JavaScript functionality in that and then the library simply runs over your document at the end and wires everything up. I personally like Vue, but there's other choices like React, Ember, Angular, etc. Consult with the documentation for each to evaluate if you might prefer this approach instead and which particular library you prefer. They all essentially do the same thing, but they each have their own unique ways of getting there.
I like Vue personally because it's light-weight and largely unobtrusive. It doesn't necessarily force you to do things in a certain way, so you can have a bit more freedom in that respect. Libraries like React and Angular tend to be more opinionated, and since they're geared more towards creating single page applications, can sometimes make it difficult to split responsibilities with server-side rendering such as Razor views. Just my opinion though.

Why does MVC automatically expand virtual URLs?

In my Layout.cshtml file I have the following line:
<link rel="stylesheet" href="~/Content/bootstrap.css" />
My assumption was that since I did not include Url.Content() call, it would actually just render what I wrote but not expand the virtual URL automatically. This assumption is wrong - the generated HTML does include the correct path to the file, not the virtual path I entered.
If I wrap the <link> in <script>
<script type="text/html">
<link rel="stylesheet" href="~/Content/bootstrap.css" />
</script>
then the URL is not expanded.
Which part of ASP.NET MVC does this automatic parsing of HTML and is there a way to control it?
This was a new feature included in Razor2 and ASP.NET MVC 4 and was designed to make life easier by not having to use Url.Content everywhere.
http://www.davidhayden.me/blog/asp.net-mvc-4-the-new-tilde-slash-feature-in-razor-2
The feature only works inside standard HTML attributes and that's why you don't get it inside your <script> tag or anywhere else.
You could use a simple output write to work around this:
<link rel="stylesheet" href="#("~/Content/bootstrap.css")" />

ASP.NET MVC 3 send <head> before <body> renders

I have asp.net mvc application, that uses razor view engine.
I want to send head to browser, before body renders, to start parallel loading css, js and images, linked in css. (You can see, how this technique works on SO in chrome developer tools - network for example)
I found question about it in asp.net web forms: Send head before body to load CSS and JS asap
I tried to use this solution, but it don't work.
For razor engine next sequense of steps is actual:
action returns view result
_ViewStart.cshtml executes (set ViewBag.Layout)
view executes from first line to last (with code inclusions and sections)
view engine checks ViewBag.Layout and if it found - executes Layout (with renderind body and sections)
I think that good solution is to divide step 3 into 3 parts:
generating content for Head and Css section
send to browser
generating other part of view
Other solution is to static include all.css and basic .js files in (without sections content, generated from view), send head, and then generate view (with generation FooterScript section).
In both ways I need to start execution from Layout page, not from view. For first: Layout (head) - view (sections) - layout (flush) - view (other) - layout (body). For second: Layout (head + flush) - view (all) + Layout (body).
My _Layout.cshtml file:
<html #Html.Raw(ViewBag.xmlns)>
<head>
<title>#ViewBag.Title</title>
#Html.Partial("_MetaTags")
<link href="#Url.ThemeCss("jquery-ui-1.8.18.custom.css")" rel="stylesheet" type="text/css" />
<link href="#Url.Css("masterPage.css")" rel="stylesheet" type="text/css" />
<link rel="shortcut icon" href="/favicon.ico"/>
#RenderSection("Css", required: false)
<script src="#Url.CommonScript("jquery.common.min.js")" type="text/javascript"></script>
<script src="#Url.Script("Master.js")" type="text/javascript"></script>
#RenderSection("Head", required: false)
</head>
<body>
<div id="pageWhithoutFooter">
<div id="main">
#RenderBody()
</div>
</div>
#RenderSection("FooterScript", required: false)
</body>
</html>
Howto?
Try putting your head section into a Partial view then call this in your controller:
PartialView("_PageHeader").ExecuteResult(ControllerContext);
Response.Flush();
// Generate model
return View(model);
Not tested this but I can't see why it wouldn't work.

Implementing CSS as a variable in the site.master page in ASP.NET MVC3

I am implementing a web application using ASP.NET MVC3. In the application I want to add the option of switching between CSS files (for themes) based on clicking some links. I did some research and I came up with the following:
To pass data to site.master, the good solution is to create an abstract controller and inherit that into the other controllers that have site.master as their template, according to this article: Passing Data to View Master Pages
I can pass a viewbag message to the link which controls the css file URL to set the current css file to use, based on the code that I am seeing being passed to scripts at the top of the site.master page:
script src="<%: Url.Content("~/Scripts/jquery-1.5.1.min.js") %>" type="text/javascript"
So I created an abstract controller, ApplicationController, with the following method:
public ApplicationController()
{ViewBag.NewMessage = "../../Content/Site2.css";}
And in the site.master, I included this link:
<link href="<%: (string) ViewBag.NewMessage %>" rel="stylesheet" type="text/css" />
However, that doesn't seem to be working, as it is being interpreted as:
<link href="<%: (string) ViewBag.NewMessage %>" rel="stylesheet" type="text/css" />
And only when I remove the quotation marks:
<link href=<%: (string) ViewBag.NewMessage %> rel="stylesheet" type="text/css" />
is it being interpreted correctly (and the webpage gets rendered with the correct css), except that it violates html standards:
<link href=../../Content/Site2.css rel="stylesheet" type="text/css" />
Any suggestion about how to go tackling this problem or is there a more elegant solution I'm missing? I was going to proceed and implement variables in the ApplicationController that get selected based on links the user clicks at the top of the page, to switch between css styles.
Thank you!
Check to make sure your <head> tag does not have runat="server".
After making this change, be sure to check your script and css tags. This change can break the paths if you use the ~/ to ref app root. To help with this, use the Url.Content(...) helper.

ASP.NET MVC 3: ViewModel for master template?

When I created my first empty MVC 3 project, this is the /Views/Shared/_Layout.cshtml file's contents:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>#ViewBag.Title</title>
<link href="#Url.Content("~/Content/Site.css")" rel="stylesheet" type="text/css" />
<script src="#Url.Content("~/Scripts/jquery-1.5.1.min.js")" type="text/javascript"></script>
<script src="#Url.Content("~/Scripts/modernizr-1.7.min.js")" type="text/javascript"></script>
</head>
<body>
#RenderBody()
</body>
</html>
The first thing I notice is that the <title>#ViewBag.Title</title>. The last thing I want to do is be using a dynamic "ViewBag" instead of a strongly-typed ViewModel.
How do I change my _Layout.cshtml so that the master template uses a strongly-typed ViewModel instead?
You can easily use #model to define the model type, but who will set the master pages's model or create it? The controller handles its own view and the model for the master page will be different based on the model created in each controller.
So, you may want to make all the models you pass to the views inherit from a base model class that has the properties required for master page and make the master page have the base type as the model type, but it'll be too ugly.
I'd suggest live with it for the simple cases like title here, if you find that you do a lot of stuff in there, have your own controller, action and view that perform the shared parts, and call Html.RenderAction() in the master page to get that executed.
http://gurustop.net

Resources