Automatic URL translation for side scripts?

May 29, 2013 at 7:05 PM
We have started to move embedded Javascript out of our .cshtml files in views into separate .js files that live right next to the view files in the Views directory (and modified our Web.config file for view directories to allow only .js and .css files to be served). We do this for two reasons, 1) it makes debugging with VS a whole lot easier since you can debug the separate JS files really easily, and 2) the JS code can then be aggressively cached in the browser, so overall response is quicker after the first page load for dynamic pages where the content is constantly changing.

Anyway, T4MVC already works nicely and detects the files so the links are already there. But the catch is you cannot reference them easily without inelegant code. Normally the hard coded method would be like this, in ASP.NET MVC 4:
<script src="~/Views/Account/LogOn.js" type="text/javascript"></script>
So if we use T4MVC I would love to be able to write this:
<script src="@MVC.Account.Views.LogOn_js" type="text/javascript"></script>
But alas that does not work because it leaves the ~/ in the output HTML files as Razor only automatically converts static strings in ASP.NET MVC4 at compile time. So you end up having to write this:
<script src="@Url.Content(MVC.Account.Views.LogOn_js)" type="text/javascript"></script>
This works, but it a bit ugly. So I think we could implement one of two solutions. The first one would be to recognize the .js files and know the links need to be converted into absolute URL's, so Url.Content could be embedded into the LogOn_js property? Or another option might be something like this:
@MVC.Account.Views.LogOn.Javascript()
which would not only handle the Url.Content() calls, but also spit out the entire script link? Then the code is nice and clean, and we have less to write to link to a local Javascript file?

We would also want to implement a sister function for any embedded CSS for the view as well. It is not as useful to put CSS into a separate file next to the view, but if you have a page that need a lot of specific CSS not in your common styles, it might make sense also.

I plan to experiment in my tree with implementing both of these, but do you have any preference on what would be a better/cleaner solution?
May 29, 2013 at 7:38 PM
Ok, so I have something basic working. Having the code generate the actual script link I don't think is a good idea, because you need to then call @Html.Raw() to generate the output, which in it's own right is kind of ugly anyway. So I am now stuck between the following two options:
<script src="@MVC.Account.Javascript.LogOn" type="text/javascript"></script>
<script src="@MVC.Account.Views.Javascript.LogOn" type="text/javascript"></script>
The first is quicker to type, the second might be a little clearer that the Javascript should be located in the Views directory? Any suggestions on what you think is cleaner?
May 29, 2013 at 7:48 PM
Or perhaps this is cleaner?
<script src="@MVC.Account.Views.LogOn.js" type="text/javascript"></script>
May 29, 2013 at 7:53 PM
Crap, the last one cannot be done because LogOn is already a class member as a variable.
Coordinator
May 29, 2013 at 8:00 PM
Related to this issue. At the root, the issue is that both the ~/ and the full path are valid, depending on the scenario. ~/ is for server side use, while other of for direct client consumption.

Note that there is already a way you can modify this behavior. In your T4MVC.tt.hooks.t4 file, look for T4MVCHelpers.ProcessVirtualPath. You can replace this by your own routine which can transform the path in other ways.
Coordinator
May 29, 2013 at 8:02 PM
Actually wait, what I mentioned above relates to how static files are handled, and you are dealing with Views. Could you simply move your script files into the Script folder? This would give you constants under the 'Links' folder that don't have the ~/. See section 2.4 in the doc.
May 29, 2013 at 8:55 PM
No, we cannot move them into the global /Scripts directory, because they are specific to a particular view. So they are named identical to the view itself, to go alongside it. So we would have /Views/Account/Index.chtml and /Views/Account/Index.js etc. So if we had them in the global scripts directory we would end up with a zillion naming conflicts.

