April 2012 - Artículos

¡Buenas! Este es un nuevo post de la serie C# Básico, que como su propio nombre indica trata sobre aspectos digamos elementales del lenguaje. Cada post es independiente y el orden de publicación no tiene porque ser el de lectura. Los temas los voy sacando de los foros o consultas que se me realizan :)

Hoy vamos a tratar un tema que veo que causa mucha confusión: el paso de parámetros por referencia. Como en todos los posts de esta serie lo haremos de forma didáctica y paso a paso.

1. Paso por valor

Para entender que es el paso por referencia, primero es necesario ver que significa el paso por valor. Que salida genera este programa?

static void Main(string[] args)

{

    var inicial = 10;

    Incrementa(inicial);

    Console.WriteLine("Valor DESPUES de incrementar es " + inicial);

}

static void Incrementa(int num)

{

    num = num + 1;

}

El sentido común dice que el programa imprimirá “Valor DESPUES de incrementar es 11”, pero la realidad es otra:

image

¿Como es posible esto? Pues porque la variable incial ha sido pasada por valor. Pasar una variable por valor significa hacer una copia de dicha variable (de ahí el nombre, ya que se pasa el valor y no la variable en sí). De este modo el parámetro num toma el valor de la variable inicial, es decir 10. Pero num es una copia de inicial, así que modificar num, no modifica para nada inicial. Al salir del método, efectivamente num vale 11 pero inicial continua valiendo 10 (además al salir del método la variable num es destruída ya que su alcance es de método).

2. Paso de objetos

Veamos ahora el siguiente código, donde en lugar de pasar un entero, pasamos un objeto de la clase Foo que tiene una propiedad entera:

class Foo

{

    public int Bar { get; set; }

}

 

class Program

{

    static void Main(string[] args)

    {

        var inicial = new Foo();

        inicial.Bar = 10;

        Incrementa(inicial);

        Console.WriteLine("Valor DESPUES de incrementar es " + inicial.Bar);

    }

    static void Incrementa(Foo foo)

    {

        foo.Bar = foo.Bar + 1;

    }

}

¿Cual es la salida del programa ahora? Si nos basamos en lo que vimos en el punto anterior deberíamos responder que va a imprimir “Valor DESPUES de incrementar es 10”, ya que el parámetro foo debería ser una copia de inicial y por lo tanto modificar foo no debe afectar para nada a inicial.

Pero la realidad es otra:

image

Pasar un objeto no crea una copia del objeto. Es por eso que decimos que los objetos no se pasan por valor, se pasan por referencia. Así pues modificar un objeto desde un método modifica el objeto original. No hay manera en C# de pasar una copia entera de un objeto entre métodos (a no ser que se haga manualmente).

Nota: En el código anterior, simplemente modifica “class” por “struct” cuando declaramos Foo. ¿Qué ocurre entonces? Pues que el programa ahora muestra “Valor DESPUES de incrementar es 10”. ¿A que es debido esto? Pues a que las estructuras se pasan por valor (¡es decir se copia su contenido!). Ver el punto (4) para más detalles.

Pero si nos quedamos en este punto obviamos una pregunta muy importante: Efectivamente los objetos se pasan por referencia… ¿pero las propias referencias como se pasan? Pues la respuesta es que las referencias se pasan por valor. Es decir la referencia foo es una copia de la referencia inicial. Pero copiar una referencia no es copiar su contenido (el objeto). Copiar una referencia significa que ahora tenemos dos referencias distintas que apuntan al mismo objeto. Por ello debemos tener muy claro que no es lo mismo modificar el contenido de una referencia (el objeto) que modificar la referencia misma. Si modificamos el contenido (es decir una propiedad del objeto apuntado, en este caso la propiedad Bar), este cambio es compartido ya que ambas referencias apuntan al mismo objeto. Pero si modificamos la referencia este cambio no será compartido:

static void Main(string[] args)

{

    var inicial = new Foo();

    inicial.Bar = 10;

    Incrementa(inicial);

    Console.WriteLine("Valor DESPUES de incrementar es " + inicial.Bar);

}

static void Incrementa(Foo foo)

{

    int valor = foo.Bar;

    foo = new Foo();

    foo.Bar = valor + 1;

}

