Twitter Facebook RSS Feed

sábado, 16 de abril de 2011 a las 18:12hs por Gustavo Cantero (The Wolf)

Cada vez es más frecuente ver aplicaciones que utilizan AJAX para obtener información de algún proceso del servidor y con ella actualizar la página. Con .NET esto se puede hacer fácilmente utilizando el ScriptManager para llamar a servicios web, pero muchas veces no queremos agregar tanta complejidad porque simplemente necesitamos llamar a un método que nos devuelva, por ejemplo, la hora de nuestro server.

Para hacer lo mencionado podemos crear un WebMethod en nuestra página y llamarlo utilizando jQuery (casi todos los sitios web actualmente utilizan jQuery para alguna tarea).

Vamos a explicar cómo hacerlo creando un ejemplo sencillo, en el cual vamos a tomar dos números ingresados por el usuario en campos de texto, los vamos a enviar al WebMethod donde vamos a sumarlo (esta tarea se va a realizar en el servidor) y luego devolveremos el resultado a la página.
En el código de nuestra página vamos a agregar el método pero teniendo en cuenta que debemos agregarle el atributo “WebMethod” como muestro a continuación con C#:

using System;
using System.Web.Services;
 
namespace Ejemplo
{
    public partial class Default : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {
 
        }
 
        [WebMethod]
        public static double Sumar(double Valor1, double Valor2)
        {
            return Valor1 + Valor2;
        }
    }
}

Si prefieren hacerlo con Visual Basic (VB.NET) es muy parecido:

Imports System.Web.Services

Public Class Default 
    Inherits System.Web.UI.Page

    Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load

    End Sub

    <WebMethod>
    Public Shared Function Sumar(Valor1 As Double, Valor2 As Double) As Double

        Return Valor1 + Valor2

    End Function

End Class

Pero para que este método pueda ser llamado desde una página también debemos agregar en el web.config la referencia al HttpModule que maneja estas llamadas:

<?xml version="1.0"?>
 
<configuration>
  <system.web>
    <compilation debug="true" targetFramework="4.0" />
    <httpModules>
      <add name="ScriptModule" type="System.Web.Handlers.ScriptModule, System.Web.Extensions, Version=1.0.61025.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
    </httpModules>
  </system.web>
</configuration>

Con esto ya terminamos de hacer todo lo necesario en .NET para que nuestros scripts puedan utilizar este método. Ahora debemos agregar la llamada desde nuestra página. Para hacerlo debemos agregar el archivo de JavaScript con la librería de jQuery a nuestra página. Podemos descargar esta librería y “apuntar” a este archivo o simplemente podemos hacer que nuestra página la descargue de los servidores de Microsoft, Google o jQuery (en el ejemplo hago esto último). En la página http://docs.jquery.com/Downloading_jQuery podemos ver las distintas opciones de descarga o utilización de los CDN (Content Delivery Network) que podemos utilizar.

Entonces, en nuestra página de ejemplo, agregamos el siguiente elemento de HTML para que obtenga la librería del CDN de Microsoft:

<script src="http://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.5.2.min.js" type="text/javascript"></script>

Ahora que tenemos la librería podemos llamar a nuestro método desde nuestra página de la siguiente manera:

$.ajax({
    //Tipo de llamada
    type: "POST",
 
    //Dirección del WebMethod, o sea, Página.aspx/Método
    url: "Default.aspx/Sumar",
 
    //Parámetros para pasarle al método 
    data: '{Valor1: 22, Valor2: 33}', 
 
    //Tipo de contenido
    contentType: "application/json; charset=utf-8",
 
    //Tipo de datos
    dataType: "json",
 
    //Función a la cual llamar cuando se pudo llamar satisfactoriamente al método
    success: resultado,
 
    //Función a la cual llamar cuando se producen errores
    error: errores
});