I have implemented my first solution in my T4 template, but the way I needed to hook it in, it was not possible to do it in the hooks file. I will put it into my git tree and send you a pull request so you can check it out.
May 29, 2013 at 9:16 PM
Ok, pull request sent. I hope I did it right :) No tabs this time!
Coordinator
May 29, 2013 at 9:28 PM
Another thing you should try: go to your T4MVC.tt.settings.xml, and add Views to the list of StaticFilesFolders. You'll then be able to write Links.Views.Account.Logon_js, or something like that.
May 29, 2013 at 9:41 PM
That would be an interesting option. The downside is it will not handle areas automatically, while the patch I created which sticks it into the controller for the view should correctly handle areas (we have a lot of areas in our project :) ). Manually adding each area to the config file would work, but it would be cumbersome?
Coordinator
May 29, 2013 at 11:21 PM
Pull request is here
May 30, 2013 at 5:34 PM
I changed my implementation slightly as I did not like using Controller.Views.JS.Blah and changed it to Controller.JS.Blah. So now you would use it like this:
<script src="@MVC.Account.JS.LogOn" type="text/javascript"></script>
I think that makes more sense because you are really saying that you want to link to the local Javascript file for the account controller. It just so happens that by convention we place them next to the view files in the views directory. I can't see the need to change where they live, but to me this is cleaner. And less typing.
May 30, 2013 at 5:34 PM
New pull request is here
Coordinator
May 30, 2013 at 6:23 PM
My takes is that this has too much overlap with the existing StaticFilesFolders feature. Potentially, there could be a new flag that automatically adds all the right set of Views folders from all areas (e.g. AddAllViewsFoldersToStaticFilesFolders). This way, it just makes use of the existing feature rather than creating a new one.

There is nothing wrong with your change, but my concern is feature creep, which to some extend T4MVC already suffers from :)
May 30, 2013 at 7:33 PM
Somehow making it work like the static files would be fine with me also, but we would need a way to automatically hunt down static files in all the Views and Areas\Views folders. If it was implemented that way, I would be fine changing my code over to using that. That seems like a lot more work for me in the T4MVC template, so maybe that is something you could add? It would probably be something to automatically add all view directories found to the static files directory, and making sure they end up in some kind of hierarchy also?
Coordinator
May 30, 2013 at 9:38 PM
I don't think it would be a big change. It's simply a matter of looping through the Areas (which we already track), and adding one entry for each to the StaticFilesFolders list (based on some new flag). So there wouldn't be any changes to the actual code generation.
Coordinator
May 30, 2013 at 10:51 PM
I pushed a basic change to a ViewLinks branch. Can you give it a try? You just need to set AddAllViewsFoldersToStaticFilesFolders to true in T4MVC.tt.settings.xml.
May 31, 2013 at 12:55 AM
I will give it a try.
May 31, 2013 at 1:00 AM
For some reason he ViewLinks branch is based on 3.5.4, not 3.6.5? I will just apply the diffs against my 3.6.5 file and try that.
May 31, 2013 at 1:48 AM
Edited May 31, 2013 at 1:48 AM
Seems to work OK. It is a bit more long winded to type in, especially for areas:
<script src="@MVC.Configuration.Countries.JS.Index" type="text/javascript"></script>
<script src="@Links.Areas.Configuration.Views.Countries.Index_js" type="text/javascript"></script>
But the new way reflects directly the file system structure used to find the files, so I guess that is reasonable. If you want to push this change into 3.6.6 I will be happy to use this mechanism :)
May 31, 2013 at 1:58 AM
It would be nice to avoid including the .js and .css files in the Views links for a controller, and also to avoid the .cshtml files showing up in the static files for a view also? Not entirely necessary, but they do add extra code to run at init time when the values will never be used?
Coordinator
May 31, 2013 at 2:19 AM
Oops, being based on 3.5.4 for not intentional. I just rebased it over the latest.

Agreed, adding a little bit of filtering might make sense here. Not sure there is much startup cost, but it does pollute the intellisense a bit.
May 31, 2013 at 3:11 AM
Sounds good. Let me know if you have something you need me to test. The filtering would be nice since it is doubtful anybody would ever use the ones you would filter out.
Coordinator
Jun 6, 2013 at 7:48 AM
I pushed some more changes to that same ViewLinks branch. Could you give it a try? You may need to
  • open your T4MVC.tt.settings.xml
  • delete the ExcludedStaticFileExtensions section
  • rerun the template so it regenerates
  • you'll also see a new ExcludedViewExtensions section pop up in there.
Jun 11, 2013 at 7:20 PM
Ok great! I will take a look at this today.
Jun 17, 2013 at 1:02 AM
Works nicely, thanks! Do you think this can make it into the mainline tree?
Coordinator
Jun 17, 2013 at 2:32 AM
Ok, pushed as 3.7.0