3

约束:我没有使用 MVC,只是在我的 Web 应用程序中使用常规 ol' .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>

即,启用捆绑时,会自动将 的内容替换为类似于以下asp:AdHocScriptBundle的单个标记:script

<script type="text/javascript" src="/webappname/bundles/mypage_bundle.js?v=dh120398dh1298dh192d8hd32d"></script>

并且当捆绑被禁用时,通常会像这样输出内容:

<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>

有什么想法吗?

无论如何都要推出我自己的,但如果已经有解决方案,请分享,谢谢!

4

3 回答 3

5

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

  • 脚本包
  • 脚本
  • StyleBundle
  • 关联

这些调用函数围绕着我的自定义捆绑库,它本身就是 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中的列表项,但之后它工作得很好,它让我可以使用波浪号运算符来轻松引用相对于应用程序根目录(使用有助于创建模块内容)。ScriptBundleStyleBundlePage.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; }
}
于 2013-07-17T00:34:33.990 回答
1

这对于 ASP.NET 中的默认捆绑/缩小是不可能的。捆绑的全部意义在于创建一个单一的文件以减少浏览器请求加载静态文件(如 .JS 和 .CSS 文件)的次数。

自己动手是你唯一的选择。但是,请注意每一<script>行都会产生一个浏览器请求。由于大多数浏览器只能同时处理 6 个请求,因此您可以有等待时间来加载这些静态文件。

仅供参考,您不必在每次使用内置捆绑更新 .JS 文件时重新编译 DLL。您可以简单地重置应用程序正在运行的应用程序池。如果您使用外部会话持久性模型运行,您的用户不会注意到这种情况何时发生。

于 2013-07-15T23:44:06.667 回答
0

您的问题是您并没有真正考虑过这个问题。如果你是,你会意识到你所要求的是行不通的。

为什么?因为脚本标签 ahs 会生成对不同url的外部链接引用。因此,您在当前文件的标题中放置的任何内容都不会影响实际包含您的包的其他 URL。因此,无法在页面本身中动态更改捆绑包,因为根据定义,捆绑包必须在外部资源中定义。

现在,没有什么说这些包必须在您自己的解决方案中编译成 DLL,但它们不能嵌入到当前正在呈现的页面中。

您可能想研究一些基于 javascript 的缩小工具,因为它们通常不会被编译。

于 2013-07-15T23:56:03.723 回答