将版本号添加到asp:ScriptManager的compositescript标记中

7
我们正在尝试实现以下场景:
我们使用asp:scriptmanager/CompositeScripts将脚本组合成几个脚本块,但是每次部署到测试系统后,我们都会遇到浏览器缓存问题,导致测试人员无法获取更新版本的CSS和JavaScript。对于CSS,我们定义了自己的CSS用户控件,它在CSS URL后附加一个Siteversion参数"?v=1.0.190"。这个siteversion在web.config/appsettings中定义,并且在每次部署时都会增加。
我们希望能够使用相同的策略来处理JavaScript,但到目前为止,我们还没有成功。当呈现脚本标记时,Scriptmanager会呈现
<script src="/ScriptResource.axd?d=..." type="text/javascript"></script> 

考虑到当前配置的站点版本为1.0.190,我希望它能够呈现出来。

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

我该如何从脚本管理器中获取“script” HTML输出,以便我可以更改它? 在Render、RenderChildren或RenderControl中呈现的内容中似乎没有出现

您的 安德烈亚斯


你开了一个悬赏,却没有选择答案,即使有人已经在工作!非常不好的态度。 - Aristos
3个回答

4

我在反编译器中进行了一番搜索,看起来这是一个很棘手的添加方式。我没有找到任何好的扩展点。但是,如果您愿意采用一些反射技巧,可以添加以下ScriptManager的ControlAdapter以解决问题:

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;
        }
    }
}

要连接这个控制适配器,您需要创建一个Web.browser文件并将其放在网站的App_Browsers文件夹中。 Web.browser文件应该类似于这样:

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

我测试过这个方法并且对我有效。希望对你有所帮助。


+1 Andy,我投票支持你的答案,因为它更接近问题,并且如果Andreas忘记选择一个人,最好让有人获得赏金。 - Aristos
我的 Web.browser 片段在原贴中丢失了。这是它的样子:<browsers> <browser refID="Default"> <controlAdapters> <adapter controlType="System.Web.UI.ScriptManager" adapterType="MyNamespace.VersionedScriptManagerAdapter"/> </controlAdapters> </browser> </browsers> - Andy
这段代码最终没有起作用!我认为它需要更多的工作和测试。 - Aristos
你确认控制适配器已经配置好了吗?尝试在适配器的OnPreRender方法中设置断点,并验证调试器是否触发了断点。如果没有触发,那么这意味着你的.browser文件没有正确配置。.browser文件需要位于你网站的App_Browsers文件夹中... - Andy

3

对于我来说,在页面的onload事件中使用一个简单的解决方案就足够了:

ScriptManager.GetCurrent(this).CompositeScript.Path += "?v=" + MyBuildNumber.Value;

1
我建议采用不同的方法。缓存是您的问题,那么更改缓存标头。 这是一个我制作并测试过的示例,它可以正常工作。在global.asax中,在调用的最开始...
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);
        }
    }
}

1
那种方法的问题在于,浏览器最终会进行大量不必要的往返访问Web服务器,只是为了发现文件自上次以来没有被修改。在一个非平凡的网站上,这可能对网站的性能产生重大影响(往返访问=糟糕)。使用版本化的URL的优点是可以非常积极地缓存脚本,并且只有在脚本已更新(通过增加版本号)时才强制浏览器进行往返访问。 - Andy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接