A continuación les dejo el HTML completo de nuestra página de ejemplo, donde toma los dos números, los envía a nuestro método y luego pone el resultado en otro campo de texto:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title></title>
    <script src="http://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.5.2.min.js" type="text/javascript"></script>
    <script type="text/javascript">
        $(function () {
            $('#btnSumar').click(function () {
                var num1 = $('#num1').val(); //Obtenemos el primer número
                var num2 = $('#num2').val(); //Obtenemos el segundo número
                $.ajax({
                    type: "POST",
                    url: "Default.aspx/Sumar",
                    data: '{Valor1: ' + num1 + ', Valor2: ' + num2 + '}',
                    contentType: "application/json; charset=utf-8",
                    dataType: "json",
                    success: resultado,
                    error: errores
                });
            });
        });
        function resultado(msg) {
            //msg.d tiene el resultado devuelto por el método
            $('#num3').val(msg.d);
        }
        function errores(msg) {
            //msg.responseText tiene el mensaje de error enviado por el servidor
            alert('Error: ' + msg.responseText);
        }
    </script>
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <br />
        <br />
        <br />
        <input type="text" id="num1" />
        +
        <input type="text" id="num2" />
        <input type="button" value="=" id="btnSumar" />
        <input type="text" id="num3" disabled />
    </div>
    </form>
</body>
</html>

Como verán utilizar WebMethods de ASP.NET desde jQuery es muy sencillo y útil, y puede servirnos en muchas tareas, por ejemplo, en formularios de envío de mail para no tener que recargar la página, para crear campos con “autocomplete”, para obtener información actualizada por otros usuarios sin necesidad de refrescar la página (como hace Facebook), etc.

También les dejo un proyecto con el ejemplo anterior funcionando:

Espero que les sea de utilidad.

Suerte!

