ASP.NET AJAX 资源脚本压缩的秘密

记得当ASP.NET AJAX还在ATLAS阶段的时候,我就有发过一篇关于脚本资源文件可以被压缩的blog Great!The Atlas client library has been compressed in release mode. 从ATLAS到正式的ASP.NET AJAX已经发生了根本性的变化了,所以当时的情况就不再多做介绍了.还是先来看看一组数据

 Resource Name DEBUG Uncompressed DEBUG  Compressed  Release Uncompressed Release Compressed
MicrosoftAjax.js 260,705bytes 44,512 bytes 84,337 bytes 23,765 bytes
MicrosoftAjaxWebForms.js 66,186 bytes 11,718 bytes 29,841 bytes 7,839 bytes

上表的数据我就不再多说了,一目了然。那么由谁来决定是使用Debug版本,还是Release版本。你可以设置ScriptManager的ScriptMode属性,也可以修改web.config的compilation节点的deubg属性。

现在我们再来讨论一下,该如何实现脚本的压缩的?其实,这个也是相当简单的,从ASP.NET AJAX给我们提供的默认配合节点中,可以非常明显的看到这样一段代码:

<scriptResourceHandler enableCompression="true" enableCaching="true" />

但是它是被注释着的,我们只需要把这个注释去掉就OK了,奇迹就这样发生了。

但是,事实完全就是这样吗?未必!我想说的是,这里的压缩并不适用于低于IE7的浏览器,包括IE6。有人肯定不信了,IE6可是绝大多数据普通用户使用的浏览器,这个都不支持,那这个功能还有什么意义啊!您还真别不信,事实就是这样。我们先来看一段代码,然后再来看一个例子。

直接找到RuntimeScriptResourceHandler类,它实现了IScriptResourceHandler 该接口只有一个方法GetScriptResourceUrl顾名思义它就是获取访问脚本资源的URL地址,在RuntimeScriptResourceHandler中,它的实现是这样的:

  
string IScriptResourceHandler.GetScriptResourceUrl(Assembly assembly, string resourceName, CultureInfo culture, bool zip, bool notifyScriptLoaded) { if ( ! ScriptResourceHandler.IsCompressionEnabled(HttpContext.Current)) { zip = false ; } Tuple tuple = new Tuple( new object [] { assembly, resourceName, culture, zip, notifyScriptLoaded }); string text = ( string ) _urlCache[tuple]; if (text == null ) { string name; ScriptResourceHandler.ScriptResourceInfo instance = ScriptResourceHandler.ScriptResourceInfo.GetInstance(assembly, resourceName); if (instance == ScriptResourceHandler.ScriptResourceInfo.Empty) { ThrowUnknownResource(resourceName); } Stream manifestResourceStream = assembly.GetManifestResourceStream(instance.ScriptName); if ((manifestResourceStream == null ) || (manifestResourceStream.ReadByte() == - 1 )) { ThrowUnknownResource(resourceName); } culture = ScriptResourceHandler.DetermineNearestAvailableCulture(assembly, resourceName, culture); Pair < AssemblyName, DateTime > assemblyInfo = ScriptResourceHandler.GetAssemblyInfo(assembly); AssemblyName first = assemblyInfo.First; DateTime second = assemblyInfo.Second; if (assembly.GlobalAssemblyCache) { StringBuilder builder = new StringBuilder(); builder.Append(first.Name); builder.Append( ' , ' ); builder.Append(first.Version); builder.Append( ' , ' ); if (first.CultureInfo != null ) { builder.Append(first.CultureInfo); } builder.Append( ' , ' ); builder.Append(HexParser.ToString(first.GetPublicKeyToken())); name = builder.ToString(); } else { name = first.Name; } if (_absoluteScriptResourceUrl == null ) { _absoluteScriptResourceUrl = VirtualPathUtility.ToAbsolute( " ~/ScriptResource.axd " ); } text = string .Concat( new object [] { _absoluteScriptResourceUrl, " ?d= " , ScriptResourceHandler.EncryptString((zip ? (notifyScriptLoaded ? " Z " : " z " ) : (notifyScriptLoaded ? " U " : " u " )) + name + " | " + resourceName + " | " + culture.ToString()), " &t= " , second.Ticks }); _urlCache[tuple] = text; } return text; } CODE 1

