MVC 在布局代码之前执行视图代码并破坏我的脚本顺序

Posted

技术标签:

【中文标题】MVC 在布局代码之前执行视图代码并破坏我的脚本顺序【英文标题】:MVC executing view code before layout code and ruining my script order 【发布时间】: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。

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

【问题讨论】:

【参考方案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 中的任何位置。这也适用于嵌套布局!

【讨论】:

以上是关于MVC 在布局代码之前执行视图代码并破坏我的脚本顺序的主要内容,如果未能解决你的问题,请参考以下文章

下拉列表的 jQuery 在布局 ASP.NET MVC 5 中不起作用

核心位置最佳布局和用户中断

fieldset 在 angularjs-material 中破坏了我的 flex 布局

Jmeter 从文件运行 SQL 脚本

网站就必须用响应式布局吗?MVC视图展现模式之移动布局

iOS 从超级视图中删除对象会破坏自动布局并禁用滚动视图