如何在不重新编译的情况下使用ASP.NET捆绑和缩小?

not*_*ary 3 javascript asp.net bundling-and-minification

约束:我没有使用MVC,而是在我的Web应用程序中使用了常规的.aspx文件。也不使用母版页-每个页面都是不同的野兽,因此该解决方案不适合我。

我为捆绑和缩小而阅读的大多数示例都需要一些特殊的MVC标记,或者要求您先识别捆绑的脚本/样式表,然后再引用这些捆绑。我想避免每次在.aspx页中添加或修改.js引用时都重新编译DLL。

我对阅读Msft文档有些困惑。是否有一种方法(如ASP.NET控件)可以包装一系列script标签(或linkCSS的标签)来动态创建和使用包?我不想重新发明轮子,而是认真考虑创建自己的用户控件/自定义控件来处理这个问题。还有其他选择吗?

例如,寻找这样的东西:

<asp:AdHocScriptBundle id="mypage_bundle" runat="server">
    <script type="text/javascript" src="~/scripts/mypage1.js"></script>
    <script type="text/javascript" src="~/scripts/mypage2.js"></script>
    <script type="text/javascript" src="~/scripts/mypage3.js"></script>
</asp:AdHocScriptBundle>
Run Code Online (Sandbox Code Playgroud)

启用捆绑后,将自动用类似于以下内容的asp:AdHocScriptBundle单个script标签替换的内容:

<script type="text/javascript" src="/webappname/bundles/mypage_bundle.js?v=dh120398dh1298dh192d8hd32d"></script>
Run Code Online (Sandbox Code Playgroud)

并且当捆绑功能被禁用时,通常按以下方式输出内容:

<script type="text/javascript" src="/webappname/scripts/mypage1.js"></script>
<script type="text/javascript" src="/webappname/scripts/mypage2.js"></script>
<script type="text/javascript" src="/webappname/scripts/mypage3.js"></script>
Run Code Online (Sandbox Code Playgroud)

有什么想法吗?

无论如何即将推出自己的游戏,但如果已经有解决方案,请分享,谢谢!

not*_*ary 5

我推出了自己的解决方案,效果很好!我创建了4个可用作自定义服务器控件的类:

  • ScriptBundle
  • 脚本
  • StyleBundle
  • 链接

这些调用函数围绕我的自定义绑定库提供,该库本身就是System.Web.Optimization API的包装。

在渲染ScriptBundleStyleBundle然后我检查内部设置(同一个,我用它来设定EnableOptimizations,告诉页面要么使用捆绑的System.Web.Optimization API中),或者干脆写出来的正常script/ link标签。如果启用了捆绑功能,它将从我的自定义捆绑库中调用此函数(对于脚本,以下代码中样式的相似代码Bundler是我的自定义捆绑库的类,以防万一Microsoft更改了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);
    }
Run Code Online (Sandbox Code Playgroud)

为了确保仅在尚不存在的捆绑包中创建它,我首先使用此方法检查捆绑包(在使用上述方法之前):

    public static bool BundleExists(string virtualTargetPath)
    {
        return System.Web.Optimization.BundleTable.Bundles.GetBundleFor(virtualTargetPath) != null;
    }
Run Code Online (Sandbox Code Playgroud)

然后,我使用此函数通过使用System.Web.Optimization将URL吐出到分发包:

    public static System.Web.IHtmlString GetScriptBundleHTML(string virtualTargetPath)
    {
        return System.Web.Optimization.Scripts.Render(virtualTargetPath);
    }
Run Code Online (Sandbox Code Playgroud)

在我的.aspx文件中,我这样做:

<%@ Register TagPrefix="cc1" Namespace="AdHocBundler" Assembly="AdHocBundler" %>
Run Code Online (Sandbox Code Playgroud)

...

<cc1:ScriptBundle name="MyBundle" runat="Server">
    <cc1:script src='~/js/script1.js'/>
    <cc1:script src='~/js/utils/script2.js'/>
</cc1:ScriptBundle>
Run Code Online (Sandbox Code Playgroud)

对我来说,诀窍在于,我必须转换scriptlink标记才能在ScriptBundleand StyleBundle控件中作为列表项使用,但之后效果很好,并且让我使用tilde运算符轻松引用相对于应用程序根目录(使用Page.ResolveClientUrl(),对创建模块内容很有帮助)。

感谢您对此SO答案的帮助,以帮助我弄清楚如何创建自定义集合控件:如何使用集合属性构建ASP.NET自定义控件?

更新:为了完全公开,我获得了共享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; }
}
Run Code Online (Sandbox Code Playgroud)

  • 听起来您正在将捆绑的脚本注入到页面中,而不是创建优化的外部链接。真的吗?如果是这种情况,那么您就败了一半的目标,那就是能够缓存脚本以供重复使用。本质上,您要取消获得的任何好处,因为是的,您正在缩小脚本,但是现在每次加载引用该捆绑软件的页面时,您都必须下载整个内容,因此实际上您可能会处于负面状态。 (2认同)