【问题标题】:MVC executing view code before layout code and ruining my script orderMVC 在布局代码之前执行视图代码并破坏我的脚本顺序
【发布时间】:2011-12-22 04:42:44
【问题描述】:

我正在尝试将我所有的 javascript 包含移动到页面底部。我将 MVC 与 Razor 一起使用。我写了一个辅助方法来注册脚本。它按照它们注册的顺序保存脚本,并且不包括欺骗。

@{ Html.RegisterScript("/scripts/someFile.js"); }

然后在结束正文标记之前,我做了这样的事情:

    @Html.RenderScripts()
</body>

如果脚本全部注册在一个页面上,这一切都可以正常工作。如果我在布局中注册一些脚本,在我的内部视图中注册一些脚本,事情就会出错。基本上,内部视图在布局之前执行。所以事件虽然内部视图中的脚本依赖于外部视图中的脚本,但它们是事先注册的。

所以如果我有 _Master.cshstml 并且它有

@{
   Html.RegisterScript("script1.js");
   Html.RegisterScript("script2.js");
}

然后我有 InnerView.cshtml 和 _Master.cshtml 作为它的布局,在我有的视图中

@{
   Html.RegisterScript("script3.js");
}

脚本按以下顺序呈现:script3、script1、script 2。

有什么办法可以强制布局在内部视图之前执行?我已经尝试过在整个地方移动东西,并且内部视图似乎总是首先执行。也许这是意料之中的?如果是这样,对我来说完成我想做的事情的最佳方式是什么?

【问题讨论】:

    标签: c# javascript html asp.net-mvc-3 razor


    【解决方案1】:

    不,这是他们执行的顺序,从最里面到最外面。我建议使用某种 LIFO(后进先出)集合,例如 Stack&lt;T&gt; 来保存脚本,然后将它们从堆栈中弹出以呈现它们;这样,最后添加的脚本,即布局中的脚本,将是最先呈现的脚本。

    编辑

    我写了 a nuget package that handles rendering scripts from Partial views and Templates 来解决这个问题。

    【讨论】:

    • 我考虑过这一点,但是对于开发人员来说,使用该方法以他们所依赖的相反顺序注册脚本很奇怪。例如,如果在同一页面上 script1 依赖于 script2,我必须以相反的顺序注册它们,所以这对我来说真的不起作用。
    • 可能是一个堆栈>。因此,视图将所有项目按顺序排列在列表中,然后调用 .Push(someListInTheNaturalOrder),因为它只需要在视图之间进行反转。我认为这基本上就是您在其他答案中所做的。
    【解决方案2】:

    我最终通过跟踪每个视图的脚本来解决这个问题。基本上我保持一个

    Dictionary<string, SortedList<int, string>> registeredScripts;
    

    还有一个

     SortedList<int, string> viewOrder;
    

    每次注册脚本时,我都会传入脚本路径和视图名称(从 ViewDataContainer.ToString() 中检索)。我跟踪在 viewOrder 对象中查看视图的顺序。然后我使用字典来跟踪每个视图的脚本。关键是视图名称。当需要渲染所有内容时,我反转 viewOrder SortedList 并按视图写出所有脚本。好的一点是,脚本是按视图排序的,并且一旦反转视图顺序是正确的。

    我做了很糟糕的解释,但出于版权原因,我不能分享代码。

    【讨论】:

      【解决方案3】:

      没有办法更改 MVC 呈现页面的顺序(据我所知),但您可以尝试更新您的呈现脚本功能,以指示布局何时注册其脚本。例如,您的布局可能是:

      @{
         Html.StartLayoutScriptRegistration();
         Html.RegisterScript("script1.js");
         Html.RegisterScript("script2.js");
      }
      

      这将触发您的系统,应该首先呈现在调用 StartLayoutScriptRegistration() 之后注册的脚本,然后是在调用 'StartLayoutScriptRegistration()' 之前注册的视图脚本注册..

      【讨论】:

      • 我喜欢这个......虽然我最终想出了一个不同的解决方案
      【解决方案4】:

      首先,创建 2 个 HTML 助手。一种按优先级添加脚本:

          public static MvcHtmlString AddScript(this HtmlHelper htmlHelper, string script, int executionSequence = int.MaxValue)
          {
              Dictionary<int, List<string>> scripts = new Dictionary<int, List<string>>();
              if (htmlHelper.ViewContext.HttpContext.Items["_scripts_"] != null)
              {
                  scripts = htmlHelper.ViewContext.HttpContext.Items["_scripts_"] as Dictionary<int, List<string>>;
              }
      
              if (!scripts.ContainsKey(executionSequence))
              {
                  scripts.Add(executionSequence, new List<string>());
              }
              string tag = string.Format("<script type=\"text/javascript\" src=\"{0}\"></script>", script);
              scripts[executionSequence].Add(tag);
              htmlHelper.ViewContext.HttpContext.Items["_scripts_"] = scripts;
              return MvcHtmlString.Empty;
          }
      

      还有一个用于将这些脚本写入您的视图:

          public static IHtmlString RenderScripts(this HtmlHelper htmlHelper)
          {
              StringBuilder sb = new StringBuilder();
              foreach (object key in htmlHelper.ViewContext.HttpContext.Items.Keys)
              {
                  if (key.ToString() == "_scripts_")
                  {
                      Dictionary<int, List<string>> scripts = htmlHelper.ViewContext.HttpContext.Items[key] as Dictionary<int, List<string>>;
                      foreach (int index in scripts.Keys.OrderBy(x => x))
                      {
                          foreach (string script in scripts[index])
                          {
                              if (script != null)
                              {
                                  sb.AppendLine(script);
                              }
                          }
                      }
                  }
              }
      
              return MvcHtmlString.Create(sb.ToString());
          }
      

      然后将脚本添加到您的局部视图中:

      @Html.AddScript("script2.js", 2)
      @Html.AddScript("script1.js", 1)
      

      最后,在 Razor 模板中渲染脚本:

      @Html.RenderScripts()
      

      结果将按优先级排序并呈现在脚本标签内:

      <script type="text/javascript" src="script1.js"></script>
      <script type="text/javascript" src="script2.js"></script>
      

      【讨论】:

        【解决方案5】:

        作为脚本依赖的准确解决方案,您可以提供一些帮助方法:

        指定依赖项

        Html.RegisterScript("script3.js", depsOn: new string[] {"script1.js", "script2.js"});
        

        指定相对优先级:

        Html.RegisterScript("script3.js", Position.Bottom);
        Html.RegisterScript("script1.js", Position.Top);
        Html.RegisterScript("script2.js", Position.Top);
        

        这样您就可以在大多数情况下获得所需的控制权。

        【讨论】:

          【解决方案6】:

          @section 部分中的代码总是按您预期的顺序执行,因此您可以利用这一事实:

          在你的_Master.cshtml中写:

          @{
             Html.RegisterScript("script1.js");
             Html.RegisterScript("script2.js");
          }
          
          @RenderSection("references", required: false)
          

          在你的 InnerView.cshtml 中:

          @section references {@{
                 Html.RegisterScript("script3.js");
          }}
          

          这将导致您想要的包含顺序:script1、script2、script3

          注意:由于“参考”部分仅包含一个代码块且没有真正的 Html 输出,因此您可以将 @RenderSection 部分放在 _Master.cshtml 中的任何位置。这也适用于嵌套布局!

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2021-04-01
            • 1970-01-01
            • 2018-10-02
            • 1970-01-01
            • 1970-01-01
            • 2015-07-21
            • 2016-12-25
            相关资源
            最近更新 更多