Cambios en el retorno de datos JSON con MVC 2

aspnetmvc Con objeto de mejorar la seguridad de nuestras aplicaciones, la Release Candidate de ASP.NET MVC 2 introdujo un cambio importante en la forma de procesar peticiones que retornan información serializada como JSON: por defecto, ahora sólo se responde a peticiones de tipo POST.

Dado que en MVC 1.0 era justo al contrario, esta pequeña reorientación hace que aplicaciones que antes funcionaban correctamente dejen de hacerlo al migrarlas a la última versión del framework. Un ejemplo lo tenemos con las aplicaciones que aprovechan la potencia de jqGrid para mostrar y editar rejillas de datos. Recordemos que el intercambio de información entre cliente y servidor se realiza mediante llamadas Ajax que retornan siempre datos en notación JSON.

Los síntomas que notaremos en estos casos son muy simples: ¡las acciones que deberían devolvernos información dejan de hacerlo! En algunas ocasiones, dependiendo del uso que se dé en la vista a la la información retornada, es posible que aparezcan errores de script en el navegador, pero otras ni siquiera eso.

En cualquier caso, no es difícil dar con la solución. Con Firebug, Fiddler, o cualquier herramienta que nos permita monitorizar las peticiones, podremos observar que en la petición Ajax se está produciendo un error HTTP 500 (error interno de servidor):

image

Y si seguimos profundizando en dicha petición, podemos ahora conocer el mensaje descriptivo que envía el servidor, un auténtico detalle por parte del equipo de desarrollo de ASP.NET MVC 2, en el que incluso nos indican la solución al problema:

<html>
<head>
<title>This request has been blocked because sensitive 
information could be disclosed to third party web sites when 
this is used in a GET request. 
To allow GET requests, set JsonRequestBehavior to AllowGet.</title>
<style>

Ante esta situación, podemos optar por dos soluciones. La primera sería indicar explícitamente en la instancia del resultado, de tipo JsonResult, que queremos permitir el método GET, así:

return Json(data, JsonRequestBehavior.AllowGet);

Y otra posibilidad sería realizar las peticiones Ajax utilizando el método POST, algo que podemos conseguir muy fácilmente la mayoría de las veces. En el caso de los ejemplos con jqGrid, sería sustituyendo el ’GET’ por ‘POST’ en código:

[...]
jQuery("#list").jqGrid({
    url: '<%= Url.Action("ObtenerDatosGrid") %>',
    datatype: 'json',
    mtype: 'POST',    // <- Aquí
    colNames: ['Apellidos', 'Nombre', 'Fecha Nac.', 'Email'],
[...]

¡Gracias, Maxi, por los comentarios que inspiraron este post!

Crossposteado desde Cambios en el retorno de datos JSON con MVC 2 @ Variable not found

11 comentarios sobre “Cambios en el retorno de datos JSON con MVC 2”

  1. MMmmm…
    Debería poderse especificar de alguna manera el comportamiento «por defecto» de Json() si no se especifica JsonRequestBehavior. Por no tener que ir tocando las aplicaciones ya hechas, básicamente… 🙂

    Un saludos!
    PD: Yo me di de bruces con eso que comentas, justo dos dias antes de que escribieras el post! xD

  2. Hola, Eduard!

    La verdad es que es un cambio que obliga a retocar las aplicaciones. Eso sí, nada que no pueda hacerse en un par de minutos. 🙂

    Poder establecer el comportamiento por defecto, como se hace en otros aspectos del framework, podría ser una solución rápida, aunque supongo que han optado por hacerlo así para obligarnos a controlar explícitamente estas peticiones, potencialmente maliciosas.

    En fin, que todo sea por la seguridad.

    Gracias por comentar!

  3. Gracias por el post, me pasé como 4 horas intentando de muchas formas obtener un JSON desde un request GET… luego ví tu post y listo!… muchas gracias de nuevo

  4. En la ultima version ¿Como pasas los datos por POST? si lo haces por POST siempre envia el parametro como nulo. Ahora siempre de debe manejar GET?

  5. Hola,

    @msn, la última versión de MVC o de jqGrid? Bueno, en cualquier caso, no creo que haya diferencias…

    Traza la petición con Fiddler o Firebug para comprobar si los parámetros se envían correctamente. Es posible que te dé alguna pista sobre lo que te está ocurriendo.

    Saludos!

  6. Pongo un poco de codigo para entenderme mejor.
    Si pongo esto no funciona por que el type:
    $(«#Text1»).autocomplete({
    source: function(request, response) {
    $.ajax({
    url: «/Usuarios/GetUsuarios/»,
    data: { pUsuario: request.term },
    dataType: «json»,
    type: «POST», //<--- No funciona contentType: "application/json; charset=utf-8", success: function(data) { response($.map(data, function(item) { return { value: item.Nick } })) }, error: function(XMLHttpRequest, textStatus, errorThrown) { alert(XMLHttpRequest.Message); } }); }, minLength: 2 }); Si pongo esto funciona: $("#Text1").autocomplete({ source: function(request, response) { $.ajax({ url: "/Usuarios/GetUsuarios/", data: { pUsuario: request.term }, dataType: "json", //type: "POST", <---funciona contentType: "application/json; charset=utf-8", success: function(data) { response($.map(data, function(item) { return { value: item.Nick } })) }, error: function(XMLHttpRequest, textStatus, errorThrown) { alert(XMLHttpRequest.Message); } }); }, minLength: 2 }); Y el parametro que no le pasa es pUsuario En el modelo el codigo esta asi: public JsonResult GetUsuarios(string pUsuario) { //UsuarioModels oUsuariosMod = new UsuarioModels(); return Json(UsuarioModels.GetUsersByName(pUsuario), JsonRequestBehavior.AllowGet); } y ya lo intente quitandole el JsonRequestBehavior.AllowGet Por que con POST no funciona?????

  7. Hola!

    El problema creo que se debe a que tus datos son enviados siempre codificados como un query string, aunque en contentType indiques que son JSON. Esto puedes comprobarlo con Fiddler o Firebug muy fácilmente.

    Si quieres que tu código funcione, deberías quitar la línea en la que cambias el contentType, o bien pasar los datos «JSONeados», por ejemplo:


    data: «{ ‘pUsuario’: ‘»+request.term+»‘ }»,

    (Por supuesto, puedes utilizar para JSONear la biblioteca que más te convenga, o bien hacerlo a mano, como en el ejemplo anterior).

    Un saludo!

  8. Saludos

    Tengo un problema que me está volviendo loco, al realizar el retorno de un objeto Json hacia la vista para cargar un jqGrid se abre cada vez un cuadro de diágolo para guardar un archivo, el mismo que contiene la estructura JSON esperada. No se a qué se debe esto. (Utilizo MVC 2).

    Este es el controlador:

    Function Index() As ActionResult

    Dim lista As IEnumerable(Of ESTADO) = SetupFakeUserCollection()
    Dim js As JsonResult = MyBase.ProcessThePagingAndSortingRequest(lista, «ID_ESTADO», «asc», 1, 20)

    Return Json(js, JsonRequestBehavior.AllowGet)
    End Function

    La vista:
    jQuery(document).ready(function() {
    jQuery(«#list»).jqGrid({
    url: ‘< %= Url.Action("Estado/Index") %>‘,
    dataType: «json»,
    mtype: «GET»,
    //contentType: «application/json; charset=utf-8»,
    colNames: [‘ID’, ‘TIPO’, ‘NOMBRE’],
    colModel: [
    { index: ‘ID_ESTADO’, width: 150, align: ‘left’ },
    { index: ‘ID_TIPO_ESTADO’, width: 150, align: ‘left’, sortable: false },
    { index: ‘NOMBRE_ESTADO’, width: 80, align: ‘center’ }],
    pager: jQuery(‘#pager’),
    rowNum: 20,
    rowList: [20, 50, 100],
    sortname: ‘NOMBRE_ESTADO’,
    sortorder: ‘asc’,
    viewrecords: true,
    imgpath: ‘/content/flick/images’,
    caption: ‘Estados’,
    height: 400,
    width: 900,
    onSelectRow: function(id) {
    alert(«Pulsado Id: » + id);
    }
    });
    });

    Esta es la respuesta que recibe el cliente:

    {«ContentEncoding»:{«BodyName»:»utf-8″,»EncodingName»:»Unicode (UTF-8)»,»HeaderName»:»utf-8″,»WebName»:»utf-8″,»WindowsCodePage»:1200,»IsBrowserDisplay»:true,»IsBrowserSave»:true,»IsMailNewsDisplay»:true,»IsMailNewsSave»:true,»IsSingleByte»:false,»EncoderFallback»:{«DefaultString»:»�»,»MaxCharCount»:1},»DecoderFallback»:{«DefaultString»:»�»,»MaxCharCount»:1},»IsReadOnly»:true,»CodePage»:65001},»ContentType»:»text/json»,»Data»:{«total»:6,»page»:1,»records»:101,»rows»:[{«ID_ESTADO»:0,»cell»:[0,1,»ESTADO 0″]},{«ID_ESTADO»:1,»cell»:[1,1,»ESTADO 1″]},{«ID_ESTADO»:2,»cell»:[2,1,»ESTADO 2″]},{«ID_ESTADO»:3,»cell»:[3,1,»ESTADO 3″]},{«ID_ESTADO»:4,»cell»:[4,1,»ESTADO 4″]},{«ID_ESTADO»:5,»cell»:[5,1,»ESTADO 5″]},{«ID_ESTADO»:6,»cell»:[6,1,»ESTADO 6″]},{«ID_ESTADO»:7,»cell»:[7,1,»ESTADO 7″]},{«ID_ESTADO»:8,»cell»:[8,1,»ESTADO 8″]},{«ID_ESTADO»:9,»cell»:[9,1,»ESTADO 9″]},{«ID_ESTADO»:10,»cell»:[10,1,»ESTADO 10″]},{«ID_ESTADO»:11,»cell»:[11,1,»ESTADO 11″]},{«ID_ESTADO»:12,»cell»:[12,1,»ESTADO 12″]},{«ID_ESTADO»:13,»cell»:[13,1,»ESTADO 13″]},{«ID_ESTADO»:14,»cell»:[14,1,»ESTADO 14″]},{«ID_ESTADO»:15,»cell»:[15,1,»ESTADO 15″]},{«ID_ESTADO»:16,»cell»:[16,1,»ESTADO 16″]},{«ID_ESTADO»:17,»cell»:[17,1,»ESTADO 17″]},{«ID_ESTADO»:18,»cell»:[18,1,»ESTADO 18″]},{«ID_ESTADO»:19,»cell»:[19,1,»ESTADO 19″]}]},»JsonRequestBehavior»:1}

  9. Hola, sacurio.

    Así a ojo, me parece que estás retornando al cliente un JsonResult serializado como Json.

    En la línea donde dice «Return Json(js, JsonRequestBehavior.AllowGet)», la variable «js» debería ser directamente el objeto a retornar, no un JsonResult, como tienes en este momento.

    Por ejemplo,

    Persona per = _servicios.ObtenerPersona(4);
    return Json(p, JsonRequestBehavior.AllowGet);

    Si observas, la llamada a Json() recibe como primer parámetro el objeto que quiero convertir a JSON.

    Suerte!

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *