Getting access to the Head tag in Master Pages
I've been busily converting one of my apps to ASP.NET 2.0 as well as updating some core components and the base databinding mechanism of my overall framework. This app didn't really need an update from a functional perspective, but I used the application as my proving ground for a number of new concepts and ideas I've been tossing around – most of which have been discussed here at one time or another.
One thing that's still throwing me for a loop once a while is MasterPages. I'm still finding a few things here and there that are not quite natural to deal with. Here's one of them.
In my app I have several master pages for different 'areas' of the application. The main public facing interface gets one masterpage, the admin areas another and the reporting end another. Master Pages work great for basic skinning support and the ability to customize the layout.
But one thing that I've repeatedly run into is styling of pages dynamically. The Masters define the head section so all style references come for the theme CSS file(s). But on a number of pages – especially the admin pages – I need to customize the existing styles and or create one off styles for a specific page.
Easier said than done with MasterPages, since the style is defined in the head tag of the MasterPage. You can't easily define a Style tag inside of a Content panel:
<style>
.gridheader { text-align:center;height:20px;padding-top:2px; }
</style>
If you do something like this right in your Content Panel, the HTML will actually work, but it's actually badly formatted HTML. Worse – Visual Studio in won't let you switch into design view with a style tag inside of your content (depending on how your page is laid out but if it's between a <td> or <div> tag it won't let you).
So how do you get a style tag into the header? You could do it programmatically but the syntax for adding individual styles is not exactly short or intuitive. In fact, on a quick search I couldn't figure out how to do it short of writing out raw text into the header with a literal control (I'm sure there's an easier way though). Anyway, I don't think this is a workable solution.
The solution I settled on is this: Using a second content panel in the MasterPage that essentially extends the header into the Content pages.
<head runat="server">
<title></title>
<link runat="server" id="styleTemp"
href="../../App_Themes/Standard/Standard.css"
rel="stylesheet" type="text/css" />
<asp:ContentPlaceHolder runat="server" id="Headers">
</asp:ContentPlaceHolder>
</head>
<body style="margin-top: 0px; margin-left: 0px">
If you don't need to add any headers to your content page – fine. Nothing further is needed. But if you now need to add say a stylesheet you can do this in the content page:
<%@ Page Language="c#" Inherits="Westwind.WebStore.Admin.EditInventoryItem" >
<asp:Content ID="Content1" runat="server" ContentPlaceHolderID="Headers">
<style>
.gridheader { text-align:center;height:20px;padding-top:2px; }
</style>
</asp:Content>
<asp:Content ContentPlaceHolderID="Content" ID="Content" runat="server">
<h1>Inventory Item Edit Form</h1>
…
</asp:Content>
This solves the problem of giving the content page access to the header which is the one place where the client might need access to the content of the master. You can add styles, meta tags, scripts or whatever you want in the header right here.
Eeer, just as I'm finishing this up I notice that K.Scott Allen posted a similar note about a month ago. I suppose this is good enough of a tip to pass on again <g>.
One thing he mentions that I missed it that the Visual Studio HTML validation complains about the content placeholder in the header. But then again VS.NET complains about a lot of things that are legit <shrug>…
Other Posts you might also like
- Map Physical Paths with an HttpContext.MapPath() Extension Method in ASP.NET
- Adding minimal OWIN Identity Authentication to an Existing ASP.NET MVC Application
- Getting the Client IP Address in ASP.NET Core
- Resolving Paths To Server Relative Paths in .NET Code
- Preventing iOS Textbox Auto Zooming and ViewPort Sizing
The Voices of Reason
# re: Getting access to the Head tag in Master Pages
I do think you really should have the ability to add information to the header from within a content page. This gives the needed flexibility without any impact if you don't use it - it's pretty unobtrusive (except for the VS.NET Html Validation warning).
# re: Getting access to the Head tag in Master Pages
I think (mostly all) style definition should always come from css-stylesheets, adding styles programmatically isn't the most optimal situation, 'cause style changes requires you to
change code and compile, instead changing style in the css-stylesheet.
I normally define several stylesheets, one for basic, one for media, several for advanced use or different pages and apply them into the pages so i can keep all stylesheets compact and short.
You can also import one stylesheet from another stylesheet. This allows you to link to your basic stylesheet from the page and then import your complicated styles into that stylesheet
like::
@import url(/css/layout.css)
@import url(/css/typografy.css)
@import url(/css/color.css)
This helps to remove some complexity from css-pages and allows you to manage all your stylesheets in one place (except theme stylesheets). Import rules need to be the first rules in a stylesheet or they may not work properly. Because imported stylesheets are considered to come before linked stylesheets, it's important to remember that the rules in your linked stylesheets will be overriding your imported rules and not the other way around.
# re: Getting access to the Head tag in Master Pages
I haven't worked with imports in a while - I tend to load styles from pages, primarily because I've always had path issues with imported CSS files.
# re: Getting access to the Head tag in Master Pages
Grrr... that throws a wrench into this whole scheme.
# re: Getting access to the Head tag in Master Pages
There is a Page.Header class you can use in ASP.Net 2.0 to add controls/etc. to..
Here's a VB.Net (yuck) example:
Dim cssLink As New HtmlLink()
cssLink.Href = "~/styles.css"
cssLink.Attributes.Add("rel", "stylesheet")
cssLink.Attributes.Add("type", "text/css")
Page.Header.Controls.Add(cssLink)
Viola.
# re: Getting access to the Head tag in Master Pages
A lot of people have mentioned how to add Style*sheets* programmatically. That's easy enough to do, but if you add styles, there's no programmatic equivalent that I know of. Other than writing a raw literal control into the header.
# re: Getting access to the Head tag in Master Pages
# re: Getting access to the Head tag in Master Pages
# re: Getting access to the Head tag in Master Pages
http://msdn.microsoft.com/asp.net/default.aspx?pull=/library/en-us/dnaspp/html/JAVAwASP2.asp#javawasp2_topic6
which talks about using Page.ClientScript.RegisterStartupScript
etc. I think your way seems much simpler but I'm having trouble getting it loaded into the <head>. Have you had any success using your technique in master and content pages to load Javascript into the header?
# re: Getting access to the Head tag in Master Pages
IAC, you shouldn't need script in the head of the page. That doesn't guarantee loading of script properly. Using RegisterStartupScript will put the script in the right place to be executed and that's usually the best way - it puts startup script just before the end of the form tag in a page. You can do this manually as well.
The right way to load script and make sure the page is loaded is to fire script from <body onload=''>.
# re: Getting access to the Head tag in Master Pages
I am trying to avoid:
a. script injection via RegisterStartupScript
b. putting in-line Javascript in <head> of the Master Page (not all my content will need the same scripts)
I would like use the idea of your "declarative" structure, extending your concept of
a. declare a content placeholder in master page <head> section for script that needs to be in the header
b. declare a content placeholder in master page after </body> for script that needs to be there
c. establish separate content files for each that reference the same .master file.
I am trying to extend my current use of the Google Maps API which is all client side Javascript to operate in a framework of ASP.NET 2.0 master page plus content page(s).
I am finding I cannot get the "composite" page built with any Javascript in the <head> nor after the </body> tag by using ASP:Content controls that refer to placeholders in the master page.
Can you please clarify your guidance in light of my longer explanation?
# re: Getting access to the Head tag in Master Pages
I think you need to actually try this out to see it work - you might be visualizing this wrong.
All I'm saying is there are many ways to get script code into the page and that is one of them you can use.
# re: Getting access to the Head tag in Master Pages
I zipped up my test web site so you can see how I have tried to follow your guidelines:
ftp://ftp.cbmiweb.com/pub/john_adams/web.zip
username = ftpuser password = cbmi
If you glance at my master page and my 3 content ASPX files you will see how I am simply trying to package stuff as you have suggested (along with script guidelines from Google).
I build the site in Visual Studio 2005 and run it and the only Javascript in the resulting page is stuff generated by ASP.NET.
# re: Getting access to the Head tag in Master Pages
# re: Getting access to the Head tag in Master Pages
# re: Getting access to the Head tag in Master Pages
HtmlGenericControl control = new HtmlGenericControl();
control.TagName = "style";
control.InnerText = ".gridheader { text-align:center;height:20px;padding-top:2px; }";
Header.Controls.Add(control);
The same technique can be used for adding meta tags, using HtmlMeta.
# re: Getting access to the Head tag in Master Pages
I am also using Atlas and doing the programmatic HtmlLink addition wasn't working on postbacks for some reason. It was processing, but the styles were reverting.
I was also concerned about the error for <asp:ContentPlaceHolder /> because I hate having warnings like that if I can help it.
Anyway, if you need to add a single css to a page programmatically, try this in the master page header:
<link id="page_css" />
...and this in your page code:
Dim objLink As HtmlLink = Page.Header.FindControl("page_css")
If Not IsNothing(objLink) Then
objLink.Href = "~/search/search.css"
objLink.Attributes("rel") = "stylesheet"
objLink.Attributes("type") = "text/css"
End If
# re: Getting access to the Head tag in Master Pages
add this 2 functions to masterpage .cs file
public void RegisterScriptSrc(string url)
{
Literal head_script;
Control ctrl = Page.Header.FindControl("ap_m_script");
if (ctrl != null) head_script = (Literal)ctrl;
else
{
head_script = new Literal();
head_script.ID = "ap_m_script";
Page.Header.Controls.Add(head_script);
}
head_script.Text += "\r\n<script type=\"text/javascript\" src=\"" + url + "\"></script>";
}
public void RegisterStyleSrc(string url)
{
HtmlLink csslink = new HtmlLink();
csslink.Href = url;
csslink.Attributes.Add("type", "text/css");
csslink.Attributes.Add("rel", "stylesheet");
Page.Header.Controls.Add(csslink);
}
# re: Getting access to the Head tag in Master Pages
# re: Getting access to the Head tag in Master Pages
It writes whatever you insert into it in the head tag?
WOrks for my purposes...
# re: Getting access to the Head tag in Master Pages
Even so there are limitations to this approach. If you use Themes the Theme is injected last in the list of header items, so the themes will always override any settings made in the Content area as well as anything else (like script) that gets injected into the Header. So if you really need to *override* styles in the content of a master page you need to do something like this:
<div runat="server">
<style>
.MyOverrideTag {...}
</style>
</div>
Without the server DIV Visual Studio will flag <style> as invalid in the document body - grumble although it will work just fine.
# re: Getting access to the Head tag in Master Pages
Someway like that would probably be how I would do it. I was looking for a way to ineject javascript to the top of each page when I stumbed onto your post here.
# re: Getting access to the Head tag in Master Pages
I don't know how I got to "grahical code" :D
# re: Getting access to the Head tag in Master Pages
http://www.codeproject.com/useritems/PageTags.asp
uses the same technique of adding things to the page header, but allows you to do it on the @Page directive.
# re: Getting access to the Head tag in Master Pages
More info:
https://connect.microsoft.com/feedback/viewfeedback.aspx?FeedbackID=273683&wa=wsignin1.0&siteid=210
# re: Getting access to the Head tag in Master Pages
Nice work!
# re: Getting access to the Head tag in Master Pages
<meta name="description" content='<asp:ContentPlaceHolder id="metaDescriptionRegion" runat="server"/>' />
complains that it:
Cannot find ContentPlaceHolder 'metaDescriptionRegion' in the master page...
Any ideas how I can access metaDesciptionRegion?
Neuralsea
# re: Getting access to the Head tag in Master Pages
# re: Getting access to the Head tag in Master Pages
# re: Getting access to the Head tag in Master Pages
does anybody can help about how can I solve this problem?
Thanks
# re: Getting access to the Head tag in Master Pages
# re: Getting access to the Head tag in Master Pages
Thanks
# re: Getting access to the Head tag in Master Pages