其中,zip参数是用于指定是否生成带有压缩版本的URL地址,如果zip为true,则返回的参数d的第一个字符为Z或z。否则为U或u。具体是如何去压缩的,我们现在先不管,反正URL地址中的地址栏参数d的第一字符为Z或z就表明访问的资源被请求到客户端前会被压缩。因此要访问压缩的脚本资源,就要保证zip参数为true。那这个参数从何而来呢?它是由ScriptManager的Zip属性原原本本的被传递到这个方法中,这在传递的过程当中没有被修改过。而ScriptManager的Zip的属性定义如下:

  
internal bool Zip { get { if ( ! this ._zipSet) { this ._zip = HeaderUtility.IsEncodingInAcceptList( this .IPage.Request.Headers[ " Accept-encoding " ], " gzip " ); this ._zipSet = true ; } return this ._zip; } } CODE 2

决定它值的是客户端HTTP请求头部是否带有Accept-Encoding: gzip。因此大部分的浏览器都支持GZIP压缩过的HTTP输出流程,因此大部份浏览器的请求头部都会有这么一段:Accept-Encoding: gzip, deflate。因此这个值在接受IE6请求时,应该是为true才对的,而它得到的应该也是个压缩版本的资源URL请求才对啊?除非CODE1中的zip参数被改为false了。再回过头来看CODE1的开始部分有这么一段代码:

  
if ( ! ScriptResourceHandler.IsCompressionEnabled(HttpContext.Current)) { zip = false ; } CODE  3

只有在这里zip的值才有可能被修改为false,那我们再来看看,ScriptResourceHandler.IsCompressionEnabled究竟做了此什么了?

  
private static bool IsCompressionEnabled(HttpContext context) { if ( ! ScriptingScriptResourceHandlerSection.ApplicationSettings.EnableCompression) { return false ; } if ((context != null ) && context.Request.Browser.IsBrowser( " IE " )) { return (context.Request.Browser.MajorVersion > 6 ); } return true ; }        CODE 4

第一,我们可以确定EnableCompression的值为true。第二,我们使用的是IE,会执行return (context.Request.Browser.MajorVersion > 6) ,因为我们使用的IE6,这边就会返回false。回到CODE 3,zip的值就会被修改成false了。而此时就会返回不被压缩的URL地址了。问题就在这里,可这是为什么呢?我想一般情况下我们肯定会不理解的,看了这个你就清楚了:http://www.microsoft.com/downloads/details.aspx?familyid=85bb441a-5bb1-4a82-86ec-a249af287513&displaylang=en 原来在IE6的SP1版本中,接收GZIP的数据会有问题,而这边就是给了解决这个问题的补丁包。ASP.NET AJAX团队,可能担心由于这个问题引起的部分IE浏览器无法正常使用ASP.NET AJAX,保险起见,在IE6的请求中永不使用压缩脚本。

分析了代码,为了让我们有更直观的印象,再来看一段代码例子:

  
protected void Page_Load( object sender, EventArgs e) { NameValueCollection queryString = HttpUtility.ParseQueryString(GetScriptResourceUrl()); Response.Write(DecryptString(queryString[ 0 ])); } private static string DecryptString( string s) { MethodInfo _decryptString = typeof (Page).GetMethod( " DecryptString " , BindingFlags.NonPublic | BindingFlags.Static); return ( string )_decryptString.Invoke( null , new object [] { s }); } private string GetScriptResourceUrl() { MethodInfo GetScriptResourceUrl = typeof (ScriptManager).GetMethod( " GetScriptResourceUrl " , BindingFlags.NonPublic | BindingFlags.Instance); return ( string )GetScriptResourceUrl.Invoke(sm, new object [] { " MicrosoftAjax.js " , sm.GetType().Assembly }); }

在IE6,它输出的是 USystem.Web.Extensions|MicrosoftAjax.js| ,而在FF中它输出的是:ZSystem.Web.Extensions|MicrosoftAjax.js|

这两个不同的输出值完全就可以体现了它们请求行为的不同。

接下来,简单讨论一下,我们如何修改让它去掉这个限制。在ScriptResourceHandler有一个这样的静态方法SetScriptResourceHandler,我们可以重新实现一个IScriptResourceHandler类,用这个法植入到ASP.NET AJAX内部,让它们使用。但问题是SetScriptResourceHandler是一个internal的方法,使用反射呗,那还能怎么样,谁让它这样设计,既提供这个方法,又不想让人用!

OK,对这个问题的研究就先到这边了。

你可能感兴趣的:(asp.net)