我推出了自己的解决方案,效果很好!我创建了 4 个可以用作自定义服务器控件的类:
这些调用函数围绕着我的自定义捆绑库,它本身就是 System.Web.Optimization API 的包装器。
在 Render of 期间,ScriptBundle
然后StyleBundle
我检查一个内部设置(与我EnableOptimizations
在 System.Web.Optimization API 中设置的设置相同),它告诉页面要么使用捆绑,要么简单地写出正常的script
/link
标记。如果启用了捆绑,它会从我的自定义捆绑库中调用此函数(对于脚本,样式的类似代码。Bundler
下面的代码是我的自定义捆绑库的类 - 以防万一微软更改 System.Web.Optimization API 我想要一个中间层,这样我就不必更改我的代码):
public static void AddScriptBundle(string virtualTargetPath, params string[] virtualSourcePaths)
{
var scriptBundle = new System.Web.Optimization.ScriptBundle(virtualTargetPath);
scriptBundle.Include(virtualSourcePaths);
System.Web.Optimization.BundleTable.Bundles.Add(scriptBundle);
}
为确保仅在 Bundle 不存在时才创建它,我首先使用此方法检查 Bundle(在使用上述方法之前):
public static bool BundleExists(string virtualTargetPath)
{
return System.Web.Optimization.BundleTable.Bundles.GetBundleFor(virtualTargetPath) != null;
}
然后我使用此函数通过 System.Web.Optimization 将 URL 吐出到包:
public static System.Web.IHtmlString GetScriptBundleHTML(string virtualTargetPath)
{
return System.Web.Optimization.Scripts.Render(virtualTargetPath);
}
在我的 .aspx 文件中,我这样做:
<%@ Register TagPrefix="cc1" Namespace="AdHocBundler" Assembly="AdHocBundler" %>
...
<cc1:ScriptBundle name="MyBundle" runat="Server">
<cc1:script src='~/js/script1.js'/>
<cc1:script src='~/js/utils/script2.js'/>
</cc1:ScriptBundle>
对我来说,诀窍是弄清楚我必须将script
和标签转换为和控件link
中的列表项,但之后它工作得很好,它让我可以使用波浪号运算符来轻松引用相对于应用程序根目录(使用有助于创建模块内容)。ScriptBundle
StyleBundle
Page.ResolveClientUrl()
感谢这个 SO 答案帮助我弄清楚如何创建自定义集合控件:How do you build an ASP.NET custom control with a collection property?
更新:为了全面披露,我获得了共享 ScriptBundle 代码的许可(StyleBundle 几乎相同,因此不包括在内):
[DefaultProperty("Name")]
[ParseChildren(true, DefaultProperty = "Scripts")]
public class ScriptBundle : Control
{
public ScriptBundle()
{
this.Enabled = true;
this.Scripts = new List<Script>();
}
[PersistenceMode(PersistenceMode.Attribute)]
public String Name { get; set; }
[PersistenceMode(PersistenceMode.Attribute)]
[DefaultValue(true)]
public Boolean Enabled { get; set; }
[PersistenceMode(PersistenceMode.InnerDefaultProperty)]
public List<Script> Scripts { get; set; }
protected override void Render(HtmlTextWriter writer)
{
if (String.IsNullOrEmpty(this.Name))
{
// Name is used to generate the bundle; tell dev if he forgot it
throw new Exception("ScriptBundle Name is not defined.");
}
writer.BeginRender();
if (this.Enabled && Bundler.EnableOptimizations)
{
if (this.Scripts.Count > 0)
{
string bundleName = String.Format("~/bundles{0}/{1}.js",
HttpContext.Current.Request.FilePath,
this.Name).ToLower();
// create a bundle if not exists
if (!Bundler.BundleExists(bundleName))
{
string[] scriptPaths = new string[this.Scripts.Count];
int len = scriptPaths.Length;
for (int i = 0; i < len; i++)
{
if (!string.IsNullOrEmpty(this.Scripts[i].Src))
{
// no need for resolve client URL here - bundler already does it for us, so paths like "~/scripts" will already be expanded
scriptPaths[i] = this.Scripts[i].Src;
}
}
Bundler.AddScriptBundle(bundleName, scriptPaths);
}
// spit out a reference to bundle
writer.Write(Bundler.GetScriptBundleHTML(bundleName));
}
}
else
{
// do not use bundling. generate normal script tags for each Script
foreach (Script s in this.Scripts)
{
if (!string.IsNullOrEmpty(s.Src))
{
// render <script type='<type>' src='<src'>/> ... and resolve URL to expand tilde, which lets us use paths relative to app root
// calling writer.Write() directly since it has less overhead than using RenderBeginTag(), etc., assumption is no special/weird chars in the cc1:script attrs
writer.Write(String.Format(Script.TAG_FORMAT_DEFAULT,
s.Type,
Page.ResolveClientUrl(s.Src)));
}
}
}
writer.EndRender();
}
}
public class Script
{
public const String ATTR_TYPE_DEFAULT = "text/javascript";
public const String TAG_FORMAT_DEFAULT = "<script type=\"{0}\" src=\"{1}\"></script>";
public Script()
{
this.Type = ATTR_TYPE_DEFAULT;
this.Src = null;
}
public String Type { get; set; }
public String Src { get; set; }
public String Language { get; set; }
}