Este código modifica la referencia foo. No modifica el contenido, modifica la referencia ya que asigna un nuevo objeto a la referencia foo. Al salir del método tenemos:

  1. Una referencia (inicial) que apunta a un objeto
  2. Otra referencia (foo) que apunta a un objeto nuevo
  3. El valor de la propiedad “Bar” del objeto apuntado por inicial es 10.
  4. El valor de la propiedad “Bar” del objeto apuntado por foo es 11.

Al salir del método la referencia foo se pierde, y su contenido (el objeto cuya propiedad Bar vale 11) al no ser apuntado por ninguna otra referencia será destruido por el Garbage Collector. Y efectivamente ahora la salida del programa es:

image

Si entiendes la diferencia entre modificar una referencia y modificar el contenido de una referencia, entonces ya estás listo para el siguiente punto…

3. El paso por referencia

En el punto anterior hemos visto que los objetos se pasan por referencia, pero las propias referencias se pasan por valor. Así que la pregunta obvia es: ¿hay alguna manera de pasar las referencias por referencia?

Y la respuesta es sí: usando la palabra clave ref:

static void Main(string[] args)

{

    var inicial = new Foo();

    inicial.Bar = 10;

    Incrementa(ref inicial);

    Console.WriteLine("Valor DESPUES de incrementar es " + inicial.Bar);

    Console.ReadLine();

}

static void Incrementa(ref Foo foo)

{

    int valor = foo.Bar;

    foo = new Foo();

    foo.Bar = valor + 1;

}

Fíjate que ref debe usarse tanto al declarar el parámetro como al invocar al método. El uso de ref significa que queremos pasar el parámetro por referencia. Si el parámetro es un objeto (como el caso que nos ocupa), ref no significa “pasar el objeto por referencia”, pues eso se hace siempre (como hemos visto en el punto (2)). En este caso ref significa “pasar la referencia por referencia”.

Es por ello que ahora foo y inicial son la misma referencia. Dado que son la misma referencia, forzosamente las dos deben apuntar al mismo objeto. Por ello cuando hacemos foo = new Foo(); estamos modificando la referencia foo haciendo que apunte a otro objeto distinto. Pero si foo y inicial son la misma referencia, al modificar foo modificamos inicial, por lo que ahora al salir del método Incrementa:

  1. La referencia foo apunta a un objeto nuevo cuya propiedad Bar vale 11.
  2. La referencia inicial, dado que es la misma que foo, apunta al mismo objeto.
  3. El antiguo objeto (el que su valor Bar valía 10) al no estar apuntado por ninguna referencia, será destruído por el Garbage Collector.

Por eso, ahora la salida del programa es:

image

4. Paso por referencia de tipos por valor

En terminología de .NET llamamos tipos por valor aquellos tipos que no son pasados por referencia. Así los objetos no son tipos por valor, ya que hemos visto en el punto (2) que se pasan por referencia. Pero p.ej. un int es un tipo por valor, ya que hemos visto en el punto (1) que se pasa por valor.

En general son tipos por valor todos los tipos simples (boolean, int, float,…), los enums, las estructuras (como DateTime). No son tipos por valor los objetos (¡ojo, que eso incluye a string!). Hay una forma sencilla de saber si un tipo es por referencia o por valor: si admite null es por referencia y si no admite tener el valor null es por valor.

Pues bien, ref puede usarse para pasar por referencia un tipo por valor, como p.ej. un int:

static void Main(string[] args)

{

    var inicial = 10;

    Incrementa(ref inicial);

    Console.WriteLine("Valor DESPUES de incrementar es " + inicial);

}

static void Incrementa(ref int valor)

{

    valor = valor + 1;

}

Como ya debes suponer ahora la salida del programa es:

image

En este caso la variable valor no es una copia de la variable inicial. Ambas variables son la misma, por lo que al modificar valor, estamos modificando también inicial. Por ello al salir del método, el valor de inicial sigue siendo 11.

5. Resumen

