一、Asp.net 自带Bundle的使用:
1. 在Globale中注册与配置
BundleConfig.RegisterBundles(BundleTable.Bundles); public class BundleConfig { // For more information on Bundling, visit http://go.microsoft.com/fwlink/?LinkId=254725 public static void RegisterBundles(BundleCollection bundles) { // Use the development version of Modernizr to develop with and learn from. Then, when you're // ready for production, use the build tool at http://modernizr.com to pick only the tests you need. bundles.Add(new ScriptBundle("~/bundles/modernizr").Include( "~/Scripts/modernizr-*")); bundles.Add(new ScriptBundle("~/bundles/jquery").Include( "~/Scripts/jquery-{version}.js")); #if !DEBUG BundleTable.EnableOptimizations = true; #endif } }
2. 页面上使用
@Scripts.Render("~/bundles/jquery")
二、扩展使用-动态Bundle
要求达到以下目标:1. 支持动态页面上的Bundle,而不必每次在Global中添加Bundle。2.支持Javascript混淆
1. 扩展方法
public static class Extension { public static IHtmlString Script(this HtmlHelper helper, params string[] urls) { var bundleDirectory = "~/bundles/" + MakeBundleName("js", urls); var bundle = BundleTable.Bundles.GetBundleFor(bundleDirectory); if (bundle == null) { var transform = new JavascriptObfuscator(); bundle = new ScriptBundle(bundleDirectory).Include(urls); bundle.Transforms.Add(transform); BundleTable.Bundles.Add(bundle); } return Scripts.Render(bundleDirectory); } public static IHtmlString Style(this HtmlHelper helper, params string[] urls) { var bundleDirectory = "~/bundles/" + MakeBundleName("css", urls); var bundle=BundleTable.Bundles.GetBundleFor(bundleDirectory); if (bundle == null) { bundle = new StyleBundle(bundleDirectory).Include(urls); BundleTable.Bundles.Add(bundle); } return Styles.Render(bundleDirectory); } private static string MakeBundleName(string type, params string[] urls) { var array = urls.SelectMany(url => url.Split('/')) .SelectMany(url => url.Split('.')) .Distinct() .Except(new[] {"~", type}); return string.Join("-", array); } }
JavascriptObfuscator 类的实现
public class JavascriptObfuscator : IBundleTransform { public void Process(BundleContext context, BundleResponse response) { var p = new ECMAScriptPacker(ECMAScriptPacker.PackerEncoding.Normal, true, false); response.Content = p.Pack(response.Content); } }
2. 页面上使用
@Html.Style("~/Scripts/JQueryUI2/themes/smoothness/jquery.ui.theme.css", "~/Scripts/JQueryUI2/themes/smoothness/jquery.ui.menu.css") @Html.Script("~/Scripts/JQueryUI2/ui/jquery.ui.core.js", "~/Scripts/JQueryUI2/ui/jquery.ui.position.js", "~/Scripts/JQueryUI2/ui/jquery.ui.widget.js", "~/Scripts/JQueryUI2/ui/jquery.ui.menu.js")
附录:javascript混淆器代码
/// <summary> /// Packs a javascript file into a smaller area, removing unnecessary characters from the output. /// </summary> public class ECMAScriptPacker : IHttpHandler { /// <summary> /// The encoding level to use. See http://dean.edwards.name/packer/usage/ for more info. /// </summary> public enum PackerEncoding { None = 0, Numeric = 10, Mid = 36, Normal = 62, HighAscii = 95 }; private PackerEncoding encoding = PackerEncoding.Normal; private bool fastDecode = true; private bool specialChars = false; private bool enabled = true; string IGNORE = "$1"; /// <summary> /// The encoding level for this instance /// </summary> public PackerEncoding Encoding { get { return encoding; } set { encoding = value; } } /// <summary> /// Adds a subroutine to the output to speed up decoding /// </summary> public bool FastDecode { get { return fastDecode; } set { fastDecode = value; } } /// <summary> /// Replaces special characters /// </summary> public bool SpecialChars { get { return specialChars; } set { specialChars = value; } } /// <summary> /// Packer enabled /// </summary> public bool Enabled { get { return enabled; } set { enabled = value; } } public ECMAScriptPacker() { Encoding = PackerEncoding.Normal; FastDecode = true; SpecialChars = false; } /// <summary> /// Constructor /// </summary> /// <param name="encoding">The encoding level for this instance</param> /// <param name="fastDecode">Adds a subroutine to the output to speed up decoding</param> /// <param name="specialChars">Replaces special characters</param> public ECMAScriptPacker(PackerEncoding encoding, bool fastDecode, bool specialChars) { Encoding = encoding; FastDecode = fastDecode; SpecialChars = specialChars; } /// <summary> /// Packs the script /// </summary> /// <param name="script">the script to pack</param> /// <returns>the packed script</returns> public string Pack(string script) { if (enabled) { script += " "; script = basicCompression(script); if (SpecialChars) script = encodeSpecialChars(script); if (Encoding != PackerEncoding.None) script = encodeKeywords(script); } return script; } //zero encoding - just removal of whitespace and comments private string basicCompression(string script) { ParseMaster parser = new ParseMaster(); // make safe parser.EscapeChar = '\'; // protect strings parser.Add("'[^'\n\r]*'", IGNORE); parser.Add(""[^"\n\r]*"", IGNORE); // remove comments parser.Add("\/\/[^\n\r]*[\n\r]"); parser.Add("\/\*[^*]*\*+([^\/][^*]*\*+)*\/"); // protect regular expressions parser.Add("\s+(\/[^\/\n\r\*][^\/\n\r]*\/g?i?)", "$2"); parser.Add("[^\w\$\/'"*)\?:]\/[^\/\n\r\*][^\/\n\r]*\/g?i?", IGNORE); // remove: ;;; doSomething(); if (specialChars) parser.Add(";;[^\n\r]+[\n\r]"); // remove redundant semi-colons parser.Add(";+\s*([};])", "$2"); // remove white-space parser.Add("(\b|\$)\s+(\b|\$)", "$2 $3"); parser.Add("([+\-])\s+([+\-])", "$2 $3"); parser.Add("\s+"); // done return parser.Exec(script); } WordList encodingLookup; private string encodeSpecialChars(string script) { ParseMaster parser = new ParseMaster(); // replace: $name -> n, $$name -> na parser.Add("((\$+)([a-zA-Z\$_]+))(\d*)", new ParseMaster.MatchGroupEvaluator(encodeLocalVars)); // replace: _name -> _0, double-underscore (__name) is ignored Regex regex = new Regex("\b_[A-Za-z\d]\w*"); // build the word list encodingLookup = analyze(script, regex, new EncodeMethod(encodePrivate)); parser.Add("\b_[A-Za-z\d]\w*", new ParseMaster.MatchGroupEvaluator(encodeWithLookup)); script = parser.Exec(script); return script; } private string encodeKeywords(string script) { // escape high-ascii values already in the script (i.e. in strings) if (Encoding == PackerEncoding.HighAscii) script = escape95(script); // create the parser ParseMaster parser = new ParseMaster(); EncodeMethod encode = getEncoder(Encoding); // for high-ascii, don't encode single character low-ascii Regex regex = new Regex( (Encoding == PackerEncoding.HighAscii) ? "\w\w+" : "\w+" ); // build the word list encodingLookup = analyze(script, regex, encode); // encode parser.Add((Encoding == PackerEncoding.HighAscii) ? "\w\w+" : "\w+", new ParseMaster.MatchGroupEvaluator(encodeWithLookup)); // if encoded, wrap the script in a decoding function return (script == string.Empty) ? "" : bootStrap(parser.Exec(script), encodingLookup); } private string bootStrap(string packed, WordList keywords) { // packed: the packed script packed = "'" + escape(packed) + "'"; // ascii: base for encoding int ascii = Math.Min(keywords.Sorted.Count, (int)Encoding); if (ascii == 0) ascii = 1; // count: number of words contained in the script int count = keywords.Sorted.Count; // keywords: list of words contained in the script foreach (object key in keywords.Protected.Keys) { keywords.Sorted[(int)key] = ""; } // convert from a string to an array StringBuilder sbKeywords = new StringBuilder("'"); foreach (string word in keywords.Sorted) sbKeywords.Append(word + "|"); sbKeywords.Remove(sbKeywords.Length - 1, 1); string keywordsout = sbKeywords.ToString() + "'.split('|')"; string encode; string inline = "c"; switch (Encoding) { case PackerEncoding.Mid: encode = "function(c){return c.toString(36)}"; inline += ".toString(a)"; break; case PackerEncoding.Normal: encode = "function(c){return(c<a?"":e(parseInt(c/a)))+" + "((c=c%a)>35?String.fromCharCode(c+29):c.toString(36))}"; inline += ".toString(a)"; break; case PackerEncoding.HighAscii: encode = "function(c){return(c<a?"":e(c/a))+" + "String.fromCharCode(c%a+161)}"; inline += ".toString(a)"; break; default: encode = "function(c){return c}"; break; } // decode: code snippet to speed up decoding string decode = ""; if (fastDecode) { decode = "if(!''.replace(/^/,String)){while(c--)d[e(c)]=k[c]||e(c);k=[function(e){return d[e]}];e=function(){return'\\w+'};c=1;}"; if (Encoding == PackerEncoding.HighAscii) decode = decode.Replace("\\w", "[\xa1-\xff]"); else if (Encoding == PackerEncoding.Numeric) decode = decode.Replace("e(c)", inline); if (count == 0) decode = decode.Replace("c=1", "c=0"); } // boot function string unpack = "function(p,a,c,k,e,d){while(c--)if(k[c])p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c]);return p;}"; Regex r; if (fastDecode) { //insert the decoder r = new Regex("\{"); unpack = r.Replace(unpack, "{" + decode + ";", 1); } if (Encoding == PackerEncoding.HighAscii) { // get rid of the word-boundries for regexp matches r = new Regex("'\\\\b'\s*\+|\+\s*'\\\\b'"); unpack = r.Replace(unpack, ""); } if (Encoding == PackerEncoding.HighAscii || ascii > (int)PackerEncoding.Normal || fastDecode) { // insert the encode function r = new Regex("\{"); unpack = r.Replace(unpack, "{e=" + encode + ";", 1); } else { r = new Regex("e\(c\)"); unpack = r.Replace(unpack, inline); } // no need to pack the boot function since i've already done it string _params = "" + packed + "," + ascii + "," + count + "," + keywordsout; if (fastDecode) { //insert placeholders for the decoder _params += ",0,{}"; } // the whole thing return "eval(" + unpack + "(" + _params + ")) "; } private string escape(string input) { Regex r = new Regex("([\\'])"); return r.Replace(input, "\$1"); } private EncodeMethod getEncoder(PackerEncoding encoding) { switch (encoding) { case PackerEncoding.Mid: return new EncodeMethod(encode36); case PackerEncoding.Normal: return new EncodeMethod(encode62); case PackerEncoding.HighAscii: return new EncodeMethod(encode95); default: return new EncodeMethod(encode10); } } private string encode10(int code) { return code.ToString(); } //lookups seemed like the easiest way to do this since // I don't know of an equivalent to .toString(36) private static string lookup36 = "0123456789abcdefghijklmnopqrstuvwxyz"; private string encode36(int code) { string encoded = ""; int i = 0; do { int digit = (code / (int)Math.Pow(36, i)) % 36; encoded = lookup36[digit] + encoded; code -= digit * (int)Math.Pow(36, i++); } while (code > 0); return encoded; } private static string lookup62 = lookup36 + "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; private string encode62(int code) { string encoded = ""; int i = 0; do { int digit = (code / (int)Math.Pow(62, i)) % 62; encoded = lookup62[digit] + encoded; code -= digit * (int)Math.Pow(62, i++); } while (code > 0); return encoded; } private static string lookup95 = "¡¢£¤¥¦§¨©ª«¬®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ"; private string encode95(int code) { string encoded = ""; int i = 0; do { int digit = (code / (int)Math.Pow(95, i)) % 95; encoded = lookup95[digit] + encoded; code -= digit * (int)Math.Pow(95, i++); } while (code > 0); return encoded; } private string escape95(string input) { Regex r = new Regex("[xa1-xff]"); return r.Replace(input, new MatchEvaluator(escape95Eval)); } private string escape95Eval(Match match) { return "\x" + ((int)match.Value[0]).ToString("x"); //return hexadecimal value } private string encodeLocalVars(Match match, int offset) { int length = match.Groups[offset + 2].Length; int start = length - Math.Max(length - match.Groups[offset + 3].Length, 0); return match.Groups[offset + 1].Value.Substring(start, length) + match.Groups[offset + 4].Value; } private string encodeWithLookup(Match match, int offset) { return (string)encodingLookup.Encoded[match.Groups[offset].Value]; } private delegate string EncodeMethod(int code); private string encodePrivate(int code) { return "_" + code; } private WordList analyze(string input, Regex regex, EncodeMethod encodeMethod) { // analyse // retreive all words in the script MatchCollection all = regex.Matches(input); WordList rtrn; rtrn.Sorted = new StringCollection(); // list of words sorted by frequency rtrn.Protected = new HybridDictionary(); // dictionary of word->encoding rtrn.Encoded = new HybridDictionary(); // instances of "protected" words if (all.Count > 0) { StringCollection unsorted = new StringCollection(); // same list, not sorted HybridDictionary Protected = new HybridDictionary(); // "protected" words (dictionary of word->"word") HybridDictionary values = new HybridDictionary(); // dictionary of charCode->encoding (eg. 256->ff) HybridDictionary count = new HybridDictionary(); // word->count int i = all.Count, j = 0; string word; // count the occurrences - used for sorting later do { word = "$" + all[--i].Value; if (count[word] == null) { count[word] = 0; unsorted.Add(word); // make a dictionary of all of the protected words in this script // these are words that might be mistaken for encoding Protected["$" + (values[j] = encodeMethod(j))] = j++; } // increment the word counter count[word] = (int)count[word] + 1; } while (i > 0); /* prepare to sort the word list, first we must protect words that are also used as codes. we assign them a code equivalent to the word itself. e.g. if "do" falls within our encoding range then we store keywords["do"] = "do"; this avoids problems when decoding */ i = unsorted.Count; string[] sortedarr = new string[unsorted.Count]; do { word = unsorted[--i]; if (Protected[word] != null) { sortedarr[(int)Protected[word]] = word.Substring(1); rtrn.Protected[(int)Protected[word]] = true; count[word] = 0; } } while (i > 0); string[] unsortedarr = new string[unsorted.Count]; unsorted.CopyTo(unsortedarr, 0); // sort the words by frequency Array.Sort(unsortedarr, (IComparer)new CountComparer(count)); j = 0; /*because there are "protected" words in the list we must add the sorted words around them */ do { if (sortedarr[i] == null) sortedarr[i] = unsortedarr[j++].Substring(1); rtrn.Encoded[sortedarr[i]] = values[i]; } while (++i < unsortedarr.Length); rtrn.Sorted.AddRange(sortedarr); } return rtrn; } private struct WordList { public StringCollection Sorted; public HybridDictionary Encoded; public HybridDictionary Protected; } private class CountComparer : IComparer { HybridDictionary count; public CountComparer(HybridDictionary count) { this.count = count; } #region IComparer Members public int Compare(object x, object y) { return (int)count[y] - (int)count[x]; } #endregion } #region IHttpHandler Members public void ProcessRequest(HttpContext context) { // try and read settings from config file if (System.Configuration.ConfigurationSettings.GetConfig("ecmascriptpacker") != null) { NameValueCollection cfg = (NameValueCollection) System.Configuration.ConfigurationSettings.GetConfig("ecmascriptpacker"); if (cfg["Encoding"] != null) { switch (cfg["Encoding"].ToLower()) { case "none": Encoding = PackerEncoding.None; break; case "numeric": Encoding = PackerEncoding.Numeric; break; case "mid": Encoding = PackerEncoding.Mid; break; case "normal": Encoding = PackerEncoding.Normal; break; case "highascii": case "high": Encoding = PackerEncoding.HighAscii; break; } } if (cfg["FastDecode"] != null) { if (cfg["FastDecode"].ToLower() == "true") FastDecode = true; else FastDecode = false; } if (cfg["SpecialChars"] != null) { if (cfg["SpecialChars"].ToLower() == "true") SpecialChars = true; else SpecialChars = false; } if (cfg["Enabled"] != null) { if (cfg["Enabled"].ToLower() == "true") Enabled = true; else Enabled = false; } } // try and read settings from URL if (context.Request.QueryString["Encoding"] != null) { switch (context.Request.QueryString["Encoding"].ToLower()) { case "none": Encoding = PackerEncoding.None; break; case "numeric": Encoding = PackerEncoding.Numeric; break; case "mid": Encoding = PackerEncoding.Mid; break; case "normal": Encoding = PackerEncoding.Normal; break; case "highascii": case "high": Encoding = PackerEncoding.HighAscii; break; } } if (context.Request.QueryString["FastDecode"] != null) { if (context.Request.QueryString["FastDecode"].ToLower() == "true") FastDecode = true; else FastDecode = false; } if (context.Request.QueryString["SpecialChars"] != null) { if (context.Request.QueryString["SpecialChars"].ToLower() == "true") SpecialChars = true; else SpecialChars = false; } if (context.Request.QueryString["Enabled"] != null) { if (context.Request.QueryString["Enabled"].ToLower() == "true") Enabled = true; else Enabled = false; } //handle the request TextReader r = new StreamReader(context.Request.PhysicalPath); string jscontent = r.ReadToEnd(); r.Close(); context.Response.ContentType = "text/javascript"; context.Response.Output.Write(Pack(jscontent)); } public bool IsReusable { get { if (System.Configuration.ConfigurationSettings.GetConfig("ecmascriptpacker") != null) { NameValueCollection cfg = (NameValueCollection) System.Configuration.ConfigurationSettings.GetConfig("ecmascriptpacker"); if (cfg["IsReusable"] != null) if (cfg["IsReusable"].ToLower() == "true") return true; } return false; } } #endregion }
总结:
1. 通过继承IBundleTransform接口能够对Asp.net MVC中自带Bundle类进行扩展。
2. 可以动态添加Bundle,而不必在Globale中一次性全都添加。
3. 除了Asp.net MVC自带的Bundle外,还有其他的Bundle插件,例如Bundle Transformer: YUI 1.8.0
本文摘自:http://blog.csdn.net/leewhoee/article/details/19107013#t3