Can I use Razor syntax inside Javascript included in a ScriptBundle?

3.1k views Asked by At

I have a control that features code similar to the following:

Javascript

$("#Postcode").autocomplete({
    source: '@(Url.Action("AutocompleteHelper"))'
});

HTML

@Html.EditorFor(model => model.Postcode)


It used to all sit directly inside a .cshtml file and Url.Action generated a working URL as expected.

Recently I needed to use this in a number of views, so I moved the JS into a separate .js file (wrapped into a ScriptBundle and included in the parent view via @Scripts.Render) and the corresponding HTML is now rendered as a partial view.

A side-effect of this is that the Razor transformation no longer occurs. I'd prefer not to replace Url.Action with a hardcoded /<Controller>/AutocompleteHelper string, so is there any other way I can dynamically generate and set this value? I could move the Javascript out of the bundle and into the partial view, but the consensus seems to be that you shouldn't have JS in a partial view.

5

There are 5 answers

2
Patrick Hofman On

No, you can't. Javascript files aren't handled by the Razor engine.

You can create a cshtml 'View' and put the code in there and include it using this code in your page, like I think you did (you don't need an action for this):

@Html.RenderPartial("_JsFileName");

This way, it will pass the Razor engine and your are set. Unfortunately you can't include it in a bundle then.

Another option is to declare only that variable in the page, and use that inside the javascript that remains 'as is'.

0
iCollect.it Ltd On

No, Razor does no substitution on bundled JS files. Self-modifying code is usually a bad thing, so I would recommend never injecting Javascript in a page if you can help it.

Just inject values into data- attribute on the required element and pick those up:

e.g.

@Html.EditorFor(model => model.Postcode, new {htmlAttributes = new {@class="postcode", data_source = Url.Action("AutocompleteHelper")}})

and use with the class only like this:

$(".postcode").each(function(){
    $(this).autocomplete({
         source: $(this).data('source')
    });
});

This allows a single piece of code to handle multiple postcode controls.

0
Simon Ryan On

Here is the approach I take to get mvc paths into my javascript files...

In your .js file have an Initialise function (you can name it what you want). Pass the route into the Initialise function and then have the initialise function assign it to a variable that is accessible by the other javascript in your js file.

In your view...

<script>
    Initialise(@(Url.Action("AutocompleteHelper")))
</script>
@Scripts.Render("~/bundles/yourscriptbundle")

In your .js...

var autoCompleteRoute;
function Initialise(route) {
  autoCompleteRoute = route;
}

the route is then available to be used in the .js file. I prefer this approach as the variable is declared and assigned it's value in the .js file where it is to be used. Using a setup/initialise function also makes it clear that there are things that need to be done before the javascript can be successfully run.

0
Chris Pratt On

@PatrickHofman is correct. I would actually follow his second piece of advice, though:

Another option is to declare only that variable in the page, and use that inside the javascript that remains 'as is'.

However, he did not go into detail on that, so I would like to follow up with a little extra advice. What you're essentially going to be doing is adding a variable to the global scope so that any external JavaScript has access to it. Adding things to global scope is dangerous, though, so you should implement a unique namespace for your application and add any variables you need to that namespace instead of directly in the global scope. This lessens the likelihood of namespace collisions. So in your view, you would add something like the following:

<script>
    var MyAwesomeApp = MyAwesomeApp || {};
    MyAwesomeApp.SomeVariable = '@Model.SomeVariable';
</script>
@Scripts.Render("~/bundles/yourscriptbundle")

And then you can access the variable you need in your external JS included in your bundle via MyAwesomeApp.SomeVariable.

0
Daniel Gabriel On

What I normally do is what @SimonRyan suggested, except I use an options object instead of passing just one string. Chances are if you need one, you will need another soon. So with an options object you have to modify less then when adding more parameters.

So the Initalise call would look like this:

Initialise({
    autoCompleteHelperUrl: '@(Url.Action("AutocompleteHelper"))',
    anAdditionalUrl: '@(Url.Action("AdditonalUrl"))'
})

I wrote a more detailed blog post about it here: http://blog.blanklabs.com/2015/02/aspnet-mvc-refactoring-friendly.html