Browse Source

[asp.net] Implemented composite scripts support in System.Web.Extensions

Marek Habersack 14 years ago
parent
commit
37a3e41b87

+ 3 - 0
mcs/class/System.Web.Extensions/System.Web.Extensions.dll.sources

@@ -58,6 +58,9 @@
 ./System.Web.Script.Services/ScriptServiceAttribute.cs
 ./System.Web.UI/AsyncPostBackErrorEventArgs.cs
 ./System.Web.UI/AsyncPostBackTrigger.cs
+./System.Web.UI/CompositeEntry.cs
+./System.Web.UI/CompositeScriptReference.cs
+./System.Web.UI/CompositeScriptReferenceEventArgs.cs
 ./System.Web.UI/AuthenticationServiceManager.cs
 ./System.Web.UI/ExtenderControl.cs
 ./System.Web.UI/IExtenderControl.cs

+ 220 - 25
mcs/class/System.Web.Extensions/System.Web.Handlers/ScriptResourceHandler.cs

@@ -1,10 +1,12 @@
 //
 // ScriptResourceHandler.cs
 //
-// Author:
+// Authors:
 //   Igor Zelmanovich <[email protected]>
+//   Marek Habersack <[email protected]>
 //
 // (C) 2007 Mainsoft, Inc.  http://www.mainsoft.com
+// (C) 2011 Novell, Inc.  http://novell.com
 //
 //
 // Permission is hereby granted, free of charge, to any person obtaining
@@ -28,13 +30,23 @@
 //
 
 using System;
+using System.Collections;
 using System.Collections.Generic;
+using System.IO;
+using System.Security.Cryptography;
+using System.Reflection;
+using System.Resources;
 using System.Text;
+using System.Threading;
+using System.Web.Configuration;
+using System.Web.Hosting;
+using System.Web.UI;
+using System.Web.Util;
 
 namespace System.Web.Handlers
 {
 	public partial class ScriptResourceHandler : IHttpHandler
-	{
+	{		
 		protected virtual bool IsReusable {
 			get { return true; }
 		}
@@ -50,7 +62,213 @@ namespace System.Web.Handlers
 		}
 
 		#endregion
+#if NET_3_5
+		void AppendResourceScriptContents (StringWriter sw, CompositeEntry entry)
+		{
+			if (entry.Assembly == null || entry.Attribute == null || String.IsNullOrEmpty (entry.NameOrPath))
+				return;
+
+			using (Stream s = entry.Assembly.GetManifestResourceStream (entry.NameOrPath)) {
+				if (s == null)
+					throw new HttpException (404, "Resource '" + entry.NameOrPath + "' not found");
+
+				if (entry.Attribute.PerformSubstitution) {
+					using (var r = new StreamReader (s)) {
+						new PerformSubstitutionHelper (entry.Assembly).PerformSubstitution (r, sw);
+					}
+				} else {
+					using (var r = new StreamReader (s)) {
+						string line = r.ReadLine ();
+						while (line != null) {
+							sw.WriteLine (line);
+							line = r.ReadLine ();
+						}
+					}
+				}
+			}
+		}
+
+		void AppendFileScriptContents (StringWriter sw, CompositeEntry entry)
+		{
+			// FIXME: should we limit the script size in any way?
+			if (String.IsNullOrEmpty (entry.NameOrPath))
+				return;
+
+			string mappedPath;
+			if (!HostingEnvironment.HaveCustomVPP) {
+				// We'll take a shortcut here by bypassing the default VPP layers
+				mappedPath = HostingEnvironment.MapPath (entry.NameOrPath);
+				if (!File.Exists (mappedPath))
+					return;
+				sw.Write (File.ReadAllText (mappedPath));
+				return;
+			}
+
+			VirtualPathProvider vpp = HostingEnvironment.VirtualPathProvider;
+			if (!vpp.FileExists (entry.NameOrPath))
+				return;
+			VirtualFile file = vpp.GetFile (entry.NameOrPath);
+			if (file == null)
+				return;
+			using (Stream s = file.Open ()) {
+				using (var r = new StreamReader (s)) {
+					string line = r.ReadLine ();
+					while (line != null) {
+						sw.WriteLine (line);
+						line = r.ReadLine ();
+					}
+				}
+			}
+		}
+		
+		void AppendScriptContents (StringWriter sw, CompositeEntry entry)
+		{
+			if (entry.Assembly != null)
+				AppendResourceScriptContents (sw, entry);
+			else
+				AppendFileScriptContents (sw, entry);
+		}
+		
+		void SendCompositeScript (HttpContext context, HttpRequest request, bool notifyScriptLoaded, List <CompositeEntry> entries)
+		{
+			if (entries.Count == 0)
+				throw new HttpException (404, "Resource not found");
+
+			long atime;
+			DateTime modifiedSince;
+			bool hasCacheControl = HasCacheControl (request, request.QueryString, out atime);
+			bool hasIfModifiedSince = HasIfModifiedSince (context.Request, out modifiedSince);
+			
+			if (hasCacheControl || hasIfModifiedSince) {
+				bool notModified = true;
+			
+				foreach (CompositeEntry entry in entries) {
+					if (entry == null)
+						continue;
+					if (notModified) {
+						if ((hasCacheControl && entry.IsModifiedSince (atime)) || (hasIfModifiedSince && entry.IsModifiedSince (modifiedSince)))
+							notModified = false;
+					}
+				}
+
+				if (notModified) {
+					RespondWithNotModified (context);
+					return;
+				}
+			}
+			
+			StringBuilder contents = new StringBuilder ();
+			using (var sw = new StringWriter (contents)) {
+				foreach (CompositeEntry entry in entries) {
+					if (entry == null)
+						continue;
+					AppendScriptContents (sw, entry);
+				}
+			}
+			if (contents.Length == 0)
+				throw new HttpException (404, "Resource not found");
+
+			HttpResponse response = context.Response;
+			DateTime utcnow = DateTime.UtcNow;
 
+			response.ContentType = "text/javascript";
+			response.Headers.Add ("Last-Modified", utcnow.ToString ("r"));
+			response.ExpiresAbsolute = utcnow.AddYears (1);
+			response.CacheControl = "public";
+
+			response.Output.Write (contents.ToString ());
+			if (notifyScriptLoaded)
+				OutputScriptLoadedNotification (response.Output);
+		}
+#endif
+		void OutputScriptLoadedNotification (TextWriter writer)
+		{
+			writer.WriteLine ();
+			writer.WriteLine ("if(typeof(Sys)!=='undefined')Sys.Application.notifyScriptLoaded();");
+		}
+		
+		protected virtual void ProcessRequest (HttpContext context)
+		{
+			HttpRequest request = context.Request;
+			bool notifyScriptLoaded = request.QueryString ["n"] == "t";
+#if NET_3_5
+			List <CompositeEntry> compositeEntries = CompositeScriptReference.GetCompositeScriptEntries (request.RawUrl);
+			if (compositeEntries != null) {
+				SendCompositeScript (context, request, notifyScriptLoaded, compositeEntries);
+				return;
+			}
+#endif
+			EmbeddedResource res;
+			Assembly assembly;			
+			SendEmbeddedResource (context, out res, out assembly);
+
+			HttpResponse response = context.Response;
+			TextWriter writer = response.Output;
+			foreach (ScriptResourceAttribute sra in assembly.GetCustomAttributes (typeof (ScriptResourceAttribute), false)) {
+				if (String.Compare (sra.ScriptName, res.Name, StringComparison.Ordinal) == 0) {
+					string scriptResourceName = sra.ScriptResourceName;
+					ResourceSet rset = null;
+					try {
+						rset = new ResourceManager (scriptResourceName, assembly).GetResourceSet (Threading.Thread.CurrentThread.CurrentUICulture, true, true);
+					}
+					catch (MissingManifestResourceException) {
+#if TARGET_JVM // GetResourceSet does not throw  MissingManifestResourceException if ressource is not exists
+					}
+					if (rset == null) {
+#endif
+						if (scriptResourceName.EndsWith (".resources", RuntimeHelpers.StringComparison)) {
+							scriptResourceName = scriptResourceName.Substring (0, scriptResourceName.Length - 10);
+							rset = new ResourceManager (scriptResourceName, assembly).GetResourceSet (Threading.Thread.CurrentThread.CurrentUICulture, true, true);
+						}
+#if !TARGET_JVM
+						else
+							throw;
+#endif
+					}
+					if (rset == null)
+						break;
+					writer.WriteLine ();
+					string ns = sra.TypeName;
+					int indx = ns.LastIndexOf ('.');
+					if (indx > 0)
+						writer.WriteLine ("Type.registerNamespace('" + ns.Substring (0, indx) + "')");
+					writer.Write ("{0}={{", sra.TypeName);
+					bool first = true;
+					foreach (DictionaryEntry de in rset) {
+						string value = de.Value as string;
+						if (value != null) {
+							if (first)
+								first = false;
+							else
+								writer.Write (',');
+							writer.WriteLine ();
+							writer.Write ("{0}:{1}", GetScriptStringLiteral ((string) de.Key), GetScriptStringLiteral (value));
+						}
+					}
+					writer.WriteLine ();
+					writer.WriteLine ("};");
+					break;
+				}
+			}
+			
+			if (notifyScriptLoaded)
+				OutputScriptLoadedNotification (writer);
+		}
+#if NET_3_5
+		static void CheckIfResourceIsCompositeScript (string resourceName, ref bool includeTimeStamp)
+		{
+			bool isCompositeScript = resourceName.StartsWith (CompositeScriptReference.COMPOSITE_SCRIPT_REFERENCE_PREFIX, StringComparison.Ordinal);
+			if (!isCompositeScript)
+				return;
+			
+			includeTimeStamp = false;
+		}
+
+		bool HandleCompositeScriptRequest (HttpContext context, HttpRequest request, string d)
+		{
+			return false;
+		}
+#endif
 		// TODO: add value cache?
 		static string GetScriptStringLiteral (string value)
 		{
@@ -90,28 +308,5 @@ namespace System.Web.Handlers
 			
 			return sb.ToString ();
 		}
-
-		class ResourceKey
-		{
-			readonly string _resourceName;
-			readonly bool _notifyScriptLoaded;
-
-			public ResourceKey (string resourceName, bool notifyScriptLoaded) {
-				_resourceName = resourceName;
-				_notifyScriptLoaded = notifyScriptLoaded;
-			}
-
-			public override bool Equals (object obj) {
-				if (!(obj is ResourceKey))
-					return base.Equals (obj);
-
-				ResourceKey resKey = (ResourceKey) obj;
-				return resKey._resourceName == this._resourceName && resKey._notifyScriptLoaded == _notifyScriptLoaded;
-			}
-
-			public override int GetHashCode () {
-				return _resourceName.GetHashCode () ^ _notifyScriptLoaded.GetHashCode ();
-			}
-		}
 	}
 }

+ 73 - 0
mcs/class/System.Web.Extensions/System.Web.UI/CompositeEntry.cs

@@ -0,0 +1,73 @@
+//
+// Authors:
+//      Marek Habersack <[email protected]>
+//
+// (C) 2011 Novell, Inc (http://novell.com)
+//
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+// 
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+// 
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.IO;
+using System.Reflection;
+using System.Web.Hosting;
+
+namespace System.Web.UI
+{	  
+	sealed class CompositeEntry
+	{
+		public Assembly Assembly;
+		public string NameOrPath;
+		public WebResourceAttribute Attribute;
+
+		string GetFilePath ()
+		{
+			if (Assembly != null)
+				return Assembly.Location;
+			else if (!String.IsNullOrEmpty (NameOrPath))
+				return HostingEnvironment.MapPath (NameOrPath);
+			else
+				return String.Empty;
+		}
+		
+		public bool IsModifiedSince (DateTime since)
+		{
+			return File.GetLastWriteTimeUtc (GetFilePath ()) > since;
+		}
+
+		public bool IsModifiedSince (long atime)
+		{
+			return File.GetLastWriteTimeUtc (GetFilePath ()).Ticks > atime;
+		}
+		
+		public override int GetHashCode ()
+		{
+			int ret = 0;
+
+			if (Assembly != null)
+				ret ^= Assembly.GetHashCode ();
+			if (NameOrPath != null)
+				ret ^= NameOrPath.GetHashCode ();
+
+			return ret;
+		}
+	}
+}

+ 178 - 0
mcs/class/System.Web.Extensions/System.Web.UI/CompositeScriptReference.cs

