📄 toolkitscriptmanager.cs
字号:
// (c) Copyright Microsoft Corporation.
// This source is subject to the Microsoft Permissive License.
// See http://www.microsoft.com/resources/sharedsource/licensingbasics/sharedsourcelicenses.mspx.
// All other rights reserved.
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.IO.Compression;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
namespace AjaxControlToolkit
{
/// <summary>
/// ScriptManager derived class to add the ability to combine multiple
/// smaller scripts into one larger one as a way to reduce the number
/// of files the client must download
/// </summary>
[Themeable(true)]
public class ToolkitScriptManager : ScriptManager
{
/// <summary>
/// Request param name for the serialized combined scripts string
/// </summary>
private const string CombinedScriptsParamName = "_TSM_CombinedScripts_";
/// <summary>
/// Request param name for the hidden field name
/// </summary>
private const string HiddenFieldParamName = "_TSM_HiddenField_";
/// <summary>
/// Regular expression for detecting WebResource/ScriptResource substitutions in script files
/// </summary>
protected static readonly Regex WebResourceRegex = new Regex("<%\\s*=\\s*(?<resourceType>WebResource|ScriptResource)\\(\"(?<resourceName>[^\"]*)\"\\)\\s*%>", RegexOptions.Singleline | RegexOptions.Multiline);
/// <summary>
/// Specifies whether or not multiple script references should be combined into a single file
/// </summary>
public bool CombineScripts
{
get { return _combineScripts; }
set { _combineScripts = value; }
}
private bool _combineScripts = true;
/// <summary>
/// Optionally specifies the URL of an HTTP handler for generating the combined script files
/// </summary>
/// <remarks>
/// The handler's ProcessRequest method should call directly through to ToolkitScriptManager.OutputCombinedScriptFile
/// </remarks>
[UrlProperty]
public Uri CombineScriptsHandlerUrl
{
get { return _combineScriptsHandlerUrl; }
set { _combineScriptsHandlerUrl = value; }
}
private Uri _combineScriptsHandlerUrl;
/// <summary>
/// List of ScriptEntry objects tracking scripts that are used by the page
/// </summary>
private List<ScriptEntry> _scriptEntries;
/// <summary>
/// Url for the browser to request to get the combined script file
/// </summary>
private string _combinedScriptUrl;
/// <summary>
/// List of script references that have been disabled
/// </summary>
private List<ScriptReference> _disabledScriptReferences;
/// <summary>
/// List of script references that have been seen and are uncombinable
/// </summary>
private List<ScriptReference> _uncombinableScriptReferences;
/// <summary>
/// OnLoad override that runs only when serving the original page
/// </summary>
/// <param name="e">event args</param>
protected override void OnLoad(EventArgs e)
{
// Initialize
_disabledScriptReferences = new List<ScriptReference>();
_uncombinableScriptReferences = new List<ScriptReference>();
// Create a hidden field to track loaded scripts - load its contents if already present
string hiddenFieldName = HiddenFieldName;
string value = "";
if (!IsInAsyncPostBack || (null == Page.Request.Form[hiddenFieldName]))
{
RegisterHiddenField(Page, hiddenFieldName, value);
}
else
{
value = Page.Request.Form[hiddenFieldName];
}
// Get the list of already-loaded scripts from the page
_scriptEntries = DeserializeScriptEntries(value, true);
base.OnLoad(e);
}
/// <summary>
/// OnResolveScriptReference override to track combinable scripts and update the script references
/// </summary>
/// <param name="e">event args</param>
protected override void OnResolveScriptReference(ScriptReferenceEventArgs e)
{
base.OnResolveScriptReference(e);
// If combining scripts and this is a candidate script
if (_combineScripts && !String.IsNullOrEmpty(e.Script.Assembly) && !String.IsNullOrEmpty(e.Script.Name))
{
// Initialize
ScriptReference scriptReference = e.Script;
ScriptEntry scriptEntry = new ScriptEntry(scriptReference);
if (IsScriptCombinable(scriptEntry))
{
if (!_scriptEntries.Contains(scriptEntry))
{
// Haven't seen this script yet; add it to the list and invalidate the Url
_scriptEntries.Add(scriptEntry);
_combinedScriptUrl = null;
}
if (null == _combinedScriptUrl)
{
// Url is invalid; update it
_combinedScriptUrl = String.Format(CultureInfo.InvariantCulture, "{0}?{1}={2}&{3}={4}", ((null != _combineScriptsHandlerUrl) ? _combineScriptsHandlerUrl.ToString() : Page.Request.Path), HiddenFieldParamName, HiddenFieldName, CombinedScriptsParamName, HttpUtility.UrlEncode(SerializeScriptEntries(_scriptEntries, false)));
}
// Remove the script from the list and track it
scriptReference.Name = "";
scriptReference.Assembly = "";
_disabledScriptReferences.Add(scriptReference);
// Update the common (combined) Url for all tracked scripts
foreach (ScriptReference disabledScriptReference in _disabledScriptReferences)
{
disabledScriptReference.Path = _combinedScriptUrl;
}
}
else
{
// See if we've already seen this uncombinable script reference
bool alreadySeen = false;
foreach (ScriptReference uncombinableScriptReference in _uncombinableScriptReferences)
{
if ((uncombinableScriptReference.Assembly == scriptReference.Assembly) && (uncombinableScriptReference.Name == scriptReference.Name))
{
alreadySeen = true;
}
}
if (!alreadySeen)
{
// Haven't seen the script reference yet, so we need to stop building the current combined script
// file and let the uncombinable script reference be output so as not to alter the ordering of
// scripts (which may have dependencies). Update our state so we'll start building a new combined
// script file with the next combinable script.
// Note: _combinedScriptUrl was initially cleared here. While that's correct behavior (and was
// released without issue), not clearing it means that we can omit an unnecessary <script> tag
// for the scenario "CombinableA, Uncombinable?, CombinableA, Uncombinable?" because the second
// instance of CombinableA will reuse the URL from the first (vs. an empty one) and ScriptManager
// will detect and omit the redundant URL.
_uncombinableScriptReferences.Add(scriptReference);
_disabledScriptReferences.Clear();
foreach (ScriptEntry se in _scriptEntries)
{
se.Loaded = true;
}
}
}
}
}
/// <summary>
/// OnInit override that runs only when serving the combined script file
/// </summary>
/// <param name="e">event args</param>
protected override void OnInit(EventArgs e)
{
if (!DesignMode && (null != Context) && OutputCombinedScriptFile(Context))
{
// This was a combined script request that was satisfied; end all processing now
Page.Response.End();
}
base.OnInit(e);
}
/// <summary>
/// Outputs the combined script file requested by the HttpRequest to the HttpResponse
/// </summary>
/// <param name="context">HttpContext for the transaction</param>
/// <returns>true if the script file was output</returns>
public static bool OutputCombinedScriptFile(HttpContext context)
{
// Initialize
bool output = false;
HttpRequest request = context.Request;
string hiddenFieldName = request.Params[HiddenFieldParamName];
string combinedScripts = request.Params[CombinedScriptsParamName];
if (!string.IsNullOrEmpty(hiddenFieldName) && !string.IsNullOrEmpty(combinedScripts))
{
// This is a request for a combined script file
HttpResponse response = context.Response;
response.ContentType = "application/x-javascript";
// Set the same (~forever) caching rules that ScriptResource.axd uses
HttpCachePolicy cache = response.Cache;
cache.SetCacheability(HttpCacheability.Public);
cache.VaryByParams[HiddenFieldParamName] = true;
cache.VaryByParams[CombinedScriptsParamName] = true;
cache.SetOmitVaryStar(true);
cache.SetExpires(DateTime.Now.AddDays(365));
cache.SetValidUntilExpires(true);
cache.SetLastModifiedFromFileDependencies();
// Get the stream to write the combined script to (using a compressed stream if requested)
// Note that certain versions of IE6 have difficulty with compressed responses, so we
// don't compress for those browsers (just like ASP.NET AJAX's ScriptResourceHandler)
Stream outputStream = response.OutputStream;
if (!request.Browser.IsBrowser("IE") || (6 < request.Browser.MajorVersion))
{
foreach (string acceptEncoding in (request.Headers["Accept-Encoding"] ?? "").ToUpperInvariant().Split(','))
{
if ("GZIP" == acceptEncoding)
{
// Browser wants GZIP; wrap the output stream with a GZipStream
response.AddHeader("Content-encoding", "gzip");
outputStream = new GZipStream(outputStream, CompressionMode.Compress);
break;
}
else if ("DEFLATE" == acceptEncoding)
{
// Browser wants Deflate; wrap the output stream with a DeflateStream
response.AddHeader("Content-encoding", "deflate");
outputStream = new DeflateStream(outputStream, CompressionMode.Compress);
break;
}
}
}
// Output the combined script
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -