470

Is it possible or is there a workaround to use Razor syntax within JavaScript that is in a view (cshtml)?

I am trying to add markers to a Google map... For example, I tried this, but I'm getting a ton of compilation errors:

<script type="text/javascript">

    // Some JavaScript code here to display map, etc.

    // Now add markers
    @foreach (var item in Model) {

        var markerlatLng = new google.maps.LatLng(@(Model.Latitude), @(Model.Longitude));
        var title = '@(Model.Title)';
        var description = '@(Model.Description)';
        var contentString = '<h3>' + title + '</h3>' + '<p>' + description + '</p>'

        var infowindow = new google.maps.InfoWindow({
            content: contentString
        });

        var marker = new google.maps.Marker({
            position: latLng,
            title: title,
            map: map,
            draggable: false
        });

        google.maps.event.addListener(marker, 'click', function () {
            infowindow.open(map, marker);
        });
    }
</script>
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
raklos
  • 28,027
  • 60
  • 183
  • 301
  • 3
    you may be interested in my update regarding `@:` syntax. – StriplingWarrior Feb 03 '11 at 20:14
  • @anyone considering doing it this way, at least put the data in a separate script tag that simply defines some JSON which is then used in the JS. Back-end-coupled JS on the front end has been a legacy PITA for me on a number of occasions. Don't write code with code if you don't have to. Hand off data instead. – Erik Reppen Apr 05 '17 at 20:40

13 Answers13

677

Use the <text> pseudo-element, as described here, to force the Razor compiler back into content mode:

<script type="text/javascript">

    // Some JavaScript code here to display map, etc.


    // Now add markers
    @foreach (var item in Model) {
        <text>
            var markerlatLng = new google.maps.LatLng(@(Model.Latitude), @(Model.Longitude));
            var title = '@(Model.Title)';
            var description = '@(Model.Description)';
            var contentString = '<h3>' + title + '</h3>' + '<p>' + description + '</p>'

            var infowindow = new google.maps.InfoWindow({
                content: contentString
            });

            var marker = new google.maps.Marker({
                position: latLng,
                title: title,
                map: map,
                draggable: false
            });

            google.maps.event.addListener(marker, 'click', function () {
                infowindow.open(map, marker);
            });
        </text>
    }
</script>

Update:

Scott Guthrie recently posted about @: syntax in Razor, which is slightly less clunky than the <text> tag if you just have one or two lines of JavaScript code to add. The following approach would probably be preferable, because it reduces the size of the generated HTML. (You could even move the addMarker function to a static, cached JavaScript file to further reduce the size):

<script type="text/javascript">

    // Some JavaScript code here to display map, etc.
    ...
    // Declare addMarker function
    function addMarker(latitude, longitude, title, description, map)
    {
        var latLng = new google.maps.LatLng(latitude, longitude);
        var contentString = '<h3>' + title + '</h3>' + '<p>' + description + '</p>';

        var infowindow = new google.maps.InfoWindow({
            content: contentString
        });

        var marker = new google.maps.Marker({
            position: latLng,
            title: title,
            map: map,
            draggable: false
        });

        google.maps.event.addListener(marker, 'click', function () {
            infowindow.open(map, marker);
        });
    }

    // Now add markers
    @foreach (var item in Model) {
        @:addMarker(@item.Latitude, @item.Longitude, '@item.Title', '@item.Description', map);
    }
</script>

Updated the above code to make the call to addMarker more correct.

To clarify, the @: forces Razor back into text mode, even though addMarker call looks a lot like C# code. Razor then picks up the @item.Property syntax to say that it should directly output the contents of those properties.

Update 2

It's worth noting that View code really isn't a good place to put JavaScript code. JavaScript code should be placed in a static .js file, and then it should get the data that it needs either from an Ajax call or by scanning data- attributes from the HTML. Besides making it possible to cache your JavaScript code, this also avoids issues with encoding, since Razor is designed to encode for HTML, but not JavaScript.

View Code

@foreach(var item in Model)
{
    <div data-marker="@Json.Encode(item)"></div>
}

JavaScript code

$('[data-marker]').each(function() {
    var markerData = $(this).data('marker');
    addMarker(markerData.Latitude, markerData.Longitude,
              markerData.Description, markerData.Title);
});
Community
  • 1
  • 1
StriplingWarrior
  • 151,543
  • 27
  • 246
  • 315
  • 3
    I don't understand your updated example. Is the addmarker function correct? – NVM Nov 14 '11 at 16:01
  • 2
    @NVM: Rather than outputting the same javascript code several times, I suggest creating a single javascript function (which can be kept in a cached .js file), and outputting several calls to that function. I have no idea whether the function is correct: I was just basing it on the OP's code. – StriplingWarrior Nov 14 '11 at 16:08
  • 1
    Why the '@Model.Latitude' in foreach. Why not item.Latitude? – NVM Nov 14 '11 at 16:10
  • Well why do you have @(Model.Latitude) etc inside the addmarker function. Trouble is I am trying to understand the syntax and I am more confused after looking at your example. – NVM Nov 14 '11 at 16:12
  • @NVM: Like I said, I just based it on the original post, which used `@Model.Latitude`. It should probably be `@item.Latitude`, so I'll change that in the answer. But the main point is that the `@:` syntax tells Razor to treat that line as text even though by Razor's normal rules it would stay in "code" mode. – StriplingWarrior Nov 14 '11 at 17:52
  • 5
    Your C# variables need to be escaped. If `@item.Title` contains a single quote this code will explode. – mpen Jan 29 '14 at 16:42
  • 8
    @Mark: Good observation. In fact, I typically don't combine javascript and Razor _at all_ in my own code: I prefer to use Razor to generate HTML with `data-` attributes, and then use static, unobtrusive javascript to glean this information from the DOM. But that whole discussion was sort of beyond the scope of the question. – StriplingWarrior Jan 29 '14 at 16:48
  • what if you have multiple line client code? should we use @: on each line? – Marco Alves Aug 28 '14 at 23:17
  • @MarcoAlves: The first part of my post shows how you can use the `` tag when necessary. The point of the second part is that it's usually a good idea to consolidate your multiple lines into a single method call. Honestly, I don't even write inline javascript code personally--I find that unobtrusive javascript techniques work best. The Javascript can reside in javascript files, and the DOM is used to communicate intent. – StriplingWarrior Aug 29 '14 at 01:42
  • @StriplingWarrior Thx. In that case, the main problem that I face is to have server access, you know. My code has a lot of business rules. Thus, sometimes it's difficult to write "js files" because of that. – Marco Alves Aug 29 '14 at 01:45
  • @MarcoAlves Business logic should not be written in Razor files--that should be separated out into another layer. Razor/HTML/Javascript should all be focused on presentation-layer logic. Razor helps translate your ViewModel into HTML, which Javascript should read and act on. If Javascript needs additional information from the server, it can also do an AJAX request and act on the results. There's no good reason to not have your Javascript in separate, static files. – StriplingWarrior Aug 29 '14 at 15:02
  • @StriplingWarrior I'm aware about separation of concerns. My app has lots of conditional business rules. Anyway, you've got my vote up. – Marco Alves Aug 29 '14 at 18:33
  • @StriplingWarrior it may be good idea to move warning about need to encode content into post directly. There are already several answers how to do it next to yours, so one line is probably enough. – Alexei Levenkov Apr 03 '15 at 05:03
  • @AlexeiLevenkov: That's a great suggestion. What do you think of my update? – StriplingWarrior Apr 03 '15 at 15:05
  • @StriplingWarrior I totally agree with your suggestion to not put strings into HTML, but I'd still mention how to properly write JavaScript in a view if one needs to do so (pointing to Mark's or SLacks' answer like `HTML.Raw(Server.JavaScriptStringEncode(...)`) – Alexei Levenkov Apr 03 '15 at 17:15
41

I just wrote this helper function. Put it in App_Code/JS.cshtml:

@using System.Web.Script.Serialization
@helper Encode(object obj)
{
    @(new HtmlString(new JavaScriptSerializer().Serialize(obj)));
}

Then in your example, you can do something like this:

var title = @JS.Encode(Model.Title);

Notice how I don't put quotes around it. If the title already contains quotes, it won't explode. Seems to handle dictionaries and anonymous objects nicely too!

Community
  • 1
  • 1
mpen
  • 272,448
  • 266
  • 850
  • 1,236
  • 20
    If you are trying to encode an object in the view there is no need for creating a helper code, one exists already. We use this all the time `@Html.Raw(Json.Encode(Model))` – PJH Jan 29 '14 at 14:47
  • 2
    Can you expand on your answer PJH? How do you specify the title if you're just encoding "Model"? – obesechicken13 Apr 01 '15 at 16:33
  • 2
    Also, when I tried this approach with Model.Title I get some extra quotes around the encoded javascript. I can't get rid of the quotes even if I concatenate it to something else. Those quotes become part of your js. – obesechicken13 Apr 01 '15 at 16:42
  • 1
    PJH's comment is superb. In a way you deserialize server side models into the javascript block. – netfed Apr 12 '17 at 18:46
24

You're trying to jam a square peg in a round hole.

Razor was intended as an HTML-generating template language. You may very well get it to generate JavaScript code, but it wasn't designed for that.

For instance: What if Model.Title contains an apostrophe? That would break your JavaScript code, and Razor won't escape it correctly by default.

It would probably be more appropriate to use a String generator in a helper function. There will likely be fewer unintended consequences of that approach.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Adam Lassek
  • 35,156
  • 14
  • 91
  • 107
19

What specific errors are you seeing?

Something like this could work better:

<script type="text/javascript">

//now add markers
 @foreach (var item in Model) {
    <text>
      var markerlatLng = new google.maps.LatLng(@Model.Latitude, @Model.Longitude);
      var title = '@(Model.Title)';
      var description = '@(Model.Description)';
      var contentString = '<h3>' + title + '</h3>' + '<p>' + description + '</p>'
    </text>
}
</script>

Note that you need the magical <text> tag after the foreach to indicate that Razor should switch into markup mode.

marcind
  • 52,944
  • 13
  • 125
  • 111
  • 1
    iterate Model (by foreach) and marked @Model.Latidue? what is function of iteration? i think that missed something. it could be @item.Latitude etc – Nuri YILMAZ Oct 28 '18 at 14:09
13

That will work fine, as long as it's in a CSHTML page and not an external JavaScript file.

The Razor template engine doesn't care what it's outputting and does not differentiate between <script> or other tags.

However, you need to encode your strings to prevent XSS attacks.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
SLaks
  • 868,454
  • 176
  • 1,908
  • 1,964
  • 2
    I've updated my question, It doesn't work for me -any ideas whats wrong? thanks – raklos Jan 04 '11 at 22:57
  • 3
    @raklos: You need to escape your strings. Call `HTML.Raw(Server.JavaScriptStringEncode(...))` – SLaks Jan 04 '11 at 22:59
  • 1
    `HTML.Raw(HttpUtility.JavaScriptStringEncode(...))` - **Server** property has no this method now. **HttpUtility** does. – it3xl Jul 08 '15 at 16:55
  • 1
    `The Razor template engine doesn't care what it's outputting and does not differentiate between – HMR Nov 12 '15 at 07:14
  • 2
    @HMR: That feature did not exist when I wrote this answer. – SLaks Nov 12 '15 at 15:13
11

I prefer "<!--" "-->" like a "text>"

<script type="text/javascript">
//some javascript here     

@foreach (var item in itens)
{                 
<!--  
   var title = @(item.name)
    ...
-->

</script>
Fernando JS
  • 4,267
  • 3
  • 31
  • 29
  • this is weirdly the one solution that worked for me, because the text I needed to include had some delimiters that Razor didn't like with the `@:` and `` methods – R-D Jan 03 '20 at 17:29
8

One thing to add - I found that Razor syntax hilighter (and probably the compiler) interpret the position of the opening bracket differently:

<script type="text/javascript">
    var somevar = new Array();

    @foreach (var item in items)
    {  // <----  placed on a separate line, NOT WORKING, HILIGHTS SYNTAX ERRORS
        <text>
        </text>
    }

    @foreach (var item in items) {  // <----  placed on the same line, WORKING !!!
        <text>
        </text>
    }
</script>
Andy
  • 2,670
  • 3
  • 30
  • 49
6

A simple and a good straight-forward example:

<script>
    // This gets the username from the Razor engine and puts it
    // in JavaScript to create a variable I can access from the
    // client side.
    //
    // It's an odd workaraound, but it works.
    @{
        var outScript = "var razorUserName = " + "\"" + @User.Identity.Name + "\"";
    }
    @MvcHtmlString.Create(outScript);
</script>

This creates a script in your page at the location you place the code above which looks like the following:

<script>
    // This gets the username from the Razor engine and puts it
    // in JavaScript to create a variable I can access from
    // client side.
    //
    // It's an odd workaraound, but it works.

    var razorUserName = "daylight";
</script>

Now you have a global JavaScript variable named razorUserName which you can access and use on the client. The Razor engine has obviously extracted the value from @User.Identity.Name (server-side variable) and put it in the code it writes to your script tag.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
raddevus
  • 8,142
  • 7
  • 66
  • 87
6

The following solution seems more accurate to me than combine JavaScript with Razor. Check this out: https://github.com/brooklynDev/NGon

You can add almost any complex data to ViewBag.Ngon and access it in JavaScript

In the controller:

public class HomeController : Controller
{
    public ActionResult Index()
    {
        var person = new Person { FirstName = "John", LastName = "Doe", Age = 30 };
        ViewBag.NGon.Person = person;
        return View();
    }
}

In JavaScript:

<script type="text/javascript">
    $(function () {
        $("#button").click(function () {
            var person = ngon.Person;
            var div = $("#output");
            div.html('');
            div.append("FirstName: " + person.FirstName);
            div.append(", LastName: " + person.LastName);
            div.append(", Age: " + person.Age);
        });
    });
</script>

It's allows any plain old CLR objects (POCOs) that can be serialized using the default JavascriptSerializer.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Ice2burn
  • 677
  • 1
  • 8
  • 19
5

There is also one more option than @: and <text></text>.

Using <script> block itself.

When you need to do large chunks of JavaScript depending on Razor code, you can do it like this:

@if(Utils.FeatureEnabled("Feature")) {
    <script>
        // If this feature is enabled
    </script>
}

<script>
    // Other JavaScript code
</script>

Pros of this manner is that it doesn't mix JavaScript and Razor too much, because mixing them a lot will cause readability issues eventually. Also large text blocks are not very readable either.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Tuukka Lindroos
  • 1,270
  • 10
  • 14
4

None of the previous solutions work correctly... I have tried all the ways, but it did not give me the expected result... At last I found that there are some errors in the code... And the full code is given below.

<script type="text/javascript">

    var map = new google.maps.Map(document.getElementById('map'), {
        zoom: 10,
        center: new google.maps.LatLng(23.00, 90.00),
        mapTypeId: google.maps.MapTypeId.ROADMAP
    });

    @foreach (var item in Model)
    {
        <text>
            var markerlatLng = new google.maps.LatLng(@(item.LATITUDE), @(item.LONGITUDE));
            var title = '@(item.EMP_ID)';
            var description = '@(item.TIME)';
            var contentString = '<h3>' + "Employee " +title+ " was here at "+description+ '</h3>' + '<p>'+" "+ '</p>'

            var infowindow = new google.maps.InfoWindow({
                // content: contentString
            });

            var marker = new google.maps.Marker({
                position: markerlatLng,
                title: title,
                map: map,
                draggable: false,
                content: contentString
            });

            google.maps.event.addListener(marker, 'click', (function (marker) {
                return function () {
                    infowindow.setContent(marker.content);
                    infowindow.open(map, marker);
                }
            })(marker));
        </text>
    }
</script>
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Atish Kumar Dipongkor
  • 10,220
  • 9
  • 49
  • 77
4

I finally found the solution (*.vbhtml):

function razorsyntax() {
    /* Double */
    @(MvcHtmlString.Create("var szam =" & mydoublevariable & ";"))
    alert(szam);

    /* String */
    var str = '@stringvariable';
    alert(str);
}
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
SZL
  • 805
  • 8
  • 12
0

Those who, like me, don't have Json.Encode available can try this.

<script type="text/javascript">
    var model = @Html.Raw(Json.Serialize(Model))
</script>
reubano
  • 5,087
  • 1
  • 42
  • 41