append versionnumber to asp:ScriptManager's compositescript tag
Asked Answered
M

3

7

we are trying to get the following scenario working:

we use asp:scriptmanager / CompositeScripts to combine our scripts into a few script blocks but after each deploy to the test system we have trouble with testers not getting updated versions of css and javascript (browser cache). For the CSS we have defined our own css user control which appends a Siteversion parameter "?v=1.0.190" for instance to the css url. This siteversion is defined in web.config / appsettings and is bumped on every deploy.

We wanted to be able to use the same strategy for javascripts, but thus far I haven't had any success. When rendering the script tag. the Scriptmanager renders

<script src="/ScriptResource.axd?d=..." type="text/javascript"></script> 

Given that the current configured siteversion was 1.0.190, I would want it to render

<script src="/ScriptResource.axd?d=...&v=1.0.190" type="text/javascript"></script> 

How do I get at the "script" html output from the script manager so I can change it? It doesn't seem to be present in the stuff rendered in Render, RenderChildren or RenderControl

Yours Andreas

Margarettmargaretta answered 9/6, 2010 at 13:57 Comment(1)
-1 You open a bounty and you do not even select an answer even there are working ! very bad attitude.Hamelin
J
3

A much simpler solution in your page's onload works just fine for me:

ScriptManager.GetCurrent(this).CompositeScript.Path += "?v=" + MyBuildNumber.Value;
Jotter answered 19/1, 2011 at 0:30 Comment(0)
F
4

I dug around in reflector for a bit and it looks like this is unfortunately a tricky thing to add. MS didn't provide any good extension points that I could find. However, if you're willing to resort to a nice reflection hack, adding the following ControlAdapter for ScriptManager should do the trick:

public class VersionedScriptManagerAdapter : ControlAdapter
{
    protected new ScriptManager Control
    {
        get { return (ScriptManager) base.Control; }
    }

    protected override void OnPreRender(System.EventArgs e)
    {
        base.OnPreRender(e);

        var compositeScriptField = Control.GetType().GetField("_compositeScript",
                                                              BindingFlags.NonPublic | BindingFlags.Instance);
        var currentCompositeScript = Control.CompositeScript;
        var versionedCompositeScript = new VersionedCompositeScriptReference();
        CopyCompositeScript(currentCompositeScript, versionedCompositeScript);
        compositeScriptField.SetValue(Control, versionedCompositeScript);
    }

    private void CopyCompositeScript(CompositeScriptReference sourceCompositeScript, CompositeScriptReference targetCompositeScript)
    {
        targetCompositeScript.Path = sourceCompositeScript.Path;
        targetCompositeScript.ResourceUICultures = sourceCompositeScript.ResourceUICultures;
        targetCompositeScript.ScriptMode = sourceCompositeScript.ScriptMode;
        foreach (var scriptReference in sourceCompositeScript.Scripts)
        {
            targetCompositeScript.Scripts.Add(scriptReference);
        }
    }

    private class VersionedCompositeScriptReference : CompositeScriptReference
    {
        protected override string GetUrl(ScriptManager scriptManager, bool zip)
        {
            string version = ConfigurationManager.AppSettings["ScriptVersion"];
            return base.GetUrl(scriptManager, zip) + "&v=" + version;
        }
    }
}

Then to hook up this control adapter, you will need to create a Web.browser file and put it in your App_Browsers folder on the website. The Web.browser file should look something like this:

<browsers>
    <browser refID="Default">
      <controlAdapters>
        <adapter controlType="System.Web.UI.ScriptManager"
          adapterType="MyNamespace.VersionedScriptManagerAdapter">
        </adapter>
      </controlAdapters>
    </browser>
</browsers>

I tested this out and it worked for me. I hope its helpful to you.

Fluviatile answered 17/6, 2010 at 5:22 Comment(4)
+1 Andy I vote your answer because its closer to the question, and because its better someone get the bounty if andreas forget to select one.Hamelin
Somehow my Web.browser snippet didn't make it into my original post. Here is what it would look like: <browsers> <browser refID="Default"> <controlAdapters> <adapter controlType="System.Web.UI.ScriptManager" adapterType="MyNamespace.VersionedScriptManagerAdapter"/> </controlAdapters> </browser> </browsers>Fluviatile
this code finally its not working ! I think that its need more work and tests.Hamelin
Have you confirmed that the control adapter is configured? Try setting a breakpoint in the OnPreRender method of the adapter and verify that the debugger hits the breakpoint. If it doesn't, then that means your .browser file is not configured correctly. The .browser file needs to live in the App_Browsers folder of your website...Fluviatile
J
3

A much simpler solution in your page's onload works just fine for me:

ScriptManager.GetCurrent(this).CompositeScript.Path += "?v=" + MyBuildNumber.Value;
Jotter answered 19/1, 2011 at 0:30 Comment(0)
H
1

I will suggest a diferent aproche. The chache is your problem, then change the cache Header. Here is an example that I make and test it and its works just fine. On the global.asax on the very start of the call...

protected void Application_BeginRequest(Object sender, EventArgs e)
{
    HttpApplication app = (HttpApplication)sender;

    string cTheFile = HttpContext.Current.Request.Path;

    if (cTheFile.EndsWith("ScriptResource.axd", StringComparison.InvariantCultureIgnoreCase))
    {
        // here is the trick with your version !
        string etag = "\"" + app.Context.Request.QueryString.ToString().GetHashCode().ToString() + "1.0.190" + "\"";
        string incomingEtag = app.Request.Headers["If-None-Match"];

        app.Response.Cache.SetETag(etag);

        if (String.Compare(incomingEtag, etag) == 0)
        {
            app.Response.StatusCode = (int)System.Net.HttpStatusCode.NotModified;
            app.Response.StatusDescription = "Not Modified";                            
        }
        else
        {
            app.Response.Cache.SetExpires(DateTime.UtcNow.AddMinutes(1));
            app.Response.Cache.SetMaxAge(new TimeSpan(0, 1, 0));
            app.Response.Cache.SetCacheability(HttpCacheability.Public);
        }
    }
}
Hamelin answered 14/6, 2010 at 14:3 Comment(1)
The issue with that approach is that then the browser will end up making lots of unnecessary round trips to the webserver only to find out that the file has not been modified since the last time. On a non-trivial website, this can have a significant impact on the performance of the site (round trips = bad). The advantage of using the versioned url's is that you can cache the scripts very aggressively, and only force the browser to make a round trip when the script has been updated (by increasing the version number).Fluviatile

© 2022 - 2024 — McMap. All rights reserved.