50 comentarios »

  1. Ernesto dice:

    Hola, excelente página.

    Intente desarrollar el efecto de Autocompletar tomando como base tu ejemplo y agregandole una consula a una BD SQL, pero en la lista me devuelve «Undefined», es decir, si encuentra 3 coincidencias ne la base de datos con la palabra escrita en el cuadro de autocompletar en la lista me aparece varias veces «Undefined», podiras ayudarme si te envio el ejemplo???

  2. Ernesto dice:

    «namespace Ejemplo
    {
    public partial class Materiales : System.Web.UI.Page
    {
    protected void Page_Load(object sender, EventArgs e)
    {

    }

    [WebMethod]
    public static List ObtenerMateriales(string Palabra)
    {
    var dc = new LinqMaterialesDataContext();
    var Resultado = from e in dc.T_AnexoC5
    where e.Concepto.Contains(Palabra)
    select new ListC5 {id=e.Partida, value = e.Concepto };
    return Resultado.ToList ();
    }

    public class ListC5
    {
    public string id { get; set; }
    public string value { get; set; }

    }

    }

    • Hola, Ernesto, perdón por la demora en responder.
      Yo creo que el problema es porque estás devolviendo un objeto «List», el cual no existe en JavaScript.
      Te recomiendo que lo pases a un vector, como te muestro a continuación:

      [WebMethod]
      public static ListC5[] ObtenerMateriales(string Palabra)
      {
          var dc = new LinqMaterialesDataContext();
          var Resultado = from e in dc.T_AnexoC5
                          where e.Concepto.Contains(Palabra)
                          select new ListC5 { id = e.Partida, value = e.Concepto };
          return Resultado.ToArray();
      }

      Suerte!

  3. Ernesto dice:

    «la parte del Jquerye es esta

    $(function () {
    $(«#TextBox2»).autocomplete({
    source: function (request, response) {
    $.ajax({
    url: «Materiales.aspx/ObtenerMateriales»,
    data: «{ ‘Palabra’: ‘» + request.term + «‘ }»,
    dataType: «json»,
    type: «POST»,
    contentType: «application/json; charset=utf-8»,
    dataFilter: function (data) { return data; },
    success: function (data) {
    response($.map(data.d, function (item) {
    return {
    value: item.Email
    }
    }))
    },
    error: function (XMLHttpRequest, textStatus, errorThrown) {
    alert(textStatus);
    }
    });
    },
    minLength: 2
    });
    });
    «

  4. Juan Carlos dice:

    Amigo, solo quisiera hacerle una consulta con respecto a seguridad. Es esto sugúro para por ejemplo implementar este código en un Inicio de sesión? Que pasa si alguien hace ver codigo? mostraría el password escrito por el usuario, ¿no?
    Desde ya muchas gracias.

    • El código no contiene el password, por lo cual, es seguro.
      De todas formas siempre está la posibilidad de que alguien snifee la red y vea tu contraseña, para lo cual, lo mejor sería implementar https para encriptar tus comunicaciones entre el javascript (que es el que va a enviar tu password) y el servidor (que es el que va a validar el password).
      Saludos.

  5. Juan Carlos dice:

    Gracias Gustavito. Gracias por responder. De todas maneras puedo encriptarla yo y enviar los datos encriptados para no usar https, verdad?
    Desde ya mil gracias.
    Un gran abrazo desde Argentina.

  6. Jose dice:

    NO me funciono el codigo.
    No hace nada.
    Estoy utilizando Master page y el codigo que pusiste arriba lo estoy usando en el default y no en el master page.
    que puede ser.

  7. felipe marin dice:

    muchas gracias por el ejemplo en verdad me sirvio para lo que tenia que hacer solo una duda en el web.config configure el httpModules y generaba un error, pero no lo utilice en el web.config y igual fuciono perfecto

  8. tito dice:

    Buen día!

    Tu sugerencia es útil y sencilla, pero ¿es posible evitar que el método «Sumar» sea static?

    Gracias!

  9. Bruno dice:

    Hola Gustavo: muy bueno tu blog. Te cuento que estoy desarrollando un Framework para crear aplicaciones sin PostBacks, en donde hago uso y abuso de los WebMethods. Me parece que es la solución cuando las aplicaciones se tornan mas y mas ricas en posibilidades.
    Te invito a participar de esta experiencia, que si no me equivoco, te va a interesar.
    Te mando un abrazo

  10. emanuel dice:

    Muy bueno y bien explicado.. Lo he probado con datos de tipo string y me da error. Como lo puedo solucionar

  11. emanuel dice:

    Ok. Funciona perfecto, muchas gracias!! Saludos

  12. emanuel dice:

    Hola, de nuevo por el blog, me gustaria consultar si el parametro que resive el
    evento success que llama a function resultado debe ser siempre un tipo primitivo
    o puede ser tambien un objeto o lista?

    • El valor que recibe la función definida en la propiedad «sucess», en nuestro ejemplo la función de javascript «resultado», puede recibir un valor complejo. Para esto desde .NET deberías devolver una estructura o clase y el motor te lo convierte en una estructura JSON.
      Aprovecho para recomendarte que, si necesitás devolver datos complejos, crees tus propias clases con la mínima información a devolver, ya que esta info va a viajar por internet y puede generar demoras en la ejecución.
      Saludos.

  13. Jorge Alberto dice:

    Hola Gustavo, con respecto a llamar metodos con jQuery, tengo una pregunta sobre algo relacionado, Como realizar post basados en Cross Domain de Manera Syncrona, algo que pueda funcionar tanto en iExplorer 7 o superiores, Firefox y Chrome! Se que iexplorer tiene el objeto XDomainRequest, que aparece a partir de la version 8, jQuery tiene algo como jsonp, con la particularidad de que son procesos asyncronos!

    Gracias y Saludos!

  14. Janet dice:

    Hola, me ha servido mucho tu ejemplo, pero para pasar los parámetros que recibe el [webmethod] a otra función dentro del mismo aspx.cs me marca el error «Referencia a objeto no establecida como instancia de un objeto.» justo en la línea en la que mando a llamar la otra función con los parámetros, la pregunta es se tiene que instanciar desde el page o cómo se instancia?

    Este es el código del webmethod

    [WebMethod]
    public static decimal Sumar(decimal Valor0, decimal Valor1, decimal Valor2,decimal Valor3,decimal Valor4,decimal Valor5,decimal Valor6,decimal Valor7, decimal Valor8, decimal Valor9,decimal Valor10, decimal Valor11, decimal Valor12, decimal Valor13, decimal Valor14, decimal Valor15, decimal Valor16, decimal Valor17, decimal Valor18, decimal Valor19)
    {
    int modificado = 0;
    modificado = (new busquedaIni()).depurarControlDoctos(Valor0, Valor1, Valor2, Valor3, Valor4, Valor5, Valor6, Valor7, Valor8, Valor9, Valor10, Valor11, Valor12, Valor13, Valor14, Valor15, Valor16, Valor17, Valor18, Valor19);
    return Valor1 + Valor2;
    }
    y la función depurarControlDoctos

    public partial class busquedaIni : System.Web.UI.Page
    {
    private int depurarControlDoctos(decimal Valor0, decimal Valor1, decimal Valor2,decimal Valor3,decimal Valor4,decimal Valor5,decimal Valor6,decimal Valor7, decimal Valor8, decimal Valor9,decimal Valor10, decimal Valor11, decimal Valor12, decimal Valor13, decimal Valor14, decimal Valor15, decimal Valor16, decimal Valor17, decimal Valor18, decimal Valor19)
    {
    decimal[] valores;
    valores = new decimal[20]{Valor0, Valor1, Valor2, Valor3, Valor4, Valor5, Valor6, Valor7, Valor8, Valor9, Valor10, Valor11, Valor12, Valor13, Valor14, Valor15, Valor16, Valor17, Valor18, Valor19};
    string ControlDoc = «»;
    int indiceCtrlDoc = gvControlDoctos.Rows.Count;
    int modificado;

    for (int i = 0; i < indiceCtrlDoc; i++)
    {
    ControlDoc = gvControlDoctos.DataKeys[i].Values[2].ToString();

    if (!(valores[i].ToString().Equals(ControlDoc)) && !(valores[i].ToString().Equals('0')))
    {
    gvControlDoctos.DataKeys[i].Values[2] = valores[i];
    decimal estatusdoc_id, docto_id;
    estatusdoc_id = Convert.ToDecimal(gvControlDoctos.DataKeys[i].Values[2]);
    docto_id = Convert.ToDecimal(gvControlDoctos.DataKeys[i].Values[0]);
    string matricula = "";
    matricula = gvBusquedaInicial.SelectedDataKey.Values[0].ToString();

    modificado = (new negocioControlDoctos()).modificaCtrlDoctos(matricula, docto_id, estatusdoc_id);
    }

    }
    return Convert.ToInt32(Valor1);
    }
    }

    bueno también hay otras cosas pero según yo éstas son las importantes.

    De antemano gracias por la atención prestada.

    • Janet, el problema es que los webmethods son métodos estáticos que se ejecutan de forma «aislada» del ciclo de vida de la página, por lo cual, la misma no está instanciada ni se puede acceder a ningún control de ésta del lado del servidor.
      Lo que puedes hacer es leer el valor de los controles que necesites en el cliente usando javascript o alguna librería como jQuery y enviar estos valores como parámetros al webmethod.
      Suerte!

  15. Jandy dice:

    Hola, una pregunta: Tengo una MasterPage, en la cual en la parte del html mando a llamar el metodo $.ajax , pero si redirecciono un WebForm (Ejemplo Response.Redirect(Inicio/Presentacion.aspx) ) que no este al nivel de la MasterPage nunca se ejecuta la funcion estatica con el atributo “WebMethod”, en cambio si redirecciono un WebForm que este al mismo nivel (directorio) de la MasterPage (Ejemplo Response.Redirect(Presentacion.aspx) ) la funcion si se ejecuta

  16. Rafael Del Moral dice:

    muchas gracias por tu aporte, tenia dias buscando la solucion.
    ahora requiero pasar un arreglo, pero me marca error
    Error: {«Message»:»Se ha pasado un objeto no válido. Se esperaba \u0027:\u0027 o \u0027}\u0027. (28): {IdInvestigaciones: 281,283, IdGestor: reerer}

    , copio mi codigo:

    js.

    var borrar = [281,283];
    $.ajax({
    type: «POST»,
    url: «Asignar.aspx/setGestorInvestigacion»,
    data: ‘{IdInvestigaciones: ‘ + borrar + ‘, IdGestor: ‘ + «reerer» + ‘}’,
    contentType: «application/json; charset=utf-8»,
    dataType: «json»,
    success: resultado,
    error: errores
    });

    c#
    public static int setGestorInvestigacion(int[] idInvestigaciones, string codigoGestor)
    {

    gracias por su ayuda.

    • Hola, Rafael.
      Para que funcione, como le estás especificando que lo que envías es json, deberías formatear el contenido como json.
      Creo que con algo como esto te debería funcionar:

      var borrar = [281,283];
      var datos = {
          IdInvestigaciones: borrar,
          IdGestor: "reerer"
      };
      $.ajax({
          type: "POST",
          url: "Asignar.aspx/setGestorInvestigacion",
          data: JSON.stringify(datos),
          contentType: "application/json; charset=utf-8",
          dataType: "json",
          success: resultado,
          error: errores
      });

      Suerte!

  17. Rafael Del Moral dice:

    gracias por tu ayuda…

    ahora me envia el error:
    Error: {«Message»:»Llamada al servicio Web no válida. Falta un valor para el parámetro: \u0027idInvestigaciones\u0027.»,»StackTrace»:» en System.Web.Script.Services.WebServiceMethodData.CallMethod(Object target, IDictionary`2 parameters)\r\n

    no logro comprender que estructura o tipo de datos recibo en el webmethod de C#.

    • Me parece que el problema es porque puse el nombre de las propiedades del JSON en mayúsculas, pero en C# las espera con minúsculas.
      Prueba con esto:

      var borrar = [281,283];
      var datos = {
          idInvestigaciones: borrar,
          codigoGestor: "reerer"
      };
      $.ajax({
          type: "POST",
          url: "Asignar.aspx/setGestorInvestigacion",
          data: JSON.stringify(datos),
          contentType: "application/json; charset=utf-8",
          dataType: "json",
          success: resultado,
          error: errores
      });

      Por favor comentame si te funcionó.
      Suerte!

    • Me olvidé de responderte la duda que tenias: lo que hace este código es tomar cada propiedad del JSON (lo que guardé en la variable «datos») y la pasa como un parámetro en la llamada al método de C#, por eso fijate que la variable tiene estos miembros:

      var datos = {
          idInvestigaciones: borrar,
          codigoGestor: "reerer"
      };

      que se tienen que llamar exáctamente igual a los parámetros que recibe C#:

      public static int setGestorInvestigacion(int[] idInvestigaciones, string codigoGestor)

      Suerte!

  18. Dionisio Carmona dice:

    muy buen aporte, justo lo que necesitaba.

  19. Juan Carlos dice:

    Muy buen ejemplo,, gracias me sirvio de ayuda!!

  20. Victor dice:

    Hola y gracias por el tutorial….nose si me podras ayudar con un problema con dialog. Trato de llamar una pagina en un dialogo.
    $(function () {
    var $dialogo = $(»)
    .load(«buscar.aspx»)
    .dialog({
    title: «Hola»,
    autoOpen: false,
    width: 500,
    height: 500,
    draggable: false,
    resizable: false,
    close:false
    });
    $(«#llamar»).click(function () {
    $dialogo.dialog(«open»);
    });
    });
    el problema es que se cierra de inmediato.como podria hacerlo para que no se cerrara.

    • Creo que es porque está vacío el selector, probá con algo así:

      $(function () {
          var $dialog = $('<div></div>')
              .html('<iframe style="border: 0px;" src="buscar.aspx" width="100%" height="100%"></iframe>')
              .dialog({
                  title: "Hola",
                  autoOpen: false,
                  modal: true,
                  height: 500,
                  width: 500,
                  draggable: false,
                  resizable: false,
                  close:false
              });
          $("#llamar").click(function () {
              $dialogo.dialog("open");
          });
      });
  21. Victor dice:

    Gracias man. Como me quedo.
    function Ventana() {
    var $dialog = $(»)
    .html(»)
    .dialog({
    title: «Busqueda»,
    autoOpen: false,
    modal: false,
    height: 300,
    width: 300,
    draggable: false,
    resizable: false
    });
    $dialog.dialog(«open»);
    };
    lo malo es que lo hago atravez de un Buscar.
    no se porque cuando lo hago con un boton no quiere funcionar. peo bueno.
    Muchas gracias por la ayuda.

  22. zz73ff dice:

    Hola Gustavo, acabo de utilizar tu código para verificar si existe un ID de un usuario en mi BD, obvio modificándolo un poco, y funciona perfecto!! Pero, quisiera hacer que hiciera la validación sin tener que darle clic a un botón, no sé, tal vez usando un onchange, pero no he podido hacerlo, si tienes alguna idea sobre como poder hacerlo utilizando el mismo código que tienes, solo quitando el botón jeje, me seria de mucha ayuda. Gracias de antemano y excelente aporte para los que no sabemos mucho de estos temas!!

    • Calculo que para eso podrías hacer que en el evento keyPress se llame al método setTimeout para que a los 2 segundos de pulsada la tecla llame a la verificación. Tené en cuenta que deberías guardar el id que te devuelve el setTimeout para que, cuando pulses de nuevo una tecla, llames al método clearTimeout con este identificador para que no se llame a la validación por cada tecla pulsada, sino sólo por la última.
      Debería ser algo así:

      var timeId = 0;
      $('#campo').keyPress(function(){
          if (timeId != 0)
              clearTimeout(intId);
          timeId = setTimeout(function(){ funcionValidacion(); }, 2000);
      });

      Después contame si te sirvió.
      Saludos.

  23. Clau Lira dice:

    Hola soy nueva en esto de Ext Js 3 y estoy teniendo algunos problemas para validar no se si alguien pueda ayudarme tengo que validar datos del lado de mi js y de mi método JSON pero al realizarlo en ambos me marca error que podria hacer en este caso
    gracias

  24. Jose dice:

    mis controles son de asp como lo hago no me funciona? ayuda

  25. ¿Es necesario que un método Web (WebMethod) le regrese algo al cliente o no?. Es decir ¿Puedo crear un método Web con la siguiente firma:
    [WebMethod]
    public static void MiMetodoWeb()?

  26. Alexis dice:

    interesante, ya lo apliqué y funciona de maravilla, pero, como podría hacerle para que, en vez de llamar un método del webform.aspx llame a un método de una clase.cs? ya lo intenté de mil y un maneras y no logro solucionarlos.
    Saludos.

    • Hola, Alexis.
      Lo que pedís no se puede hacer porque una clase «común» no tiene un punto de entrada desde Internet, en caso las páginas web, al igual que los servicios web, handlers, etc., si reciben las llamadas desde el IIS.
      Si el problema es que querés centralizar tus métodos tal vez podrías hacer un servicio WCF (el cual no tiene interfaz HTML como las páginas) para que responda a los llamados recibidos desde tu página.
      Otra opción (aunque me gusta más la opción anterior) es que crees un Handler que en lugar de devolver HTML devuelva un JSON.
      Espero te sirva.
      Saludos.

  27. Oscar Arevalo dice:

    Necesito ayuda, acabo de comenzar a programar javascript, ajax etc, tengo una clase llamada calcular en mi codigo de aspx.cs que contiene un metodo que calcula unos valores
    pero necesito mostrar ese resultado en un label el cual el codigo lo tengo en mi pagina de javascript,

Deja un comentario

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.