Utilisation de Razor dans JavaScript

435

Est-il possible ou existe-t-il une solution de contournement pour utiliser la syntaxe Razor dans JavaScript qui est dans une vue ( cshtml)?

J'essaie d'ajouter des marqueurs à une carte Google ... Par exemple, j'ai essayé cela, mais je reçois une tonne d'erreurs de compilation:

<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>
raklos
la source
3
vous pourriez être intéressé par ma mise à jour concernant la @:syntaxe.
StriplingWarrior
@n'importe qui envisage de le faire de cette façon, au moins mettez les données dans une balise de script distincte qui définit simplement du JSON qui est ensuite utilisé dans le JS. Le JS couplé à l'arrière à l'avant a été un PITA hérité pour moi à plusieurs reprises. N'écrivez pas de code avec du code si vous n'êtes pas obligé. Remettez plutôt les données.
Erik Reppen

Réponses:

637

Utilisez le <text>pseudo-élément, comme décrit ici , pour forcer le compilateur Razor à revenir en mode contenu:

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

Mise à jour:

Scott Guthrie a récemment publié un article sur la @:syntaxe dans Razor, qui est légèrement moins maladroite que la <text>balise si vous n'avez qu'une ou deux lignes de code JavaScript à ajouter. L'approche suivante serait probablement préférable, car elle réduit la taille du code HTML généré. (Vous pouvez même déplacer la fonction addMarker vers un fichier JavaScript statique et mis en cache pour réduire davantage la taille):

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

Mise à jour du code ci-dessus pour rendre l'appel addMarkerplus correct.

Pour clarifier, le @:Razor force à revenir en mode texte, même si l' addMarkerappel ressemble beaucoup au code C #. Razor récupère ensuite la @item.Propertysyntaxe pour dire qu'il doit sortir directement le contenu de ces propriétés.

Update 2

Il convient de noter que le code View n'est vraiment pas un bon endroit pour mettre du code JavaScript. Le code JavaScript doit être placé dans un .jsfichier statique , puis il doit obtenir les données dont il a besoin soit à partir d'un appel Ajax ou en scannant les data-attributs à partir du HTML. En plus de permettre de mettre en cache votre code JavaScript, cela évite également les problèmes d'encodage, car Razor est conçu pour encoder en HTML, mais pas en JavaScript.

Afficher le code

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

Code JavaScript

$('[data-marker]').each(function() {
    var markerData = $(this).data('marker');
    addMarker(markerData.Latitude, markerData.Longitude,
              markerData.Description, markerData.Title);
});
StriplingWarrior
la source
3
Je ne comprends pas votre exemple mis à jour. La fonction addmarker est-elle correcte?
NVM
2
@NVM: Plutôt que de générer plusieurs fois le même code javascript, je suggère de créer une seule fonction javascript (qui peut être conservée dans un fichier .js mis en cache) et de générer plusieurs appels vers cette fonction. Je n'ai aucune idée si la fonction est correcte: je la basais simplement sur le code de l'OP.
StriplingWarrior
1
Pourquoi le '@ Model.Latitude' dans foreach. Pourquoi pas item.Latitude?
NVM
5
Vos variables C # doivent être échappées. S'il @item.Titlecontient une seule citation, ce code explosera.
mpen
7
@Mark: Bonne observation. En fait, je généralement ne combinons pas javascript et rasoir du tout dans mon propre code: Je préfère utiliser Razor pour générer HTML avec des data-attributs, puis utilisez statique, javascript discret pour glaner ces informations du DOM. Mais toute cette discussion dépassait en quelque sorte la portée de la question.
StriplingWarrior
39

Je viens d'écrire cette fonction d'aide. Mettez-le dans App_Code/JS.cshtml:

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

Ensuite, dans votre exemple, vous pouvez faire quelque chose comme ceci:

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

Remarquez comment je ne mets pas de guillemets autour. Si le titre contient déjà des guillemets, il n'explosera pas. Semble bien gérer les dictionnaires et les objets anonymes!

mpen
la source
19
Si vous essayez de coder un objet dans la vue, il n'est pas nécessaire de créer un code d'assistance, il en existe déjà un. Nous l'utilisons tout le temps@Html.Raw(Json.Encode(Model))
PJH
2
Pouvez-vous développer votre réponse PJH? Comment spécifiez-vous le titre si vous encodez simplement "Model"?
obesechicken13
2
De plus, lorsque j'ai essayé cette approche avec Model.Title, je reçois des devis supplémentaires autour du javascript codé. Je ne peux pas me débarrasser des citations même si je les concatène à autre chose. Ces citations font partie de votre js.
obesechicken13
1
Le commentaire de PJH est superbe. D'une certaine manière, vous désérialisez les modèles côté serveur dans le bloc javascript.
netfed le
24