@@ -0,0 +1,178 @@
+//
+// Authors:
+//   Marek Habersack <[email protected]>
+//
+// (C) 2011 Novell, Inc (http://novell.com/)
+//
+
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+// 
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+// 
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+#if NET_3_5
+using System;
+using System.Collections;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.IO;
+using System.Reflection;
+using System.Text;
+using System.Web;
+using System.Web.Handlers;
+using System.Web.Hosting;
+
+namespace System.Web.UI
+{
+	[DefaultProperty ("Path")]
+	public class CompositeScriptReference : ScriptReferenceBase
+	{
+		public const string COMPOSITE_SCRIPT_REFERENCE_PREFIX = "CSR:";
+
+		static SplitOrderedList <string, List <CompositeEntry>> entriesCache;
+		
+		ScriptReferenceCollection scripts;
+		
+		[PersistenceMode (PersistenceMode.InnerProperty)]
+		[Editor ("System.Web.UI.Design.CollectionEditorBase, " + Consts.AssemblySystem_Design, "System.Drawing.Design.UITypeEditor, "+ Consts.AssemblySystem_Design)]
+		[MergableProperty (false)]
+		[Category ("Behavior")]
+		[DefaultValue (null)]
+		[NotifyParentProperty (true)]	
+		public ScriptReferenceCollection Scripts {
+			get {
+				if (scripts == null)
+					scripts = new ScriptReferenceCollection ();
+				return scripts;
+			}
+		}
+
+		static CompositeScriptReference ()
+		{
+			entriesCache = new SplitOrderedList <string, List <CompositeEntry>> (StringComparer.Ordinal);
+		}
+
+		internal static List <CompositeEntry> GetCompositeScriptEntries (string url)
+		{
+			if (String.IsNullOrEmpty (url) || entriesCache.Count == 0)
+				return null;
+			
+			List <CompositeEntry> ret;
+			if (!entriesCache.Find ((uint)url.GetHashCode (), url, out ret))
+				return null;
+
+			return ret;
+		}
+		
+		protected internal override string GetUrl (ScriptManager scriptManager, bool zip)
+		{
+			if (scriptManager == null)
+				// .NET emulation...
+				throw new NullReferenceException (".NET emulation");
+			
+			var url = new StringBuilder (COMPOSITE_SCRIPT_REFERENCE_PREFIX);
+			string path;
+			string name;
+			CompositeEntry entry;
+			List <CompositeEntry> entries = null;
+			WebResourceAttribute wra;
+			
+			foreach (ScriptReference sr in Scripts) {
+				if (sr == null)
+					continue;
+
+				name = sr.Name;
+				if (!String.IsNullOrEmpty (name)) {
+					Assembly assembly = sr.ResolvedAssembly;
+					name = GetScriptName (name, sr.IsDebugMode (scriptManager), null, assembly, out wra);
+					path = scriptManager.ScriptPath;
+					if (sr.IgnoreScriptPath || String.IsNullOrEmpty (path)) {
+						entry = new CompositeEntry {
+							Assembly = assembly,
+							NameOrPath = name,
+							Attribute = wra
+						};
+					} else {
+						AssemblyName an = assembly.GetName ();
+						entry = new CompositeEntry {
+							NameOrPath = String.Concat (VirtualPathUtility.AppendTrailingSlash (path), an.Name, '/', an.Version, '/', name),
+							Attribute = wra
+						};
+					}
+				} else if (!String.IsNullOrEmpty ((path = sr.Path))) {
+					bool notFound = false;
+					name = GetScriptName (path, sr.IsDebugMode (scriptManager), scriptManager.EnableScriptLocalization ? ResourceUICultures : null, null, out wra);
+					if (!HostingEnvironment.HaveCustomVPP)
+						notFound = !File.Exists (HostingEnvironment.MapPath (name));
+					else 
+						notFound = !HostingEnvironment.VirtualPathProvider.FileExists (name);
+
+					if (notFound)
+						throw new HttpException ("Web resource '" + name + "' was not found.");
+					
+					entry = new CompositeEntry {
+						NameOrPath = name
+					};
+				} else
+					entry = null;
+
+				if (entry != null) {
+					if (entries == null)
+						entries = new List <CompositeEntry> ();
+					entries.Add (entry);
+					url.Append (entry.GetHashCode ().ToString ("x"));
+					entry = null;
+				}
+			}
+			
+			if (entries == null || entries.Count == 0)
+				return String.Empty;
+
+			string ret = ScriptResourceHandler.GetResourceUrl (ThisAssembly, url.ToString (), NotifyScriptLoaded);
+			entriesCache.InsertOrUpdate ((uint)ret.GetHashCode (), ret, entries, entries);
+			return ret;
+		}
+#if NET_4_0
+		protected internal override bool IsAjaxFrameworkScript (ScriptManager scriptManager)
+		{
+			return false;
+		}
+		
+		[Obsolete ("Use IsAjaxFrameworkScript(ScriptManager)")]
+#endif
+		protected internal override bool IsFromSystemWebExtensions ()
+		{
+			if (scripts == null || scripts.Count == 0)
+				return false;
+
+			Assembly myAssembly = ThisAssembly;
+			foreach (ScriptReference sr in scripts)
+				if (sr.ResolvedAssembly == myAssembly)
+					return true;
+
+			return false;
+		}
+
+		internal bool HaveScripts ()
+		{
+			return (scripts != null && scripts.Count > 0);
+		}
+	}
+}
+#endif

+ 53 - 0
mcs/class/System.Web.Extensions/System.Web.UI/CompositeScriptReferenceEventArgs.cs

@@ -0,0 +1,53 @@
+//
+// Authors:
+//   Marek Habersack <[email protected]>
+//
+// (C) 2010 Novell, Inc (http://novell.com/)
+//
+
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+// 
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+// 
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+#if NET_3_5
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Collections.Specialized;
+using System.ComponentModel;
+using System.Drawing;
+using System.Web;
+using System.Web.UI;
+using System.Web.UI.HtmlControls;
+
+namespace System.Web.UI
+{
+	public class CompositeScriptReferenceEventArgs : EventArgs
+	{
+		public CompositeScriptReference CompositeScript {
+			get; private set;
+		}
+		
+		public CompositeScriptReferenceEventArgs (CompositeScriptReference compositeScript)
+		{
+			this.CompositeScript = compositeScript;
+		}
+	}
+}
+#endif

+ 59 - 18
mcs/class/System.Web.Extensions/System.Web.UI/ScriptManager.cs

@@ -97,6 +97,9 @@ namespace System.Web.UI
 		List<UpdatePanel> _panelsToRefresh;
 		List<UpdatePanel> _updatePanels;
 		ScriptReferenceCollection _scripts;
+#if NET_3_5
+		CompositeScriptReference _compositeScript;
+#endif
 		ServiceReferenceCollection _services;
 		bool _isInAsyncPostBack;
 		bool _isInPartialRendering;
@@ -339,7 +342,20 @@ namespace System.Web.UI
 				return _scripts;
 			}
 		}
-
+#if NET_3_5
+		[PersistenceMode (PersistenceMode.InnerProperty)]
+		[Category ("Behavior")]
+		[DefaultValue (null)]
+		[DesignerSerializationVisibility (DesignerSerializationVisibility.Content)]
+		[MergableProperty (false)]
+		public CompositeScriptReference CompositeScript {
+			get {
+				if (_compositeScript == null)
+					_compositeScript = new CompositeScriptReference ();
+				return _compositeScript;
+			}
+		}
+#endif
 		[PersistenceMode (PersistenceMode.InnerProperty)]
 		[DefaultValue ("")]
 		[MergableProperty (false)]
@@ -395,7 +411,10 @@ namespace System.Web.UI
 
 		[Category ("Action")]
 		public event EventHandler<ScriptReferenceEventArgs> ResolveScriptReference;
-
+#if NET_3_5
+		[Category ("Action")]
+		public event EventHandler<CompositeScriptReferenceEventArgs> ResolveCompositeScriptReference;
+#endif
 		public static ScriptManager GetCurrent (Page page) {
 			if (page == null)
 				throw new ArgumentNullException ("page");
@@ -513,24 +532,31 @@ namespace System.Web.UI
 			}
 		}
 