En resumen, hay cuatro puntos a tener en cuenta:

  1. Los tipos simples, estructuras y enums se pasan por valor (de ahí que digamos que son tipos por valor). Es decir, se pasa una copia de su contenido.
  2. Los objetos se pasan por referencia. Pero la referencia se pasa por valor, es decir el método recibe otra referencia que apunta al mismo objeto.
  3. La palabra clave “ref” permite pasar un parámetro por referencia.
    1. Si este parámetro es un tipo por valor se pasa por referencia, es decir no se pasa una copia sino que se pasa la propia variable.
    2. Si este parámetro es un objeto, lo que se pasa por referencia es la propia referencia.

¡Espero que este post os haya aclarado un poco el tema de paso por valor y paso por referencia! ;-)

con 4 comment(s)
Archivado en: ,

Yeah!!!

Nada, cuatro palabras para decir que este sábado 21 de Abril estaré en Andorra, en el primer Geek-â-Paloozaaa compartiendo charlas con otros auténticos monstruos.

Yo voy a hablar sobre async y await, lo que en el fondo me va a dar una excusa perfecta para aburriros a todos sobre el desarrollo de aplicaciones asíncronas en .NET y como ha ido evolucionando el tema a lo largo de las versiones del framework…

El bueno de Lluís, el alma mater de todo este meollo, ha tenido en consideración de colocarme en el último lugar de las charlas, justo cuando el hambre os asole y estéis por tanto despiertos y así obligados a escuchar mi charla!!! :)

Luego iremos a comer y por la tarde/noche un Geek-and-Beers en algún bareto de Andorra, donde supongo que podremos ver el clásico entre el Barça y el Madrid (¿eh Lluíiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiis?)… Bueno, si no podemos no pasa nada… mientras haya cervezas y buena compañía (me refiero a geeks, ¡malpensados!).

Si estás cerca de Andorra, pásate por donde se celebra el evento! Te aseguro que valdrá la pena (aunque solo sea para vernos con las camisetas que nos ha preparado el tito Lluís! :P).

Nos vamos a divertir! ;-)

con 1 comment(s)
Archivado en:

Jejejee… Sí, aunque no lo parezca a veces hago temillas con Webforms, y es que uno tiene que conocer al enemigo! :P

Lo que voy a comentar hoy, es como forzar un postback desde un control propio. Una búsqueda en google da varios resultados, pongo un par de ejemplo:

  1. http://tratadooscuro.blogspot.com.es/2009/02/dopostback-ese-gran-desconocido.html
  2. http://programacion.porexpertos.es/provocar-un-postback-desde-javascript-con-aspnet/

Ambos ejemplos dicen lo mismo pero lo cierto es que, en mi opinión, hay una manera ligeramente mejor que hacerlo, pero parece que se desconoce bastante porque buscando en google aparecen menos resultados.

Bueno, si miráis los dos enlaces que he puesto arriba, forzar un postback desde un control propio es tan simple como llamar a __doPostback. Este método lo añade automáticamente Webforms cuando lo necesita.

Si seguimos las instrucciones de cualquiera de los dos enlaces anteriores, si queremos generar un control que sea p.ej. un enlace que al pulsarlo genere un postback vemos que debemos usar un código como este:

[DefaultProperty("Text")]

[ToolboxData("<{0}:MyControl runat=server></{0}:MyControl>")]

public class MyControl : WebControl

{

    [Bindable(true)]

    [Category("Appearance")]

    [DefaultValue("")]

    [Localizable(true)]

    public string Text

    {

        get

        {

            String s = (String)ViewState["Text"];

            return s ?? string.Empty;

        }

 

        set

        {

            ViewState["Text"] = value;

        }

    }

 

    protected override void RenderContents(HtmlTextWriter output)

    {

        output.AddAttribute(HtmlTextWriterAttribute.Href,

            "BLOCKED SCRIPT__doPostBack('','');");

        output.RenderBeginTag(HtmlTextWriterTag.A);

        output.Write(Text);

        output.RenderEndTag();

    }

}

Si creamos una Webform vacío y añadimos el control y probamos la página, efectivamente se renderiza el tag <a> que incluye la llamada a __doPostBack. Pero fijaos que este es el código HTML generado por el Webform:

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

 

</title></head>

<body>

    <form name="form1" method="post" action="default.aspx" id="form1">

<div>

<input type="hidden" name="__VIEWSTATE" id="__VIEWSTATE" value="/wEPDwUKLTk1OTYxMjkxMmRkGui/AV2Pv2fTTQJWmL8w2grZiZY=" />

</div>

 

    <div>

 

        <span id="MyControl1"><a href="BLOCKED SCRIPT__doPostBack('','');">Demo</a></span>

 

    </div>

    </form>

</body>

</html>

¿Alguien ve el método __doPostBack? No, ¿verdad? Eso es porque Webforms no lo ha generado, y no lo ha generado porque no sabe que alguien va a usarlo. La verdad es que Webforms solo genera este método cuando sabe que algún control lo requiere. En páginas medio complejas la probabilidad de que algún control requiera postback es tan grande que por eso mucha gente cree que siempre se genera. Pero no es así.

Poner código “a saco” para llamar a __doPostBack funciona en la mayoría de casos pero no es una buena práctica porque estamos usando una característica interna de Webforms. Si en la siguiente release de Webforms Microsoft decide renombrar esta función todo nuestro código se viene abajo.

Vale… entonces si no podemos llamara a __doPostBack a saco, que debemos hacer? Pues usar el método GetPostBackClientHyperlink de la clase ClientScriptManager. Para obtener una instancia de ClientScriptManager se puede usar la propiedad ClientScript del objeto Page:

protected override void RenderContents(HtmlTextWriter output)

{

    output.AddAttribute(HtmlTextWriterAttribute.Href,

        this.Page.ClientScript.GetPostBackClientHyperlink(this, ""));

    output.RenderBeginTag(HtmlTextWriterTag.A);

    output.Write(Text);

    output.RenderEndTag();

}

¡Listos! Ahora si ejecutamos de nuevo el Webform, vemos que el código generado es:

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

 

</title></head>

<body>

    <form name="form1" method="post" action="default.aspx" id="form1">

<div>

<input type="hidden" name="__VIEWSTATE" id="__VIEWSTATE" value="/wEPDwUKLTk1OTYxMjkxMmRkGui/AV2Pv2fTTQJWmL8w2grZiZY=" />

</div>

 

<div>

 

    <input type="hidden" name="__EVENTTARGET" id="__EVENTTARGET" value="" />

    <input type="hidden" name="__EVENTARGUMENT" id="__EVENTARGUMENT" value="" />

</div>

    <div>

 

        <span id="MyControl1"><a href="BLOCKED SCRIPT__doPostBack('MyControl1','')">Demo</a></span>

 

    </div>

 

<script type="text/javascript">

//<![CDATA[

var theForm = document.forms['form1'];

if (!theForm) {

    theForm = document.form1;

}

function __doPostBack(eventTarget, eventArgument) {

    if (!theForm.onsubmit || (theForm.onsubmit() != false)) {

        theForm.__EVENTTARGET.value = eventTarget;

        theForm.__EVENTARGUMENT.value = eventArgument;

        theForm.submit();

    }

}

//]]>

</script>

 

</form>

</body>

</html>

Por un lado ahora Webforms nos ha generado el __doPostBack y por otro como podemos ver el el atributo href de nuestro enlace tenemos la llamada a doPostBack correcta (por norma general el primer parámetro es el ID del control que realiza la llamada).

Y ya que sabemos generar PostBacks… ¿como recibirlos?

Hemos visto como generar un PostBack desde nuestro control, ahora… como podemos decirle a Webforms que nuestro control quiere enterarse de los postbacks que él haya efecutado?

Pues para esto basta con implementar la interfaz IPostBackEventHandler que tiene un solo método:

public interface IPostBackEventHandler

{

    void RaisePostBackEvent(string eventArgument);

}

Esta interfaz nos permite hacer algo cuando hay un postback generado por el propio control. Por norma general lo que se hace es lanzar un evento de .NET (p.ej. Click). Vamos a ver un ejemplo muy rápido. Para ello añadimos un evento Click a nuestro control:

public event EventHandler Click;

protected virtual void OnClicked()

{

    var handler = Click;

    if (handler != null) handler(this, EventArgs.Empty);

}

Ahora implementamos la interfaz IPostBackEventHandler y el método RaisePostBackEvent:

public void RaisePostBackEvent(string eventArgument)

{

    OnClicked();

}

Cuando ahora se pulse nuestro enlace se generará un evento Click que podemos capturar desde el Webform, como cualquier otro evento.

Además fijaos que RaisePostBackEvent recibe una cadena (eventArgument). El valor de esta cadena es el valor del segundo parámetro de GetClientPostBackHyperlink que hemos usado para generar la llamada a __doPostBack. De esta manera podemos crear controles que lancen varios eventos en función del valor de eventArgument.

Un saludo!

con 1 comment(s)
Archivado en:

Bueno… este es un post por “encargo”… Hoy he recibido un tweet de @JanoRuiz que decía lo siguiente: Hola, Saludos, una Consulta, Como Hacer Un Catalogo En asp.net mvc3 Sin Usar BD, Hacer Altas, Bajas y Modificaciones.

Bueno, vamos a explorar algunas “formas de hacerlo”… :D

Vamos a utilizar el siguiente modelo para representar los productos:

  1. public class Producto
  2. {
  3.     public string Nombre { get; set; }
  4.     public int Precio { get; set; }
  5. }

Y el catálogo en sí:

  1. public class Catalogo
  2. {
  3.     private readonly string _nombre;
  4.     private readonly List<Producto> _productos;
  5.  
  6.     public Catalogo()
  7.     {
  8.         _productos = new List<Producto>();
  9.     }
  10.  
  11.     public IEnumerable<Producto> Productos
  12.     {
  13.         get { return _productos; }
  14.     }
  15.  
  16.     public void AnyadirProducto(Producto p)
  17.     {
  18.         _productos.Add(p);
  19.     }
  20. }

Opción 1. Usar la sesión

Bueno, la opción más sencilla es usar la sesión para mantener nuestro catálogo. Para ello directamente creamos el catálogo cuando se crea la sesión, añadiendo el siguiente código en global.asax.cs:

  1. protected  void Session_Start()
  2. {
  3.     Session["catalogo"] = new Catalogo();
  4. }

La sesión se crea en la primera petición que realiza el usuario a nuestra web y está vinculada al usuario (usuario en este contexto = ventana de navegador, por norma general).

Ahora cuando queramos acceder al catálogo:

  1. [HttpPost]
  2. public ActionResult Nuevo(Producto producto)
  3. {
  4.     var catalogo = Session["Catalogo"] as Catalogo;
  5.     catalogo.AnyadirProducto(producto);
  6.     return RedirectToAction("Index");
  7. }

Y listos!

Un par de consideraciones sobre el uso de sesión:

  1. No es persistente. Es decir, cuando el usuario cierre la ventana del navegador y vuelva más tarde a nuestra web, habremos perdido los datos. Si queremos hacer los datos persistentes hay que guardarlos en algún almacenamiento persistente, esto es… una base de datos :P (aunque podría ser un fichero en el servidor también).
  2. Se crea incluso si el usuario no está autenticado. Si queremos que tan solo los usuarios autenticados tengan catálogos, podemos crear el catálogo justo cuando autenticamos al usuario (y por supuesto tener protegidos con [Authorize] las acciones correspondientes de los controladores que acceden a la sesión).

Por lo tanto, la sesión nos evita el uso de una BBDD solo mientras el usuario navega por nuestra aplicación, pero si queremos que sea persistente deberemos usar un almacenamiento externo que persista (o sea, una BBDD).

Opcion 2: Serializar los datos en cada petición

De acuerdo, la sesión la podemos usar pero es un recurso digamos… caro. Tiene ciertas implicaciones en escalabilidad y tolerancia a fallos así que quizá no queremos o podemos usarla. Hay alguna manera de mantener el estado como si tuviéramos sesión? Pues sí, serializar los datos de la sesión en cada petición y colocarlas en un campo hidden. ¿Te suena a algo esto? ¿Cómo? ¿Quien ha dicho viewstate? Premio!

Vamos a ver una prueba de concepto de esto. Para empezar necesitamos una clase que dado un objeto nos devuelva su representación en Base64 y vicerversa:

  1. public class ObjectToBase64
  2. {
  3.  
  4.     public static string ToBase64(object source)
  5.     {
  6.         var base64 = string.Empty;
  7.         if (source != null)
  8.         {
  9.             using (var ms = new MemoryStream())
  10.             {
  11.                 var formatter = new BinaryFormatter();
  12.                 formatter.Serialize(ms, source);
  13.                 var buffer = ms.ToArray();
  14.                 base64 = Convert.ToBase64String(buffer);
  15.             }
  16.         }
  17.         return base64;
  18.     }
  19.  
  20.     public static object FromBase64 (string base64)
  21.     {
  22.         if (string.IsNullOrEmpty(base64)) return null;
  23.         var buffer = Convert.FromBase64String(base64);
  24.         using (var ms = new MemoryStream(buffer))
  25.         {
  26.             var formatter = new BinaryFormatter();
  27.             return formatter.Deserialize(ms);
  28.         }
  29.     }
  30. }

La idea ahora es que:

  1. Cada acción tiene un parámetro adicional (p.ej. llamado _viewstate).
  2. Este parámetro debe ser pasado en todas las llamadas, ya sea via GET o via POST.

Así, si p.ej. en una vista tenemos un formulario, podemos hacer:

  1. @if (ViewBag.ViewState != null)
  2. {
  3.     <input type="hidden" name="_viewstate" value="@ViewBag.ViewState"/>
  4. }

Y si generamos un enlace:

@Html.ActionLink("Nuevo", "Nuevo", new {_viewstate = ViewBag.ViewState})

Del mismo modo en el caso de una redirección de un controlador a otro:

return RedirectToAction("Index", new { _viewstate = ObjectToBase64.ToBase64(catalogo) });

Bueno… creo que la idea se entiende, no? ;-)

En el fondo lo que estamos haciendo es serializar la “sesión” cada vez y mandarla arriba y abajo. Hacerlo via GET (como en el ActionLink o en RedirectToAction) hace que esta sesión sea visible en la URL:

image

Lo ideal es usar siempre POST (que es lo que hace Webforms). Esta técnica se puede mejorar bastante, pero bueno… como prueba de concepto no está mal. Si alguna vez queréis usar esta técnica echad un vistazo al post del Maestro: http://www.variablenotfound.com/2010/07/un-viewstate-en-aspnet-mvc.html

He de decir que a mi esta técnica no me gusta nada, salvo que no sea para casos muy, muy, muy puntuales. Y por supuesto tiene el mismo “problema” que la sesión: no es persistente.

Y así llegamos a la opción 3…

Opción 3 – LocalStorage

Bueno… en la Opción 1 se trata de mantener el estado en el servidor y la Opción 2 mantiene el estado en el cliente pasándolo a través de campos hidden o en la URL. Ahora toca la tercera opción: mantener el estado en el cliente. No solo eso, sino que además lo haremos persistente. ¿Y eso como? Pues… usaremos un almacenamiento persistente… En este caso el Local Storage.

Local Storage es básicamente un almacén de objetos en el navegador. Llámalo base de datos si quieres, pero ten presente que no hay tablas, ni filas, ni columnas. Se trataría más bien de un diccionario (clave, valor). ¿Y qué guardas? Pues cadenas. Simple y llanamente cadenas.

La idea es la siguiente:

  1. El catálogo se guarda en el local storage del cliente
  2. El cliente añade el producto en el almacén

Todo se realiza en cliente, no hay comunicación en el servidor. Evidentemente en una aplicación real antes de añadir el producto en cliente, este preguntaría al servidor (mediante una llamada ajax) si puede añadirlo. Pero en nuestra prueba de concepto no lo haremos… :P

La prueba de concepto es muy, sencilla, en la página de Index accedemos al localStorage, lo recorremos y ponemos su contenido en una lista:

  1. <h2>Index</h2>
  2. <script type="text/javascript">
  3.     $(document).ready(function () {
  4.         for (var x = 0; x <= localStorage.length - 1; x++) {
  5.             var key = localStorage.key(x);
  6.             var value = JSON.parse(localStorage.getItem(key));
  7.             $("#lstCatalog").append(
  8.                 $("<li>").append(value.nombre + " " + value.precio)
  9.             );
  10.         }
  11.     });
  12.  
  13.  
  14. </script>
  15.  
  16. <ul id="lstCatalog">
  17.     
  18. </ul>

Fijaos en el uso de JSON.parse para pasar la cadena JSON que guardamos en el localStorage a un objeto javascript. El resto del código se entiende bastante bien, ¿no?

