MVC executes the view code before the layout code and destroys my order script

I am trying to move all my javascript to the bottom of the page. I am using MVC with Razor. I wrote a helper method for registering scripts. It stores scripts in the order in which they are registered, and eliminates duplication.

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

And then right before the closing body tag, I do something like this:

  @Html.RenderScripts() </body> 

All this works fine if the scripts are registered on one page. If I register some scripts in my layout and some scripts in the internal representation, everything will go wrong. Basically, the internal view is performed before the layout. Thus, the event, although the scripts in the internal representation depend on the scripts in the external representation, they are registered earlier.

So, if I have _Master.cshstml and it has

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

And then I have InnerView.cshtml with _Master.cshtml as its layout, and in the view I have

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

The scripts are obtained in the following order: script3, script1, script 2.

Is there any way to get the layout to execute before the internal representation? I tried moving things everywhere, and everything that seems internal always seems to be the first. Perhaps this is expected? If so, is it best for me to accomplish what I'm trying to do?

+7
source share
6 answers

I decided to solve this problem by tracking scripts for each view. I mainly save

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

and a

  SortedList<int, string> viewOrder; 

Each time I register a script, I pass the script path and view name (retrieved from ViewDataContainer.ToString ()). I keep track of the order in which I saw the views in the viewOrder. Then I use the script tracking dictionary for each view. The key is the name of the view. When the time comes to render everything, I cancel the viewOrder SortedList <> and write all the scripts according to the view. A good bit is the scripts in order, and the viewing order, once the reverse, is correct.

I did a terrible job explaining this, but for copyright reasons I cannot share this code.

+2
source

No, that is the order that they carry out, from the most internal to the external. I would recommend using some kind of LIFO collection (the last in the first part), for example Stack<T> , to hold scripts and then push them out of the stack to take them out; thus, the scripts added last, that is, those that are in the layout, will be the ones that are displayed first.

EDIT

I wrote a nuget package that handles rendering scripts from partial views and templates that solve this problem.

+3
source

It is not possible to change the order in which MVC displays pages (as far as I know), but what you can try to do is update the script's rendering functionality to indicate when its scripts are registered in the layout. So, for example, your layout might be:

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

This will cause your system to output the scripts registered after StartLayoutScriptRegistration() , first, and then your script logs registered before calling "StartLayoutScriptRegistration ()".

+1
source

First create 2 HTML helpers. One for adding scripts by priority:

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

And one for writing these scripts in your opinion:

  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()); } 

Then add the scripts to the partial views:

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

Finally, visualize the scripts in the Razor template:

 @Html.RenderScripts() 

Results will be sorted by priority and displayed inside script tags:

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

As an exact solution for script dependencies, you can provide some helper methods for:

Specify Dependencies

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

Indicate relative priority:

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

Thus, you have the control needed in most situations.

0
source

The code located in the @section parts is always executed in the expected order, so you can take advantage of this fact:

In your _Master.cshtml write:

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

And in your InnerView.cshtml:

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

This will result in your desired order: script1, script2, script3

Notes: Since the β€œlinks” section will consist of only one block of code and without real Html output, you can place the @RenderSection part somewhere in _Master.cshtml. This will also work with nested layouts!

0
source

All Articles