Korzystanie z Razor w JavaScript

435

Czy jest to możliwe, czy istnieje obejście polegające na korzystaniu ze składni Razor w JavaScript, który jest w view ( cshtml)?

Próbuję dodać znaczniki do mapy Google ... Na przykład próbowałem tego, ale otrzymuję mnóstwo błędów kompilacji:

<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
źródło
3
możesz być zainteresowany moją aktualizacją dotyczącą @:składni.
StriplingWarrior
@ każdy rozważa zrobienie tego w ten sposób, przynajmniej umieść dane w osobnym znaczniku skryptu, który po prostu definiuje niektóre JSON, które są następnie używane w JS. Sprzężona z back-endem JS z przodu była dla mnie starszą PITA przy wielu okazjach. Nie pisz kodu za pomocą kodu, jeśli nie musisz. Zamiast tego przekaż dane.
Erik Reppen,

Odpowiedzi:

637

Użyj <text>pseudoelementu, jak opisano tutaj , aby zmusić kompilator Razor z powrotem do trybu zawartości:

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

Aktualizacja:

Scott Guthrie niedawno napisał o @:składni w Razor, która jest nieco mniej niezręczna niż <text>tag, jeśli masz tylko jeden lub dwa wiersze kodu JavaScript do dodania. Preferowane byłoby prawdopodobnie następujące podejście, ponieważ zmniejsza ono rozmiar generowanego HTML. (Możesz nawet przenieść funkcję addMarker do statycznego, buforowanego pliku JavaScript, aby dodatkowo zmniejszyć rozmiar):

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

Zaktualizowałem powyższy kod, aby poprawić addMarkerpoprawność połączenia.

Aby to wyjaśnić, @:zmusza Razor z powrotem do trybu tekstowego, mimo że addMarkerwywołanie przypomina kod C #. Razor pobiera następnie @item.Propertyskładnię, aby powiedzieć, że powinna bezpośrednio wypisywać zawartość tych właściwości.

Aktualizacja 2

Warto zauważyć, że kod View naprawdę nie jest dobrym miejscem do umieszczenia kodu JavaScript. Kod JavaScript powinien zostać umieszczony w .jspliku statycznym , a następnie powinien uzyskać potrzebne dane albo z wywołania Ajax, albo poprzez skanowanie data-atrybutów z HTML. Oprócz umożliwienia buforowania kodu JavaScript, pozwala to również uniknąć problemów z kodowaniem, ponieważ Razor został zaprojektowany do kodowania HTML, ale nie JavaScript.

Wyświetl kod

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

Kod JavaScript

$('[data-marker]').each(function() {
    var markerData = $(this).data('marker');
    addMarker(markerData.Latitude, markerData.Longitude,
              markerData.Description, markerData.Title);
});
StriplingWarrior
źródło
3
Nie rozumiem twojego zaktualizowanego przykładu. Czy funkcja dodawania znaczników jest poprawna?
NVM
2
@NVM: Zamiast kilkakrotnie wypisywać ten sam kod javascript, sugeruję utworzenie pojedynczej funkcji javascript (która może być przechowywana w zbuforowanym pliku .js) i wysłanie kilku wywołań do tej funkcji. Nie mam pojęcia, czy funkcja jest poprawna: po prostu bazowałem na kodzie OP.
StriplingWarrior
1
Dlaczego „@ Model.Latitude” w Foreach. Dlaczego nie item.Latitude?
NVM,
5
Zmienne C # muszą zostać zmienione. Jeśli @item.Titlezawiera pojedynczy cytat, ten kod eksploduje.
mpen
7
@ Mark: Dobra obserwacja. W rzeczywistości, zwykle nie łączą JavaScript i wyjątkowo w ogóle w moim kodu: Wolę używać Razor do generowania HTML z data-atrybutów, a następnie za pomocą statycznego, dyskretny javascript, aby zebrać te informacje z DOM. Ale cała ta dyskusja wykraczała poza zakres pytania.
StriplingWarrior
39

Właśnie napisałem tę funkcję pomocnika. Wstaw to App_Code/JS.cshtml:

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

Następnie w swoim przykładzie możesz zrobić coś takiego:

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

Zauważ, że nie umieszczam wokół niego cudzysłowów. Jeśli tytuł zawiera już cytaty, nie wybuchnie. Wydaje się też ładnie obsługiwać słowniki i anonimowe obiekty!

mpen
źródło
19
Jeśli próbujesz zakodować obiekt w widoku, nie ma potrzeby tworzenia kodu pomocnika, jeden już istnieje. Używamy tego cały czas@Html.Raw(Json.Encode(Model))
PJH
2
Czy potrafisz rozwinąć swoją odpowiedź PJH? Jak określić tytuł, jeśli tylko kodujesz „Model”?
obesechicken13
2
Ponadto, kiedy wypróbowałem to podejście z Model.Title, otrzymuję dodatkowe cytaty wokół zakodowanego javascript. Nie mogę pozbyć się cytatów, nawet jeśli połączę to z czymś innym. Te cytaty stają się częścią twojego js.
obesechicken13
1
Komentarz PJH jest znakomity. W pewien sposób deserializujesz modele po stronie serwera do bloku javascript.
netfed
24