Vous essayez de coincer une cheville carrée dans un trou rond.

Razor était conçu comme un langage de modèle générant du HTML. Vous pouvez très bien le faire générer du code JavaScript, mais il n'a pas été conçu pour cela.

Par exemple: que faire si Model.Titlecontient une apostrophe? Cela casserait votre code JavaScript et Razor ne l'échapperait pas correctement par défaut.

Il serait probablement plus approprié d'utiliser un générateur de chaînes dans une fonction d'assistance. Il y aura probablement moins de conséquences imprévues de cette approche.

Adam Lassek
la source
17

Quelles erreurs spécifiques voyez-vous?

Quelque chose comme ça pourrait mieux fonctionner:

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

Notez que vous avez besoin de la <text>balise magique après le foreachpour indiquer que Razor doit passer en mode de balisage.

marcind
la source
1
itérer le modèle (par foreach) et marqué @ Model.Latidue? quelle est la fonction de l'itération? je pense que quelque chose a manqué. cela pourrait être @ item.Latitude etc
Nuri YILMAZ
12

Cela fonctionnera bien, tant qu'il se trouve dans une page CSHTML et non dans un fichier JavaScript externe.

Le moteur de modèle Razor ne se soucie pas de ce qu'il génère et ne fait pas de différence entre <script>ou d'autres balises.

Cependant, vous devez encoder vos chaînes pour empêcher les attaques XSS .

SLaks
la source
2
J'ai mis à jour ma question, cela ne fonctionne pas pour moi - des idées erronées? merci
raklos
3
@raklos: Vous devez échapper à vos cordes. AppelHTML.Raw(Server.JavaScriptStringEncode(...))
SLaks
1
HTML.Raw(HttpUtility.JavaScriptStringEncode(...))- La propriété du serveur n'a plus cette méthode maintenant. HttpUtility le fait.
it3xl
1
The Razor template engine doesn't care what it's outputting and does not differentiate between <script> or other tags.Êtes-vous sûr de cela? stackoverflow.com/questions/33666065/…
HMR
2
@HMR: Cette fonctionnalité n'existait pas lorsque j'ai écrit cette réponse.
SLaks
11

Je préfère "<! -" "->" comme un "texte>"

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

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

</script>
Fernando JS
la source
c'est étrangement la seule solution qui a fonctionné pour moi, car le texte que je devais inclure avait des délimiteurs que Razor n'aimait pas avec les méthodes @:et<text>
BuddyZ
8

Une chose à ajouter - j'ai trouvé que la syntaxe Razor hilighter (et probablement le compilateur) interprétait différemment la position du support d'ouverture:

<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
la source
6

Un exemple simple et bon:

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

Cela crée un script dans votre page à l'emplacement où vous placez le code ci-dessus qui ressemble à ceci:

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

Vous avez maintenant une variable JavaScript globale nommée à razorUserNamelaquelle vous pouvez accéder et utiliser sur le client. Le moteur Razor a évidemment extrait la valeur de @User.Identity.Name(variable côté serveur) et l'a mise dans le code qu'il écrit dans votre balise de script.

raddevus
la source
6

La solution suivante me semble plus précise que de combiner JavaScript avec Razor. Vérifiez ceci: https://github.com/brooklynDev/NGon

Vous pouvez ajouter presque toutes les données complexes à ViewBag.Ngon et y accéder en JavaScript

Dans le contrôleur:

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

En 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>

Il autorise tous les anciens objets CLR (POCO) simples qui peuvent être sérialisés en utilisant la valeur par défaut JavascriptSerializer.

Ice2burn
la source
5

Il existe également une option de plus que @: et <text></text>.

Utiliser le <script>bloc lui-même.

Lorsque vous devez faire de gros morceaux de JavaScript en fonction du code Razor, vous pouvez le faire comme ceci:

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

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

Les avantages de cette manière est qu'il ne mélange pas trop JavaScript et Razor, car les mélanger beaucoup entraînera éventuellement des problèmes de lisibilité. Les gros blocs de texte ne sont pas non plus très lisibles.

Tuukka Lindroos
la source
4

Aucune des solutions précédentes ne fonctionne correctement ... J'ai essayé de toutes les manières, mais cela ne m'a pas donné le résultat attendu ... Enfin j'ai trouvé qu'il y a quelques erreurs dans le code ... Et le code complet est donné au dessous de.

<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>
Atish Dipongkor - MVP
la source
4

J'ai finalement trouvé la solution (* .vbhtml):

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

    /* String */
    var str = '@stringvariable';
    alert(str);
}
SZL
la source