【问题标题】:Include JavaScript file in partial views在部分视图中包含 JavaScript 文件
【发布时间】:2010-10-29 02:30:58
【问题描述】:

我想知道在局部视图中包含 javascript 文件的最佳做法是什么。一旦呈现,这将最终成为我页面 html 中间的一个 js 包含标记。从我的角度来看,这不是一个很好的方法。它们属于 head 标签,因此不应阻止浏览器一次性渲染 html。

一个例子: 我在“PictureGallery”部分视图中使用 jquery 图片库插件,因为该部分视图将在多个页面上使用。此插件仅在使用此视图时才需要加载,并且我不想知道每个部分视图正在使用哪些插件...

感谢您的回答。

【问题讨论】:

  • 最佳实践是在 html 页面底部实际包含 javascript,以在渲染页面时提高浏览器的性能。
  • 我从来没有听说过这个......你能提供一些来源来支持这个声明吗?
  • 我认为实际问题是能够更改母版页中包含的 js 文件,而不是它们所在的位置。有时 - 如果 js 文件在加载之前对页面至关重要 - 顶部是放置它的正确位置。这是一个有趣的问题。
  • 我查过了,确实,yahoo 性能团队确认最高效的方法是将它们包含在结束 body 标记之前。感谢您提供此信息。尽管如此,正如 edeverett 指出的那样,我想知道一些将它们添加到中心位置的方法,而不是整个页面。
  • YSlow 是一个非常好的网络性能分析插件。这是 javascript 的文档:developer.yahoo.com/performance/rules.html#js_bottom

标签: javascript jquery asp.net-mvc partial-views


【解决方案1】:

似乎与这个问题非常相似:Linking JavaScript Libraries in User Controls

我将在此处重新发布我对该问题的回答。

出于您提到的原因,我绝对建议不要将它们放在部分中。一个视图很有可能会拉入两个都引用同一个 js 文件的部分。在加载其余 html 之前加载 js 也会对性能造成影响。

我不知道最佳实践,但我选择在母版页中包含任何常见的 js 文件,然后为特定于特定或少量视图的一些附加 js 文件定义单独的 ContentPlaceHolder。

这是一个示例母版页 - 它非常易于解释。

<%@ Master Language="C#" Inherits="System.Web.Mvc.ViewMasterPage" %>
<head runat="server">
    ... BLAH ...
    <asp:ContentPlaceHolder ID="AdditionalHead" runat="server" />
    ... BLAH ...
    <%= Html.CSSBlock("/styles/site.css") %>
    <%= Html.CSSBlock("/styles/ie6.css", 6) %>
    <%= Html.CSSBlock("/styles/ie7.css", 7) %>
    <asp:ContentPlaceHolder ID="AdditionalCSS" runat="server" />
</head>
<body>
    ... BLAH ...
    <%= Html.JSBlock("/scripts/jquery-1.3.2.js", "/scripts/jquery-1.3.2.min.js") %>
    <%= Html.JSBlock("/scripts/global.js", "/scripts/global.min.js") %>
    <asp:ContentPlaceHolder ID="AdditionalJS" runat="server" />
</body>

Html.CSSBlock 和 Html.JSBlock 显然是我自己的扩展,但同样,它们的作用是不言自明的。

然后说我会有一个 SignUp.aspx 视图

<asp:Content ID="signUpContent" ContentPlaceHolderID="AdditionalJS" runat="server">
    <%= Html.JSBlock("/scripts/pages/account.signup.js", "/scripts/pages/account.signup.min.js") %>
</asp:Content>

HTH,查尔斯

附言。这是我询问的有关缩小和连接 js 文件的后续问题: Concatenate & Minify JS on the fly OR at build time - ASP.NET MVC

编辑:根据我的其他答案的要求,我按要求实现 .JSBlock(a, b)

public static MvcHtmlString JSBlock(this HtmlHelper html, string fileName)
{
    return html.JSBlock(fileName, string.Empty);
}

public static MvcHtmlString JSBlock(this HtmlHelper html, string fileName, string releaseFileName)
{
    if (string.IsNullOrEmpty(fileName))
        throw new ArgumentNullException("fileName");

    string jsTag = string.Format("<script type=\"text/javascript\" src=\"{0}\"></script>",
                                 html.MEDebugReleaseString(fileName, releaseFileName));

    return MvcHtmlString.Create(jsTag);
}

然后魔法发生的地方......

    public static MvcHtmlString MEDebugReleaseString(this HtmlHelper html, string debugString, string releaseString)
    {
        string toReturn = debugString;
#if DEBUG
#else
        if (!string.IsNullOrEmpty(releaseString))
            toReturn = releaseString;
#endif
        return MvcHtmlString.Create(toReturn);
    }

