Usando Razor dentro de JavaScript

435

¿Es posible o hay una solución alternativa para usar la sintaxis Razor dentro de JavaScript que está en una vista ( cshtml)?

Estoy intentando agregar marcadores a un mapa de Google ... Por ejemplo, intenté esto, pero recibo un montón de errores de compilación:

<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
fuente
3
Puede interesarle mi actualización sobre la @:sintaxis.
StriplingWarrior
@cualquier persona que considere hacerlo de esta manera, al menos ponga los datos en una etiqueta de script separada que simplemente defina algunos JSON que luego se usan en el JS. El JS acoplado en el back-end en el front-end ha sido un PITA heredado para mí en varias ocasiones. No escriba código con código si no tiene que hacerlo. Entregue los datos en su lugar.
Erik Reppen

Respuestas:

637

Utilice el <text>pseudo-elemento, como se describe aquí , para forzar al compilador Razor a volver al modo de contenido:

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

Actualizar:

Scott Guthrie publicó recientemente sobre la @:sintaxis en Razor, que es un poco menos torpe que la <text>etiqueta si solo tiene que agregar una o dos líneas de código JavaScript. El siguiente enfoque probablemente sería preferible, ya que reduce el tamaño del HTML generado. (Incluso podría mover la función addMarker a un archivo JavaScript estático en caché para reducir aún más el tamaño):

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

Se actualizó el código anterior para que la llamada sea addMarkermás correcta.

Para aclarar, las @:fuerzas de Razor vuelven al modo de texto, a pesar de que la addMarkerllamada se parece mucho al código C #. Razor luego recoge la @item.Propertysintaxis para decir que debería generar directamente el contenido de esas propiedades.

Actualización 2

Vale la pena señalar que Ver código realmente no es un buen lugar para poner código JavaScript. El código JavaScript debe colocarse en un .jsarchivo estático , y luego debe obtener los datos que necesita, ya sea de una llamada Ajax o escaneando data-atributos del HTML. Además de hacer posible almacenar en caché su código JavaScript, esto también evita problemas con la codificación, ya que Razor está diseñado para codificar HTML, pero no JavaScript.

Ver código

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

Código JavaScript

$('[data-marker]').each(function() {
    var markerData = $(this).data('marker');
    addMarker(markerData.Latitude, markerData.Longitude,
              markerData.Description, markerData.Title);
});
StriplingWarrior
fuente
3
No entiendo tu ejemplo actualizado. ¿Es correcta la función addmarker?
NVM
2
@NVM: en lugar de generar varias veces el mismo código de JavaScript, sugiero crear una única función de JavaScript (que se puede guardar en un archivo .js en caché) y generar varias llamadas a esa función. No tengo idea de si la función es correcta: solo la estaba basando en el código del OP.
StriplingWarrior
1
¿Por qué el '@ Model.Latitude' en foreach? ¿Por qué no item.Latitude?
NVM
55
Sus variables de C # deben escaparse. Si @item.Titlecontiene una comilla simple, este código explotará.
mpen
77
@ Mark: Buena observación. De hecho, normalmente no Combino javascript y la maquinilla de afeitar en absoluto en mi propio código: Yo prefiero usar la maquinilla de afeitar para generar HTML con data-atributos, y luego usar estática, Javascript discreta para recopilar esta información de la DOM. Pero toda esa discusión estaba más allá del alcance de la pregunta.
StriplingWarrior
39

Acabo de escribir esta función auxiliar. Ponlo en App_Code/JS.cshtml:

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

Luego, en su ejemplo, puede hacer algo como esto:

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

Observe cómo no pongo citas a su alrededor. Si el título ya contiene comillas, no explotará. ¡Parece manejar bien los diccionarios y los objetos anónimos también!

mpen
fuente
19
Si está intentando codificar un objeto en la vista, no es necesario crear un código auxiliar, ya existe uno. Usamos esto todo el tiempo@Html.Raw(Json.Encode(Model))
PJH
2
¿Puedes ampliar tu respuesta PJH? ¿Cómo se especifica el título si solo está codificando "Modelo"?
obesechicken13 01 de
2
Además, cuando probé este enfoque con Model.Title, obtuve algunas citas adicionales sobre el JavaScript codificado. No puedo deshacerme de las comillas incluso si las concateno con otra cosa. Esas citas se convierten en parte de tu js.
obesechicken13 01 de
1
El comentario de PJH es excelente. En cierto modo, deserializa los modelos del lado del servidor en el bloque javascript.
netfed
24

