Script Tags and Master Pages in ASP
Saturday, February 23rd, 2008Introduction
In a recent ASP .NET project I decided to use a global “Master Page” to contain my major theme code and use individual ASPX pages strictly for content. As a personal preference I like to use absolute paths relative to the site root. I very rarely have path issues when using absolute paths. This project requires that relative paths be used since the web application would be switching domains and placed within a sub-directory. One of the advantages of Master pages is that any pages in a sub-directory that utilize a Master page in the application’s root directory have their relative links updated automatically. The server adds the correct number of “../”’s to the front of the path. According to the MSDN documentation this occurs on all ASP web controls (anything that is <asp:….>). I noticed that my <link> paths were also automatically being updated, neat.
The Problem:
The problems started to show up when I added JavaScript to the Master page for navigation. Everything worked fine in the application’s root directory (also the location of the Master Page file), but for pages in a sub-directory the script tag’s “src” attribute was not updating. Thus my navigation did not work and any other scripts would not load.
The Solution:
To rectify this error I had to write a custom server control. Having never done this before I hit some problems, but I will go through all of the steps to get the custom control created and working.
- Create a new Class in your project, name it “Script.cs”, it should be placed in the App_Code directory.
- Import the necessary namespaces and declare your namespace (this is very important)
using System; using System.ComponentModel; using System.Security.Permissions; using System.Web; using System.Web.UI; using System.Web.UI.WebControls; namespace Ameronix - Next declare a couple of enums for the script type and script language, these will make future expansion easier.
public enum ScriptType { javascript }; public enum ScriptLanguage { javascript }; - Since this is a control that is not visible to the user we are extending the .NET class “Control”. We also need to set some security levels and toolbox data.
[ AspNetHostingPermission(SecurityAction.Demand, Level = AspNetHostingPermissionLevel.Minimal), AspNetHostingPermission(SecurityAction.InheritanceDemand, Level = AspNetHostingPermissionLevel.Minimal), DefaultProperty("ScriptSource"), ToolboxData("<{0}:Javascript runat\"server\" />") ] public class Script : Control - Next we declare our properties that will be accessible. I have included the Script’s source, type, and language, as these are the most common.
/// <summary> /// The source url for the script /// </summary> [ Bindable(true), Category("Behavior"), DefaultValue(""), Description("The relative path to the javascript file."), Localizable(false) ] public string ScriptSource { get { return ViewState["ScriptSource"] as string; } set { ViewState["ScriptSource"] = value; } } /// <summary> /// The type of script this is /// </summary> public ScriptType ScriptType { get { return (ViewState["ScriptType"] == null) ? ScriptType.javascript : (ScriptType)ViewState["ScriptType"]; } set { ViewState["ScriptType"] = value; } } /// <summary> /// The language the script is written in /// </summary> public ScriptLanguage ScriptLanguage { get { return (ViewState["ScriptLanguage"] == null) ? ScriptLanguage.javascript : (ScriptLanguage)ViewState["ScriptLanguage"]; } set { ViewState["ScriptLanguage"] = value; } } - Finally we override the Render() method so we can actually output the element.
/// <summary> /// Renders the control at runtime /// </summary> /// <param name="writer">The writer to use to output the HTML</param> protected override void Render(HtmlTextWriter writer) { base.Render(writer);string type; string language; switch (ScriptType) { case ScriptType.javascript: default: type = “text/javascript”; break; } switch (ScriptLanguage) { case ScriptLanguage.javascript: default: language = “javascript”; break; } writer.Write(”<script src=\”{0}\” type=\”{1}\” language=\”{2}\”></script>”, ResolveClientUrl(ScriptSource), type, language); writer.Write(Environment.NewLine); }The most important line converts our relative link for any page underneath a sub-directory.
writer.Write("<script src=\"{0}\" type=\"{1}\" language=\"{2}\"></script>", ResolveClientUrl(ScriptSource), type, language);The code should resemble the following:
using System; using System.ComponentModel; using System.Security.Permissions; using System.Web; using System.Web.UI; using System.Web.UI.WebControls;namespace Ameronix { public enum ScriptType { javascript }; public enum ScriptLanguage { javascript }; [ AspNetHostingPermission(SecurityAction.Demand, Level = AspNetHostingPermissionLevel.Minimal), AspNetHostingPermission(SecurityAction.InheritanceDemand, Level = AspNetHostingPermissionLevel.Minimal), DefaultProperty(”ScriptSource”), ToolboxData(”<{0}:Javascript runat\”server\” />”) ] public class Script : Control { /// <summary> /// The source url for the script /// </summary> [ Bindable(true), Category(”Behavior”), DefaultValue(”"), Description(”The relative path to the javascript file.”), Localizable(false) ] public string ScriptSource { get { return ViewState[”ScriptSource”] as string; } set { ViewState[”ScriptSource”] = value; } } /// <summary> /// The type of script this is /// </summary> public ScriptType ScriptType { get { return (ViewState[”ScriptType”] == null) ? ScriptType.javascript : (ScriptType)ViewState[”ScriptType”]; } set { ViewState[”ScriptType”] = value; } } /// <summary> /// The language the script is written in /// </summary> public ScriptLanguage ScriptLanguage { get { return (ViewState[”ScriptLanguage”] == null) ? ScriptLanguage.javascript : (ScriptLanguage)ViewState[”ScriptLanguage”]; } set { ViewState[”ScriptLanguage”] = value; } } /// <summary> /// Renders the control at runtime /// </summary> /// <param name=”writer”>The writer to use to output the HTML</param> protected override void Render(HtmlTextWriter writer) { base.Render(writer); string type; string language; switch (ScriptType) { case ScriptType.javascript: default: type = “text/javascript”; break; } switch (ScriptLanguage) { case ScriptLanguage.javascript: default: language = “javascript”; break; } writer.Write(”<script src=\”{0}\” type=\”{1}\” language=\”{2}\”></script>”, ResolveClientUrl(ScriptSource), type, language); writer.Write(Environment.NewLine); } } } - Now we have a new control, but our ASP pages cannot see it. We need to map the namespace in our application. Open your web.config file and add the following to your system.web directive.
<pages> <controls> <add tagPrefix="amx" namespace="Ameronix"/> </controls> </pages>The tag prefix will be useful shortly so don’t forget it.
- In your master page file start a new tag “<amx:” auto complete should display “Script” now. Fill in the required variables and your new control will be used in the master page. Note: you MUST include runat=”server” or the control will not be converted into the appropriate script tag. For example:
<amx:Script ID="js_jquery" ScriptSource="resources/js/jquery.js" ScriptType="javascript" ScriptLanguage="javascript" runat="server" />
Tags: ASP .NET, C#, Custom Control, Master Pages, Script tags