【讨论】:

  • 感谢您的详细解释。昨晚我正在考虑覆盖基本页面并添加一个 Scripts/Css 集合属性,然后由一些 htmlhelpers 填充。然后该页面会将它们吐出就像您的内容占位符一样。这会奏效还是你会反对它?
  • 对不起,我没有理解你的意思。 “覆盖基本页面” - 您的意思是覆盖 System.Web.Mvc.Controller 或 System.Web.Mvc.ViewPage 还是只是创建自己的自定义视图数据模型? “由一些 htmlhelpers 填充”这也让我感到震惊......如果您正在使用自定义 HtmlHelpers(从视图内部),那么只需使用上面提到的 ContentPlaceHolder 方法。如果没有更多信息,我建议您直接按照上面的示例进行操作......即使有更多信息,我可能仍然建议您执行上述操作。 ;-D
  • 我会看看我是否可以在一天结束之前制作一个有效的示例并将其发布在这里作为答案,如果有效,我想征求意见。我会覆盖 ViewPage 并添加一个名为 Scripts 的属性,它是一个集合。在局部视图中,您将调用 htmlhelper 来添加脚本 Html.JSBlock("path/to/script.js" new{some options})。此帮助程序将脚本添加到父页面的 Scripts 集合中。在父页面的视图中,在 headplaceholder 内,它调用另一个 htmlhelper,该 htmlhelper 呈现来自 Scripts 属性的脚本标签。还是那是糟糕的设计?
  • 回答我自己的评论,是的,这是糟糕的设计。我不知道,直到知道但 html 助手应该只渲染 html。他们不应该进行数据访问
【解决方案2】:

您将脚本放在页面底部的原因是确保在尝试任何操作之前已加载 dom。这也可以通过类似 $(document).ready(callback); 的方式实现。 jQuery 方法。

我同意不将嵌入式 JavaScript 放入 html 的观点。相反,我使用 Html 助手来创建一个空 div 以用作服务器指令。 helper sig 是 Html.ServerData(String name, Object data);

我使用这个 ServerData 方法从服务器到客户端的指令。 div标签保持干净,“data-”属性是有效的html5。

对于 loadScript 指令,我可以在我的 ascx 或 aspx 中执行类似的操作:

或者添加另一个看起来更干净的助手来执行此操作:



html 输出将是:

然后我有一个可以找到任何服务器数据标签的 jQuery 方法: $(包含元素).serverData("loadScript"); // 返回解码后的 json 对象的类似 jQuery 的数组。

客户端可能看起来像这样:

var script = $(containingelement").serverData("loadScript"); $.getScript(script.url, function () { // 脚本已经加载 - 现在可以用它做事了 });

这种技术非常适合需要在 ajax 加载的内容中加载的用户控件或脚本。我写的版本更多地涉及处理缓存脚本,因此它们在每次完整页面加载时仅加载一次,并包含回调和 jQuery 触发器,因此您可以在它准备好时挂钩它。

如果有人对此的完整版本(从 MVC 到 jQuery 扩展)感兴趣,我很乐意更详细地展示这项技术。否则 - 希望它能给人们一种解决这个棘手问题的新方法。

【讨论】:

  • 伙计,我很想和你一起喝一杯,然后讨论这个问题:) 你的想法看起来很不错。它不会将数据(如脚本路径)保留在代码隐藏中,但会将它们保留在等待由 jquery 加载的 html 中。您改进了 getScript 方法这一事实只会加载相同的脚本一次,您甚至可以挂接一个缩小器!蛋糕上的樱桃是让加载脚本的 jquery 代码更透明一点,这样它就会呈现自己 ;-) 太糟糕了,我们还不能真正使用 html5 数据集,请参阅tinyurl.com/2vfeunh
  • 谢谢,我同意。我使用类似 $.scriptLoaded 的方法,它在客户端上用作回调。它还需要加载一个可选的依赖项数组。该脚本对样式表的工作方式相同。 serverData 技术不仅可以用于加载脚本,还可以用于向客户端发送的任何服务器指令。我将它用于基本的插件初始化。检索服务器数据的客户端方法在检索后从 dom 中删除该元素以保持其清洁。
  • 将它们放在页面底部的原因并不是为了确保 dom 已经加载。原因是因为它们阻止并行下载,正如雅虎指南中明确指出的那样。将脚本放在页面底部也不能确保 dom 已正确加载。
【解决方案3】:

今天,我创建了自己的解决方案,完全符合要求。设计好不好,大家自己决定,但觉得我应该分享一下!

下面是我的 HtmlExtensions 类,它允许您在母版页中执行此操作:

<%=Html.RenderJScripts() %>

我的 HtmlExtensions 类:

public static class HtmlExtensions
{
    private const string JSCRIPT_VIEWDATA = "__js";

    #region Javascript Inclusions

    public static void JScript(this HtmlHelper html, string scriptLocation)
    {
        html.JScript(scriptLocation, string.Empty);
    }

    public static void JScript(this HtmlHelper html, string scriptLocationDebug, string scriptLocationRelease)
    {
        if (string.IsNullOrEmpty(scriptLocationDebug))
            throw new ArgumentNullException("fileName");

        string jsTag = "<script type=\"text/javascript\" src=\"{0}\"></script>";

#if DEBUG
        jsTag = string.Format(jsTag, scriptLocationDebug);
#else
        jsTag = string.Format(jsTag, !string.IsNullOrEmpty(scriptLocationRelease) ? scriptLocationRelease : scriptLocationDebug);
#endif

        registerJScript(html, jsTag);
    }

    public static MvcHtmlString RenderJScripts(this HtmlHelper html)
    {
        List<string> jscripts = html.ViewContext.TempData[JSCRIPT_VIEWDATA] as List<string>;
        string result = string.Empty; ;
        if(jscripts != null)
        {
            result = string.Join("\r\n", jscripts);
        }
        return MvcHtmlString.Create(result);
    }

    private static void registerJScript(HtmlHelper html, string jsTag)
    {
        List<string> jscripts = html.ViewContext.TempData[JSCRIPT_VIEWDATA] as List<string>;
        if(jscripts == null) jscripts = new List<string>();

        if(!jscripts.Contains(jsTag))
            jscripts.Add(jsTag);

        html.ViewContext.TempData[JSCRIPT_VIEWDATA] = jscripts;
    }

    #endregion

}

发生了什么?
上面的类将扩展 HtmlHelper 方法,将 JavaScript 链接添加到由 HtmlHelper.ViewContext.TempData 集合存储的集合。在母版页的末尾,我将 &lt;%=Html.RenderJScripts() %&gt; 放在了 HtmlHelper.ViewContext.TempData 内循环 jscripts 集合并将它们呈现到输出。

但是有一个缺点,.. 您必须确保在添加脚本之前不渲染脚本。例如,如果您想对 css 链接执行此操作,则它不会起作用,因为您必须将它们放在页面的 &lt;head&gt; 标记中,并且 htmlhelper 会在您添加链接之前呈现输出。

【讨论】:

  • tempdata 是否适合此操作?我 tempdata 是一个存储数据的地方,可以在重定向后幸存下来。我认为您在任何后续页面请求中都不需要此数据
  • 好点,不是。真的很好奇什么地方会是个好地方。
  • 看起来这是最好的选择,因为得票最多的答案在部分视图中不起作用(asp:content is not allowed in partial views -> 内容控件必须是顶级的内容页或引用母版页的嵌套母版页中的控件。)我仍然希望将它们放在页眉中,但我们需要一个预注册机制,如可怕的 ajax 脚本管理器。
【解决方案4】:

这似乎是一个类似(尽管不完全)的问题。 Is it bad practice to return partial views that contain javascript?

【讨论】:

  • 我已经阅读了整篇文章,但它没有回答我的问题。将它们全部放在一个 js 文件中将是一场性能噩梦,因为即使不使用它们也会加载多个脚本(或插件,如果你愿意的话)。
【解决方案5】:

我的偏好是创建一个继承自您的主 Site.master 的 plugin.master 页面。这个想法是你把这些插件塞进plugin.master,然后制作8个左右的页面,这些页面将使用这个部分视图从plugin.master继承。

【讨论】:

  • 我感觉到了一场管理噩梦。每次我决定删除或添加插件到视图时,我都需要更改母版页吗?
  • 那么,你让对象继承听起来像是一件可怕的事情。对整个站点仅使用一个 MasterPage 的幼稚方法实际上是对 MasterPages 的功能和意图的利用不足。一个平均复杂的网站通常有多达 4 个母版页,这是有充分理由的,其中一个就是您当前的需求。必须有另一个原因为什么这听起来对你来说是个问题。顺便说一句,“每次我需要......”这句话显然强调了你对这个建议的误解。尝试为您的网站制作原型,这样更容易将内容组合在一起。
【解决方案6】:

首选方法是put scripts at the bottom,但是,如果您无法避免,那么将它们放在中间是合理的。

浏览器通常会并行加载各种页面元素,但是,当浏览器下载 Javascript 文件时,它不会并行下载任何其他页面元素,直到 Javascript 完成下载。这意味着您的图像和其他图像将不得不等待,因此通常认为将脚本移动到底部会更好,但如果您的脚本很小,那么我不会担心。

【讨论】:

    【解决方案7】:

    hejdig.

    在使用 Aspnetmvc3 时,我决定暂时将我的 javascript 文件包含在部分中

    然后这个 js 文件是特定于我的 /user/List.schtml 文件的。

    /OF

    【讨论】:

      【解决方案8】:

      所以,这是一个老问题,但我在尝试解决最近的一个问题时发现了它。我问了一个问题并回答了here。这是该答案的副本。它类似于 OP 的答案,但避免使用 TempData。

      一种解决方案是实现一个 Html 辅助扩展函数,该函数只会加载一次脚本。见下文。 (这也适用于 CSS 和其他包含的文件)。

      实现 HtmlHelper 类的扩展和私有支持类作为每个 HttpContext 的单例。

      public static class YourHtmlHelperExtensionClass 
      {
          private class TagSrcAttrTracker
          {
              private TagSrcAttrTracker() { }
      
              public Dictionary<string, string> sources { get; } = new Dictionary<string, string>();
      
              public static TagSrcAttrTrackerInstance {
                  get {
                      IDictionary items = HttpContext.Current.Items;
                      const string instanceName = "YourClassInstanceNameMakeSureItIsUniqueInThisDictionary";
      
                      if(!items.Contains(instanceName)) 
                          items[instanceName] = new TagSrcAttrTracker();
      
                      return items[instanceName] as TagSrcAttrTracker;
                  }
              }
          }
      
          public static MvcHtmlString IncludeScriptOnlyOnce(this HtmlHelper helper, string urlOfScript) 
          {
              if(TagSrcAttrTracker.Instance.sources.ContainsKey(urlOfScript))
                  return null;
      
              TagSrcAttrTracker.Instance.sources[urlOfScript] = urlOfScript;
      
              TagBuilder script = new TagBuilder("script");
              scriptTag.MergeAttribute("src", urlOfScript);
      
              return MvcHtmlString.Create(script.ToString());
          }
      }
      

      然后,将 JavaScript 和其他代码分成单独的文件。

      .js 文件内容示例

      class MyClass{
          myFunction() {
              constructor(divId){
                  this._divId = divId
              }
      
              doSomething() {
                  // do something with a div with id == divId
              }
          }
      }
      

      部分视图的示例 .cshtml 文件内容

      <link rel="stylesheet" type="text/css" href="~/Content/CustomCSS/MyPartialView.css"/>
      
      <div id="@ViewBag.id">
          Some content!  Yay!
      </div>
      
      @Html.IncludeScriptOnlyOnce("/Scripts/CustomScripts/MyPartialView.js")
      

      使用局部视图的示例 .cshtml 文件

      ...
      <body>
          <h1>Here is some content!</h1>
          @Html.Partial("/Views/MyPartial.cshtml", new ViewDataDictionary() { { "id", "id_1"} })
          @Html.Partial("/Views/MyPartial.cshtml", new ViewDataDictionary() { { "id", "id_2"} })
          @Html.Partial("/Views/MyPartial.cshtml", new ViewDataDictionary() { { "id", "id_3"} })
      </body>
      <script>
      $().ready(
          const id1Functionality = new MyClass("id_1") // forgive the poor naming :-)
          const id2Functionality = new MyClass("id_3")
          const id3Functionality = new MyClass("id_2")
      
          id1Functionality.doSomething();
          id2Functionality.doSomething();
          id3Functionality.doSomething();
      )
      </script>
      

      分部视图可能被多次包含,并且 JavaScript 与分部视图一起打包,但 .js 文件只包含在页面中一次,因此浏览器不会抱怨 MyClass 被多次声明。

      【讨论】:

        【解决方案9】:

        MVC的设计者费了好大劲才阻止我们使用code-behinds,但是通过创建一个名为.aspx.cs的文件并相应地修改aspx页面的inherits属性,还是可以的得到它们。

        我会说,放置包含的最佳位置是代码隐藏中的 Page_Load 处理程序(使用 Page.ClientScript.RegisterClientScriptInclude)。

        【讨论】:

        • 感谢您的建议,但我真的想坚持这种模式,而不是使用代码隐藏。肯定有其他方法可以做到这一点吗?
        • 为什么我被否决了?我想我已经明确表示我知道这违反了规则,但我可以证明我这样做的原因。如果您知道自己在做什么,就可以打破规则。
        • 你不能从.aspx文件中调用RegisterClientScriptInclude吗?还是必须在PageLoad 期间调用它?
        • 当然可以,但我们谈论的是 ASP.NET MVC,它不适用于页面,但适用于控制器。
        猜你喜欢
        • 1970-01-01
        • 2011-06-10
        • 2011-01-26
        • 2014-11-12
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-01-03
        • 1970-01-01
        相关资源
        最近更新 更多