Próbujesz wbić kwadratowy kołek w okrągły otwór.

Razor został zaprojektowany jako język szablonów do generowania HTML. Możesz bardzo dobrze wygenerować kod JavaScript, ale nie został do tego przeznaczony.

Na przykład: co jeśli Model.Titlezawiera apostrof? Spowodowałoby to uszkodzenie twojego kodu JavaScript, a Razor domyślnie nie uniknie go poprawnie.

Prawdopodobnie bardziej odpowiednie byłoby użycie generatora String w funkcji pomocniczej. Prawdopodobnie będzie mniej niezamierzonych konsekwencji takiego podejścia.

Adam Lassek
źródło
17

Jakie konkretne błędy widzisz?

Coś takiego może działać lepiej:

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

Zauważ, że potrzebujesz magicznego <text>znacznika po, foreachaby wskazać, że Razor powinien przełączyć się w tryb znaczników.

marcind
źródło
1
iterować Model (wg foreach) i oznaczyć @ Model.Latidue? jaka jest funkcja iteracji? myślę, że coś przegapiłem. to może być @ item.Latitude itp.
Nuri YILMAZ
12

Będzie to działać dobrze, o ile znajduje się na stronie CSHTML, a nie w zewnętrznym pliku JavaScript.

Silnik szablonów Razor nie dba o to, co wyprowadza i nie rozróżnia między <script>innymi tagami.

Musisz jednak zakodować swoje łańcuchy, aby zapobiec atakom XSS .

SLaks
źródło
2
Zaktualizowałem moje pytanie: Nie działa na mnie - wszystkie pomysły, co jest nie tak? dzięki
raklos
3
@raklos: Musisz uciec od swoich sznurków. ZadzwońHTML.Raw(Server.JavaScriptStringEncode(...))
SLaks
1
HTML.Raw(HttpUtility.JavaScriptStringEncode(...))- Właściwość serwera nie ma teraz tej metody. HttpUtility robi.
it3xl
1
The Razor template engine doesn't care what it's outputting and does not differentiate between <script> or other tags.Jesteś tego pewien? stackoverflow.com/questions/33666065/…
HMR
2
@HMR: Ta funkcja nie istniała, kiedy napisałem tę odpowiedź.
SLaks,
11

Wolę „<! -” ”->” jak „tekst>”

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

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

</script>
Fernando JS
źródło
jest to dziwnie jedno rozwiązanie, które zadziałało dla mnie, ponieważ tekst, który musiałem dołączyć, zawierał ograniczniki, których Razor nie lubił za pomocą metod @:i<text>
BuddyZ
8

Jedną rzecz do dodania - odkryłem, że hilighter składni Razor (i prawdopodobnie kompilator) interpretuje położenie nawiasu otwierającego inaczej:

<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
źródło
6

Prosty i dobry prosty przykład:

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

Spowoduje to utworzenie skryptu na stronie w miejscu, w którym umieścisz kod, który wygląda następująco:

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

Teraz masz globalną zmienną JavaScript o nazwie, do razorUserNamektórej możesz uzyskać dostęp i używać na kliencie. Silnik Razor oczywiście wyodrębnił wartość @User.Identity.Name(zmienna po stronie serwera) i umieścił ją w kodzie, który zapisuje w skrypcie.

raddevus
źródło
6

Poniższe rozwiązanie wydaje mi się bardziej dokładne niż połączenie JavaScript z Razor. Sprawdź to: https://github.com/brooklynDev/NGon

Możesz dodać prawie wszystkie złożone dane do ViewBag.Ngon i uzyskać do nich dostęp w JavaScript

W kontrolerze:

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

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

Umożliwia dowolne stare zwykłe obiekty CLR (POCO), które mogą być serializowane przy użyciu domyślnych JavascriptSerializer.

Ice2burn
źródło
5

Jest też jeszcze jedna opcja niż @: i <text></text>.

Używanie <script>samego bloku.

Jeśli potrzebujesz zrobić dużą część kodu JavaScript w zależności od kodu Razor, możesz to zrobić w następujący sposób:

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

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

Zaletą tego sposobu jest to, że nie miesza zbyt mocno JavaScript i Razor, ponieważ ich mieszanie spowoduje problemy z czytelnością. Również duże bloki tekstowe nie są zbyt czytelne.

Tuukka Lindroos
źródło
4

Żadne z poprzednich rozwiązań nie działa poprawnie ... Próbowałem wszystkich sposobów, ale nie dało to oczekiwanego rezultatu ... W końcu odkryłem, że są pewne błędy w kodzie ... I podano pełny kod poniżej.

<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
źródło
4

W końcu znalazłem rozwiązanie (* .vbhtml):

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

    /* String */
    var str = '@stringvariable';
    alert(str);
}
SZL
źródło