ASP.NET MVC 3: ViewModel for master template? - asp.net

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

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.

include style sheets in all pages asp vbscript

How can I include pages, style sheets, or links to them, automatically into my ASP VBscript pages? I read something about 'global' pages, but I am unsure what they mean and how it is that I can accomplish such a thing. I'm sure this is an easy question, but it's of great help to me as I've been writing VBscript for 2 days now! I'm not exactly an expert on HTML in general either, but I feel I have a reasonably good grasp of things. I would appreciate a good detailed example of how a 'global' page plays with my other ASP pages.
I'm setting up my first site...a management site for the main site I intend to build afterward. I want to get all my ducks in a row before moving forward with the public site. Can someone please give me some detailed information on how to include these pages/links automatically (page includes(header/footer), style sheets, etc) globally throughout my site without the need of using <!--#include file.... on each page I make, because that is kind of a pain and I'm sure there is an easier way. If there is, I know you can help! Thanks in advance, I look forward to hearing what options/possibilities are available.
If you insist on using ASP Classic you may find some method for handling masterpage like functionality but it is, to the best of my knowledge, not suppoerted as such by the framework.
[Edit] Given the edit of the original question the method first demonstrated is not so interesting, hence I suggest an alternative method too.
You could make a general ASP-page which serves all traffic to the site. A queryparameter then specifies which subpage should be displayed. Subpages are made as seperate ASP-pages which are executed by the general/master page or by another subpage. A very crude example of this could look like this:
<%
url = Request.QueryString("url") & ""
if url = "/" or url = "" then
subpage = "home.asp"
else
subpage = url & ".asp"
end if
%>
<!DOCTYPE html>
<html>
<head>
<title>Header for all pages</title>
<link rel="stylesheet" href="/css/site.css" />
</head>
<body>
<% Server.Execute(subpage) %>
</body>
</html>
The site should then be addressed in this fashion:
www.domain.com/default.asp?url=/contact
which would load the contact.asp subpage into the masterpage or:
www.domain.com/default.asp?url=/user/1234/profile
to load a user's profilepage (displayed by the profile.asp in the folder user/1234). This last example raises some issues because then every user has to have a folder containing all the asp-files (which is far from optimal) so you might want to employ some interpretation of the url queryparameter to redirect input in a more intelligent way.
Another issue is the fact that subpages are ASP-pages themselves which means someone could reference them directly. This calls for some action to protect those subpages from direct reference. It can be done but this would probably mean including some code => back to square one!
Another disadvantages of this approach is that subpages are rendered in their own context and hence can't access functionality and data from the calling page's context. This means that global data has to be shared in some other way (session, application, database or some other way). Data can't be passed to the subpage either (and no, Server.Execute doesn't allow query-parameters).
The include-way
Personally I think you get the most flexibility by using header/footer includes as demonstrated in my original post and shown below.
One way is to put your general stuff in includes and then includes those bits on each ASP-page. E.g.:
<!-- #include virtual="/includes/header.asp" -->
content goes here
<!-- #include virtual="/includes/footer.asp" -->
And your header.asp could look something like this:
<!DOCTYPE html>
<html>
<head>
<title>Header for all pages</title>
<link rel="stylesheet" href="/css/site.css" />
</head>
<body>
and footer.asp like so:
</body>
</html>
This strategy has some disadvantages. The header is fairly static which could present some problems with SEO; For one the title should fit the pagecontent which is hard to accomplish when the include contains the header-markup. This could be facilitated by some global variables that are set prior to the include-section i.e.:
<%
title = "Title for this page's content"
%>
<!-- #include virtual="/includes/header.asp" -->
content goes here
<!-- #include virtual="/includes/footer.asp" -->
and then in the header like so
<!DOCTYPE html>
<html>
<head>
<title><%=title%></title>
<link rel="stylesheet" href="/css/site.css" />
</head>
<body>
but that already begins to "smell" a little because you set up some expectations for the including page inside the include-file. At least you have to be very disciplined when constructing your pages.
The term you're looking for is Master Page, not Global Page, that may be why you're having a hard time finding what you're looking for on Google. Basically consider a master page a template. You create a master page, then load other pages into it. There are content place holders that you put in the master then populate on your other pages.
So a very basic example would look something like this.
<%# Master Language="VB" CodeFile="general.master.vb" Inherits="master1_general"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head id="Head1" runat="server">
<link rel="stylesheet" type="text/css" href="/styles/main.css?v2"/>
<asp:ContentPlaceHolder id="head" runat="server">
</asp:ContentPlaceHolder>
</head>
<body>
<form id="form1" runat="server">
<asp:ContentPlaceHolder id="body" runat="server">
</asp:ContentPlaceHolder>
</form>
</body>
</html>
Then your individual pages would look like this:
<%# Page Language="VB" MasterPageFile="~/master/general.master" AutoEventWireup="false" CodeFile="base.aspx.vb" Inherits="_Default" title="Opportunities" %>
<asp:Content ID="Content1" ContentPlaceHolderID="head" Runat="Server">
//any additional head stuff specific to this page goes here.
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="body" Runat="Server" >
//your body mark up goes here.
</asp:Content>
Notice how the Master page is actually a web page. Then it has place holders in certain spots. In this one there is a place holder in the head and one in the body. Then on individual pages I identify which master page to use and what data (if any) goes in the place holders. I always include a placeholder in the head so I can load js or resources that specific pages need on that page only.
Then the individual pages are just the content that goes in the placeholders.

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.

Best Approach: Mutliple content place holders in MVC

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

Resources