Estás intentando atascar una clavija cuadrada en un agujero redondo.

Razor fue pensado como un lenguaje de plantilla generadora de HTML. Es muy posible que genere código JavaScript, pero no fue diseñado para eso.

Por ejemplo: ¿Qué pasa si Model.Titlecontiene un apóstrofe? Eso rompería su código JavaScript, y Razor no lo escapará correctamente de forma predeterminada.

Probablemente sería más apropiado usar un generador de cadenas en una función auxiliar. Probablemente habrá menos consecuencias no deseadas de ese enfoque.

Adam Lassek
fuente
17

¿Qué errores específicos estás viendo?

Algo como esto podría funcionar mejor:

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

Tenga en cuenta que necesita la <text>etiqueta mágica después de la foreachpara indicar que Razor debe cambiar al modo de marcado.

marcind
fuente
1
iterar modelo (por foreach) y marcado @ Model.Latidue? ¿Cuál es la función de la iteración? Creo que se perdió algo. podría ser @ item.Latitude, etc.
Nuri YILMAZ
12

Eso funcionará bien, siempre que esté en una página CSHTML y no en un archivo JavaScript externo.

El motor de plantillas Razor no le importa lo que está generando y no diferencia entre <script>otras etiquetas.

Sin embargo, debe codificar sus cadenas para evitar ataques XSS .

SLaks
fuente
2
He actualizado mi pregunta, no funciona para mí. ¿Alguna idea está mal? gracias
raklos
3
@raklos: necesitas escapar de tus cuerdas. LlamadaHTML.Raw(Server.JavaScriptStringEncode(...))
SLaks
1
HTML.Raw(HttpUtility.JavaScriptStringEncode(...))- La propiedad del servidor no tiene este método ahora. HttpUtility hace.
it3xl
1
The Razor template engine doesn't care what it's outputting and does not differentiate between <script> or other tags.¿Estás seguro de eso? stackoverflow.com/questions/33666065/…
HMR
2
@HMR: Esa característica no existía cuando escribí esta respuesta.
SLaks
11

Prefiero "<! -" "->" como un "texto>"

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

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

</script>
Fernando JS
fuente
Esta es extrañamente la única solución que funcionó para mí, porque el texto que necesitaba incluir tenía algunos delimitadores que a Razor no le gustaban con los métodos @:y<text>
BuddyZ
8

Una cosa para agregar: descubrí que Razor sintaxis hilighter (y probablemente el compilador) interpretan la posición del paréntesis de apertura de manera diferente:

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

Un ejemplo simple y bueno:

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

Esto crea una secuencia de comandos en su página en la ubicación donde coloca el código de arriba que se parece a lo siguiente:

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

Ahora tiene una variable global de JavaScript denominada a la razorUserNameque puede acceder y usar en el cliente. El motor Razor obviamente ha extraído el valor de @User.Identity.Name(variable del lado del servidor) y lo ha colocado en el código que escribe en su etiqueta de script.

raddevus
fuente
6

La siguiente solución me parece más precisa que combinar JavaScript con Razor. Mira esto: https://github.com/brooklynDev/NGon

Puede agregar casi cualquier dato complejo a ViewBag.Ngon y acceder a él en JavaScript

En el controlador:

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>

Permite cualquier objeto CLR antiguo (POCO) que se pueda serializar utilizando el valor predeterminado JavascriptSerializer.

Ice2burn
fuente
5

También hay una opción más que @: y <text></text>.

Usando el <script>bloque en sí.

Cuando necesite hacer grandes porciones de JavaScript dependiendo del código de Razor, puede hacerlo así:

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

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

Las ventajas de esta manera es que no mezcla JavaScript y Razor demasiado, porque mezclarlos mucho causará problemas de legibilidad eventualmente. Además, los bloques de texto grandes tampoco son muy legibles.

Tuukka Lindroos
fuente
4

Ninguna de las soluciones anteriores funciona correctamente ... He intentado todas las formas, pero no me dio el resultado esperado ... Finalmente encontré que hay algunos errores en el código ... Y se proporciona el código completo abajo.

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

Finalmente encontré la solución (* .vbhtml):

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

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