Y la vista de alta? Bueno, pues se trata de un formulario de alta normal, salvo que el botón de enviar no es un botón de submit sino un botón normal. Y luego tenemos el siguiente código js:

  1. <script type="text/javascript">
  2.     $(document).ready(function () {
  3.         $("#btnAdd").click(function () {
  4.             var nombre = $("#Nombre").val();
  5.             var precio = $("#Precio").val();
  6.             localStorage.setItem(nombre, JSON.stringify({ nombre: nombre, precio: precio }));
  7.             location.href = "@Url.Action("Index")";
  8.         });
  9.     });
  10. </script>

(btnAdd es el ID del botón del formulario).

Es simple: accedemos al valor del input “Nombre” y del “Precio”. Construimos un objeto javascript, lo pasamos a cadena con JSON.stringify y lo guardamos en el localStorage. Para la clave usamos el nombre del producto. Es aquí, donde antes de insertar el elemento en el localStorage, nos iriamos al servidor via Ajax para confirmar que podemos añadirlo.

Al final nos volvemos a la página de Index.

Y ya hemos terminado. Como prueba de concepto, no está mal: 5 minutos para tener una lista de productos en el cliente. El servidor ni se ha enterado, este es el código del controlador:

  1. public class LocalStorageController : Controller
  2. {
  3.     public ActionResult Index()
  4.     {
  5.         return View();
  6.     }
  7.  
  8.     public ActionResult Nuevo()
  9.     {
  10.         return View();
  11.     }
  12. }

Por supuesto falta soporte para eliminar un elemento del localStorage, pero eso es trivial usando los métodos clear() (borra todo el localStorage) y removeItem (borra un elemento).

Nota: localStorage es persistente. Es decir, entre ejecución y ejecución de la aplicación (cerrar el browser y abrirlo de nuevo mañana) los datos se mantienen. Viene a ser como una cookie pero con esteroides. Si quieres usar un localStorage “no persistente” (que dure solo mientras la ventana está abierta” existe el sessionStorage. Además recuerda que localStorage, al estar en el cliente, es totalmente hackeable.

En resumen: Realizar un catálogo en MVC sin base de datos en el servidor es posible si queremos que los datos no sean persistentes. Pero si queremos que esos sean persistentes (que el usuario pueda cerrar el navegador, abrirlo y continuar viendo sus datos) debe usarse una BBDD (o algún otro almacenamiento persistente) sí o sí. Hasta antes de HTML5 este almacenamiento persistente tenía que estar en el servidor. Con HTML5 y localStorage puede estar en el cliente.

Nota final:

Además de esas tres opciones se me ocurren al menos DOS más para realizar lo mismo:

  1. Usar cookies (seria la versión pre-html5 de usar el localStorage, pero hay serias limitaciones en el tamaño de los datos a guardar).
  2. Usar indexedDB (sería parecido en filosofía a usar el localStorage).

Algunos links:

  1. WebStorage (localStorage y sessionStorage): http://dev.w3.org/html5/webstorage/
  2. Sesión en ASP.NET MVC (en mi blog): http://geeks.ms/blogs/etomas/archive/2010/06/30/asp-net-mvc-q-amp-a-c-243-mo-usar-la-sesi-243-n.aspx
  3. IndexedDB: http://www.w3.org/TR/IndexedDB/

Espero que el ejercicio os haya sido de interés! ;-)

PD: Os dejo un zip con el código de las opciones 2 y 3 en https://skydrive.live.com/redir.aspx?cid=6521c259e9b1bec6&resid=6521C259E9B1BEC6!233&parid=6521C259E9B1BEC6!167&authkey=!AHLp6PeH4Uw1hEg

con no comments
Archivado en: ,

Buenas :)

Al estilo de muchos blogs que visito habitualmente y que proponen pequeños acertijos en base a un código que tiene un error (muchas veces no aparente, otras más evidente), os propongo hoy uno, que me he encontrado revisando código.

Así que, amigos ¿qué hay de malo en este código?

if (!File.Exists(fname))

{

    File.Create(fname);

}

// Hacemos lo que sea...

Sencillo, ¿no? Si el fichero no existe lo creamos y luego hacemos lo que se supone que tengamos que hacer. Parece sencillo, pero hay algo que se nos escapa… ¿Qué puede ser?

Un saludo!

con 10 comment(s)
Archivado en: ,