简体   繁体   中英

Automatically add version to js files inside asp.net header runat=“server”

I'm trying to reload reviewed css/js files automatically to prevent cache. I can loop trough header (which is runat="server") and add a value to link href references but the loop doesn't catch script tags as controls.

if (Page.Header != null)
{
    foreach (var sc in Page.Header.Controls)
    {
        var type = sc.GetType();

        if (type == typeof(HtmlLink))
        {
            ((HtmlLink)sc).Href += "?v=" + Tools.BuildDate();
        }
        else if (type == typeof(HtmlGenericControl))
        {
            ((HtmlGenericControl)sc).Attributes["src"] += "?v=" + Tools.BuildDate();
        }
    }
}

I've tried to add runat="server" to script tags but in this way asp.net tries to compile those files and build fails. I cannot add version every time because the project has many files. Also code blocks is not allowed when runat="server" is applied.

I want to use a commonly accepted solution so I don't have to worry for css/js file versions any more.

In MVC Core 1.0 there is a tag helper for this exact purpose. If you are using a different version of MVC then the previous answers should help.

<link rel="stylesheet" src="wherever.css" asp-append-version="true" />

A script tag is not going to be picked up as a Control, so you need to wrap it in a custom control if you want to handle it in the controller. Or you could just add the timestamp directly in the view:

<script type="text/javascript" src="path/to/file.js?v=<%= Tools.BuildDate() %>" />

Here is some more info: http://madskristensen.net/post/cache-busting-in-aspnet

This should do

<script src="ScriptsFolder/ScriptFile.js?v=<%=Tools.BuildDate() %>"></script>
<link href="StylesFolder/StyleFile.css?v=<%= Tools.BuildDate() %>" rel="stylesheet" />

When the value returned by Tools.BuildDate() changes, the browser will be forced to reload the script/style file since the resource url will have changed eg from url/ScriptsFolder/ScriptFile.js?v=1234 to url/ScriptsFolder/ScriptFile.js?v=5678. This by far should be the simplest and fastest solution, since it will only be appending the query string v=value to the resource url. The appended querystring will completely be ignored by the server when fetching the resource specified.

Without knowing what ver of ASP.NET (or MVC) you are using, I can give you an easy way. You can always build a "helper" to do the heavy lifting for you. This is the way that MVC and ASP.NET 5 helpers handle it, but will work with just about any version. I generally don't prefer inline code, but a static helper can be made safe to protect against runtime errors.

First build a static helper utility:

public static class ScriptHelper
{
    public static IHtmlString Render(params string[] paths)
    {
        return RenderFormat(DefaultTagFormat, paths);
    }

    public static IHtmlString RenderFormat(string tagFormat, params string[] paths)
    {
        if (string.IsNullOrEmpty(tagFormat))
        {
            throw new ArgumentException("Tag Format cannot be null");
        }
        if (paths == null)
        {
            throw new ArgumentNullException("Paths cannot be empty");
        }
        if (paths.Any(string.IsNullOrEmpty))
        {
            throw new ArgumentException("paths");
        }
        return BuildHtml(tagFormat, paths);
    }

    private static string defaultFormat = "<script src=\"{0}?ver={1}\"></script>";

    public static string DefaultTagFormat
    {
        get
        {
            return defaultFormat;
        }
        set { defaultFormat = value; }
    }

    private static IHtmlString BuildHtml(string tagFormat, params string[] paths)
    {
        StringBuilder builder = new StringBuilder();
        foreach (string path in paths)
        {
            StringBuilder builder = new StringBuilder();
        foreach (string path in paths)
        {
            // Substitute your logic for version number
            var version = "1234";
            // You could factory this to a concrete type based on file extension etc.
            var fileToOutPut = new VersionedJsFile(path,version,tagFormat);
            builder.Append(fileToOutPut.RenderOutput());
            builder.Append(Environment.NewLine);
        }
        return new HtmlString(builder.ToString());
    }

}

You can add extra logic to wrap around the path, validation of path, virtual paths, etc. Best to create an object with a render method that you can pass in the path (from the helper) and the version. This is the oop way to do it and is more flexible. Doing that you can then have this be a "VersionHelper" instead and have it deal with css/js/etc.

 internal abstract class HtmlFile
{
    public abstract string RenderOutput();
}

internal class VersionedJsFile : HtmlFile
{

    private string _version;
    private string _path;
    private string _format;
    public VersionedJsFile(
        string path, 
        string version,
        string format)
    {
        if (version != null) _version = version;
        if (path != null) _path = path;
        if(!string.IsNullOrEmpty(format))
            _format = format;
    }

    public override string RenderOutput()
    {
        if (!string.IsNullOrEmpty(_path)
            && !string.IsNullOrEmpty(_format))
        {
            string versionedFilePath = string.Format(_format, _path, _version);
            return versionedFilePath;

        }
        return string.Empty;
    }
}

Then add the helper to your page/master page/layout:

 <%: ScriptHelper.Render("/scripts/bootstrap.js") %>

As you can see, the helper takes a params object so you can apply to multiple files at once.

Output:

<script src="/scripts/bootstrap.js?ver=1234"></script>

The helper can easily be enhanced.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM