I'm trying to move all of my javascript includes to the bottom of the page. I'm using MVC with Razor. I've written a helper method to register scripts. It keeps the scripts in the order they're registered and it excludes dupes.
@{ Html.RegisterScript("/scripts/someFile.js"); }
And then right before the closing body tag I do something like this:
@Html.RenderScripts()
</body>
This all works fine if the scripts are registered all on one page. If I register some scripts in my layout, and some scripts in my inner view, things go awry. Basically, the inner view is executing before the layout. So event though the scripts in the inner view depend on the scripts in the outer view, they're being registered prior.
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 get rendered out in this order: script3, script1, script 2.
Is there any way I can force the layout to execute before the inner view? I've tried moving things all over the place and everything the inner view always seems to execute first. Perhaps this is expected? If so, what is the best way for me to accomplish what I'm trying to do?
There is no way to change the order that MVC renders the pages (as far as I know) but what you can try to do is update your render script functionality to denote when a layout is having its scripts registered. So for instance your layout may be:
This would trigger to your system that the scripts registered after
StartLayoutScriptRegistration()
is called should be rendered first, then followed by your view's script registrations registered prior to the 'StartLayoutScriptRegistration()' being called..Code placed in @section parts always get executed in your expected order, so you could make use of that fact:
In your _Master.cshtml write:
And in your InnerView.cshtml:
This will lead to your desired include order: script1, script2, script3
Notes: Since the "references" section will only consists of one code block and no real Html output, you could place the @RenderSection part anywhere in _Master.cshtml. This will work with nested layouts too!
No, that's the order that they execute, from inner-most to outer-most. I would recommend using some kind of LIFO (last in first out) collection like
Stack<T>
to hold the scripts in and then pop them off the stack to render them out; this way, the scripts added last i.e. the ones in the layout, will be the ones that are rendered first.EDIT
I wrote a nuget package that handles rendering scripts from Partial views and Templates that solves this problem.
First, create 2 HTML helpers. One for adding scripts by priority:
And one for writing those scripts to your view:
Then add the scripts to your partial views:
And finally, render the scripts in your Razor template:
The results will be sorted by priority and rendered inside script tags:
I ended up solving this problem by keeping track of the scripts per view. Basically I keep a
and a
Every time I register a script I pass in the script path and the view name (retrieved from ViewDataContainer.ToString()). I keep track of the order in which I've seen the views in the viewOrder object. I then use the dictionary to keep track of the scripts per view. The key is the view name. When it comes time to render everything out, I reverse the viewOrder SortedList<> and write out all the scripts by view. The nice bit is, the scripts are in order by view, and the view order once reversed is correct.
I did a horrible job explaining that, but for copyright reasons I cannot share the code.
As an accurate solution for script depedencies, you can provide some helper methods to:
Specify dependencies
Specify relative priority:
This way you have the control needed in most situations.