-		void OnPreRenderComplete (object sender, EventArgs e) {
-			// Resolve Scripts
-			ScriptReference ajaxScript = new ScriptReference ("MicrosoftAjax.js", String.Empty);
-			ajaxScript.NotifyScriptLoaded = false;
-			OnResolveScriptReference (new ScriptReferenceEventArgs (ajaxScript));
+		ScriptReference CreateScriptReference (string path, string assembly, bool notifyScriptLoaded)
+		{
+			var ret = new ScriptReference (path, assembly);
+			if (!notifyScriptLoaded)
+				ret.NotifyScriptLoaded = false;
+			OnResolveScriptReference (new ScriptReferenceEventArgs (ret));
 
-			ScriptReference ajaxWebFormsScript = new ScriptReference ("MicrosoftAjaxWebForms.js", String.Empty);
-			ajaxWebFormsScript.NotifyScriptLoaded = false;
-			OnResolveScriptReference (new ScriptReferenceEventArgs (ajaxWebFormsScript));
+			return ret;
+		}
 
+		ScriptReference CreateScriptReference (string path, string assembly)
+		{
+			return CreateScriptReference (path, assembly, true);
+		}
+		
+		void OnPreRenderComplete (object sender, EventArgs e)
+		{
+			// Resolve Scripts
+			ScriptReference ajaxScript = CreateScriptReference ("MicrosoftAjax.js", String.Empty, false);
+			ScriptReference ajaxWebFormsScript = CreateScriptReference ("MicrosoftAjaxWebForms.js", String.Empty, false);
 			ScriptReference ajaxExtensionScript = null;
 			ScriptReference ajaxWebFormsExtensionScript = null;
 			if (IsMultiForm) {
-				ajaxExtensionScript = new ScriptReference ("MicrosoftAjaxExtension.js", String.Empty);
-				OnResolveScriptReference (new ScriptReferenceEventArgs (ajaxExtensionScript));
-
-				ajaxWebFormsExtensionScript = new ScriptReference ("MicrosoftAjaxWebFormsExtension.js", String.Empty);
-				OnResolveScriptReference (new ScriptReferenceEventArgs (ajaxWebFormsExtensionScript));
+				ajaxExtensionScript = CreateScriptReference ("MicrosoftAjaxExtension.js", String.Empty);
+				ajaxWebFormsExtensionScript = CreateScriptReference ("MicrosoftAjaxWebFormsExtension.js", String.Empty);
 			}
 
 			foreach (ScriptReferenceEntry script in GetScriptReferences ()) {
@@ -575,7 +601,12 @@ namespace System.Web.UI
 				if (IsMultiForm)
 					RegisterScriptReference (this, ajaxWebFormsExtensionScript, true);
 			}
-			
+#if NET_3_5
+			if (_compositeScript != null && _compositeScript.HaveScripts ()) {
+				OnResolveCompositeScriptReference (new CompositeScriptReferenceEventArgs (_compositeScript));
+				RegisterScriptReference (this, _compositeScript, true);
+			}
+#endif			
 			// Register Scripts
 			if (_scriptToRegister != null)
 				for (int i = 0; i < _scriptToRegister.Count; i++)
@@ -749,7 +780,14 @@ namespace System.Web.UI
 				}
 			}
 		}
-
+#if NET_3_5
+		protected virtual void OnResolveCompositeScriptReference (CompositeScriptReferenceEventArgs e)
+		{
+			EventHandler <CompositeScriptReferenceEventArgs> evt = ResolveCompositeScriptReference;
+			if (evt != null)
+				evt (this, e);
+		}
+#endif
 		protected virtual void OnResolveScriptReference (ScriptReferenceEventArgs e) {
 			if (ResolveScriptReference != null)
 				ResolveScriptReference (this, e);
@@ -835,12 +873,15 @@ namespace System.Web.UI
 			RegisterClientScriptInclude (control, type, resourceName, ScriptResourceHandler.GetResourceUrl (type.Assembly, resourceName, true));
 		}
 
-		void RegisterScriptReference (Control control, ScriptReference script, bool loadScriptsBeforeUI)
+		void RegisterScriptReference (Control control, ScriptReferenceBase script, bool loadScriptsBeforeUI)
 		{
 			string scriptPath = script.Path;
 			string url = script.GetUrl (this, false);
 			if (control != this && !String.IsNullOrEmpty (scriptPath))
 				url = control.ResolveClientUrl (url);
+
+			if (String.IsNullOrEmpty (url))
+				return;
 			
 			if (loadScriptsBeforeUI)
 				RegisterClientScriptInclude (control, typeof (ScriptManager), url, url);

+ 17 - 1
mcs/class/System.Web.Extensions/System.Web.UI/ScriptManagerProxy.cs

@@ -42,6 +42,9 @@ namespace System.Web.UI
 	{
 		ScriptManager _scriptManager;
 		ScriptReferenceCollection _scripts;
+#if NET_3_5
+		CompositeScriptReference _compositeScript;
+#endif
 		ServiceReferenceCollection _services;
 		AuthenticationServiceManager _authenticationService;
 		ProfileServiceManager _profileService;
@@ -81,7 +84,20 @@ namespace System.Web.UI
 				return _scripts;
 			}
 		}
-
+#if NET_3_5
+		[PersistenceMode (PersistenceMode.InnerProperty)]
+		[Category ("Behavior")]
+		[DefaultValue (null)]
+		[DesignerSerializationVisibility (DesignerSerializationVisibility.Content)]
+		[MergableProperty (false)]
+		public CompositeScriptReference CompositeScript {
+			get {
+				if (_compositeScript == null)
+					_compositeScript = new CompositeScriptReference ();
+				return _compositeScript;
+			}
+		}
+#endif
 		[MergableProperty (false)]
 		[PersistenceMode (PersistenceMode.InnerProperty)]
 		[Category ("Behavior")]

+ 73 - 50
mcs/class/System.Web.Extensions/System.Web.UI/ScriptReference.cs

@@ -1,11 +1,12 @@
 //
 // ScriptReference.cs
 //
-// Author:
+// Authors:
 //   Igor Zelmanovich <[email protected]>
+//   Marek Habersack <[email protected]>
 //
 // (C) 2007 Mainsoft, Inc.  http://www.mainsoft.com
-//
+// (C) 2011 Novell, Inc. http://novell.com/
 //
 // Permission is hereby granted, free of charge, to any person obtaining
 // a copy of this software and associated documentation files (the
@@ -30,7 +31,9 @@
 using System;
 using System.Collections.Generic;
 using System.ComponentModel;
+using System.IO;
 using System.Reflection;
+using System.Resources;
 using System.Text;
 using System.Threading;
 using System.Web.Handlers;
@@ -44,7 +47,8 @@ namespace System.Web.UI
 		string _name;
 		string _assembly;
 		bool _ignoreScriptPath;
-
+		Assembly _resolvedAssembly;
+		
 		public ScriptReference ()
 		{
 		}
@@ -66,9 +70,37 @@ namespace System.Web.UI
 			}
 			set {
 				_assembly = value;
+				_resolvedAssembly = null;
+			}
+		}
+
+		internal Assembly ResolvedAssembly {
+			get {
+				if (_resolvedAssembly == null) {
+					string assemblyName = this.Assembly;
+				
+					if (String.IsNullOrEmpty (assemblyName))
+						_resolvedAssembly = typeof (ScriptManager).Assembly;
+					else
+						_resolvedAssembly = global::System.Reflection.Assembly.Load (assemblyName);
+				}
+				return _resolvedAssembly;
 			}
 		}
 
+		ScriptMode ScriptModeInternal {
+			get {
+				if (ScriptMode == ScriptMode.Auto) {
+					if (!String.IsNullOrEmpty (Name))
+						return ScriptMode.Inherit;
+					else
+						return ScriptMode.Release;
+				}
+				else
+					return ScriptMode;
+			}
+		}
+		
 		public bool IgnoreScriptPath {
 			get {
 				return _ignoreScriptPath;
@@ -87,74 +119,65 @@ namespace System.Web.UI
 			}
 		}
 
-		internal ScriptMode ScriptModeInternal {
-			get {
-				if (ScriptMode == ScriptMode.Auto) {
-					if (!String.IsNullOrEmpty (Name))
-						return ScriptMode.Inherit;
-					else
-						return ScriptMode.Release;
-				}
-				else
-					return ScriptMode;
+		internal bool IsDebugMode (ScriptManager scriptManager)
+		{
+			if (scriptManager == null)
+				return ScriptModeInternal == ScriptMode.Debug;
+			
+			if (scriptManager.IsDeploymentRetail)
+				return false;
+
+			switch (ScriptModeInternal) {
+				case ScriptMode.Inherit:
+					return scriptManager.IsDebuggingEnabled;
+
+				case ScriptMode.Debug:
+					return true;
+
+				default:
+					return false;
 			}
 		}
-
+		
 		[MonoTODO ("Compression not supported yet.")]
 		protected internal override string GetUrl (ScriptManager scriptManager, bool zip)
 		{
-			bool isDebugMode = scriptManager.IsDeploymentRetail ? false :
-				(ScriptModeInternal == ScriptMode.Inherit ? scriptManager.IsDebuggingEnabled : (ScriptModeInternal == ScriptMode.Debug));
-			string path = Path;
+			bool isDebugMode = IsDebugMode (scriptManager);
+			string path;
 			string url = String.Empty;
+			string name = Name;
+			WebResourceAttribute wra;
 			
-			if (!String.IsNullOrEmpty (path)) {
-				url = GetScriptName (path, isDebugMode, scriptManager.EnableScriptLocalization ? ResourceUICultures : null);
-			} else if (!String.IsNullOrEmpty (Name)) {
-				Assembly assembly;
-				string assemblyName = this.Assembly;
-				
-				if (String.IsNullOrEmpty (assemblyName))
-					assembly = typeof (ScriptManager).Assembly;
-				else
-					assembly = global::System.Reflection.Assembly.Load (assemblyName);
-				string name = GetScriptName (Name, isDebugMode, null);
-				string scriptPath = scriptManager.ScriptPath;
-				if (IgnoreScriptPath || String.IsNullOrEmpty (scriptPath))
+			// LAMESPEC: Name property takes precedence
+			if (!String.IsNullOrEmpty (name)) {
+				Assembly assembly = ResolvedAssembly;
+				name = GetScriptName (name, isDebugMode, null, assembly, out wra);
+				path = scriptManager.ScriptPath;
+				if (IgnoreScriptPath || String.IsNullOrEmpty (path))
 					url = ScriptResourceHandler.GetResourceUrl (assembly, name, NotifyScriptLoaded);
 				else {
 					AssemblyName an = assembly.GetName ();
-					url = scriptManager.ResolveClientUrl (String.Concat (VirtualPathUtility.AppendTrailingSlash (scriptPath), an.Name, '/', an.Version, '/', name));
+					url = scriptManager.ResolveClientUrl (String.Concat (VirtualPathUtility.AppendTrailingSlash (path), an.Name, '/', an.Version, '/', name));
 				}
+			} else if (!String.IsNullOrEmpty ((path = Path))) {
+				url = GetScriptName (path, isDebugMode, scriptManager.EnableScriptLocalization ? ResourceUICultures : null, null, out wra);
 			} else {
 				throw new InvalidOperationException ("Name and Path cannot both be empty.");
 			}
 
 			return url;
 		}
-
-		static string GetScriptName (string releaseName, bool isDebugMode, string [] supportedUICultures) {
-			if (!isDebugMode && (supportedUICultures == null || supportedUICultures.Length == 0))
-				return releaseName;
-
-			if (releaseName.Length < 3 || !releaseName.EndsWith (".js", StringComparison.OrdinalIgnoreCase))
-				throw new InvalidOperationException (String.Format ("'{0}' is not a valid script path.  The path must end in '.js'.", releaseName));
-
-			StringBuilder sb = new StringBuilder (releaseName);
-			sb.Length -= 3;
-			if (isDebugMode)
-				sb.Append (".debug");
-			string culture = Thread.CurrentThread.CurrentUICulture.Name;
-			if (supportedUICultures != null && Array.IndexOf<string> (supportedUICultures, culture) >= 0)
-				sb.AppendFormat (".{0}", culture);
-			sb.Append (".js");
-
-			return sb.ToString ();
+#if NET_4_0
+		protected internal override bool IsAjaxFrameworkScript (ScriptManager scriptManager)
+		{
+			return false;
 		}
 		
+		[Obsolete ("Use IsAjaxFrameworkScript(ScriptManager)")]
+#endif		
 		protected internal override bool IsFromSystemWebExtensions ()
 		{
-			return false;
+			return ResolvedAssembly == ThisAssembly;
 		}
 		
 		public override string ToString ()

+ 108 - 7
mcs/class/System.Web.Extensions/System.Web.UI/ScriptReferenceBase.cs

@@ -1,10 +1,8 @@
 //
-// JsonDeserializer.cs
-//
 // Author:
-//   Marek Habersack <[email protected]>
+//   Marek Habersack <[email protected]>
 //
-// (C) 2009 Novell, Inc.  http://novell.com/
+// (C) 2009-2011 Novell, Inc.  http://novell.com/
 //
 // Permission is hereby granted, free of charge, to any person obtaining
 // a copy of this software and associated documentation files (the
@@ -27,9 +25,13 @@
 
 using System;
 using System.Collections.Generic;
+using System.Collections.Concurrent;
+using System.IO;
 using System.Text;
 using System.ComponentModel;
+using System.Reflection;
 using System.Security.Permissions;
+using System.Threading;
 using System.Web.UI.WebControls;
 
 namespace System.Web.UI
@@ -38,6 +40,7 @@ namespace System.Web.UI
 	[AspNetHostingPermissionAttribute(SecurityAction.LinkDemand, Level = AspNetHostingPermissionLevel.Minimal)]
 	public abstract class ScriptReferenceBase
 	{
+		static SplitOrderedList <ResourceCacheEntry, bool> resourceCache;
 		string _path;
 		
 		public bool NotifyScriptLoaded {
@@ -53,19 +56,36 @@ namespace System.Web.UI
 		public string[] ResourceUICultures {
 			get; set;
 		}
-
+		
 		public ScriptMode ScriptMode {
 			get; set;
 		}
 
+		internal static Assembly ThisAssembly {
+			get; private set;
+		}
+		
+		static ScriptReferenceBase ()
+		{
+			ThisAssembly = typeof (ScriptReferenceBase).Assembly;
+			resourceCache = new SplitOrderedList <ResourceCacheEntry, bool> (EqualityComparer <ResourceCacheEntry>.Default);
+		}
+		
 		protected ScriptReferenceBase ()
 		{
 			this.NotifyScriptLoaded = true;
 			this.ScriptMode = ScriptMode.Auto;
 		}
-
-		protected internal abstract string GetUrl (ScriptManager scriptManager, bool zip);
+#if NET_4_0
+		protected internal virtual bool IsAjaxFrameworkScript (ScriptManager scriptManager)
+		{
+			return false;
+		}
+		
+		[Obsolete ("Use IsAjaxFrameworkScript(ScriptManager)")]
+#endif
 		protected internal abstract bool IsFromSystemWebExtensions ();
+		protected internal abstract string GetUrl (ScriptManager scriptManager, bool zip);
 
 		// This method is an example of particularily bad coding - .NET performs NO checks
 		// on pathOrName!
@@ -79,5 +99,86 @@ namespace System.Web.UI
 			// either. Ugh.
 			return pathOrName.Substring (0, pathOrName.Length - 2) + "debug.js";
 		}
+
+		internal static string GetScriptName (string releaseName, bool isDebugMode, string [] supportedUICultures, Assembly assembly, out WebResourceAttribute wra)
+		{
+			if (assembly != null)
+				VerifyAssemblyContainsResource (assembly, releaseName, out wra);
+			else
+				wra = null;
+			
+			if (!isDebugMode && (supportedUICultures == null || supportedUICultures.Length == 0))
+				return releaseName;
+
+			if (releaseName.Length < 3 || !releaseName.EndsWith (".js", StringComparison.OrdinalIgnoreCase))
+				throw new InvalidOperationException (String.Format ("'{0}' is not a valid script path.  The path must end in '.js'.", releaseName));
+			
+			StringBuilder sb = new StringBuilder (releaseName);
+			sb.Length -= 3;
+			if (isDebugMode)
+				sb.Append (".debug");
+			string culture = Thread.CurrentThread.CurrentUICulture.Name;
+			if (supportedUICultures != null && Array.IndexOf<string> (supportedUICultures, culture) >= 0)
+				sb.AppendFormat (".{0}", culture);
+			sb.Append (".js");
+
+			string ret = sb.ToString ();
+			WebResourceAttribute debugWra;
+			if (!CheckIfAssemblyContainsResource (assembly, ret, out debugWra))
+				return releaseName;
+			wra = debugWra;
+			
+			return ret;
+		}
+		
+		static void VerifyAssemblyContainsResource (Assembly assembly, string resourceName, out WebResourceAttribute wra)
+		{
+			var rce = new ResourceCacheEntry {
+				Assembly = assembly,
+				ResourceName = resourceName
+			};
+
+			WebResourceAttribute attr = null;
+			if (!resourceCache.InsertOrGet ((uint)rce.GetHashCode (), rce, false, () => CheckIfAssemblyContainsResource (assembly, resourceName, out attr)))
+				throw new InvalidOperationException (String.Format ("Assembly '{0}' does not contain a Web resource with name '{1}'.",
+										    assembly.FullName, resourceName));
+			wra = attr;
+		}
+
+		static bool CheckIfAssemblyContainsResource (Assembly assembly, string resourceName, out WebResourceAttribute wra)
+		{
+			foreach (WebResourceAttribute attr in assembly.GetCustomAttributes (typeof (WebResourceAttribute), false)) {
+				if (String.Compare (resourceName, attr.WebResource, StringComparison.Ordinal) == 0) {
+					using (Stream rs = assembly.GetManifestResourceStream (resourceName)) {
+						if (rs == null)
+							throw new InvalidOperationException (
+								String.Format ("Assembly '{0}' contains a Web resource with name '{1}' but does not contain an embedded resource with name '{1}'.",
+									       assembly.FullName, resourceName)
+							);
+					}
+					wra = attr;
+					return true;
+				
+				}
+			}
+			wra = null;
+			return false;
+		}
+
+		sealed class ResourceCacheEntry
+		{
+			public Assembly Assembly;
+			public string ResourceName;
+
+			public override int GetHashCode ()
+			{
+				int ret = 0;
+				if (Assembly != null)
+					ret ^= Assembly.GetHashCode ();
+				if (ResourceName != null)
+					ret ^= ResourceName.GetHashCode ();
+				return ret;
+			}
+		}
 	}
 }

+ 123 - 127
mcs/class/System.Web/System.Web.Handlers/AssemblyResourceLoader.cs

@@ -36,6 +36,7 @@ using System.IO;
 using System.Resources;
 using System.Collections;
 using System.Collections.Generic;
+using System.Collections.Specialized;
 using System.Security.Cryptography;
 using System.Text;
 using System.Text.RegularExpressions;
@@ -51,12 +52,7 @@ namespace System.Web.Handlers
 		const string HandlerFileName = "ScriptResource.axd";
 		static Assembly currAsm = typeof (ScriptResourceHandler).Assembly;
 #else
-	#if NET_2_0
-	public sealed
-	#else
-	internal // since this is in the .config file, we need to support it, since we dont have versoned support.
-	#endif
-	class AssemblyResourceLoader : IHttpHandler
+	public sealed class AssemblyResourceLoader : IHttpHandler
 	{
 		const string HandlerFileName = "WebResource.axd";
 		static Assembly currAsm = typeof (AssemblyResourceLoader).Assembly;
@@ -141,7 +137,7 @@ namespace System.Web.Handlers
 						var er = new EmbeddedResource () {
 							Name = resourceName,
 							Attribute = attr, 
-							Url = CreateResourceUrl (kha, assemblyName, assemblyHash, apath, rkNoNotify, debug, false)
+							Url = CreateResourceUrl (kha, assemblyName, assemblyHash, apath, rkNoNotify, debug, false, true)
 						};
 						
 						entry.Resources.Add (rkNoNotify, er);
@@ -151,7 +147,7 @@ namespace System.Web.Handlers
 						var er = new EmbeddedResource () {
 							Name = resourceName,
 							Attribute = attr, 
-							Url = CreateResourceUrl (kha, assemblyName, assemblyHash, apath, rkNotify, debug, true)
+							Url = CreateResourceUrl (kha, assemblyName, assemblyHash, apath, rkNotify, debug, true, true)
 						};
 						
 						entry.Resources.Add (rkNotify, er);
@@ -161,7 +157,7 @@ namespace System.Web.Handlers
 						var er = new EmbeddedResource () {
 							Name = resourceName,
 							Attribute = attr, 
-							Url = CreateResourceUrl (kha, assemblyName, assemblyHash, apath, resourceNameHash, false, false)
+							Url = CreateResourceUrl (kha, assemblyName, assemblyHash, apath, resourceNameHash, false, false, true)
 						};
 						entry.Resources.Add (resourceNameHash, er);
 					}
@@ -184,11 +180,8 @@ namespace System.Web.Handlers
 			if (parts.Length != 3)
 				return null;
 
-			Encoding enc = Encoding.UTF8;
 			string asmNameHash = parts [0];
 			string resNameHash = parts [1];
-			bool debug = parts [2] == "t";
-			
 			try {
 				_embeddedResourcesLock.EnterReadLock ();
 				if (!_embeddedResources.TryGetValue (asmNameHash, out entry) || entry == null)
@@ -197,6 +190,7 @@ namespace System.Web.Handlers
 				EmbeddedResource res;
 				if (!entry.Resources.TryGetValue (resNameHash, out res) || res == null) {
 #if SYSTEM_WEB_EXTENSIONS
+					bool debug = parts [2] == "t";
 					if (!debug)
 						return null;
 
@@ -213,6 +207,34 @@ namespace System.Web.Handlers
 			}
 		}
 
+		static void GetAssemblyNameAndHashes (KeyedHashAlgorithm kha, Assembly assembly, string resourceName, out string assemblyName, out string assemblyNameHash, out string resourceNameHash)
+		{
+			assemblyName = assembly == currAsm ? "s" : assembly.GetName ().FullName;
+			assemblyNameHash = GetStringHash (kha, assemblyName);
+			resourceNameHash = GetStringHash (kha, resourceName);
+		}
+		
+		// MUST be called with the _embeddedResourcesLock taken in the upgradeable read lock mode
+		static AssemblyEmbeddedResources GetAssemblyEmbeddedResource (KeyedHashAlgorithm kha, Assembly assembly, string assemblyNameHash, string assemblyName)
+		{
+			AssemblyEmbeddedResources entry;
+			
+			if (!_embeddedResources.TryGetValue (assemblyNameHash, out entry) || entry == null) {
+				try {
+					_embeddedResourcesLock.EnterWriteLock ();
+					entry = new AssemblyEmbeddedResources () {
+							AssemblyName = assemblyName
+								};
+					InitEmbeddedResourcesUrls (kha, assembly, assemblyName, assemblyNameHash, entry);
+					_embeddedResources.Add (assemblyNameHash, entry);
+				} finally {
+					_embeddedResourcesLock.ExitWriteLock ();
+				}
+			}
+
+			return entry;
+		}
+		
 		internal static string GetResourceUrl (Assembly assembly, string resourceName, bool notifyScriptLoaded)
 		{
 			if (assembly == null)
@@ -232,32 +254,27 @@ namespace System.Web.Handlers
 
 		static string GetResourceUrl (KeyedHashAlgorithm kha, Assembly assembly, string resourceName, bool notifyScriptLoaded)
 		{
-			string assemblyName = assembly == currAsm ? "s" : assembly.GetName ().FullName;
-			string assemblyNameHash = GetStringHash (kha, assemblyName);
-			string resourceNameHash = GetStringHash (kha, resourceName);
+			string assemblyName;
+			string assemblyNameHash;
+			string resourceNameHash;
+
+			GetAssemblyNameAndHashes (kha, assembly, resourceName, out assemblyName, out assemblyNameHash, out resourceNameHash);
 			bool debug = false;
 			string url;
 			AssemblyEmbeddedResources entry;
+			bool includeTimeStamp = true;
 
 			try {
 				_embeddedResourcesLock.EnterUpgradeableReadLock ();
-				if (!_embeddedResources.TryGetValue (assemblyNameHash, out entry) || entry == null) {
-					try {
-						_embeddedResourcesLock.EnterWriteLock ();
-						entry = new AssemblyEmbeddedResources () {
-							AssemblyName = assemblyName
-						};
-						InitEmbeddedResourcesUrls (kha, assembly, assemblyName, assemblyNameHash, entry);
-						_embeddedResources.Add (assemblyNameHash, entry);
-					} finally {
-						_embeddedResourcesLock.ExitWriteLock ();
-					}
-				}
+				entry = GetAssemblyEmbeddedResource (kha, assembly, assemblyNameHash, assemblyName);
 				string lookupKey;
 #if SYSTEM_WEB_EXTENSIONS
 				debug = resourceName.EndsWith (".debug.js", StringComparison.OrdinalIgnoreCase);
 				string dbgTail = debug ? "d" : String.Empty;
 				lookupKey = resourceNameHash + (notifyScriptLoaded ? "t" : "f") + dbgTail;
+#if NET_3_5
+				CheckIfResourceIsCompositeScript (resourceName, ref includeTimeStamp);
+#endif
 #else
 				lookupKey = resourceNameHash;
 #endif
@@ -283,12 +300,13 @@ namespace System.Web.Handlers
 			}
 
 			if (url == null)
-				url = CreateResourceUrl (kha, assemblyName, assemblyNameHash, assembly.Location, resourceNameHash, debug, notifyScriptLoaded);
+				url = CreateResourceUrl (kha, assemblyName, assemblyNameHash, assembly.Location, resourceNameHash, debug, notifyScriptLoaded, includeTimeStamp);
 			
 			return url;
 		}
 		
-		static string CreateResourceUrl (KeyedHashAlgorithm kha, string assemblyName, string assemblyNameHash, string assemblyPath, string resourceNameHash, bool debug, bool notifyScriptLoaded)
+		static string CreateResourceUrl (KeyedHashAlgorithm kha, string assemblyName, string assemblyNameHash, string assemblyPath, string resourceNameHash, bool debug,
+						 bool notifyScriptLoaded, bool includeTimeStamp)
 		{
 			string atime = String.Empty;
 			string extra = String.Empty;
@@ -299,10 +317,12 @@ namespace System.Web.Handlers
 #if TARGET_JVM
 			atime = QueryParamSeparator + "t=" + assemblyName.GetHashCode ();
 #else
-			if (!String.IsNullOrEmpty (assemblyPath) && File.Exists (assemblyPath))
-				atime = QueryParamSeparator + "t=" + File.GetLastWriteTimeUtc (assemblyPath).Ticks;
-			else
-				atime = QueryParamSeparator + "t=" + DateTime.UtcNow.Ticks;
+			if (includeTimeStamp) {
+				if (!String.IsNullOrEmpty (assemblyPath) && File.Exists (assemblyPath))
+					atime = QueryParamSeparator + "t=" + File.GetLastWriteTimeUtc (assemblyPath).Ticks;
+				else
+					atime = QueryParamSeparator + "t=" + DateTime.UtcNow.Ticks;
+			}
 #endif
 			string d = assemblyNameHash + "_" + resourceNameHash +  (debug ? "_t" : "_f");
 			string href = HandlerFileName + "?d=" + d + atime + extra;
@@ -316,63 +336,85 @@ namespace System.Web.Handlers
 			return href;
 		}
 
-#if SYSTEM_WEB_EXTENSIONS
-		protected virtual void ProcessRequest (HttpContext context)
-#else
-		void System.Web.IHttpHandler.ProcessRequest (HttpContext context)
-#endif
+		bool HasCacheControl (HttpRequest request, NameValueCollection queryString, out long atime)
+		{
+			if (String.Compare (request.Headers ["Cache-Control"], "max-age=0", StringComparison.Ordinal) != 0) {
+				atime = 0;
+				return false;
+			}
+			
+			if (Int64.TryParse (request.QueryString ["t"], out atime))
+				return true;
+
+			return false;
+		}
+
+		bool HasIfModifiedSince (HttpRequest request, out DateTime modified)
+		{
+			string modif_since = request.Headers ["If-Modified-Since"];
+			if (String.IsNullOrEmpty (modif_since)) {
+				modified = DateTime.MinValue;
+				return false;
+			}
+
+			try {
+				if (DateTime.TryParseExact (modif_since, "r", null, 0, out modified))
+					return true;
+			} catch {
+			}
+
+			return false;
+		}
+
+		void RespondWithNotModified (HttpContext context)
+		{
+			HttpResponse response = context.Response;
+			response.Clear ();
+			response.StatusCode = 304;
+			response.ContentType = null;
+			response.CacheControl = "public"; // easier to set it to public as MS than remove it
+			context.ApplicationInstance.CompleteRequest ();
+		}
+		
+		void SendEmbeddedResource (HttpContext context, out EmbeddedResource res, out Assembly assembly)
 		{
 			HttpRequest request = context.Request;
+			NameValueCollection queryString = request.QueryString;
+			
 			// val is URL-encoded, which means every + has been replaced with ' ', we
 			// need to revert that or the base64 conversion will fail.
-			string d = request.QueryString ["d"];
+			string d = queryString ["d"];
 			if (!String.IsNullOrEmpty (d))
 				d = d.Replace (' ', '+');
 
 			AssemblyEmbeddedResources entry;
-			EmbeddedResource res = DecryptAssemblyResource (d, out entry);
+			res = DecryptAssemblyResource (d, out entry);
 			WebResourceAttribute wra = res != null ? res.Attribute : null;
 			if (wra == null)
 				throw new HttpException (404, "Resource not found");
-			
-			Assembly assembly;
+
 			if (entry.AssemblyName == "s")
 				assembly = currAsm;
 			else
 				assembly = Assembly.Load (entry.AssemblyName);
 			
-			HttpResponse response = context.Response;
-			string req_cache = request.Headers ["Cache-Control"];
-			if (String.Compare (req_cache, "max-age=0", StringComparison.Ordinal) == 0) {
-				long atime;
-				if (Int64.TryParse (request.QueryString ["t"], out atime)) {
-					if (atime == File.GetLastWriteTimeUtc (assembly.Location).Ticks) {
-						response.Clear ();
-						response.StatusCode = 304;
-						response.ContentType = null;
-						response.CacheControl = "public"; // easier to set it to public as MS than remove it
-						context.ApplicationInstance.CompleteRequest ();
-						return;
-					}
+			long atime;
+			if (HasCacheControl (request, queryString, out atime)) {
+				if (atime == File.GetLastWriteTimeUtc (assembly.Location).Ticks) {
+					RespondWithNotModified (context);
+					return;
 				}
 			}
-			string modif_since = request.Headers ["If-Modified-Since"];
-			if (!String.IsNullOrEmpty (modif_since)) {
-				try {
-					DateTime modif;
-					if (DateTime.TryParseExact (modif_since, "r", null, 0, out modif)) {
-						if (File.GetLastWriteTimeUtc (assembly.Location) <= modif) {
-							response.Clear ();
-							response.StatusCode = 304;
-							response.ContentType = null;
-							response.CacheControl = "public"; // easier to set it to public as MS than remove it
-							context.ApplicationInstance.CompleteRequest ();
-							return;
-						}
-					}
-				} catch {}
+
+			DateTime modified;
+			if (HasIfModifiedSince (request, out modified)) {
+				if (File.GetLastWriteTimeUtc (assembly.Location) <= modified) {
+					RespondWithNotModified (context);
+					return;
+				}
 			}
 
+			HttpResponse response = context.Response;
 			response.ContentType = wra.ContentType;
 
 			DateTime utcnow = DateTime.UtcNow;
@@ -404,63 +446,17 @@ namespace System.Web.Handlers
 					output.Write (buf, 0, c);
 				} while (c > 0);
 			}
-#if SYSTEM_WEB_EXTENSIONS
-			TextWriter writer = response.Output;
-			foreach (ScriptResourceAttribute sra in assembly.GetCustomAttributes (typeof (ScriptResourceAttribute), false)) {
-				if (String.Compare (sra.ScriptName, res.Name, StringComparison.Ordinal) == 0) {
-					string scriptResourceName = sra.ScriptResourceName;
-					ResourceSet rset = null;
-					try {
-						rset = new ResourceManager (scriptResourceName, assembly).GetResourceSet (Threading.Thread.CurrentThread.CurrentUICulture, true, true);
-					}
-					catch (MissingManifestResourceException) {
-#if TARGET_JVM // GetResourceSet does not throw  MissingManifestResourceException if ressource is not exists
-					}
-					if (rset == null) {
-#endif
-						if (scriptResourceName.EndsWith (".resources")) {
-							scriptResourceName = scriptResourceName.Substring (0, scriptResourceName.Length - 10);
-							rset = new ResourceManager (scriptResourceName, assembly).GetResourceSet (Threading.Thread.CurrentThread.CurrentUICulture, true, true);
-						}
-#if !TARGET_JVM
-						else
-							throw;
-#endif
-					}
-					if (rset == null)
-						break;
-					writer.WriteLine ();
-					string ns = sra.TypeName;
-					int indx = ns.LastIndexOf ('.');
-					if (indx > 0)
-						writer.WriteLine ("Type.registerNamespace('" + ns.Substring (0, indx) + "')");
-					writer.Write ("{0}={{", sra.TypeName);
-					bool first = true;
-					foreach (DictionaryEntry de in rset) {
-						string value = de.Value as string;
-						if (value != null) {
-							if (first)
-								first = false;
-							else
-								writer.Write (',');
-							writer.WriteLine ();
-							writer.Write ("{0}:{1}", GetScriptStringLiteral ((string) de.Key), GetScriptStringLiteral (value));
-						}
-					}
-					writer.WriteLine ();
-					writer.WriteLine ("};");
-					break;
-				}
-			}
-
-			bool notifyScriptLoaded = request.QueryString ["n"] == "t";
-			if (notifyScriptLoaded) {
-				writer.WriteLine ();
-				writer.WriteLine ("if(typeof(Sys)!=='undefined')Sys.Application.notifyScriptLoaded();");
-			}
-#endif
 		}
-
+		
+#if !SYSTEM_WEB_EXTENSIONS
+		void System.Web.IHttpHandler.ProcessRequest (HttpContext context)
+		{
+			EmbeddedResource res;
+			Assembly assembly;
+			
+			SendEmbeddedResource (context, out res, out assembly);
+		}
+#endif
 		sealed class PerformSubstitutionHelper
 		{
 			readonly Assembly _assembly;
@@ -504,7 +500,7 @@ namespace System.Web.Handlers
 		{
 			public string AssemblyName = String.Empty;
 			public Dictionary <string, EmbeddedResource> Resources = new Dictionary <string, EmbeddedResource> (StringComparer.Ordinal);
-		}
+		}		
 	}
 }