views:

221

answers:

1

I have a MSBuild script set up to minify and combine my javascript and css files. What I need now is a way to version them. How are you guys currently handling this. What is the best way to incrementally version the file and update the <script/> tag with the new file name?

A: 

I was originally going to suggest using resource expressions to include a version tag from AppSettings, but after testing found that it only works if it is the entire value of a server control property.

The AppSettings value can be updated by the build script for every release; can be any format you like:

<appSettings>
 <add key="versionTag" value="27" />
</appSettings>

Works:

<asp:Label runat="server" Text="<%$ AppSettings: versionTag %>" />

Doesn't work:

<link runat="server" rel="Stylesheet" type="text/css"
      href='/css/site.css?v=<%$ AppSettings: versionTag %>' />

So my actual recommendation is to create your own controls that read the version tag and include that in their output. Here's how my CSS control looks on a page (NOTE: server controls must be inside the server-side form, even though it may render inside the head element):

...
<form id="form1" runat="server">
 <my:Stylesheet runat="server" Url="~/css/site.css" />
</form>
...

The code for my Stylesheet control:

namespace MyNamespace
{
    using System;
    using System.ComponentModel;
    using System.Configuration;
    using System.Security.Permissions;
    using System.Web;
    using System.Web.UI;
    using System.Web.UI.HtmlControls;
    using System.Web.UI.WebControls;

    /// <summary>
    /// Outputs a CSS stylesheet link that supports versionable caching via a
    /// build-specific query parameter.
    /// </summary>
    [
        AspNetHostingPermission(SecurityAction.InheritanceDemand,
                Level = AspNetHostingPermissionLevel.Minimal),
        AspNetHostingPermission(SecurityAction.LinkDemand,
                Level = AspNetHostingPermissionLevel.Minimal),
        DefaultProperty("Href"),
        ToolboxData(@"<{0}:Stylesheet runat=""server"" />")
    ]
    public class Stylesheet : WebControl
    {
        private static string versionTag = Stylesheet.GetVersionTag();

        /// <summary>
        /// Gets or sets the stylesheet URL.
        /// </summary>
        public string Href
        {
            get
            {
                return this.ViewState["Href"] as string;
            }

            set
            {
                this.ViewState["Href"] = value;
            }
        }

        /// <summary>
        /// Raises the PreRender event.
        /// </summary>
        /// <param name="e">Contains the event data.</param>
        protected override void OnPreRender(EventArgs e)
        {
            base.OnPreRender(e);

            HtmlLink link = new HtmlLink();
            link.Href = String.Format(
                    "{0}?v={1}",
                    this.Page.ResolveUrl(this.Href),
                    HttpUtility.UrlEncode(Stylesheet.versionTag));

            if (!Stylesheet.HeadContainsLinkHref(this.Page, link.Href))
            {
                link.Attributes["type"] = "text/css";
                link.Attributes["rel"] = "Stylesheet";
                this.Page.Header.Controls.Add(link);
            }
        }

        /// <summary>
        /// Generates content to be rendered on the client.
        /// </summary>
        /// <param name="writer">Receives the server control content.</param>
        protected override void Render(HtmlTextWriter writer)
        {
            // Do nothing.
        }

        /// <summary>
        /// Retrieves the script version tag for this build.
        /// </summary>
        /// <returns>Returns the script version tag.</returns>
        private static string GetVersionTag()
        {
            string tag = ConfigurationManager.AppSettings["versionTag"];
            if (String.IsNullOrEmpty(tag))
            {
                tag = "1";
            }

            return tag;
        }

        /// <summary>
        /// Determines if the page's <c>head</c> contains a <c>link</c> tag
        /// with a matching <c>href</c> attribute value.
        /// </summary>
        /// <param name="thePage">The Page to be tested.</param>
        /// <param name="href">The <c>href</c> URL to be matched.</param>
        /// <returns>Returns true if a matching link is already part of the
        /// page <c>head</c> or false otherwise.</returns>
        public static bool HeadContainsLinkHref(Page thePage, string href)
        {
            if (thePage == null)
            {
                throw new ArgumentNullException("thePage");
            }

            foreach (Control control in thePage.Header.Controls)
            {
                if ((control is HtmlLink) &&
                    (control as HtmlLink).Href == href)
                {
                    return true;
                }
            }

            return false;
        }
    }
}

HTH.

devstuff