tag:blogger.com,1999:blog-8978743476760113872024-03-19T20:18:38.971+01:00burbujas en .NETMi blog sobre desarrollo en .NET :)eiximenishttp://www.blogger.com/profile/02568454383791432108noreply@blogger.comBlogger147125tag:blogger.com,1999:blog-897874347676011387.post-14072966465032241162011-08-10T18:25:00.001+02:002011-08-10T18:25:47.602+02:00C# Básico: Objetos y referencias<p>La verdad es que ahora hacía bastantes meses que no publicaba nada de la serie “C# básico”. En esta serie pongo posts sobre temas básicos del lenguaje. No es un libro por fascículos, ni un tutorial al uso puesto que los posts no tienen orden en concreto y <em>nacen</em> a partir de inquietudes que observo (mayoritariamente en los foros, pero también por correos que recibo). Todos <a href="http://geeks.ms/blogs/etomas/archive/tags/c_2300_+basico/default.aspx" target="_blank">los posts de esta serie los podéis ver aquí</a>.</p> <p>En el post de hoy quiero hablar de la diferencia entre objetos y referencias ya que observo que no siempre está clara. Gente que entiende los conceptos básicos de <em><a href="http://geeks.ms/blogs/etomas/archive/2010/07/14/c-b-225-sico-191-que-es-la-herencia.aspx" target="_blank">herencia</a></em> parece liarse en este punto. Muchas veces es un tema pasado rápidamente en muchos libros y tutoriales. Y es que, la verdad, es un tema muy sencillo… ;-) </p> <div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: 'Courier New', courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px" id="codeSnippet">MiClase miObjeto = <span style="color: #0000ff">new</span> MiClase();</pre><br /><br /> <br /></div><br /><br /><p>¿Qué hace este código? En muchos sitios leerás que lo que hace es crear un objeto de la clase <em>MiClase</em>. Eso es cierto, pero describe lo que hace lo que hay <em>a la derecha</em> del símbolo de asignación. Qué hace el código que está a la <em>izquierda</em>? Pues lo que hace es <em>declarar una referencia de tipo MiClase</em>. Otra palabra que se usa muchas veces en lugar de referencia es <em>variable</em> aunque no son técnicamente lo mismo (hay variables que no son referencias y las referencias pueden asignarse a otros elementos que no llamamos usualmente variables como p.ej. los parámetros a una función).</p><br /><br /><p>Las referencias contienen objetos. Yo prefiero decir que las referencias <em>apuntan a</em> objetos (aunque esta palabra parece como “maldita”, sin duda por culpa de los punteros) para que quede claro que <strong>un mismo objeto puede estar contenido en (apuntado por) más de una referencia</strong>.</p><br /><br /><p><strong>El tipo de una referencia</strong></p><br /><br /><p>Todas las refencias tienen un tipo. Este tipo <strong>es único e inmutable durante toda la vida de la referencia.</strong> El tipo de una referencia determina que objetos puede contener dicha referencia. En concreto:</p><br /><br /><ol><br /> <li>Objetos del mismo tipo. Es decir, una referencia de tipo MiClase puede contener objetos de la clase MiClase. </li><br /><br /> <li>Objetos de una clase derivada de la clase del tipo de la referencia. Si la referencia es de tipo MiClase puede contener objetos de cualquier clase derivada de MiClase. </li><br /><br /> <li>Objetos de cualquier clase que implemente el tipo de la referencia. Eso aplica sólo si el tipo de la referencia es una interfaz. En este caso la referencia puede contener un objeto de cualquier clase que implemente dicha interfaz. </li><br /></ol><br /><br /><p>Todas las clases en .NET <strong>derivan de Object</strong>. Por lo tanto, según el punto (2) una referencia de tipo Object, puede contener cualquier objeto de cualquier clase:</p><br /><br /><div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: 'Courier New', courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"><br /> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px" id="codeSnippet">Object objeto = <span style="color: #0000ff">new</span> CualquierClase();</pre><br /><br /> <br /></div><br /><br /><p>¿Condiciona alguna cosa más el tipo de la referencia? Pues sí: el tipo de la referencia condiciona <em>como vemos al objeto contenido en dicha referencia.</em> Es decir, la referencia <em>es como un disfraz</em> para el objeto. Le permite “ocultar su tipo real” y mostrarse como el “tipo de la referencia”.</p><br /><br /><p>P.ej. dado el siguiente código:</p><br /><br /><div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: 'Courier New', courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"><br /> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px" id="codeSnippet"><span style="color: #0000ff">class</span> MiClase<br />{<br /> <span style="color: #0000ff">public</span> <span style="color: #0000ff">void</span> Foo() {}<br />}<br /><br /><span style="color: #0000ff">class</span> MiClaseDerivada : MiClase<br />{<br /> <span style="color: #0000ff">public</span> <span style="color: #0000ff">void</span> Bar() {}<br />}<br /><br />MiClase c1 = <span style="color: #0000ff">new</span> MiClaseDerivada();<br />MiClaseDerivada c2 = <span style="color: #0000ff">new</span> MiClaseDerivada();</pre><br /><br /> <br /></div><br /><br /><p>Podemos ver como <em>MiClase</em> define un método (Foo) y <em>MiClaseDerivada</em> que deriva de MiClase añade el método Bar. Luego c1 es una referencia de tipo MiClase que contiene un objeto de MiClaseDerivada (puede según el punto 2 anterior). Y c2 es una referencia de tipo MiClaseDerivada que contiene un objeto de MiClaseDerivada (posible según el punto 1 anterior). Entonces tenemos que:</p><br /><br /><div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: 'Courier New', courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"><br /> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px" id="codeSnippet">c1.Foo(); <span style="color: #008000">// Ok.</span><br />c1.Bar(); <span style="color: #008000">// No compila.</span><br />c2.Foo(); <span style="color: #008000">// Ok.</span><br />c2.Bar(); <span style="color: #008000">// Ok.</span><br /></pre><br /><br /> <br /></div><br /><br /><p>La llamada c1.Bar() <strong>no compila</strong>. ¿Por que? Pues simplemente porque la referencia es de tipo MiClase. Y MiClase no tiene ningún método Bar. Da igual que el objeto contenido por dicha referencia sea de tipo MiClaseDerivada, que sí que tiene el método Bar. El <strong>compilador no se fija en los tipos de los objetos. Se fija en los tipos de las referencias</strong>.</p><br /><br /><p><strong>Objetos compartidos</strong></p><br /><br /><p>Como hemos dicho antes un mismo objeto puede estar contenido por más de una referencia:</p><br /><br /><div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: 'Courier New', courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"><br /> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px" id="codeSnippet">MiClaseDerivada c1 = <span style="color: #0000ff">new</span> MiClaseDerivada();<br />MiClase c2 = c1;<br /></pre><br /><br /> <br /></div><br /><br /><p>En este punto <strong>tenemos dos referencias. Pero un sólo objeto</strong>. Es decir, c1 y c2 contienen el mismo objeto, que es un objeto de tipo MiClaseDerivada. Si accedo al objeto a través de c1 lo veo como un objeto de tipo MiClaseDerivada (ya que este es el tipo de c1). Por otro lado si accedo al objeto a través de c2 lo veo como un objeto de tipo MiClase (al ser este el tipo de c2). Por lo tanto c1.Bar() es correcto y c2.Bar() no compila.</p><br /><br /><p>Pero insisto: son el <strong>mismo</strong> objeto. Observad el siguiente código:</p><br /><br /><div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: 'Courier New', courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"><br /> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px" id="codeSnippet"><span style="color: #0000ff">class</span> Program<br /> {<br /> <span style="color: #0000ff">public</span> <span style="color: #0000ff">static</span> <span style="color: #0000ff">void</span> Main()<br /> {<br /> MiClaseDerivada c1 = <span style="color: #0000ff">new</span> MiClaseDerivada();<br /> MiClase c2 = c1;<br /> c1.Incrementar();<br /> c2.Incrementar();<br /> Console.WriteLine(<span style="color: #006080">"El valor de c1 es:"</span> + c1.Valor);<br /> Console.WriteLine(<span style="color: #006080">"El valor de c2 es:"</span> + c2.Valor);<br /> }<br /> }<br /><br /><br /> <span style="color: #0000ff">class</span> MiClase<br /> {<br /> <span style="color: #0000ff">private</span> <span style="color: #0000ff">int</span> valor;<br /><br /> <span style="color: #0000ff">public</span> <span style="color: #0000ff">int</span> Valor { get { <span style="color: #0000ff">return</span> <span style="color: #0000ff">this</span>.valor; } }<br /> <span style="color: #0000ff">public</span> <span style="color: #0000ff">void</span> Incrementar()<br /> {<br /> valor++;<br /> }<br /> }<br /><br /> <span style="color: #0000ff">class</span> MiClaseDerivada : MiClase<br /> {<br /> <span style="color: #008000">// Código</span><br /> }</pre><br /><br /> <br /></div><br /><br /><p>¿Cual es la salida por pantalla de dicho código? Pensadlo con detenimiento. Pues  la siguiente:</p><br /><br /><p><font face="Courier New">El valor de c1 es:2 <br /> <br />El valor de c2 es:2</font></p><br /><br /><p>Eso es debido porque c1 y c2 contienen el mismo objeto. Por lo tanto inicialmente tenemos que el valor de dicho objeto es 0. Al llamar a c1.Incrementar() el valor pasa a ser 1. Y al llamar a c2.Incrementar(), el valor pasa a ser 2, ya que el objeto que contiene c2 <strong>es el mismo</strong> que el objeto que contiene c1.</p><br /><br /><p>Así pues recordadlo siempre: <strong>Asignar una referencia a otra NO crea un nuevo objeto. Simplemente hace que la referencia contenida a la izquierda de la asignación contenga EL MISMO objeto que la referencia situada a la derecha</strong>.</p><br /><br /><p><strong>Comparando objetos y referencias.</strong></p><br /><br /><p>De nuevo la forma más fácil es verlo con un código de ejemplo:</p><br /><br /><div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: 'Courier New', courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"><br /> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px" id="codeSnippet"><span style="color: #0000ff">class</span> Program<br />{<br /> <span style="color: #0000ff">public</span> <span style="color: #0000ff">static</span> <span style="color: #0000ff">void</span> Main()<br /> {<br /> Persona p1 = <span style="color: #0000ff">new</span> Persona();<br /> p1.Nombre = <span style="color: #006080">"Pepito"</span>;<br /> p1.Edad = 20;<br /> Persona p2 = p1;<br /> Persona p3 = <span style="color: #0000ff">new</span> Persona();<br /> p3.Nombre = <span style="color: #006080">"Pepito"</span>;<br /> p3.Edad = 20;<br /> <span style="color: #0000ff">bool</span> b = p2 == p1;<br /> <span style="color: #0000ff">bool</span> b2 = p3 == p2;<br /> }<br />}<br /><br /><span style="color: #0000ff">class</span> Persona<br />{<br /> <span style="color: #0000ff">public</span> <span style="color: #0000ff">string</span> Nombre { get; set; }<br /> <span style="color: #0000ff">public</span> <span style="color: #0000ff">int</span> Edad { get; set; }<br />}<br /></pre><br /><br /> <br /></div><br /><br /><p>¿Cual es el valor de b y b2?</p><br /><br /><ul><br /> <li>b vale <em>true</em> porque p1 y p2 contienen el mismo objeto </li><br /><br /> <li>b2 vale <em>false</em> porque p3 y p2 contienen objetos distintos. Da igual que los dos objetos sean del mismo tipo y sean idénticos. En este caso son dos <em>Personas</em> idénticas: mismo nombre y edad. Pero el operador == compara referencias, no objetos. </li><br /></ul><br /><br /><p>Así pues recuerda: El operador == al comparar referencias devuelve <em>true</em> sólo si las dos referencias contienen el mismo objeto. En caso contrario devuelve<em> false</em> (aunque las dos referencias apunten a dos objetos idénticos).</p><br /><br /><blockquote><br /> <p><strong>Nota:</strong> Este comportamiento del operador == puede <strong>modificarse</strong> para que compare el valor de los objetos en lugar de indicar si las dos referencias contienen el mismo objeto. P.ej. la clase string tienen modificado dicho operador para comparar el <em>valor</em> de las cadenas. Esto queda fuera del alcance de este post.</p><br /></blockquote><br /><br /><p>La comparación de objetos (es decir, determinar si dos objetos son idénticos pese a ser dos objetos distintos) es algo que por norma general depende de la clase. P.ej. dos Personas serán iguales si tienen el mismo nombre y edad. Dos cadenas serán iguales si contienen los mismos carácteres. Depende de cada clase <em>determinar que significa que dos objetos son iguales</em>. Para estandarizar un poco la comparación de objetos, en .NET tenemos el método Equals. Dicho método está definido en la clase Object y por lo tanto, por herencia, existe en todas las clases. Si quiero indicarle al framework como comparar dos objetos de tipo Persona puedo añadir a la clase Persona el siguiente código:</p><br /><br /><div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: 'Courier New', courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"><br /> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px" id="codeSnippet"><span style="color: #0000ff">public</span> <span style="color: #0000ff">override</span> <span style="color: #0000ff">bool</span> Equals(<span style="color: #0000ff">object</span> obj)<br />{<br /> <span style="color: #0000ff">if</span> (obj <span style="color: #0000ff">is</span> Persona)<br /> {<br /> Persona otro = (Persona) obj;<br /> <span style="color: #0000ff">return</span> otro.Edad == Edad &&<br /> otro.Nombre == Nombre;<br /> }<br /> <span style="color: #0000ff">return</span> <span style="color: #0000ff">false</span>;<br />}<br /></pre><br /></div><br /><br /><p>Y para comparar los objetos, debo llamar a Equals en lugar del operador ==</p><br /><br /><div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: 'Courier New', courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"><br /> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px" id="codeSnippet"><span style="color: #0000ff">bool</span> b2 = p3.Equals(p2);</pre><br /><br /> <br /></div><br /><br /><p><strong>Conversiones (castings)</strong></p><br /><br /><p>En el código del método Equals anterior hay el siguiente código:</p><br /><br /><div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: 'Courier New', courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"><br /> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px" id="codeSnippet">Persona otro = (Persona)obj;</pre><br /><br /> <br /></div><br /><br /><p>El código (Persona) es lo que se llama casting. El casting lo que hace es <strong>cambiar el tipo de una referencia</strong>. Es decir en el caso anterior obj era una referencia de tipo object (los parámetros también pueden ser referencias). Recordad que las referencias de tipo object pueden contener cualquier objeto. Pero yo quiero acceder a Nombre y Edad que son campos definidos en la clase Persona y por ello necesito una referencia de tipo Persona que me contenga el mismo objeto que la referencia obj.</p><br /><br /><p>Si directamente probáramos:</p><br /><br /><div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: 'Courier New', courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"><br /> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px" id="codeSnippet">Persona otro = obj;</pre><br /><br /> <br /></div><br /><br /><p>Dicho código no compila. ¿Porque? Pues porque otro es una referencia de tipo Persona y por lo tanto solo puede contener:</p><br /><br /><ol><br /> <li>Un objeto de tipo Persona </li><br /><br /> <li>Un objeto de cualquier clase que derive de Persona </li><br /></ol><br /><br /><p>Pero obj es una referencia de tipo object y puede contener un objeto de tipo object o bien un objeto de cualquier clase que derive de object… es decir, de cualquier clase. Imaginad, entonces:</p><br /><br /><div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: 'Courier New', courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"><br /> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px" id="codeSnippet"><span style="color: #0000ff">object</span> obj = <span style="color: #0000ff">new</span> Perro();<br />Persona otro = obj;</pre><br /><br /> <br /></div><br /><br /><p>Es evidente que el objeto contenido por obj es un perro y no una persona. Si el código de la segunda línea compilase estaríamos viendo un perro como una persona y bueno… se supone que no se puede, no? Por eso, como el compilador no puede garantizar que el objeto (recordad que el compilador no se fija en objetos) contenido por la referencia obj sea de un tipo válido para la referencia otro, se cura en salud y no nos deja compilar el código.</p><br /><br /><p>Pero… tu no eres el compilador y tu sí te fijas en los objetos. ¿Qué pasa en aquellos casos en que <strong>tu sabes</strong> que el objeto contenido por la referencia obj es de un tipo válido para la referencia Persona? Pues que debes decírselo al compilador. ¿Cómo? Usando el casting:</p><br /><br /><div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: 'Courier New', courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"><br /> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px" id="codeSnippet">Persona otro = (Persona)obj;</pre><br /><br /> <br /></div><br /><br /><p>Aquí le estás diciendo al compilador: <strong>Quiero que la referencia otro contenga el mismo objeto que la referencia obj y tranquilo, no te quejes porque yo te digo que el objeto es de tipo Persona</strong>. Con el casting el compilador te cree y te deja hacer la asignación.</p><br /><br /><p>Eh… que te crea el compilador no significa que te crea el CLR. El CLR no se fía ni de su madre, así que si tu haces:</p><br /><br /><div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: 'Courier New', courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"><br /> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px" id="codeSnippet"><span style="color: #0000ff">object</span> perro = <span style="color: #0000ff">new</span> Perro();<br />Persona persona = (Persona)perro;<br /></pre><br /><br /> <br /></div><br /><br /><p>El compilador no se quejará, pero cuando ejecutes, vas a recibir una hermosa InvalidCastException. El CLR sí que se fija en los objetos, como tu :)</p><br /><br /><p>Ah! Y aunque el compilador no se fije en objetos… no lo insultes, eh? No intentes algo como:</p><br /><br /><div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: 'Courier New', courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"><br /> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px" id="codeSnippet">Perro perro = <span style="color: #0000ff">new</span> Perro();<br />Persona persona = (Persona)perro;<br /></pre><br /><br /> <br /></div><br /><br /><p>Eso no compila. La razón es porque no es necesario fijarse en los objetos para ver que una referencia de tipo Persona <strong>nunca podrá contener el mismo objeto</strong> que una referencia de tipo Perro: Persona y Perro no tienen nada en común. El compilador puede no fijarse en los objetos, pero no es tonto!</p><br /><br /><p>Un saludo!</p> eiximenishttp://www.blogger.com/profile/02568454383791432108noreply@blogger.com0tag:blogger.com,1999:blog-897874347676011387.post-21956117695310334272011-05-11T22:28:00.001+02:002011-05-11T22:28:39.416+02:00[Webcast–AUGES] Introducción a ASP.NET MVC<p>Seguramente la mayoría ya sabréis que gracias al empuje del maestro <a href="http://geeks.ms/blogs/lruiz/" target="_blank">Luis Ruiz Pavón</a> (que nos ha ido <em>convenciendo</em> a varios), se ha creado <a href="http://www.auges.org/" target="_blank">AUGES</a>, el grupo de usuarios de ASP.NET de España.</p> <p>Para mi es un honor y un placer poder formar parte de este grupo, pero todavía es un placer más grande inaugurar la agenda de eventos del grupo. Y como no podía ser de otro modo el evento será un Webcast de ASP.NET MVC. :) </p> <p><strong>La fecha? El Miércoles 18. La hora? A las 19:30</strong> (hora española peninsular).</p> <p>La idea es hacer un evento 100% introductorio: explicar que es esto de ASP.NET MVC, ver ejemplos y comentar cosillas básicas. Más adelante ya tendremos tiempo de hacer eventos más <em>hardcore</em> sobre el tema! ;-)</p> <p>Así que, ya sabes… si has oído a hablar de MVC y quieres ver de que se trata, o si conoces implementaciones de MVC en otros sistemas y quieres ver la de Microsoft, o si simplmente quieres escucharme un rato (mmmm…. :p) pásate por la web de <a href="https://msevents.microsoft.com/CUI/WebCastEventDetails.aspx?EventID=1032486954&EventCategory=4&culture=es-ES&CountryCode=ES" target="_blank">registro</a> y… nos conectamos el miércoles!!</p> <p>Saludos… y nos vemos! ;-)</p> <p>PD: Como siempre… crosspost desde <a href="http://geeks.ms/blogs/etomas/default.aspx" target="_blank">mi blog de geeks.ms</a></p> eiximenishttp://www.blogger.com/profile/02568454383791432108noreply@blogger.com0tag:blogger.com,1999:blog-897874347676011387.post-8916627893949282872011-04-03T21:09:00.001+02:002011-04-03T21:09:02.844+02:00[ASP.NET MVC] Pasar parámetros a través del PathInfo<p>¡Muy buenas! Bueno, el título del post no queda demasiado claro, pero a ver si consigo explicar un poco la idea. ;-)</p> <p>Los que habéis usado ASP.NET MVC estáis muy acostumbradas a las URLs del estilo /controlador/accion/id, es decir algo como:</p> <ul> <li>/Home/Index/10 </li> <li>/Articles/View/Eiximenis </li> <li>/Blog/View/10293 </li> </ul> <p>Sabemos que gracias a la tabla de rutas podemos pasar tantos parámetros como queramos, y así podríamos tener URLs del tipo:</p> <ul> <li>/Articles/View/Eiximenis/MVC/2011 </li> </ul> <p>Que podría devolverme los articulos de “Eiximenis” con el tag “MVC” y del año 2011. </p> <p>El único punto a tener presente es que <em>el orden de los parámetros importa</em>, es decir no es lo mismo /Articles/View/Eiximenis/MVC/2011 que /Articles/View/2011/MVC/Eiximenis. En el primer caso buscamos los artículos de Eiximenis sobre MVC en el 2011 y en el segundo caso buscaríamos los artículos del <em>blogger</em> 2011, sobre MVC en el año de Eiximenis. Y sin duda <a href="http://es.wikipedia.org/wiki/Francesc_Eiximenis">Fra Francesc Eiximenis</a>, fue un gran escritor, pero que yo sepa todavía no se le ha dedicado un año (algo totalmente injusto, por supuesto :p).</p> <p>En este artículo quiero enseñaros una manera para que podáis gestionar URLs del tipo:</p> <ul> <li>/Articles/View/Author/Eiximenis/Tag/MVC/Year/2011 </li> <li>/Articles/View/Tag/MVC/Year/2011/Author/Eiximenis </li> </ul> <p>Y que ambas URLs sean tratadas de forma idéntica. En este caso estaríamos pasando tres parámetros: Author, Tag y Year.</p> <p>Para conseguir este efecto nos bastan dos acciones muy simples: definir un route handler nuevo y una entrada a la tabla de rutas.</p> <p>El route handler lo único que debe hacer es recoger la información de la URL y parsearla en “tokens” (usando el ‘/’ como separador). Y por cada par de tokens añadir una entrada en los valores de ruta (route values). El código es muy simple:</p> <div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: 'Courier New', courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px" id="codeSnippet"><span style="color: #0000ff">public</span> <span style="color: #0000ff">class</span> UrlRouteHandler : MvcRouteHandler<br />{<br /> <span style="color: #0000ff">protected</span> <span style="color: #0000ff">override</span> IHttpHandler GetHttpHandler(RequestContext requestContext)<br /> {<br /> <span style="color: #0000ff">var</span> path = requestContext.RouteData.Values[<span style="color: #006080">"pathInfo"</span>];<br /> <span style="color: #0000ff">if</span> (path != <span style="color: #0000ff">null</span>)<br /> {<br /> <span style="color: #0000ff">var</span> tokens = path.ToString().Split(<span style="color: #006080">'/'</span>);<br /> <span style="color: #0000ff">for</span> (<span style="color: #0000ff">var</span> idx =0; idx<tokens.Length; idx+=2)<br /> {<br /> <span style="color: #0000ff">if</span> (idx+1 < tokens.Length)<br /> {<br /> requestContext.RouteData.Values.Add(tokens[idx], tokens[idx+1]);<br /> }<br /> }<br /> }<br /><br /> <span style="color: #0000ff">return</span> <span style="color: #0000ff">base</span>.GetHttpHandler(requestContext);<br /> } <br />}<br /></pre><br /><br /> <br /></div><br /><br /><p>Una pequeña nota es que la cadena que separamos en tokens, no es toda la URL sino “pathInfo” un parámetro de ruta que ya nos vendrá dado. Este parámetro de ruta contendrá todo aquello que no es ni el controlador ni la acción. Es decir en la URL /Articles/View/Author/Eiximenis/Tag/MVC/Year/2011 el valor de pathInfo será Author/Eiximenis/Tag/MVC/Year/2011 (que son justo los parámetros).</p><br /><br /><p>Ahora nos queda añadir la entrada a la tabla de rutas. En mi ejemplo yo he eliminado la entrada “Default” que genera VS2010 y la he sustituído por:</p><br /><br /><div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: 'Courier New', courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"><br /> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px" id="codeSnippet">routes.Add(<span style="color: #006080">"Default"</span>, <span style="color: #0000ff">new</span> Route(url: <span style="color: #006080">"{controller}/{action}/{*pathInfo}"</span>,<br /> routeHandler: <span style="color: #0000ff">new</span> UrlRouteHandler(),<br /> defaults: <span style="color: #0000ff">new</span> RouteValueDictionary(<span style="color: #0000ff">new</span> {controller = <span style="color: #006080">"Home"</span>, action = <span style="color: #006080">"Index"</span>})));<br /></pre><br /><br /> <br /></div><br /><br /><p>La clave aquí está en el {*pathInfo}. Aquí le digo al sistema de rutas que coja todo lo que venga después de /{controller}/{action} y me lo añada a un parámetro de ruta llamado pathInfo. Además de eso, en esta ruta le indico que su routeHandler será una instancia de la clase UrlRouteHandler que hemos creado antes.</p><br /><br /><p>Y listos! Una vez los datos están en el route value ya puede entrar en acción el sistema de binding de ASP.NET MVC lo que quiere decir que puedo crear un controlador como el siguiente:</p><br /><br /><div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: 'Courier New', courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"><br /> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px" id="codeSnippet"><span style="color: #0000ff">public</span> <span style="color: #0000ff">class</span> ArticlesController : Controller<br />{<br /> <span style="color: #0000ff">public</span> ActionResult View(<span style="color: #0000ff">string</span> author, <span style="color: #0000ff">string</span> tag, <span style="color: #0000ff">int</span>? year)<br /> {<br /> dynamic data = <span style="color: #0000ff">new</span> ExpandoObject();<br /> data.Author = author ?? <span style="color: #006080">"Sin formato"</span>;<br /> data.Tag = tag ?? <span style="color: #006080">"Sin confirmación"</span>;<br /> data.Year = year.HasValue ? year.ToString() : <span style="color: #006080">"Sin año"</span>;<br /> <span style="color: #0000ff">return</span> View(data);<br /> }<br />}<br /></pre><br /><br /> <br /></div><br /><br /><p>Que recibiría los parámetros de las URLs que hemos visto anteriormente.</p><br /><br /><p>Un saludo a todos!</p><br /><br /><p><strong>PD: </strong>De nuevo eso es un crosspost desde <a href="http://geeks.ms/blogs/etomas/default.aspx">mi blog en geeks.ms</a>!</p> eiximenishttp://www.blogger.com/profile/02568454383791432108noreply@blogger.com0tag:blogger.com,1999:blog-897874347676011387.post-20133648789694971362011-03-28T17:33:00.001+02:002011-03-28T17:33:57.707+02:00[HTML/JS] Module pattern<p>Muy buenas! Cuando creas un sitio web, es normal que vayas añadiendo cada vez más código javascript en él. Al final, seguramente terminaréis desarollando una mini-api, propia que muchas veces reside en un archivo .js, lleno de funciones. Algo como:</p> <div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: 'Courier New', courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px" id="codeSnippet"><span style="color: #0000ff">function</span> HazAlgo(id, <span style="color: #0000ff">params</span>, callback)<br />{<br />}<br /><br /><span style="color: #0000ff">function</span> _HazAlgoHelper(domObj, <span style="color: #0000ff">params</span>)<br />{<br />}</pre><br /><br /> <br /></div><br /><br /><p>En este caso tenemos dos funciones, HazAlgo y _HazAlgoHelper. La función _HazAlgoHelper es realmente una función <em>privada</em>, es decir está pensada para ser llamada únicamente dentro de HazAlgo. Pero en Javascript no existe directamente el concepto de <em>función privada</em> así que le ponemos un idicador (que empieze por _) y asumimos que todas las funciones que empiecen por _, son privadas.</p><br /><br /><p>Como podemos ver, el código javascript no está muy “organizado”: tenemos todas las funciones juntas y además podemos ver aquellas que no deberíamos. El patrón de módulo existe para solventar esos problemas.</p><br /><br /><p><strong>El patrón de módulo</strong></p><br /><br /><p>La idea del patrón de módulo (module pattern) en Javascript es simular el concepto de una clase estática con métodos públicos y métodos privados. Lo de clase estática viene porque no quiero ir creando objetos de mi módulo, simplemente quiero <em>tener ahí</em>, las funciones públicas. Pero sin ver las privadas. Para los que no lo sepáis: en javascript no existe el concepto de <em>variable estática</em>.</p><br /><br /><p>La idea que hay detrás del patrón de módulo es muy simple, y como casi siempre pasa simple significa también brillante: Se trata de crear un <em>objeto anónimo</em>, cuyos campos públicos sean las funciones públicas. Finalmente se crea una instancia de ese objeto y se asigna al namespace <em>global</em> (eso es, al objeto window).</p><br /><br /><p>El código de base sería algo como:</p><br /><br /><div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: 'Courier New', courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"><br /> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px" id="codeSnippet">(<span style="color: #0000ff">function</span>(wnd) {<br /> <span style="color: #0000ff">var</span> miModulo = <span style="color: #0000ff">function</span>() {<br /> <span style="color: #0000ff">var</span> _HazAlgoHelper = <span style="color: #0000ff">function</span>(domObj, <span style="color: #0000ff">params</span>) {alert(<span style="color: #006080">"método privado"</span>);};<br /> <span style="color: #0000ff">var</span> _HazAlgo = <span style="color: #0000ff">function</span>(id, <span style="color: #0000ff">params</span>, callback) { _HazAlgoHelper(); alert(<span style="color: #006080">"método público"</span>}; };<br /><br /> <span style="color: #0000ff">return</span> {<br /> HazAlgo : _HazAlgo<br /> };<br /> }<br /><br /> wnd.m$ = miModulo();<br />})(window);</pre><br /><br /> <br /></div><br /><br /><p>Bien… se que el código puede parecer lioso, la primera vez, pero vamos a analizarlo.</p><br /><br /><p>Podemos ver que estamos definiendo una función <strong>anónima</strong> que acepta un parámetro (llamado <em>wnd</em>).</p><br /><br /><p>¿Y que hace esa función anónima)? Pues para empezar define una variable miModulo que resulta ser… una función (<em>var miModulo = funcion() { …}</em>).</p><br /><br /><p>Y que hará esa función cuando se invoque? Pues tres cosas:</p><br /><br /><ol><br /> <li>Definir una variable llamada _HazAlgoHelper… que es <em>otra función</em>. </li><br /><br /> <li>Definir una variable llamada _HazAlgo… <em>que es otra función</em>. </li><br /><br /> <li>Devolver un objeto anónimo con un campo, llamado HazAlgo. El valor de HazAlgo es el mismo que _HazAlgo (es decir una función). </li><br /></ol><br /><br /><p>Con eso tenemos una función (miModulo) que cuando la invoquemos nos devolverá un objeto con un solo campo (llamado HazAlgo). Así, teoricamente:</p><br /><br /><div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: 'Courier New', courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"><br /> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px" id="codeSnippet">miModulo.HazAlgo(); <span style="color: #008000">// Valido</span><br />miModulo._HazAlgo(); <span style="color: #008000">// No válido</span><br />miModulo._HazAlgoHelper(); // NO válido</pre><br /><br /> <br /></div><br /><br /><p>Fijaos que la gracia está en que HazAlgo() realmente es lo mismo que _HazAlgo()… y desde _HazAlgo() podemos llamar sin ningún problema a _HazAlgoHelper() que es nuestra función privada. Así la clave es mapear las funciones públicas como campos del objeto anónimo devuelto.</p><br /><br /><p>¡Con eso hemos simulado el concepto de funciones privadas!</p><br /><br /><p>Pero, miModulo es una variable local, es local a la función anónima que estamos definiendo. Así que todavía nos queda un paso más: Guardar miModulo en alguna propiedad del parámetro wnd. Eso es lo que hacemos con la línea:</p><br /><br /><div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: 'Courier New', courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"><br /> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px" id="codeSnippet">wnd.m$ = miModulo();</pre><br /><br /> <br /></div><br /><br /><p>Invocamos miModulo (con parentesis, pues es una función) y guardamos el resultado (el objeto anónimo con el campo HazAlgo) en la propiedad m$ del parámetro wnd.</p><br /><br /><p>Ya casi estamos… Hasta este punto hemos definido simplemente una función anónima. Ahora toca invocarla. Fijaos en un detalle importante: la primera línea empieza con un paréntesis abierto. Con eso, de hecho, estamos <strong>invocando</strong> nuestra función anónima. Pero nuestra función anónima espera un parámetro (wnd) así que debemos pasárselo. Eso es lo que hacemos en la última línea: le pasamos window. Y porque window? Pues porque window es un namespace global: todo lo que esté en window es global.</p><br /><br /><p>Por lo tanto ahora, desde cualquier parte de mi página puedo hacer:</p><br /><br /><div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: 'Courier New', courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"><br /> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px" id="codeSnippet"><script>m$.HazAlgo(1,2,3);</script></pre><br /><br /> <br /></div><br /><br /><p>Y eso es correcto, mientras que hacer.</p><br /><br /><div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: 'Courier New', courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"><br /> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px" id="codeSnippet"><script>m$._HazAlgoHelper(1,2);</script></pre><br /><br /> <br /></div><br /><br /><p>Nos dará un error.</p><br /><br /><p>Para tener nuestro módulo listo para ser usado, basta con tenerlo en un .js propio e incluirlo en aquellas páginas donde lo necesitemos. ¡Y listos!</p><br /><br /><p>Espero que esto os parezca interesante y os ayude a organizar vuestro código javascript!</p><br /><br /><p>Un saludo!</p><br /><br /><p>PD: Como siempre… un crosspost desde <a href="http://geeks.ms/blogs/etomas/default.aspx" target="_blank">mi blog en geeks.ms</a>!</p> eiximenishttp://www.blogger.com/profile/02568454383791432108noreply@blogger.com0tag:blogger.com,1999:blog-897874347676011387.post-44614539191886597402011-03-15T14:56:00.001+01:002011-03-15T14:56:52.042+01:00ASP.NET MVC: Previsualizar imágenes subidas (2)<p>Buenas! Donde dije digo, digo Diego… Sí, ya sé que dije que el segundo post sería como hacerlo con Ajax, pero bueno… la culpa es de twitter, concretamente de <a href="http://twitter.com/pablonete" target="_blank">@pablonete</a> con el que hemos empezado a hablar sobre si es posible evitar el guardar la imágen físicamente en el servidor. Hay un mecanismo obvio, que es usar la sesión (guardar el array de bytes que conforman la imágen en la sesión). Pero… hay otra? Pues sí: usar data urls!</p> <p><strong>Data urls</strong></p> <p>Lo que mucha gente no conoce es que el formato de URL permite <em>incrustar</em> datos que son <em>interpretados</em> por el navegador como si se los hubiese descargado externamente. P.ej. la siguiente url es válida:</p> <p>data:image/jpeg;base64,xxxxxxx</p> <p>donde xxxxxxx es la codificación en base64 de la imágen.</p> <p>P.ej. eso es totalmente correcto:</p> <div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: 'Courier New', courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px" id="codeSnippet"><span style="color: #0000ff"><</span><span style="color: #800000">img</span> <span style="color: #ff0000">src</span>=”<span style="color: #ff0000">data:image</span>/<span style="color: #ff0000">jpeg</span>;<span style="color: #ff0000">base64</span>,<span style="color: #ff0000">xxxxxx</span>” <span style="color: #0000ff">/></span></pre><br /><br /> <br /></div><br /><br /><p>Veamos como podríamos modificar nuestro proyecto anterior, para usar data urls en lugar de un fichero temporal en el servidor… Fijaos que eso <strong>sólo evita guardar el fichero, la imagen debe ser subida al servidor</strong>.</p><br /><br /><p>Las modificaciones son muy simples, por un lado primero vamos a modificar la acción del controlador, para que quede así:</p><br /><br /><div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: 'Courier New', courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"><br /> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px" id="codeSnippet">[HttpPost]<br /><span style="color: #0000ff">public</span> ActionResult SendImage(HttpPostedFileBase img, <span style="color: #0000ff">string</span> base64, <span style="color: #0000ff">string</span> contenttype)<br />{<br /> <span style="color: #0000ff">if</span> (base64 != <span style="color: #0000ff">null</span> && contenttype != <span style="color: #0000ff">null</span> && img==<span style="color: #0000ff">null</span>)<br /> {<br /> <span style="color: #008000">// Aquí podríamos guardar la imagen (en base64 tenemos los datos)</span><br /> <span style="color: #0000ff">return</span> View(<span style="color: #006080">"Step2"</span>);<br /> }<br /> var data = <span style="color: #0000ff">new</span> <span style="color: #0000ff">byte</span>[img.ContentLength];<br /> img.InputStream.Read(data, 0, img.ContentLength);<br /> var base64Data = Convert.ToBase64String(data);<br /> ViewBag.ImageData = base64Data;<br /> ViewBag.ImageContentType = img.ContentType;<br /> <span style="color: #0000ff">return</span> View(<span style="color: #006080">"Index"</span>);<br />}</pre><br /><br /> <br /></div><br /><br /><p>La acción recibe tres parámetros:</p><br /><br /><ol><br /> <li>img: El fichero seleccionado (contenido del <input type=”file”). </li><br /><br /> <li>base64: Codificación en base64 de la imagen <strong>que se está previsualizándo.</strong> </li><br /><br /> <li>contenttype: Content-type de la imagen <strong>que se está previsualizando.</strong> </li><br /></ol><br /><br /><p>Por supuesto base64 y contenttype sólo se envían si se está previsualizando una imagen. En caso contrario valen null.</p><br /><br /><p>Fijémonos lo que hace la acción, si base64 y contenttype valen null, o bien el usuario ha seleccionado una imagen nueva (img != null):</p><br /><br /><ol><br /> <li>Obtenemos los datos en base64 de la imagen enviada por el usuario (base64Data). </li><br /><br /> <li>Pasamos esos datos (junto con el content-type) a la vista, usando el ViewBag. </li><br /></ol><br /><br /><p>Y la vista que hace? Pues poca cosa:</p><br /><br /><div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: 'Courier New', courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"><br /> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px" id="codeSnippet"><span style="color: #0000ff"><</span><span style="color: #800000">form</span> <span style="color: #ff0000">enctype</span><span style="color: #0000ff">="multipart/form-data"</span> <span style="color: #ff0000">method</span><span style="color: #0000ff">="post"</span> <span style="color: #ff0000">action</span><span style="color: #0000ff">="@Url.Action("</span><span style="color: #ff0000">SendImage</span><span style="color: #0000ff">")"</span><span style="color: #0000ff">></span><br /> <span style="color: #0000ff"><</span><span style="color: #800000">label</span> <span style="color: #ff0000">for</span><span style="color: #0000ff">="img"</span><span style="color: #0000ff">></span>Seleccionar imagen:<span style="color: #0000ff"></</span><span style="color: #800000">label</span><span style="color: #0000ff">></span><br /> <span style="color: #0000ff"><</span><span style="color: #800000">input</span> <span style="color: #ff0000">type</span><span style="color: #0000ff">="file"</span> <span style="color: #ff0000">name</span><span style="color: #0000ff">="img"</span> <span style="color: #0000ff">/></span> <span style="color: #0000ff"><</span><span style="color: #800000">br</span> <span style="color: #0000ff">/></span><br /> <span style="color: #0000ff"><</span><span style="color: #800000">input</span> <span style="color: #ff0000">type</span><span style="color: #0000ff">="submit"</span> <span style="color: #0000ff">/></span><br /><br /> @if (ViewBag.ImageData != null)<br /> { <br /> <span style="color: #0000ff"><</span><span style="color: #800000">img</span> <span style="color: #ff0000">src</span><span style="color: #0000ff">="data:@ViewBag.ImageContentType;base64,@ViewBag.ImageData"</span> <span style="color: #0000ff">/></span><br /> <span style="color: #0000ff"><</span><span style="color: #800000">input</span> <span style="color: #ff0000">type</span><span style="color: #0000ff">="hidden"</span> <span style="color: #ff0000">value</span><span style="color: #0000ff">="@ViewBag.ImageData"</span> <span style="color: #ff0000">name</span><span style="color: #0000ff">="base64"</span> <span style="color: #0000ff">/></span><br /> <span style="color: #0000ff"><</span><span style="color: #800000">input</span> <span style="color: #ff0000">type</span><span style="color: #0000ff">="hidden"</span> <span style="color: #ff0000">value</span><span style="color: #0000ff">="@ViewBag.ImageContentType"</span> <span style="color: #ff0000">name</span><span style="color: #0000ff">="contenttype"</span> <span style="color: #0000ff">/></span><br /> }<br /><span style="color: #0000ff"></</span><span style="color: #800000">form</span><span style="color: #0000ff">></span></pre><br /><br /> <br /></div><br /><br /><p>Tenemos el formulario con el input type=”submit”, y luego si en el ViewBag vienen los datos de la imagen que se debe previsualizar, genera 3 campos más:</p><br /><br /><ol><br /> <li>Una imagen, cuyo src es <strong>una data url </strong>(formato <em>data:content-type;base64,[datos en base64]</em>) </li><br /><br /> <li>Dos campos hidden (para guardar el content-type y los datos para mandarlos de vuelta al servidor). </li><br /></ol><br /><br /><p>Y listos! Con eso tenemos la previsualización de las imágenes <strong>sin necesidad de generar fichero temporal alguno</strong>.</p><br /><br /><p>Finalmente, tres aclaraciones: </p><br /><br /><ol><br /> <li>Fijaos que la codificación en base64 <strong>se incrusta en la página</strong> (en este caso se incrusta dos veces, aunque con un poco de javascript podría incrustarse solo una), por lo que esto puede generar páginas <strong>muy grandes</strong> si la imagen ocupa mucho. </li><br /><br /> <li>Si no voy errado, los navegadores sólo están obligados a soportar URLs de hasta 1024 bytes. Todo lo que exceda de aquí… depende del navegador. Firefox p.ej. acepta URLs muy largas, pero recordad que Base64 genera fácilmente URLs no muy largas, sinó descomunalmente largas (si la imágen ocupa 100Ks, la URL en Base64 ocupará <strong>más</strong> de 100Ks). Así que para imágenes pequeñas igual tiene sentido, pero para imágenes largas, honestamente no creo que sea una buena solución. </li><br /><br /> <li>La mayoría de navegadores modernos soportan data urls (IE lo empezó a hacer con su versión 8). </li><br /></ol><br /><br /><p>En fin… bueno, como curiosidad no está mal, eh? ;-)</p><br /><br /><p>Gracias a <a href="http://twitter.com/pablonete" target="_blank">Pablo Nuñez</a> por la divertida e interesante conversación en twitter!</p><br /><br /><p>Saludos!</p><br /><br /><p>PD: Enlace al post anterior de esa serie: <a href="http://geeks.ms/blogs/etomas/archive/2011/03/15/asp-net-mvc-previsualizar-im-225-genes-subidas-1.aspx" target="_blank">http://geeks.ms/blogs/etomas/archive/2011/03/15/asp-net-mvc-previsualizar-im-225-genes-subidas-1.aspx</a></p><br /><br /><p>PD2: Otro crosspost des de <a href="http://geeks.ms/blogs/etomas/default.aspx" target="_blank">mi blog en geeks.ms</a>… </p> eiximenishttp://www.blogger.com/profile/02568454383791432108noreply@blogger.com1tag:blogger.com,1999:blog-897874347676011387.post-53661764663860370252011-03-15T10:01:00.001+01:002011-03-15T10:01:59.204+01:00ASP.NET MVC: Previsualizar imágenes subidas (1)<p>Buenas! Una pregunta que últimamente parece que se pregunta varias veces en los foros de ASP.NET MVC es como previsualizar una imagen que se quiere subir al servidor.</p> <p>Antes que nada aclarar que, técnicamente, la pregunta está mal hecha: <strong>no es posible previsualizar la imagen <u>antes</u> de que sea subida</strong>. Antiguamente en algunos navegadores, y con un poco de javascript, eso era posible, pero ahora por suerte eso ya no funciona :)</p> <p>Básicamente <em>previsualizar</em> una imagen consiste en:</p> <ol> <li>Recibir los datos de la imagen </li> <li>Guardarla en algún sitio “temporal” </li> <li>Mandarla de <em>vuelta</em> al navegador </li> <li>Borrar la imagen del sitio “temporal” si el usuario no la acepta </li> </ol> <p>En este primer post vamos a ver como hacerlo sin usar Ajax. Luego habrá un segundo post y veremos como hacerlo usando Ajax (no es tan sencillo porque XMLHttpRequest no soporta el formato de datos multipart/form-data que es el que se usa cuando se mandan ficheros).</p> <p>Bien, vamos a verlo rápidamente, ya veréis cuan sencillo es :)</p> <p>Lo primero es tener la vista que tenga el formulario para enviar los datos. La siguiente servirá (si queréis más detalles de como hacer upload de ficheros en MVC mirad <a href="http://geeks.ms/blogs/etomas/archive/2010/09/08/subir-ficheros-al-servidor-en-asp-net-mvc.aspx" target="_blank">mi post al respecto</a>):</p> <div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: 'Courier New', courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px" id="codeSnippet"><span style="color: #0000ff"><</span><span style="color: #800000">h2</span><span style="color: #0000ff">></span>Index<span style="color: #0000ff"></</span><span style="color: #800000">h2</span><span style="color: #0000ff">></span><br /><br /><span style="color: #0000ff"><</span><span style="color: #800000">form</span> <span style="color: #ff0000">enctype</span><span style="color: #0000ff">="multipart/form-data"</span> <span style="color: #ff0000">method</span><span style="color: #0000ff">="post"</span> <span style="color: #ff0000">action</span><span style="color: #0000ff">="@Url.Action("</span><span style="color: #ff0000">SendImage</span><span style="color: #0000ff">")"</span><span style="color: #0000ff">></span><br /> <span style="color: #0000ff"><</span><span style="color: #800000">label</span> <span style="color: #ff0000">for</span><span style="color: #0000ff">="img"</span><span style="color: #0000ff">></span>Seleccionar imagen:<span style="color: #0000ff"></</span><span style="color: #800000">label</span><span style="color: #0000ff">></span><br /> <span style="color: #0000ff"><</span><span style="color: #800000">input</span> <span style="color: #ff0000">type</span><span style="color: #0000ff">="file"</span> <span style="color: #ff0000">name</span><span style="color: #0000ff">="img"</span> <span style="color: #0000ff">/></span> <span style="color: #0000ff"><</span><span style="color: #800000">br</span> <span style="color: #0000ff">/></span><br /> <span style="color: #0000ff"><</span><span style="color: #800000">input</span> <span style="color: #ff0000">type</span><span style="color: #0000ff">="submit"</span> <span style="color: #0000ff">/></span><br /><span style="color: #0000ff"></</span><span style="color: #800000">form</span><span style="color: #0000ff">></span></pre><br /><br /> <br /></div><br /><br /><p>Trivial: un form con multipart/form-data que enviará sus datos a la acción SendImage. La acción podría ser algo como:</p><br /><br /><div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: 'Courier New', courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"><br /> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px" id="codeSnippet">[HttpPost]<br /><span style="color: #0000ff">public</span> ActionResult SendImage(HttpPostedFileBase img)<br />{<br /> var data = <span style="color: #0000ff">new</span> <span style="color: #0000ff">byte</span>[img.ContentLength];<br /> img.InputStream.Read(data, 0, img.ContentLength);<br /> var path = ControllerContext.HttpContext.Server.MapPath(<span style="color: #006080">"/"</span>);<br /> var filename = Path.Combine(path, Path.GetFileName(img.FileName));<br /> System.IO.File.WriteAllBytes(Path.Combine(path, filename), data);<br /> ViewBag.ImageUploaded = filename;<br /> <span style="color: #0000ff">return</span> View(<span style="color: #006080">"Index"</span>);<br />}</pre><br /><br /> <br /></div><br /><br /><p>Vale, fijaos que recibimos el HttpPostedFileBase (llamado img como el atributo <em>name</em> del <em>input type=”file”</em>). Lo que hacemos en la acción es muy simple:</p><br /><br /><ol><br /> <li>Guardamos la imagen en disco (en este caso por pura pereza uso el directorio raíz de la aplicación web. Por supuesto lo suyo es usar un directorio configurado en web.config y con los permisos NTFS correspondientes). </li><br /><br /> <li>Coloco en el <em>ViewBag</em> el nombre de la imagen que se ha guardado (luego vemos porque). </li><br /><br /> <li>Devuelvo la vista “Index” (la misma de la cual venimos) </li><br /></ol><br /><br /><p>Ok, tal y como lo tenemos ahora, si lo probáis veréis que podéis hacer un upload de la imagen, y la imagen se graba en el disco (en el directorio raíz de la aplicación web), pero no se previsualiza nada. Vamos a modificar la vista Index para que haya esa <em>previsualización</em>.</p><br /><br /><p>Para previsualizar la imagen basta con añadir el siguiente código, despues del </form> en la vista:</p><br /><br /><div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: 'Courier New', courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"><br /> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px" id="codeSnippet">@if (!string.IsNullOrEmpty(ViewBag.ImageUploaded))<br />{<br /> <span style="color: #0000ff"><</span><span style="color: #800000">img</span> <span style="color: #ff0000">src</span><span style="color: #0000ff">="@Url.Action("</span><span style="color: #ff0000">Preview</span><span style="color: #0000ff">", new {file=ViewBag.ImageUploaded})"</span> <span style="color: #0000ff">/></span><br />}</pre><br /><br /> <br /></div><br /><br /><p>Simple, no? Si en el ViewBag existe la entrada ImageUploaded generamos un tag <img> cuya dirección es la acción “Preview” y le pasamos el parámetro file con el nombre de la imagen. Y como es la acción Preview? Pues super sencilla:</p><br /><br /><div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: 'Courier New', courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"><br /> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px" id="codeSnippet"><span style="color: #0000ff">public</span> ActionResult Preview(<span style="color: #0000ff">string</span> file)<br />{<br /> var path = ControllerContext.HttpContext.Server.MapPath(<span style="color: #006080">"/"</span>);<br /> <span style="color: #0000ff">if</span> (System.IO.File.Exists(Path.Combine(path, file)))<br /> {<br /> <span style="color: #0000ff">return</span> File(Path.Combine(path, file), <span style="color: #006080">"image/jpeg"</span>);<br /> }<br /> <span style="color: #0000ff">return</span> <span style="color: #0000ff">new</span> HttpNotFoundResult();<br />}</pre><br /><br /> <br /></div><br /><br /><p>Simplemente leemos la imagen guardada y la devolvemos usando un FilePathResult. Simple, eh? ;-)</p><br /><br /><p>Con eso, si subís una imagen, cuando le deis a submit, aparecerá de nuevo la vista, pero ahora previsualizando la imagen.</p><br /><br /><p>Ahora sólo nos queda el punto final: Que el usuario pueda <em>aceptar</em> esa imagen como correcta. Para ello lo más simple es hacer lo siguiente:</p><br /><br /><ol><br /> <li>Si el usuario está previsualizando una imagen y NO ha seleccionado otra, cuando hace el submit se entiende que acepta dicha imagen. </li><br /><br /> <li>Si el usuario NO está previsualizando una imagen, o bien está previsualizando una pero selecciona otra, al hacer el submit se entiende que <em>descarta</em> la imagen anterior y quiere previsualizar la nueva. </li><br /></ol><br /><br /><p>Para ello, tenemos que hacer que la vista le indique al controlador <em>si se está previsualizando una imagen, y cual és</em>. Por suerte eso es muy sencillo. Basta con modificar el <form> de la vista para que su atributo action quede como:</p><br /><br /><div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: 'Courier New', courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"><br /> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px" id="codeSnippet"><form enctype=<span style="color: #006080">"multipart/form-data"</span> method=<span style="color: #006080">"post"</span> <br />action=<span style="color: #006080">"@Url.Action("</span>SendImage<span style="color: #006080">", new {previewed=ViewBag.ImageUploaded})"</span>></pre><br /><br /> <br /></div><br /><br /><p>Fijaos que añadimos un parámetro <em>previewed</em> que la vista mandará a la acción y que será el nombre de la imagen que se está previsualizando.</p><br /><br /><p>Vamos a modificar la acción para que reciba ese parámetro y actúe en consecuencia:</p><br /><br /><div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: 'Courier New', courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"><br /> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px" id="codeSnippet">[HttpPost]<br /><span style="color: #0000ff">public</span> ActionResult SendImage(HttpPostedFileBase img, <span style="color: #0000ff">string</span> previewed)<br />{<br /> <span style="color: #0000ff">if</span> (!<span style="color: #0000ff">string</span>.IsNullOrEmpty(previewed) && img == <span style="color: #0000ff">null</span>)<br /> {<br /> ViewBag.ImageName = previewed;<br /> <span style="color: #0000ff">return</span> View(<span style="color: #006080">"Step2"</span>);<br /> }<br /><br /> <span style="color: #008000">// Código tal cual estaba</span><br />}</pre><br /><br /> <br /></div><br /><br /><p>Añadimos ese if al inicio: Si el usuario está previsualizando una imagen y no ha seleccionado otra, entendemos que acepta esta imagen. Entonces guardamos el nombre en el ViewBag y lo mandamos a la siguiente vista (Step2).</p><br /><br /><p>La vista es muy sencilla:</p><br /><br /><div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: 'Courier New', courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"><br /> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px" id="codeSnippet"><h2>Step2</h2><br /><br />Siguiente paso. El usuario ha aceptado la imagen: <br /><br /><br /><img src=<span style="color: #006080">"@Url.Action("</span>Preview<span style="color: #006080">", new {file=ViewBag.ImageName})"</span> /></pre><br /><br /> <br /></div><br /><br /><p>Simplemente mostramos la imagen. Fijaos que de nuevo usamos la acción Preview, puesto que la imagen ya la tenemos guardada en el servidor.</p><br /><br /><p><strong>Notas finales</strong></p><br /><br /><p>Faltarían, al menos, dos cosas para que dicho proyecto funcionase “de forma aceptable”:</p><br /><br /><ol><br /> <li>Borrar las imagenes descartadas por el usuario (en la acción SendImage si se recibe una imagen <em>nueva </em>y se estaba previsualizando una, borrar esta ya que el usuario la ha descartado). </li><br /><br /> <li>La acción  Preview siempre devuelve content-type a image/jpeg, eso debería hacerse según la extensión de la imagen, o guardarlo en algún sitio. </li><br /></ol><br /><br /><p>Ambas cosas son triviales y no las añado porque lo único que consiguiría es liar el código un poco más ;-)</p><br /><br /><p>En el próximo post… lo mismo pero usando Ajax.</p><br /><br /><p>Saludos!</p><br /><br /><p>PD: Pues sí… otro crosspost de <a href="http://geeks.ms/blogs/etomas/default.aspx" target="_blank">mi blog de geeks.ms</a>! Que raro, no? :P</p> eiximenishttp://www.blogger.com/profile/02568454383791432108noreply@blogger.com2tag:blogger.com,1999:blog-897874347676011387.post-84212912992050658232011-02-25T08:55:00.001+01:002011-02-25T08:55:20.922+01:00Rendering de vistas parciales en Razor y MVC3<p>Buenas! Una de las dudas que he visto que se van repitiendo por ahí tiene que ver con <strong>como renderizar vistas parciales en MVC3 usando Razor</strong>.</p> <p>En MVC2 y anteriores (o en MVC3 usando el ViewEngine de WebForms) la forma de renderizar una vista parcial era sencilla:</p> <div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: 'Courier New', courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px" id="codeSnippet"><% Html.RenderPartial(<span style="color: #006080">"VistaParcial"</span>, modelo); %></pre><br /><br /> <br /></div><br /><br /><p>Mucha gente traduce eso a Razor y usa lo siguiente para renderizar una vista parcial:</p><br /><br /><div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: 'Courier New', courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"><br /> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px" id="codeSnippet">@Html.RenderPartial(<span style="color: #006080">"VistaParcial"</span>)</pre><br /><br /> <br /></div><br /><br /><p>Y se obtiene un error, quizá un poco críptico, que dice lo siguiente: <em>CS1502: The best overloaded method match for 'System.Web.WebPages.WebPageExecutingBase.Write(System.Web.WebPages.HelperResult)' has some invalid arguments</em></p><br /><br /><p>El error <strong>no</strong> está en que Html.RenderPartial no pueda usarse con Razor, el error está en la sintaxis que estamos usando. Cuando en Razor usamos la @ para indicar el inicio de código de servidor, la expresión que viene a continuación <strong>debe devolver un valor</strong>, que será incluído en la respuesta a enviar al navegador. La excepción a esa norma es cuando lo que sigue a la @ es una palabra clave reservada de Razor (como @model) o una palabra clave reservada del lenguaje que estemos usando (como @foreach). Esos casos especiales Razor los sabe tratar y actúa en consecuencia. Pero en el resto de casos <strong>siempre, siempre, siempre la expresión debe devolver un valor</strong>.</p><br /><br /><p>Hablando en términos del engine de WebForms, el código Razor:</p><br /><br /><div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: 'Courier New', courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"><br /> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px" id="codeSnippet">@Html.RenderPartial(<span style="color: #006080">"VistaParcial"</span>, modelo)</pre><br /><br /> <br /></div><br /><br /><p>Se corresponde a:</p><br /><br /><div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: 'Courier New', courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"><br /> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px" id="codeSnippet"><%: Html.RenderPartial(<span style="color: #006080">"VistaParcial"</span>,modelo) %></pre><br /><br /> <br /></div><br /><br /><p>Que es erróneo (y da el error <em>CS1502: The best overloaded method match for 'System.Web.HttpUtility.HtmlEncode(string)' has some invalid arguments</em>).</p><br /><br /><p>Entonces… como usar Html.RenderPartial en Razor? Fácil: usando llaves para indicarle al motor de Razor que eso es un código que debe ejecutar, en lugar de un valor que debe incrustar en la respuesta:</p><br /><br /><div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: 'Courier New', courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"><br /> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px" id="codeSnippet">@{ Html.RenderPartial(<span style="color: #006080">"VistaParcial"</span>); }</pre><br /><br /> <br /></div><br /><br /><p>Así pues: Html.RenderPartial <em>puede</em> usarse en Razor sin ningún problema… como el resto de Helpers que conozcáis. Si el método lo usábais con <% … %> en Razor es @{ … }, mientras que si usábais <%: … %> en Razor es simplemente @…</p><br /><br /><p><strong>Otras maneras de incrustar vistas parciales </strong></p><br /><br /><p>De todas formas hay un par de métodos más para incrustar vistas parciales.</p><br /><br /><p>El primer método es <strong>Html.Partial()</strong> un método de extensión adicional. Para llamarlo se usan los mismos parámetros que Html.RenderPartial. La diferencia es que Html.Partial devuelve una IHtmlString con los contenidos de la vista renderizada. Por lo tanto, para incrustar una vista usando Html.Partial() usamos:</p><br /><br /><div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: 'Courier New', courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"><br /> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px" id="codeSnippet"><span style="color: #008000">// Razor</span><br />@Html.Partial(<span style="color: #006080">"VistaParcial"</span>)<br /><span style="color: #008000">// Webforms viewengine</span><br /><%: Html.Partial(<span style="color: #006080">"VistaParcial"</span>) %><br /></pre><br /><br /> <br /></div><br /><br /><p>El segundo método es propio de Razor, ya que está definido dentro del framework que se conoce como “WebPages” y es usar el método RenderPage, definido en la clase WebPageBase de la cual heredan las vistas Razor.</p><br /><br /><p>Dicho método acepta dos parámetros:</p><br /><br /><ol><br /> <li>La localización de la vista. Ojo! No el nombre, sinó su localización (incluyendo directorios) </li><br /><br /> <li>Parámetros a pasar a la vista (params object[]). </li><br /></ol><br /><br /><p>P.ej. para renderizar la vista parcial usaríamos:</p><br /><br /><div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: 'Courier New', courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"><br /> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px" id="codeSnippet">@RenderPage(<span style="color: #006080">"~/Views/Home/VistaParcial.cshtml"</span>)</pre><br /><br /> <br /></div><br /><br /><p>Fijaos en que se debe usar el nombre del archivo de la vista a incluir (incluyendo extensión .cshtml).</p><br /><br /><p>Si se pasan parámetros a la vista parcial, estos <strong>no</strong> están disponibles usando la propiedad Model en la vista, sinó que debe usarse la propiedad <em>PageData</em>. P.ej. podríamos pasar una cadena y un entero a la vista:</p><br /><br /><div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: 'Courier New', courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"><br /> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px" id="codeSnippet">@RenderPage(<span style="color: #006080">"~/Views/Home/VistaParcial.cshtml"</span>, <span style="color: #006080">"Parametro 1"</span>, 10)</pre><br /><br /> <br /></div><br /><br /><p>Y mostrarlos desde la vista con el uso de PageData:</p><br /><br /><div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: 'Courier New', courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"><br /> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px" id="codeSnippet">@foreach (var item in PageData)<br />{<br /> <span style="color: #0000ff"><</span><span style="color: #800000">div</span><span style="color: #0000ff">></span>@item.Value<span style="color: #0000ff"></</span><span style="color: #800000">div</span><span style="color: #0000ff">></span><br />}</pre><br /><br /> <br /></div><br /><br /><p>Mi opinión sobre el método RenderPage (en MVC): Sobra totalmente, y espero que nunca, nunca, nunca lo uséis. Porque? Pues porque RenderPage <strong>rompe</strong> la encapsulación del framework (en lugar de especificar un <em>nombre</em> de vista debéis especificar un <em>fichero</em>). Es evidente que existe para dar soporte a WebPages pero WebPages y MVC se parecen sólo porque usan Razor como sintaxis, pero en concepción son dos cosas totalmente distintas… Aunque por razones (supongo que técnicas) Razor depende de WebPages y esa dependencia se arrastra a MVC3, cosa que personalmente no me gusta demasiado. Pero es lo hay… ;-)</p><br /><br /><p><strong>Conclusiones</strong></p><br /><br /><ol><br /> <li>Html.RenderPartial funciona correctamente en Razor, al igual que el resto de métodos de <em>siempre</em>. Sólo debemos tener cuidado en usar la sintaxis correcta. </li><br /><br /> <li>Html.Partial es un método adicional para renderizar vistas parciales. La diferencia con Html.RenderPartial() es que este último escribe en la response directamente el contenido de la vista, mientras que Partial() lo devuelve dentro de una cadena. No tengo claras las implicaciones de rendimiento que puede tener empezar a crear mutltiud de cadenas que serán eliminadas por el GC casi de inmediato. </li><br /><br /> <li>RenderPage es el método de WebPages para renderizar vistas parciales. Desde el punto de vista de MVC es un método que sobra totalmente y que rompe la encapsulación del framework. </li><br /></ol><br /><br /><p>Espero que os sea útil!</p><br /><br /><p>Un saludo!</p> eiximenishttp://www.blogger.com/profile/02568454383791432108noreply@blogger.com0tag:blogger.com,1999:blog-897874347676011387.post-17217052529784492502011-02-17T20:38:00.001+01:002011-02-17T20:38:32.955+01:00De los requerimientos…<p>Mi hermano y yo ambos somos arquitectos. Aunque en su caso él tiene un título universitario que lo acredita como tal y yo sólo una tarjeta de trabajo donde mi empresa ha decidido poner eso… y en inglés que se supone que queda mejor. Los dos nos dedicamos a <em>pensar y diseñar</em> cosas: él piensa y diseña espacios habitables (o sea pisos y casas) y yo pienso y diseño soluciones informáticas (o sea programas).</p> <p>Ayer mi hermano (que no sólo es arquitecto, sinó que regenta una empresa de obras) me vino riéndose por una petición de presupuesto en broma que había recibido. El correo electrónico decía, más o menos, así…</p> <p><em><font color="#646b86">Apreciado hermano de eiximenis,</font></em></p> <p><font color="#646b86"><em>Nos dirigimos a Ud. para que nos haga un presupuesto para la construcción de una nueva mansión para un concejal cualquiera (a partir de ahora “el cliente”) que, con un golpe de fortuna, ha ganado unos dinerillos que gastar</em>. <em>A continuación le detallamos como debe la mansión:</em></font></p> <ol> <li><em><font color="#646b86">La mansión contará con el número de habitaciones que el cliente estime cómo necesarias para el desarrollo de sus funciones de descanso y/o copulación. Además el cliente debe poder realizar otras funciones (a especificar en algún momento del futuro) sin menoscabo de la comodidad mínima exigida.</font></em> </li> <li><em><font color="#646b86">Los materiales de construcción deben tener el nivel de calidad requerido por el cliente, nivel que por supuesto no aparece en ningún estándard y que sólo el cliente conoce y que puede variar en función de su estado de ánimo.</font></em> </li> <li><em><font color="#646b86">Deberán integrarse, adaptarse o construirse distintos módulos de diversión, para aquellas actividades de ocio que el cliente considere necesarias. No es necesario especificar que las necesidades de ocio del cliente pueden variar en cualquier momento durante el periodo de tres años que se exige como mantenimiento.</font></em> </li> <li><em><font color="#646b86">El número de baños deberá ser el justo y necesario para que pueda usarlos el cliente, así como los invitados a sus fiestas. El número de invitados no ha sido proporcionado por el cliente.</font></em> </li> <li><em><font color="#646b86">Se valorará positivamente que los garajes estén listos al cabo de dos semanas y más positivamente si lo están al cabo de una.</font></em> </li> <li><em><font color="#646b86">El cliente ha comprado un módulo de domótica que deberá ser instalado y configurado. El modelo es desconocido al igual que el fabricante.</font></em> </li> <li><em><font color="#646b86">Existe un periodo de tres años de garantía, donde el cliente puede solicitar cambios en los materiales, disposición de los módulos, módulos de ocio nuevos, etc, etc</font></em> </li> </ol> <p><em><font color="#646b86">Dado que el cliente es un concejal debe asignar la obra de forma totalmente legal y por ello se deberán entregar tres sobres:</font></em></p> <ol> <li><em><font color="#646b86">El primero con la propuesta económica final. Dicha propuesta contendrá el precio exacto de la obra, incluyendo materiales y mano de obra y mantenimiento durante los 3 años. El mantenimiento implica adaptar la mansión a las nuevas necesidades que le puedan surgir al cliente. Hay total libertad de precio, siempre que no pase de 500000€</font></em> </li> <li><em><font color="#646b86">El segundo con el calendario exacto de las obras: cuando estará el garaje, las distintas habitaciones, la fachada principal, los módulos de ocio, etc. El inclumpimiento del calendario acarreará sanciones económicas a determinar.</font></em> </li> <li><em><font color="#646b86">El tercero con los detalles técnicos, planos de obra, materiales usados, etc</font></em> </li> </ol> <p><em><font color="#646b86">La valoración por concurso será tal y como sigue:</font></em></p> <ol> <li><em><font color="#646b86">Valoración económica: Un 50% (De 0 a 5 puntos)</font></em> </li> <li><em><font color="#646b86">Calendario: Un 40% (de 0 a 4 puntos)</font></em> </li> <li><em><font color="#646b86">Detalles técnicos: Un 10% (de 0 a 1 punto)</font></em> </li> </ol> <p><em><font color="#646b86">Se pide envío de los tres sobres antes de 2 días. El cliente no está disponible para contactar ya que está de vacaciones.</font></em></p> <p>Mi hermano entre carcajadas y carcajada, me insistía en que le viera la gracia a la broma, que si “fíjate que aquí no concretan nada, que si eso es buenísimo porque ni dicen el número de las habitaciones, en que si tal y cual”…</p> <p>… yo, la verdad es que sonreí un poco por compromiso mientras por dentro pensaba en las muchas veces que mi trabajo parece una broma… y de mal gusto.</p> <p>Saludos!</p> eiximenishttp://www.blogger.com/profile/02568454383791432108noreply@blogger.com0tag:blogger.com,1999:blog-897874347676011387.post-67682986964985050512011-02-08T08:53:00.001+01:002011-02-08T08:53:17.481+01:00[Material] WebCast ASP.NET MVC<p>Buenas.</p> <p>Este post simplemente es para agradeceros a todos el interés que mostrasteis en el <a href="http://geeks.ms/blogs/etomas/archive/2011/02/02/webcast-191-te-quedaste-con-m-225-s-ganas-asp-net-mvc-ii.aspx" target="_blank">Webcast de ASP.NET MVC que tuve el placer de realizar</a> para la gente del <a href="http://lleida.dotnetclubs.com/" target="_blank">Lledia DotNetClub</a>.</p> <p>Antes que nada os dejo la página desde donde os podéis ver o descargaros el WebCast: <a title="https://msevents.microsoft.com/CUI/WebCastEventDetails.aspx?culture=es-ES&EventID=1032476809&CountryCode=ES" href="https://msevents.microsoft.com/CUI/WebCastEventDetails.aspx?culture=es-ES&EventID=1032476809&CountryCode=ES">https://msevents.microsoft.com/CUI/WebCastEventDetails.aspx?culture=es-ES&EventID=1032476809&CountryCode=ES</a>. En esa página veréis un enlace que pone “<em>Regístrese sin cuenta de Windows Live ID</em>” que es el que os llevará a la página de descarga.</p> <p>Increíble, eh? Esta vez no la pifié con el livemeeting y quedó grabado! :)</p> <p>Os dejo:</p> <ol> <li>El código fuente de Porrazo completo. Porrazo, el sistema de porras on-line, fue lo que más o menos fuimos desarrollando a lo largo de los dos webcasts. En el código veréis implementadas todas las técnicas de las que hablamos (con la excepción del helper WebGrid). Por supuesto que Porrazo es un conglomerado de técnicas de hacer las cosas: no significa que todas deban implementarse en una misma aplicación MVC. A vosotros os toca juzgar en cada caso cual es la mejor opción! </li> <li>Los dos ppts, aunqué no tienen apenas información, ya que son un mero índice. </li> <li>El script SQL para crear las tablas (sin datos… los datos deberéis ponerlos vosotros… :p). </li> </ol> <p>Lo tenéis en: <a title="http://cid-6521c259e9b1bec6.office.live.com/browse.aspx/BurbujasNet/ZipsPosts/Porrazo" href="http://cid-6521c259e9b1bec6.office.live.com/browse.aspx/BurbujasNet/ZipsPosts/Porrazo">http://cid-6521c259e9b1bec6.office.live.com/browse.aspx/BurbujasNet/ZipsPosts/Porrazo</a></p> <p>En breve me gustaría hacer un documento word (en esa misma carpeta) para que podáis realizar step-by-step Porrazo desde cero y podáis practicar las técnicas que vimos. Pero como ello me va a llevar cierto tiempo, prefiero dejaros los fuentes ya disponibles, y actualizar este post cuando tenga el documento realizado.</p> <p>Si alquien quiere que le avise personalmente cuando tenga el documento, puede enviarme un mail (mi correo está en los ppts) o contactar conmigo a través del formulario de contacto del blog.</p> <p>Muchas gracias!</p> <p>PD: Eso es un crosspost desde <a href="http://geeks.ms/blogs/etomas/default.aspx" target="_blank">mi blog en geeks.ms!</a></p> eiximenishttp://www.blogger.com/profile/02568454383791432108noreply@blogger.com0tag:blogger.com,1999:blog-897874347676011387.post-57942726792332596242011-02-02T10:20:00.001+01:002011-02-02T10:20:32.482+01:00[WebCast] ¿Te quedaste con más ganas? ASP.NET MVC II<p>Muy buenas! Este jueves <strong>03 de febrero</strong> (o sea mañana) gracias a la gente del <a href="http://lleida.dotnetclubs.com/" target="_blank">Lleida dotnet club</a>, tengo el gusto de dar un WebCast sobre ASP.NET MVC :)</p> <p>La verdad es que el WebCast está pensado para ser la continuación del que <a href="http://geeks.ms/blogs/etomas/archive/2010/12/01/evento-recordatorio-este-jueves-hasta-d-243-nde-podemos-llegar-con-asp-net-mvc.aspx" target="_blank">dí el pasado 2 de</a> diciembre. En aquel webcast vimos:</p> <ol> <li>Introducción a ASP.NET MVC </li> <li>Membership providers para autenticación y autorización </li> <li>Uso de Unity para desacoplar dependencias </li> <li>El patrón repositorio para acceder a la BBDD </li> <li>Uso de Ajax con Json </li> <li>Templates con jquery-tmpl </li> <li>Binding de datos en los controladores </li> <li>Guardando datos en la bbdd: Unit of Work </li> </ol> <p>Todo ello lo vimos desarrollando una aplicación desde cero (al que mi departamento de marketing llamó <em>Porrazo</em>), y este nuevo webcast continuará donde se quedó aquel. Lamentablemente, debido a que soy un negado en eso del livemeeting el webcast no quedó grabado, así que al principio vamos a dar un rápido resumen a lo visto en aquel webcast, para que estemos todos en situación… Y luego proseguiremos con los puntos que se nos quedaron en el tintero:</p> <ol> <li>Uso de un Role Provider </li> <li>Añadiendo un módulo de administración (áreas) </li> <li>Componiendo presentación (uso de vistas parciales) </li> <li>Grids!! (Sí… esa es una <em>feature</em> que la gente pide mucho) </li> <li>Ventanas flotantes (popups) </li> </ol> <p>Si me da tiempo (espero no volver a enrollarme) veremos algunos temas addicionales como:</p> <ol> <li>Validación de datos en cliente </li> <li>Unobtrusive javascript </li> </ol> <p>Y al final una sesión de preguntas y respuestas (pero… no os paséis: que sean facilitas, eh? ;-))</p> <p>Todo ello con ASP.NET MVC3, con VS2010 a tope (nada de power points!) y desarrollando Porrazo, el sistema de porras online que cuando esté finalizado colgaré aquí con la esperanza de que lo compre Google y me retire.</p> <p>La <a href="https://msevents.microsoft.com/CUI/Register.aspx?culture=es-ES&EventID=1032476808&CountryCode=ES&IsRedirect=false" target="_blank">página de registro</a> esá en: <a title="https://msevents.microsoft.com/CUI/Register.aspx?culture=es-ES&EventID=1032476808&CountryCode=ES&IsRedirect=false" href="https://msevents.microsoft.com/CUI/Register.aspx?culture=es-ES&EventID=1032476808&CountryCode=ES&IsRedirect=false">https://msevents.microsoft.com/CUI/Register.aspx?culture=es-ES&EventID=1032476808&CountryCode=ES&IsRedirect=false</a></p> <p>Un saludo a todos!</p> eiximenishttp://www.blogger.com/profile/02568454383791432108noreply@blogger.com0tag:blogger.com,1999:blog-897874347676011387.post-9478106035064679152011-01-26T09:37:00.001+01:002011-01-26T09:37:06.310+01:00ASP.NET MVC3: Un helper Repeater<p>Muy buenas! En el post anterior comenté la característica de los <a href="http://geeks.ms/blogs/etomas/archive/2011/01/25/asp-net-mvc3-razor-templates.aspx" target="_blank">templates de Razor</a> y hoy vamos a ver como podríamos crear un <em>helper</em> que emule un poco el <a href="http://msdn.microsoft.com/es-es/library/6weyd81h(VS.80).aspx" target="_blank">control Repeater</a> que hay en webforms (salvando las distancias, claro).</p> <p>Vamos a crear un <em>helper externo</em>, es decir que sea reutilizable en distintos proyectos: para ello nuestro helper va a residir en una clase (en mi ejemplo en el propio proyecto web, pero se podría situar en una librería de clases para ser reutilizable).</p> <p><strong>Esqueleto inicial</strong></p> <p>A nuestro helper se le pasará lo siguiente:</p> <ol> <li>Una cabecera: template Razor que se renderizará una sola vez al principio. </li> <li>Un cuerpo: template Razor que se renderizará una vez por cada elemento </li> <li>Un pie: template Razor que se renderizará una sola vez al final. </li> <li>Una colección de elementos (por cada elemento se renderizará el cuerpo). </li> </ol> <p>Recordáis que ayer comentamos que los templates Razor son Func<T, HelperResult>? </p> <p>Bien, pues vamos a declarar nuestro helper:</p> <p> </p> <div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: 'Courier New', courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px" id="codeSnippet"><span style="color: #0000ff">public</span> <span style="color: #0000ff">static</span> IHtmlString Repeat<TItem>(<br /> IEnumerable<TItem> items,<br /> Func<dynamic, HelperResult> header,<br /> Func<TItem, HelperResult> body,<br /> Func<dynamic, HelperResult> footer<br /> )<br />{<br /> var builder = <span style="color: #0000ff">new</span> StringBuilder();<br /> <span style="color: #0000ff">if</span> (header != <span style="color: #0000ff">null</span>)<br /> {<br /> builder.Append(header(<span style="color: #0000ff">null</span>).ToHtmlString());<br /> }<br /> <br /> <span style="color: #0000ff">foreach</span> (var item <span style="color: #0000ff">in</span> items)<br /> {<br /> builder.Append(body(item).ToHtmlString());<br /> }<br /> <span style="color: #0000ff">if</span> (footer != <span style="color: #0000ff">null</span>)<br /> {<br /> builder.Append(footer(<span style="color: #0000ff">null</span>).ToHtmlString());<br /> }<br /><br /> <span style="color: #0000ff">return</span> MvcHtmlString.Create(builder.ToString());<br />}<br /></pre><br /><br /> <br /></div><br /><br /><p>Los templates siempre están declarados como Func<T, HelperResult>, en este caso declaramos tres templates:</p><br /><br /><ol><br /> <li>header, cuyo parámetro @item es dynamic. </li><br /><br /> <li>body, cuyo parámetro @item es TItem, donde TItem es el tipo del enumerable que se le pasa al helper </li><br /><br /> <li>footer, cuyo parámetro @item es dynamic </li><br /></ol><br /><br /><p>Para invocar los templates y obtener el html asociado simplemente invocamos el Func (pasándole el parámetro del tipo correcto) y sobre el resultado llamamos a <strong>ToHtmlString</strong>: Este método nos devuelve la cadena HTML que ha parseado Razor.</p><br /><br /><p>Ahora p.ej. me creo una acción tal en el controlador:</p><br /><br /><div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: 'Courier New', courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"><br /> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px" id="codeSnippet"><span style="color: #0000ff">public</span> ActionResult List()<br />{<br /> <span style="color: #0000ff">return</span> View(<span style="color: #0000ff">new</span> List<Product><br /> {<br /> <span style="color: #0000ff">new</span> Product() {Nombre=<span style="color: #006080">"PS3"</span>, Precio=300},<br /> <span style="color: #0000ff">new</span> Product() {Nombre=<span style="color: #006080">"XBox360"</span>, Precio=150},<br /> <span style="color: #0000ff">new</span> Product() {Nombre=<span style="color: #006080">"Wii"</span>, Precio=100}<br /> });<br />}</pre><br /><br /> <br /></div><br /><br /><p>Y en la vista asociada:</p><br /><br /><div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: 'Courier New', courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"><br /> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px" id="codeSnippet">@using MvcApplication14.Helpers<br />@model IEnumerable<span style="color: #0000ff"><</span><span style="color: #800000">MvcApplication14.Models.Product</span><span style="color: #0000ff">></span><br /><br /><span style="color: #0000ff"><</span><span style="color: #800000">table</span><span style="color: #0000ff">></span><br />@Repeater.Repeat(Model, <br /> @<span style="color: #0000ff"><</span><span style="color: #800000">thead</span><span style="color: #0000ff">><</span><span style="color: #800000">tr</span><span style="color: #0000ff">><</span><span style="color: #800000">td</span><span style="color: #0000ff">></span>Nombre<span style="color: #0000ff"></</span><span style="color: #800000">td</span><span style="color: #0000ff">><</span><span style="color: #800000">td</span><span style="color: #0000ff">></span>Precio<span style="color: #0000ff"></</span><span style="color: #800000">td</span><span style="color: #0000ff">></</span><span style="color: #800000">tr</span><span style="color: #0000ff">></</span><span style="color: #800000">thead</span><span style="color: #0000ff">></span>, <br /> @<span style="color: #0000ff"><</span><span style="color: #800000">tr</span><span style="color: #0000ff">><</span><span style="color: #800000">td</span><span style="color: #0000ff">></span>@item.Nombre<span style="color: #0000ff"></</span><span style="color: #800000">td</span><span style="color: #0000ff">><</span><span style="color: #800000">td</span><span style="color: #0000ff">></span>@item.Precio<span style="color: #0000ff"></</span><span style="color: #800000">td</span><span style="color: #0000ff">></</span><span style="color: #800000">tr</span><span style="color: #0000ff">></span>, <br /> null)<br /><span style="color: #0000ff"></</span><span style="color: #800000">table</span><span style="color: #0000ff">></span></pre><br /><br /> <br /></div><br /><br /><p>Fijaos en la llamada a Repeater.Repeat, donde le paso el modelo que recibe la vista (que es un IEnumerable de Product) y los tres templates Razor (en este caso no le paso footer y por ello pongo null).</p><br /><br /><p>En el segundo template (body) puedo acceder al elemento que se está renderizando mediante @item. Además, dado que en helper he declarado el template body de tipo Func<TItem, HelperResult>, tengo soporte de Intellisense:</p><br /><br /><p><a href="http://lh3.ggpht.com/_1JRa_Jfnm20/TT_dKk0h20I/AAAAAAAAEJI/86HJSGr889U/s1600-h/image%5B2%5D.png"><img style="background-image: none; border-right-width: 0px; margin: ; padding-left: 0px; padding-right: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px; padding-top: 0px" title="image" border="0" alt="image" src="http://lh3.ggpht.com/_1JRa_Jfnm20/TT_dLLXDGaI/AAAAAAAAEJM/_0P2YtqD9eE/image_thumb.png?imgmax=800" width="244" height="107" /></a></p><br /><br /><p>Si teclease @item en el template header o footer no tendría intellisense porque los he declarado dynamic en el helper (además ojo, que en el helper le paso null, por lo que rebentaría si usamos @item en el template header o footer).</p><br /><br /><p>Y listos! El código HTML generado es:</p><br /><br /><div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: 'Courier New', courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"><br /> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px" id="codeSnippet"><span style="color: #0000ff"><</span><span style="color: #800000">thead</span><span style="color: #0000ff">><</span><span style="color: #800000">tr</span><span style="color: #0000ff">><</span><span style="color: #800000">td</span><span style="color: #0000ff">></span>Nombre<span style="color: #0000ff"></</span><span style="color: #800000">td</span><span style="color: #0000ff">><</span><span style="color: #800000">td</span><span style="color: #0000ff">></span>Precio<span style="color: #0000ff"></</span><span style="color: #800000">td</span><span style="color: #0000ff">></</span><span style="color: #800000">tr</span><span style="color: #0000ff">></</span><span style="color: #800000">thead</span><span style="color: #0000ff">></span><br /><span style="color: #0000ff"><</span><span style="color: #800000">tr</span><span style="color: #0000ff">><</span><span style="color: #800000">td</span><span style="color: #0000ff">></span>PS3<span style="color: #0000ff"></</span><span style="color: #800000">td</span><span style="color: #0000ff">><</span><span style="color: #800000">td</span><span style="color: #0000ff">></span>300<span style="color: #0000ff"></</span><span style="color: #800000">td</span><span style="color: #0000ff">></</span><span style="color: #800000">tr</span><span style="color: #0000ff">></span><br /><span style="color: #0000ff"><</span><span style="color: #800000">tr</span><span style="color: #0000ff">><</span><span style="color: #800000">td</span><span style="color: #0000ff">></span>XBox360<span style="color: #0000ff"></</span><span style="color: #800000">td</span><span style="color: #0000ff">><</span><span style="color: #800000">td</span><span style="color: #0000ff">></span>150<span style="color: #0000ff"></</span><span style="color: #800000">td</span><span style="color: #0000ff">></</span><span style="color: #800000">tr</span><span style="color: #0000ff">></span><br /><span style="color: #0000ff"><</span><span style="color: #800000">tr</span><span style="color: #0000ff">><</span><span style="color: #800000">td</span><span style="color: #0000ff">></span>Wii<span style="color: #0000ff"></</span><span style="color: #800000">td</span><span style="color: #0000ff">><</span><span style="color: #800000">td</span><span style="color: #0000ff">></span>100<span style="color: #0000ff"></</span><span style="color: #800000">td</span><span style="color: #0000ff">></</span><span style="color: #800000">tr</span><span style="color: #0000ff">></span></pre><br /><br /> <br /></div><br /><br /><p>Guay, no?</p><br /><br /><p>Podríamos “complicar” un poco el helper, para que desde los templates supiesemos si estamos en una fila par o impar y así aplicar clases… Para ello nos basta con declarar una clase adicional en nuestor helper:</p><br /><br /><div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: 'Courier New', courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"><br /> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px" id="codeSnippet"><span style="color: #0000ff">public</span> <span style="color: #0000ff">class</span> RepeaterItem<TItem><br />{<br /> <span style="color: #0000ff">public</span> <span style="color: #0000ff">int</span> Index { get; <span style="color: #0000ff">private</span> set; }<br /> <span style="color: #0000ff">public</span> <span style="color: #0000ff">bool</span> IsEven { get { <span style="color: #0000ff">return</span> Index % 2 == 0; } }<br /> <span style="color: #0000ff">public</span> TItem Item { get; <span style="color: #0000ff">private</span> set; }<br /><br /> <span style="color: #0000ff">public</span> RepeaterItem(TItem item, <span style="color: #0000ff">int</span> index)<br /> {<br /> Index = index;<br /> Item = item;<br /> }<br />}</pre><br /><br /> <br /></div><br /><br /><p>Esa clase simplemente contiene el índice del elemento actual, el propio elemento y una propiedad que indica si es par o no.</p><br /><br /><p>Ahora modificamos el helper para pasarle al template body un objeto RepeaterItem<TItem>:</p><br /><br /><div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: 'Courier New', courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"><br /> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px" id="codeSnippet"><span style="color: #0000ff">public</span> <span style="color: #0000ff">static</span> <span style="color: #0000ff">class</span> Repeater<br />{<br /> <span style="color: #0000ff">public</span> <span style="color: #0000ff">static</span> IHtmlString Repeat<TItem>(<br /> IEnumerable<TItem> items,<br /> Func<dynamic, HelperResult> header,<br /> Func<RepeaterItem<TItem>, HelperResult> body,<br /> Func<dynamic, HelperResult> footer<br /> )<br /> {<br /> var builder = <span style="color: #0000ff">new</span> StringBuilder();<br /> <span style="color: #0000ff">if</span> (header != <span style="color: #0000ff">null</span>)<br /> {<br /> builder.Append(header(<span style="color: #0000ff">null</span>).ToHtmlString());<br /> }<br /><br /> var count = 0;<br /> <span style="color: #0000ff">foreach</span> (var item <span style="color: #0000ff">in</span> items)<br /> {<br /> var repeaterItem = <span style="color: #0000ff">new</span> RepeaterItem<TItem>(item, count++);<br /> builder.Append(body(repeaterItem).ToHtmlString());<br /> }<br /> <span style="color: #0000ff">if</span> (footer != <span style="color: #0000ff">null</span>)<br /> {<br /> builder.Append(footer(<span style="color: #0000ff">null</span>).ToHtmlString());<br /> }<br /><br /> <span style="color: #0000ff">return</span> MvcHtmlString.Create(builder.ToString());<br /> }<br />}</pre><br /><br /> <br /></div><br /><br /><p>Los cambios básicamente son declarar el template domo Func<RepeaterItem<Titem>> y cuando invocamos el template, crear antes un objeto RepeaterItem y pasárselo como parámetro.</p><br /><br /><p>Finalmente ahora debemos modificar la vista, ya que el parámetro @item de nuestro template es ahora un RepeaterItem<TItem>:</p><br /><br /><div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: 'Courier New', courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"><br /> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px" id="codeSnippet">@Repeater.Repeat(Model, <br /> @<span style="color: #0000ff"><</span><span style="color: #800000">thead</span><span style="color: #0000ff">><</span><span style="color: #800000">tr</span><span style="color: #0000ff">><</span><span style="color: #800000">td</span><span style="color: #0000ff">></span>Nombre<span style="color: #0000ff"></</span><span style="color: #800000">td</span><span style="color: #0000ff">><</span><span style="color: #800000">td</span><span style="color: #0000ff">></span>Precio<span style="color: #0000ff"></</span><span style="color: #800000">td</span><span style="color: #0000ff">></</span><span style="color: #800000">tr</span><span style="color: #0000ff">></</span><span style="color: #800000">thead</span><span style="color: #0000ff">></span>, <br /> @<span style="color: #0000ff"><</span><span style="color: #800000">tr</span> <span style="color: #ff0000">style</span><span style="color: #0000ff">="@(item.IsEven ? "</span><span style="color: #ff0000">background-color:</span> <span style="color: #ff0000">white</span><span style="color: #0000ff">" : "</span><span style="color: #ff0000">background-color:pink</span><span style="color: #0000ff">")"</span><span style="color: #0000ff">></span><br /> <span style="color: #0000ff"><</span><span style="color: #800000">td</span><span style="color: #0000ff">></span>@item.Item.Nombre<span style="color: #0000ff"></</span><span style="color: #800000">td</span><span style="color: #0000ff">><</span><span style="color: #800000">td</span><span style="color: #0000ff">></span>@item.Item.Precio<span style="color: #0000ff"></</span><span style="color: #800000">td</span><span style="color: #0000ff">></</span><span style="color: #800000">tr</span><span style="color: #0000ff">></span>, <br /> null)</pre><br /><br /> <br /></div><br /><br /><p>Fijaos como dentro del template body puedo preguntar si el elemento actual es par (@item.IsEven y acceder al propio elemento @item.Item). En este caso uso @item.IsEven para cambiar el color de fondo de la fila:</p><br /><br /><p><a href="http://lh6.ggpht.com/_1JRa_Jfnm20/TT_dLp0Y0II/AAAAAAAAEJQ/zhS_-2k_pP4/s1600-h/image%5B5%5D.png"><img style="background-image: none; border-right-width: 0px; margin: ; padding-left: 0px; padding-right: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px; padding-top: 0px" title="image" border="0" alt="image" src="http://lh4.ggpht.com/_1JRa_Jfnm20/TT_dMbanAeI/AAAAAAAAEJU/kLoAqFt4vEY/image_thumb%5B1%5D.png?imgmax=800" width="145" height="133" /></a></p><br /><br /><p>Y listos! Espero que esos dos posts sobre templates Razor os hayan parecido interesantes! </p><br /><br /><p>Un saludo a todos! ;-)</p><br /><br /><p>PD: Como siempre… eso es un crosspost desde <a href="http://geeks.ms/blogs/etomas" target="_blank">mi blog en geeks.ms</a>!</p> eiximenishttp://www.blogger.com/profile/02568454383791432108noreply@blogger.com0tag:blogger.com,1999:blog-897874347676011387.post-62624874059966630672011-01-25T16:41:00.001+01:002011-01-25T16:41:15.814+01:00ASP.NET MVC3: Razor Templates<p>Muy buenas!</p> <p>En este post quiero comentaros una característica de Razor que yo considero que es una <strong><u>auténtica pasada:</u></strong> los <strong>templates</strong>.</p> <p>Básicamente <strong>el meollo de todo está en la posibilidad de guardar el resultado de un parseo de Razor en un Func<T, HelperResult></strong> siendo T el tipo del modelo que renderiza el template.</p> <p>Veámoslo con código:</p> <div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: 'Courier New', courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px" id="codeSnippet">@{<br /> Func<span style="color: #0000ff"><</span><span style="color: #800000">string</span>, <span style="color: #ff0000">HelperResult</span><span style="color: #0000ff">></span> h =<br /> @<span style="color: #0000ff"><</span><span style="color: #800000">h2</span><span style="color: #0000ff">></span>Esto es un template al que se le han pasado los datos: @item<span style="color: #0000ff"></</span><span style="color: #800000">h2</span><span style="color: #0000ff">></span><br /> ;<br />}<br /><span style="color: #0000ff"><</span><span style="color: #800000">p</span><span style="color: #0000ff">></span><br /> Renderizamos el template: @h("datos")<br /><span style="color: #0000ff"></</span><span style="color: #800000">p</span><span style="color: #0000ff">></span></pre><br /><br /> <br /></div><br /><br /><p>Fijaos como nos guardamos en una Func<string, HelperResult> el resultado de renderizar un template razor (en este caso el template <h2>…</h2>). Fijaos en tres detalles:</p><br /><br /><ol><br /> <li>Dado que la variable h está declarada como Func<string, HelperResult> el tipo del modelo en este template es “string” </li><br /><br /> <li>Para acceder al modelo que se pasa al template se usa @item </li><br /><br /> <li>Al final del template Razor ponemos un ; (eso es una declaración C# y como tal <em>debe</em> terminar en punto y coma). </li><br /></ol><br /><br /><p>Luego más adelante renderizamos el template, con el código: @h("datos")</p><br /><br /><h2><strong>Eh!! Eso no es lo mismo que @helper?</strong></h2><br /><br /><p>Si conoces @helper te puede parecer que esto no es muy novedoso. Con @helper podemos crear helpers inline de la siguiente manera:</p><br /><br /><div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: 'Courier New', courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"><br /> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px" id="codeSnippet">@helper h2(string s) {<span style="color: #0000ff"><</span><span style="color: #800000">h2</span><span style="color: #0000ff">></span>Esto es un helper: @s<span style="color: #0000ff"></</span><span style="color: #800000">h2</span><span style="color: #0000ff">></span>}</pre><br /><br /> <br /></div><br /><br /><p>Y lo podíamos invocar con:</p><br /><br /><div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: 'Courier New', courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"><br /> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px" id="codeSnippet">@h2("ufo")</pre><br /><br /> <br /></div><br /><br /><p>Por lo que parece que viene a ser lo mismo. Y es que, en el fondo, ambas construcciones devuelven un HelperResult. </p><br /><br /><blockquote><br /> <p><strong>Nota: </strong>Si quieres más información sobre @helper, léete el post que hizo el maestro hace algún tiempecillo: <a title="http://www.variablenotfound.com/2010/11/y-mas-sobre-helpers-en-razor.html" href="http://www.variablenotfound.com/2010/11/y-mas-sobre-helpers-en-razor.html">http://www.variablenotfound.com/2010/11/y-mas-sobre-helpers-en-razor.html</a></p><br /></blockquote><br /><br /><p>Lo interesante no es que ambas construccione se parezcan, lo que quiero recalcar es que…</p><br /><br /><p><strong>… Los templates son Func<T, HelperResult>!</strong></p><br /><br /><p>Lo pongo así en negrita porque eso es importante, y a la vez me sirve de título. Si los templates Razor son Func<T, HelperResult>… <em>cualquier método que reciba un Fun<T, HelperResult> puede recibir un template Razor</em>…</p><br /><br /><p>… Incluídos los helpers!</p><br /><br /><div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: 'Courier New', courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"><br /> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px" id="codeSnippet">@helper Repeater(<span style="color: #0000ff">int</span> count,Func<dynamic, HelperResult> template, <span style="color: #0000ff">string</span> data) {<br /> <ul><br /> @<span style="color: #0000ff">for</span> (<span style="color: #0000ff">int</span> idx=0; idx<count; idx++)<br /> {<br /> <li>@template(<span style="color: #0000ff">new</span> { Text = data, Index = idx })</li><br /> }<br /> </ul> <br />}<br /><br />@Repeater(10, @<span>@item.Text (item: #@item.Index)</span>, <span style="color: #006080">"Ufo"</span>)</pre><br /><br /> <br /></div><br /><br /><p>En este código:</p><br /><br /><ol><br /> <li>Declaramos un helper, llamado Repeater que acepta tres parámetros: <br /> <ol><br /> <li>Un entero </li><br /><br /> <li>Un Func<dynamic, HelperResult> que <em>por lo tanto podrá ser un template Razor</em> </li><br /><br /> <li>Una cadena </li><br /> </ol><br /> </li><br /><br /> <li>El helper se limita a crear una lista y luego: <br /> <ol><br /> <li>Crea tantos <li> como indica el paràmetro count y en cada id <br /> <ol><br /> <li>Evalúa el segundo parámetro <em>(que es el Func) y le pasa como parámetro un objeto anonimo con dos propiedades (Text e Index), donde Text es el tercer parámetro que recibe (y index el valor de la iteración actual</em>). </li><br /> </ol><br /> </li><br /> </ol><br /> </li><br /><br /> <li>Cuando invocamos al helper, le pasamos como primer parámetro el número de repeticiones, como segundo parámetro <strong>un template de Razor</strong>. Fijaos que dentro de dicho template: <br /><br /> <ol><br /> <li>Accedemos a las propiedades @item.Text y @item.Index. Eso podemos hacerlo porque hemos declarado el tipo del modelo de dicho template como <em>dynamic</em> y por eso nos compila (y nos funciona porque el helper cuando invoca el template crea esas propiedades en el objeto anónimo). </li><br /> </ol><br /> </li><br /></ol><br /><br /><p>El código HTML generado por dicha llamada al helper Repeater es:</p><br /><br /><div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: 'Courier New', courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"><br /> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px" id="codeSnippet"><span style="color: #0000ff"><</span><span style="color: #800000">ul</span><span style="color: #0000ff">></span><br /> <span style="color: #0000ff"><</span><span style="color: #800000">li</span><span style="color: #0000ff">></span> <span style="color: #0000ff"><</span><span style="color: #800000">span</span><span style="color: #0000ff">></span>Ufo (item: #0)<span style="color: #0000ff"></</span><span style="color: #800000">span</span><span style="color: #0000ff">></</span><span style="color: #800000">li</span><span style="color: #0000ff">></span><br /> <span style="color: #0000ff"><</span><span style="color: #800000">li</span><span style="color: #0000ff">></span> <span style="color: #0000ff"><</span><span style="color: #800000">span</span><span style="color: #0000ff">></span>Ufo (item: #1)<span style="color: #0000ff"></</span><span style="color: #800000">span</span><span style="color: #0000ff">></</span><span style="color: #800000">li</span><span style="color: #0000ff">></span><br /> <span style="color: #0000ff"><</span><span style="color: #800000">li</span><span style="color: #0000ff">></span> <span style="color: #0000ff"><</span><span style="color: #800000">span</span><span style="color: #0000ff">></span>Ufo (item: #2)<span style="color: #0000ff"></</span><span style="color: #800000">span</span><span style="color: #0000ff">></</span><span style="color: #800000">li</span><span style="color: #0000ff">></span><br /> <span style="color: #0000ff"><</span><span style="color: #800000">li</span><span style="color: #0000ff">></span> <span style="color: #0000ff"><</span><span style="color: #800000">span</span><span style="color: #0000ff">></span>Ufo (item: #3)<span style="color: #0000ff"></</span><span style="color: #800000">span</span><span style="color: #0000ff">></</span><span style="color: #800000">li</span><span style="color: #0000ff">></span><br /> <span style="color: #0000ff"><</span><span style="color: #800000">li</span><span style="color: #0000ff">></span> <span style="color: #0000ff"><</span><span style="color: #800000">span</span><span style="color: #0000ff">></span>Ufo (item: #4)<span style="color: #0000ff"></</span><span style="color: #800000">span</span><span style="color: #0000ff">></</span><span style="color: #800000">li</span><span style="color: #0000ff">></span><br /> <span style="color: #0000ff"><</span><span style="color: #800000">li</span><span style="color: #0000ff">></span> <span style="color: #0000ff"><</span><span style="color: #800000">span</span><span style="color: #0000ff">></span>Ufo (item: #5)<span style="color: #0000ff"></</span><span style="color: #800000">span</span><span style="color: #0000ff">></</span><span style="color: #800000">li</span><span style="color: #0000ff">></span><br /> <span style="color: #0000ff"><</span><span style="color: #800000">li</span><span style="color: #0000ff">></span> <span style="color: #0000ff"><</span><span style="color: #800000">span</span><span style="color: #0000ff">></span>Ufo (item: #6)<span style="color: #0000ff"></</span><span style="color: #800000">span</span><span style="color: #0000ff">></</span><span style="color: #800000">li</span><span style="color: #0000ff">></span><br /> <span style="color: #0000ff"><</span><span style="color: #800000">li</span><span style="color: #0000ff">></span> <span style="color: #0000ff"><</span><span style="color: #800000">span</span><span style="color: #0000ff">></span>Ufo (item: #7)<span style="color: #0000ff"></</span><span style="color: #800000">span</span><span style="color: #0000ff">></</span><span style="color: #800000">li</span><span style="color: #0000ff">></span><br /> <span style="color: #0000ff"><</span><span style="color: #800000">li</span><span style="color: #0000ff">></span> <span style="color: #0000ff"><</span><span style="color: #800000">span</span><span style="color: #0000ff">></span>Ufo (item: #8)<span style="color: #0000ff"></</span><span style="color: #800000">span</span><span style="color: #0000ff">></</span><span style="color: #800000">li</span><span style="color: #0000ff">></span><br /> <span style="color: #0000ff"><</span><span style="color: #800000">li</span><span style="color: #0000ff">></span> <span style="color: #0000ff"><</span><span style="color: #800000">span</span><span style="color: #0000ff">></span>Ufo (item: #9)<span style="color: #0000ff"></</span><span style="color: #800000">span</span><span style="color: #0000ff">></</span><span style="color: #800000">li</span><span style="color: #0000ff">></span><br /><span style="color: #0000ff"></</span><span style="color: #800000">ul</span><span style="color: #0000ff">></span> </pre><br /><br /> <br /></div><br /><br /><p>Espero que el post os haya resultado interesante!!! ;-)</p><br /><br /><p>Saludos!</p><br /><br /><p>PD: Como siempre… otro crosspost desde <a href="http://geeks.ms/blogs/etomas/" target="_blank">mi blog en geeks.ms</a>!</p> eiximenishttp://www.blogger.com/profile/02568454383791432108noreply@blogger.com0tag:blogger.com,1999:blog-897874347676011387.post-8339410825718881382011-01-19T11:07:00.001+01:002011-01-19T11:07:27.509+01:00ASP.NET MVC: Como recuperar un dato de una cookie para cada petición… Una alternativa ¿igual?<p>Muy buenas! Hace algunos días escribí el post <a href="http://geeks.ms/blogs/etomas/archive/2011/01/14/asp-net-mvc-como-recuperar-un-dato-de-una-cookie-para-cada-petici-243-n.aspx" target="_blank">ASP.NET MVC: Como recuperar datos de una cookie en cada petición</a>, donde mostraba el uso de un <em>route handler</em> propio para recuperar los datos de una cookie y colocarlos en el Route Data. En el ejemplo era una cookie de cultura de la aplicación, pero se puede aplicar a lo que queráis.</p> <p>Lo que más me gusta de ASP.NET MVC es que muy expandible, que muchas cosas pueden hacerse de más de una forma. Pues bien, una de las novedades más interesantes de MVC3 (al margen de Razor) son los action filters <strong>globales</strong>.</p> <p>En este post os propongo una <strong>solución alternativa </strong>(aunque ya veremos que tiene una <em>ligerísima</em> diferencia) al mismo problema. La diferencia es que <strong>no se debe alterar la tabla de rutas para nada</strong>. Y dicha solución pasa por usar un action filter global.</p> <p>Una de las cosas que en MVC nos debe quedar claro es que <em>cuando repitamos muchas veces un mismo código</em> <em>de un controlador </em>debemos considerar de ponerlo en un Action Filter. El “problema” está que los action filters deben aplicarse controlador a controlador (o acción a acción). Si tenemos un filtro que debe aplicarse a todos los controladores podemos considerar crear una clase base que lo tenga y heredar todos los controladores de ella…</p> <p>… o al menos eso era así <em>antes</em> de MVC3.</p> <p>Con MVC3 y los filtros globales podemos aplicar un filtro <em>a todas las acciones de todos los controladores</em>. Y todo ello con <strong>una sola línea en global.asax. </strong>Es brutal!</p> <p><strong>El filtro global…</strong></p> <p>Lo bueno es que los filtros globales <strong>se implementan igual</strong> que los filtros no globales clásicos que teníamos en MVC2. En este caso la implementación es super sencilla:</p> <div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: 'Courier New', courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px" id="codeSnippet"><span style="color: #0000ff">public</span> <span style="color: #0000ff">class</span> CookieCultureFilterAttribute : ActionFilterAttribute<br />{<br /> <span style="color: #0000ff">public</span> <span style="color: #0000ff">override</span> <span style="color: #0000ff">void</span> OnActionExecuting(ActionExecutingContext filterContext)<br /> {<br /> var cultureCookieVal = GetCultureFromCookie(filterContext.HttpContext.Request.Cookies);<br /> var culture = <span style="color: #0000ff">new</span> CultureInfo(cultureCookieVal);<br /> filterContext.RouteData.Values.Add(<span style="color: #006080">"culture"</span>, cultureCookieVal);<br /> Thread.CurrentThread.CurrentCulture = culture;<br /> Thread.CurrentThread.CurrentUICulture = culture;<br /> }<br /><br /> <span style="color: #0000ff">private</span> <span style="color: #0000ff">string</span> GetCultureFromCookie(HttpCookieCollection cookies)<br /> {<br /> var retValue = <span style="color: #006080">"ca-ES"</span>;<br /> <span style="color: #0000ff">if</span> (cookies.AllKeys.Contains(<span style="color: #006080">"userculture"</span>))<br /> {<br /> retValue = cookies[<span style="color: #006080">"userculture"</span>].Value;<br /> }<br /> <span style="color: #0000ff">return</span> retValue;<br /> }<br />}</pre><br /><br /> <br /></div><br /><br /><p>El código es trivial: derivo de ActionFilterAttribute (clase que ya existía) y redefino el método <em>OnActionExecuting</em> que se ejecuta <em>antes</em> de ejecutar la acción del controlador. En este método tengo el código para leer la cookie de cultura (exactamente lo mismo que tenía antes en el Route Handler).</p><br /><br /><p><strong>Activar el filtro global</strong></p><br /><br /><p>Os dije que era una sóla línea en global.asax, verdad? Pues concretamente esta:</p><br /><br /><div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: 'Courier New', courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"><br /> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px" id="codeSnippet">GlobalFilters.Filters.Add(<span style="color: #0000ff">new</span> CookieCultureFilterAttribute());</pre><br /><br /> <br /></div><br /><br /><p>Otra opción es colocar esa línea dentro de la función <em>RegisterGlobalFilters</em> que crea el VS2010, aunque entonces no es necesario usar la clase GlobalFilters (usad en su lugar el parámetro <em>filters</em>):</p><br /><br /><div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: 'Courier New', courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"><br /> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px" id="codeSnippet">filters.Add(<span style="color: #0000ff">new</span> CookieCultureFilterAttribute());</pre><br /><br /> <br /></div><br /><br /><p><strong>Y ya está</strong>. Nada más. Antes de cada acción de cualquier controlador se ejecutará el código de nuestro filtro global. <strong>No es necesario</strong> modificar la tabla de rutas para añadir nuestro route handler.</p><br /><br /><p><strong>¿Puedo usar el filtro en una aplicación MVC2?</strong></p><br /><br /><p>Si, si que puedes, pero entonces debes:</p><br /><br /><ol><br /> <li>Aplicarlo en cada controlador (usando [CookieCultureFilter] <em>antes</em> de cada acción o cada controlador que quieras que use la cookie). </li><br /><br /> <li>Derivar todos tus controladores de un controlador base que tenga [CookieCultureFilter] aplicado. </li><br /></ol><br /><br /><p><strong>Y lo más importante… ¿Ambas soluciones son equivalentes?</strong></p><br /><br /><p>Pues NO. Ambas soluciones no son equivalentes… En el caso del post anterior, si recordáis, si declaraba un parámetro <em>culture</em> en una acción, recibía el valor de la cookie, ya que el route handler me añadía este parámetro en el RouteData. Pues bien, <strong>eso</strong> dejará de funcionar. Es decir, en este caso la acción:</p><br /><br /><div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: 'Courier New', courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"><br /> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px" id="codeSnippet"><span style="color: #0000ff">public</span> ActionResult Foo(<span style="color: #0000ff">string</span> culture)<br />{<br />}</pre><br /><br /> <br /></div><br /><br /><p>que en el post anterior recibía el valor de la cookie en <em>culture</em>, con esa nueva aproximación siempre recibirá <em>null</em>.</p><br /><br /><p>¿Y por que si mi filtro también añade en el RouteData el valor de la cookie? Pues muy sencillo: el Model Binder (que se encarga de hacer binding a los parámetros de las acciones) se ejecuta <strong>antes</strong> que el filtro. Simplificando, el flujo de ejecuciones sería:</p><br /><br /><ol><br /> <li>Route Handler </li><br /><br /> <li>Model Binder </li><br /><br /> <li>Action Filters </li><br /><br /> <li>Acción del controlador </li><br /></ol><br /><br /><p>En este caso, estamos añadiendo un valor en el RouteData <em>después</em> de que el Model Binder haya actuado. Por eso el parámetro no tendrá el valor. Eso no quita que desde el controlador lo podáis consultar (tenéis acceso al RouteData).</p><br /><br /><p>Y con esto termino… espero que el post os haya ayudado un poco más a entender como funciona MVC y ver distintas alternativas de cómo hacer las cosas!</p><br /><br /><p>PD: Eso es <em>otroooooooooooooooooo</em> crosspost desde <a href="http://geeks.ms/blogs/etomas/default.aspx" target="_blank">mi blog de geeks.ms</a></p> eiximenishttp://www.blogger.com/profile/02568454383791432108noreply@blogger.com0tag:blogger.com,1999:blog-897874347676011387.post-87827442099709700392011-01-14T13:42:00.001+01:002011-01-14T13:42:35.010+01:00ASP.NET MVC: Como recuperar un dato de una cookie para cada petición…<p>EEhhhmm… bueno, no se me ocurre un título mejor. Este post nace gracias a un tweet de <a href="http://twitter.com/lluisfranco" target="_blank">Lluis Franco</a>. En el tweet Lluís preguntaba <a href="http://twitter.com/lluisfranco/status/25846781329801216" target="_blank">dónde guardar la cultura de una aplicación MVC</a> si no se podía poner en la URL. Después de varios tweets comentando algunas cosillas yo he respondido diciendo que veía <a href="http://twitter.com/eiximenis/status/25849807952154624" target="_blank">dos opciones: o en una cookie o en la base de datos</a>. Una de las cosas que más me gustan de HTTP es que es simple: no hay muchas maneras de pasar estado entre cliente y servidor ;-)</p> <p>En este post vamos a ver como podemos solucionar fácilmente el problema asumiendo que se guarda la cultura del usuario en una cookie.</p> <p>De mi tweet, la parte importante es la segunda: independizar a los controladores de donde esta la cultura de la aplicación. Ya lo he comentado en varios posts: evitad acceder desde los controladores a objetos que dependen de HTTP: sesión, aplicación, cache y… cookies.</p> <p>En un post anterior ya comenté <a href="http://geeks.ms/blogs/etomas/archive/2010/07/09/asp-net-mvc-q-amp-a-191-c-243-mo-se-usan-las-cookies.aspx" target="_blank">como usar un value provider para hacer binding de datos de una cookie a un controlador</a>. Esa es una buena solución si los datos de la cookie se usan en algunas pocas acciones de un controlador. Pero ese no es nuestro caso ahora: ahora queremos que la cultura se establezca siempre, para <strong>todas</strong> las acciones de <strong>todos</strong> los controladores.</p> <p>La solución en este caso pasa por un <em>Route Handler</em> nuevo. Los route handlers son los objetos que se encargan de procesar las rutas (crear los controladores y cederles el control). Son pues objetos <em>de bajo nivel</em>. Cuando la tabla de rutas enruta una URL para ser procesada por una ruta concreta, se usa el RouteHandler asociado a dicha ruta para crear toda la infrastructura que MVC necesita para procesar la petición.</p> <p>Recordad que la tabla de rutas se define en Global.asax y que por defecto tiene el siguiente código:</p> <div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: 'Courier New', courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px" id="codeSnippet"><span style="color: #0000ff">public</span> <span style="color: #0000ff">static</span> <span style="color: #0000ff">void</span> RegisterRoutes(RouteCollection routes)<br />{<br /> routes.IgnoreRoute(<span style="color: #006080">"{resource}.axd/{*pathInfo}"</span>);<br /> routes.MapRoute(<br /> <span style="color: #006080">"Default"</span>, <span style="color: #008000">// Route name</span><br /> <span style="color: #006080">"{controller}/{action}/{id}"</span>, <span style="color: #008000">// URL with parameters</span><br /> <span style="color: #0000ff">new</span> { controller = <span style="color: #006080">"Home"</span>, action = <span style="color: #006080">"Index"</span>, id = UrlParameter.Optional } <span style="color: #008000">// Parameter defaults</span><br /> );<br />}</pre><br /><br /> <br /></div><br /><br /><p>Aquí no estamos especificando ningún route handler, por lo que se usará el que tiene MVC por defecto… Pero como (casi) todo en MVC lo podemos cambiar :)</p><br /><br /><p>En lugar de usar el método MapRoute (que por si alguien no lo sabe es un método de extensión) podemos crear un objeto Route y añadirlo directamente a la tabla de rutas. El constructor de Route tiene un parámetro que es de tipo <a href="http://msdn.microsoft.com/es-es/library/system.web.routing.iroutehandler.aspx" target="_blank">IRouteHandler</a> y que es el route handler para esta ruta. Así que puedo transformar la tabla de rutas anterior en esta:</p><br /><br /><div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: 'Courier New', courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"><br /> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px" id="codeSnippet"><span style="color: #0000ff">public</span> <span style="color: #0000ff">static</span> <span style="color: #0000ff">void</span> RegisterRoutes(RouteCollection routes)<br />{<br /> routes.IgnoreRoute(<span style="color: #006080">"{resource}.axd/{*pathInfo}"</span>);<br /><br /> routes.Add(<span style="color: #006080">"Default"</span>, <span style="color: #0000ff">new</span> Route(<span style="color: #006080">"{controller}/{action}/{id}"</span>, <br /> <span style="color: #0000ff">new</span> CultureRouteHandler())<br /> {<br /> Defaults = <span style="color: #0000ff">new</span> RouteValueDictionary(<br /> <span style="color: #0000ff">new</span><br /> {<br /> controller = <span style="color: #006080">"Home"</span>, action = <span style="color: #006080">"Index"</span>, id = UrlParameter.Optional<br /> })<br /> });<br />}<br /></pre><br /><br /> <br /></div><br /><br /><p>Ambas son equivalentes, salvo que esta usará un objeto de tipo <em>CultureRouteHandler</em> para procesar las peticiones.</p><br /><br /><p>Ahora vamos a ver como es el CultureRouteHandler:</p><br /><br /><div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: 'Courier New', courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"><br /> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px" id="codeSnippet"><span style="color: #0000ff">public</span> <span style="color: #0000ff">class</span> CultureRouteHandler : MvcRouteHandler<br />{<br /> <span style="color: #0000ff">protected</span> <span style="color: #0000ff">override</span> IHttpHandler GetHttpHandler(System.Web.Routing.RequestContext requestContext)<br /> {<br /> var cultureCookieVal = GetCultureFromCookie(requestContext.HttpContext.Request.Cookies);<br /> var culture = <span style="color: #0000ff">new</span> CultureInfo(cultureCookieVal);<br /> requestContext.RouteData.Values.Add(<span style="color: #006080">"culture"</span>, cultureCookieVal);<br /> Thread.CurrentThread.CurrentCulture = culture;<br /> Thread.CurrentThread.CurrentUICulture = culture;<br /> <span style="color: #0000ff">return</span> <span style="color: #0000ff">base</span>.GetHttpHandler(requestContext);<br /> }<br /><br /> <span style="color: #0000ff">private</span> <span style="color: #0000ff">string</span> GetCultureFromCookie(HttpCookieCollection cookies)<br /> {<br /> var retValue = <span style="color: #006080">"ca-ES"</span>;<br /> <span style="color: #0000ff">if</span> (cookies.AllKeys.Contains(<span style="color: #006080">"userculture"</span>))<br /> {<br /> retValue = cookies[<span style="color: #006080">"userculture"</span>].Value;<br /> }<br /> <span style="color: #0000ff">return</span> retValue;<br /> }<br />}</pre><br /><br /> <br /></div><br /><br /><p>En este caso derivo de MvcRouteHandler (como casi siempre en MVC es mucho más sencillo derivar de alguna clase base que implementar la interfaz entera), y en el método <em>GetHttpHandler</em> lo que hago es llamar al método de la clase base pero <strong>antes</strong>:</p><br /><br /><ol><br /> <li>Recupero el valor de la cookie de cultura </li><br /><br /> <li>Guardo este valor en el route data con el nombre culture (por si alguien quiere consultarlo) </li><br /><br /> <li>Creo un CultureInfo a partir de los datos de la cookie y establezco la cultura del thread actual a este valor: así cualquier mecanismo que tenga de “localización” debería funcionar igualmente. </li><br /></ol><br /><br /><p>Finalmente para probar el tema me he creado un pequeño controlador:</p><br /><br /><div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: 'Courier New', courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"><br /> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px" id="codeSnippet"><span style="color: #0000ff">public</span> <span style="color: #0000ff">class</span> HomeController : Controller<br />{<br /> [OutputCache(NoStore = <span style="color: #0000ff">true</span>, Location = OutputCacheLocation.None)]<br /> <span style="color: #0000ff">public</span> ActionResult Index()<br /> {<br /> <span style="color: #0000ff">return</span> View();<br /> }<br /><br /> <span style="color: #0000ff">public</span> ActionResult SetCookie(<span style="color: #0000ff">string</span> id)<br /> {<br /> <span style="color: #0000ff">if</span> (!<span style="color: #0000ff">string</span>.IsNullOrEmpty(id))<br /> {<br /> <span style="color: #0000ff">this</span>.ControllerContext.HttpContext.Response.Cookies.Add(<span style="color: #0000ff">new</span> HttpCookie(<span style="color: #006080">"userculture"</span>, id));<br /> }<br /> <span style="color: #0000ff">return</span> RedirectToAction(<span style="color: #006080">"Index"</span>);<br /> }<br />}</pre><br /><br /> <br /></div><br /><br /><p>La acción /Home/Index simplemente retorna una vista. La acción /Home/SetCookie/id establece la cookie de cultura (se supone que el id es válido, algo así como /Home/SetCookie/es-ES p.ej.).</p><br /><br /><p>La vista que devuelve /Home/Index simplemente muestra la cultura actual:</p><br /><br /><div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: 'Courier New', courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"><br /> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px" id="codeSnippet"><span style="color: #0000ff"><</span><span style="color: #800000">p</span><span style="color: #0000ff">></span><br /> Cultura actual: @System.Threading.Thread.CurrentThread.CurrentUICulture.ToString();<br /><span style="color: #0000ff"></</span><span style="color: #800000">p</span><span style="color: #0000ff">></span></pre><br /><br /> <br /></div><br /><br /><p><strong>Bonus track:</strong> Y si quiero que algún controlador <em>reciba</em> la cultura actual como parámetro de alguna de sus acciones?</p><br /><br /><p>Bien, recordad que hemos hecho que el route handler guardase el valor de cultura en los route values. MVC tiene un value provider que permite realizar bindings desde los route values hacia los controladores. Guardábamos el valor con el nombre “culture” así que nos basta con:</p><br /><br /><div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: 'Courier New', courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"><br /> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px" id="codeSnippet"><span style="color: #0000ff">public</span> ActionResult Foo(<span style="color: #0000ff">string</span> culture)<br />{<br /> <span style="color: #008000">// Código...</span><br />}</pre><br /><br /> <br /></div><br /><br /><p>El parámetro <em>culture</em> tiene el valor de la cultura.</p><br /><br /><p>Si quieres saber exactamente <a href="http://geeks.ms/blogs/etomas/archive/2010/07/02/asp-net-mvc-q-amp-a-191-como-reciben-par-225-metros-los-controladores.aspx" target="_blank">cómo reciben los datos los controladores, hace algún tiempecillo escribí un post al respecto</a>.</p><br /><br /><p>De esa manera conseguimos lo que yo decía en mi tweet: agnostizar los controladores de <em>dónde</em> se guarda la cultura!</p><br /><br /><p>Un saludo!</p><br /><br /><p>PD: Esto es (como siempre, vamos) un crosspost desde <a href="http://geeks.ms/blogs/etomas" target="_blank">mi blog en geeks.ms</a></p> eiximenishttp://www.blogger.com/profile/02568454383791432108noreply@blogger.com0tag:blogger.com,1999:blog-897874347676011387.post-82296328843100423962011-01-14T10:26:00.001+01:002011-01-14T10:26:06.479+01:00ASP.NET MVC3: Validación remota<p>Muy buenas!</p> <p>Una de las novedades que nos trae ASP.NET MVC3, con respecto a MVC2 es poder usar <em>fácilmente</em> la validación remota: eso es, desde <em>cliente</em> llamar a un método del servidor que nos diga si un dato (entrado p.ej. en un campo de texto es válido o no). Y cuando digo <em>fácilmente</em> me refiero a <em>fácilmente, muy fácilmente</em>.</p> <p>Vamos a ver un ejemplo: para ello vamos a modificar la aplicación de ejemplo que crea MVC3 para que al darnos de alta, consulte si el usuario ya existe y si es el caso no nos deje. Os recuerdo que esa validación es Ajax, eso significa: el campo de texto pierda el foco se realizará la petición (en background) al servidor que comprobará si el usuario entrado ya existe y si es así mostrará un error en el campo de texto asociado.</p> <p>Vamos pues, a verlo paso a paso ;-)</p> <p><strong>1. Viendo que nos genera MVC3</strong></p> <p>Para empezar creamos un nuevo proyecto ASP.NET MVC3. Cuando os pida el template, usad el de <em>Internet Application</em> (no uséis el <em>Empty)</em>. De esa manera MVC3 nos crea el esqueleto de la aplicación inicial:</p> <p><a href="http://lh4.ggpht.com/_1JRa_Jfnm20/TTAWl2ocFfI/AAAAAAAAEHc/B5JeWoHbE4w/s1600-h/image2.png"><img style="background-image: none; border-right-width: 0px; margin: ; padding-left: 0px; padding-right: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px; padding-top: 0px" title="image" border="0" alt="image" src="http://lh4.ggpht.com/_1JRa_Jfnm20/TTAWmqCd_kI/AAAAAAAAEHg/gvSfWiS1Fc8/image_thumb.png?imgmax=800" width="215" height="244" /></a></p> <p>El fichero AccountModels.cs tiene distintos <em>Viewmodel</em>s que usan las acciones del controlador Account. La acción que da de alta un usuario es Register que está definida en el controlador Account:</p> <div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: 'Courier New', courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px" id="codeSnippet">[HttpPost]<br /><span style="color: #0000ff">public</span> ActionResult Register(RegisterModel model)<br />{<br /> <span style="color: #008000">// Código...</span><br />}</pre><br /><br /> <br /></div><br /><br /><p>La acción usa la clase RegisterModel que es uno de los <em>viewmodel</em>s definidos en AccountModels.cs:</p><br /><br /><div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: 'Courier New', courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"><br /> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px" id="codeSnippet"><span style="color: #0000ff">public</span> <span style="color: #0000ff">class</span> RegisterModel<br />{<br /> [Required]<br /> [Display(Name = <span style="color: #006080">"User name"</span>)]<br /> <span style="color: #0000ff">public</span> <span style="color: #0000ff">string</span> UserName { get; set; }<br /><br /> [Required]<br /> [DataType(DataType.EmailAddress)]<br /> [Display(Name = <span style="color: #006080">"Email address"</span>)]<br /> <span style="color: #0000ff">public</span> <span style="color: #0000ff">string</span> Email { get; set; }<br /><br /> [Required]<br /> [ValidatePasswordLength]<br /> [DataType(DataType.Password)]<br /> [Display(Name = <span style="color: #006080">"Password"</span>)]<br /> <span style="color: #0000ff">public</span> <span style="color: #0000ff">string</span> Password { get; set; }<br /><br /> [DataType(DataType.Password)]<br /> [Display(Name = <span style="color: #006080">"Confirm password"</span>)]<br /> [Compare(<span style="color: #006080">"Password"</span>, ErrorMessage = <span style="color: #006080">"The password and confirmation password do not match."</span>)]<br /> <span style="color: #0000ff">public</span> <span style="color: #0000ff">string</span> ConfirmPassword { get; set; }<br />}</pre><br /><br /> <br /></div><br /><br /><p><strong>2. Habilitando la validación remota</strong></p><br /><br /><p>La validación remota en MVC3 se habilita, como el resto de validaciones, usando DataAnnotations, concretamente con el atributo <a href="http://msdn.microsoft.com/en-us/library/system.web.mvc.remoteattribute(v=VS.98).aspx" target="_blank">Remote</a>. En nuestro caso queremos habilitar la validación remota sobre la propiedad UserName. Para ello añado el atributo Remote:</p><br /><br /><div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: 'Courier New', courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"><br /> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px" id="codeSnippet">[Required]<br />[Display(Name = <span style="color: #006080">"User name"</span>)]<br />[Remote(<span style="color: #006080">"CheckUserAvailability"</span>, <span style="color: #006080">"Account"</span>, ErrorMessage = <span style="color: #006080">"This user name is not allowed."</span>)]<br /><span style="color: #0000ff">public</span> <span style="color: #0000ff">string</span> UserName { get; set; }</pre><br /><br /> <br /></div><br /><br /><p>El primer parámetro es la acción que va a validar el campo, la segunda es el controlador. Finalmente ponemos el mensaje de error (igual que las otras validaciones).</p><br /><br /><p><strong>3. Creando el código de validación</strong></p><br /><br /><p>Finalmente tenemos que crear la acción del controlador:</p><br /><br /><div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: 'Courier New', courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"><br /> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px" id="codeSnippet">[OutputCache(Location = OutputCacheLocation.None, NoStore = <span style="color: #0000ff">true</span>)]<br /><span style="color: #0000ff">public</span> ActionResult CheckUserAvailability(<span style="color: #0000ff">string</span> username)<br />{<br /> var validUserName = Membership.FindUsersByName(username).Count == 0;<br /> <span style="color: #0000ff">return</span> Json(validUserName, JsonRequestBehavior.AllowGet);<br />}</pre><br /><br /> <br /></div><br /><br /><p>Fijaos lo simple que es: Usamos el membership provider para ver si existe algún otro usuario con el mismo nombre, y devolvemos un booleano (codificado en json). El uso de [<a href="http://msdn.microsoft.com/es-es/library/system.web.mvc.outputcacheattribute.aspx" target="_blank">OutputCache</a>] es para evitar que MVC me cachee los resultados de las peticiones.</p><br /><br /><p>Y listos! Con eso ya hemos terminado.</p><br /><br /><p>Podemos ejecutar la aplicación, registrar un usuario y luego intentar darnos de alta de nuevo y veremos el error:</p><br /><br /><p><a href="http://lh5.ggpht.com/_1JRa_Jfnm20/TTAWnDHEhXI/AAAAAAAAEHk/rilKKLWELl8/s1600-h/image5.png"><img style="background-image: none; border-right-width: 0px; margin: ; padding-left: 0px; padding-right: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px; padding-top: 0px" title="image" border="0" alt="image" src="http://lh5.ggpht.com/_1JRa_Jfnm20/TTAWnocucnI/AAAAAAAAEHo/VbrEb4xQxm8/image_thumb1.png?imgmax=800" width="244" height="110" /></a></p><br /><br /><p><strong>4. Y que pasa “en la trastienda”?</strong></p><br /><br /><p>Si inspeccionamos las peticiones veremos que <em>cada vez que cambia el texto</em> se realiza una petición Ajax. Esta vez en lugar de <a href="http://getfirebug.com/" target="_blank">firebug</a> he usado las <em><a href="http://msdn.microsoft.com/en-us/library/dd565628(v=vs.85).aspx" target="_blank">Developer Tools</a></em> de IE9 (pulsar F12 para que os aparezcan):</p><br /><br /><p><a href="http://lh3.ggpht.com/_1JRa_Jfnm20/TTAWoDQS4yI/AAAAAAAAEHs/_xW3-ePWqzA/s1600-h/image8.png"><img style="background-image: none; border-right-width: 0px; margin: ; padding-left: 0px; padding-right: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px; padding-top: 0px" title="image" border="0" alt="image" src="http://lh4.ggpht.com/_1JRa_Jfnm20/TTAWohvJkNI/AAAAAAAAEHw/rtitL9H4E18/image_thumb2.png?imgmax=800" width="244" height="43" /></a></p><br /><br /><p>Estas son las peticiones que se realizan si entro “eiximenis” en el campo de nombre de usuario. Si las contáis <em>veréis que faltan peticiones</em>. Eso es simplemente he pulsado dos teclas  muy rápido y las peticiones supongo que no se encolan</p><br /><br /><p>Si miramos los detalles de una petición vemos lo siguiente:</p><br /><br /><p><a href="http://lh6.ggpht.com/_1JRa_Jfnm20/TTAWpcUgd0I/AAAAAAAAEH0/U7sw-gZ58VA/s1600-h/image11.png"><img style="background-image: none; border-right-width: 0px; margin: ; padding-left: 0px; padding-right: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px; padding-top: 0px" title="image" border="0" alt="image" src="http://lh3.ggpht.com/_1JRa_Jfnm20/TTAWp1KTlPI/AAAAAAAAEH4/QJrOv9q4uPg/image_thumb3.png?imgmax=800" width="244" height="86" /></a></p><br /><br /><p>Fijaos en el primer campo (Request) me indica la URL de la petición. En mi caso es /Account/CheckUserAvailability?UserName=eiximenis</p><br /><br /><p>Es decir se pasa el valor por querystring (eso no es problema alguno para MVC que soporta el binding de datos en el querystring desde siempre).</p><br /><br /><p><strong>5. Y qué código me genera eso en el navegador?</strong></p><br /><br /><p>Recordáis que MVC3 apuesta fuertamente por el <a href="http://geeks.ms/blogs/etomas/archive/2010/11/12/saca-tus-scripts-de-tu-c-243-digo-html.aspx" target="_blank">unobstrusive javascript</a>? Pues eso es lo que nos genera:</p><br /><br /><div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: 'Courier New', courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"><br /> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px" id="codeSnippet"><span style="color: #0000ff"><</span><span style="color: #800000">input</span> <span style="color: #ff0000">data-val</span><span style="color: #0000ff">="true"</span> <br /><span style="color: #ff0000">data-val-remote</span><span style="color: #0000ff">="This user name is not allowed."</span> <br /><span style="color: #ff0000">data-val-remote-additionalfields</span><span style="color: #0000ff">="*.UserName"</span> <br /><span style="color: #ff0000">data-val-remote-url</span><span style="color: #0000ff">="/Account/CheckUserAvailability"</span> <br /><span style="color: #ff0000">data-val-required</span><span style="color: #0000ff">="The User name field is required."</span> <br /><span style="color: #ff0000">id</span><span style="color: #0000ff">="UserName"</span> <span style="color: #ff0000">name</span><span style="color: #0000ff">="UserName"</span> <span style="color: #ff0000">type</span><span style="color: #0000ff">="text"</span> <span style="color: #ff0000">value</span><span style="color: #0000ff">=""</span> <span style="color: #0000ff">/></span><br /></pre><br /><br /> <br /></div><br /><br /><p>Precioso no? Nada de código javascript mezclado por ahí que “ensucie” el html: sólo atributos.</p><br /><br /><p>Los atributos data-val-remote-* son los que controlan la validación remota. Para que eso funcione, es necesario que los scripts siguientes estén referenciados:</p><br /><br /><ol><br /> <li>jquery-1.4.4.min.js </li><br /><br /> <li>jquery.validate.min.js </li><br /><br /> <li>jquery.validate.unobtrusive.min.js </li><br /></ol><br /><br /><p>Yo los tengo siempre colocados en mi página Master principal (Views/Shared/_Layout.cshtml si usas Razor).</p><br /><br /><p><strong>6. Controlando algunos aspectos “avanzados”</strong></p><br /><br /><p>Vamos a ver como controlar algunos aspectos más de la validación remota. P.ej. te puede interesar que la llamada a la acción de validación (CheckUserAvailability) se realice via POST y no via GET. Para ello simplemente usa la propiedad HttpMethod del atributo Remote:</p><br /><br /><div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: 'Courier New', courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"><br /> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px" id="codeSnippet">[Remote(<span style="color: #006080">"CheckUserAvailability"</span>, <span style="color: #006080">"Account"</span>,<br /> HttpMethod = <span style="color: #006080">"POST"</span>, ErrorMessage = <span style="color: #006080">"This user name is not allowed."</span>)]</pre><br /><br /> <br /></div><br /><br /><p>Eso añade el atributo <strong>data-val-remote-type="POST"</strong> al campo HTML generado lo que fuerza que la petición sea usando POST. </p><br /><br /><p>Otra cosa interesante es que podemos <strong>enviar más de un parámetro </strong>a la acción quen realiza la “validación remota”. P.ej. imaginad que a CheckUserAvailability le queremos también pasar el password que ha introducido el usuario (vale, no tiene lógica en ese ejemplo, pero imaginadlo igual :p).</p><br /><br /><p>Para ello podemos usar la propiedad <em>AdditionalFields</em> del atributo Remote:</p><br /><br /><div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: 'Courier New', courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"><br /> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px" id="codeSnippet">[Remote(<span style="color: #006080">"CheckUserAvailability"</span>, <span style="color: #006080">"Account"</span>, HttpMethod = <span style="color: #006080">"POST"</span>,<br /> AdditionalFields = <span style="color: #006080">"Password"</span>,<br /> ErrorMessage = <span style="color: #006080">"This user name is not allowed."</span>)]<br /><span style="color: #0000ff">public</span> <span style="color: #0000ff">string</span> UserName { get; set; }</pre><br /><br /> <br /></div><br /><br /><p>Ahora si miramos de nuevo lo que nos manda la petición veremos que junto al campo UserName nos manda el valor del campo Password:</p><br /><br /><p><a href="http://lh6.ggpht.com/_1JRa_Jfnm20/TTAWqZRwvrI/AAAAAAAAEH8/FVDbLFXA8eQ/s1600-h/image%5B3%5D.png"><img style="background-image: none; border-right-width: 0px; margin: ; padding-left: 0px; padding-right: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px; padding-top: 0px" title="image" border="0" alt="image" src="http://lh4.ggpht.com/_1JRa_Jfnm20/TTAWrY66axI/AAAAAAAAEIA/0GDFbWn5hzU/image_thumb%5B1%5D.png?imgmax=800" width="244" height="67" /></a></p><br /><br /><p>Tened presente que el valor de Password puede ser vacío (el usuario puede no haber introducido nada allí todavía). Pero lo importante de esto es que las validaciones remotas <strong>no están limitadas a validar UN SOLO campo</strong>.</p><br /><br /><p>Y con eso terminamos el post de las validaciones remotas en ASP.NET MVC3… Como podeis ver, es un método sumamente potente y sumamente fácil!</p><br /><br /><p>Un saludo! ;)</p><br /><br /><p>PD: Para variar eso es un crosspost desde <a href="http://geeks.ms/blogs/etomas/" target="_blank">mi blog en geeks.ms</a>!</p> eiximenishttp://www.blogger.com/profile/02568454383791432108noreply@blogger.com0tag:blogger.com,1999:blog-897874347676011387.post-53135309742188983682011-01-03T11:11:00.001+01:002011-01-03T11:11:39.383+01:00ASP.NET MVC: Binding de datos de sesión a controladores<p>Muy buenas! Que tal el fin de año? Empachados con turrones, polvorones y demás? En fin, vamos a inaugurar el 2011 y que mejor manera que hacerlo que con un post! ;-)</p> <p>En realidad hubiese querido que este post fuese el último del año anterior, pero no puede publicarlo antes por problemas logísticos. La idea del post surge <a href="http://twitter.com/luisruizpavon/status/20062211649052672" target="_blank">de un tweet que publicó Luis Ruiz Pavón.</a> Su pregunta era que tal acceder a la sesión desde un Model Binder para poner datos a disposición de los controladores. <a href="http://twitter.com/eiximenis/status/20062646694846464" target="_blank">Mi respuesta fue que yo usaría un value provider</a>, y así llegamos a este post.</p> <p>Para conseguir binding de los datos de la sesión a los parámetros de un controlador no es necesario crear ningún Model Binder. En MVC2 se introdujo un concepto nuevo (del que ya he hablado varias veces por aquí) que se llama ValueProvider y que es el encargado de <em>acceder donde están los datos y ponerlos a disposición de los Model Binders</em>. Si ignoramos los value providers y hacemos un model binder que acceda a la sesión, entonces realmente nuestro model binder hace dos cosas:</p> <ol> <li>Ir donde están los datos (la sesión) y recogerlos </li> <li>Enlazar los datos con los parámetros de los controladores </li> </ol> <p>Según la arquitectura de ASP.NET MVC los model binders sólo se encargan de lo segundo, y son los value providers quienes se encargan de lo primero. Así, pues, tened presente la regla:</p> <ol> <li>Si lo que queréis canviar es <strong>cómo</strong> se enlazan los datos (vengan de donde vengan) a los controladores: cread un model binder </li> <li>Si lo que queréis es <strong>modificar de dónde </strong>se obtienen los datos que se enlazan a los controladores: usad un value provider. </li> </ol> <p>En nuestro caso, tal y como se enlazan los valores a los controladores ya nos va bien (el DefaultModelBinder es realmente bueno en su tarea), sólo que queremos que si un dato está en la sesión se coja de allí: necesitamos un value provider nuevo.</p> <p><strong>Factoría de value providers</strong></p> <p>En ASP.NET MVC los value providers se crean siempre mediante una factoría y lo que realmente registramos en el runtime son esas factorías. En cada petición ASP.NET MVC le pide a las distintas factorías que creen los value providers necesarios.</p> <p>Así pues lo primero va a ser crear nuestra factoría, que devolverá objetos de <em>nuestro</em> value provider vinculado a sesión:</p> <div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: 'Courier New', courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px" id="codeSnippet"><span style="color: #0000ff">class</span> SessionValueProviderFactory : ValueProviderFactory<br />{<br /> <span style="color: #0000ff">public</span> <span style="color: #0000ff">override</span> IValueProvider GetValueProvider(ControllerContext controllerContext)<br /> {<br /> <span style="color: #0000ff">return</span> <span style="color: #0000ff">new</span> SessionValueProvider(controllerContext.HttpContext.Session);<br /> }<br />}</pre><br /><br /> <br /></div><br /><br /><p>Simplemente debemos derivar de ValueProviderFactory y en el método GetValueProvider devolver una instancia de nuestro value provider. En mi caso devuelvo una instancia del SessionValueProvider y le paso la sesión en el constructor.</p><br /><br /><p>Debemos registrar esa factoría de value providers en el runtime de ASP.NET MVC. Para ello en el Global.asax basta con meter la siguiente línea (usualmente en el Application_Start):</p><br /><br /><div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: 'Courier New', courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"><br /> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px" id="codeSnippet">ValueProviderFactories.Factories.Add(<span style="color: #0000ff">new</span> SessionValueProviderFactory());</pre><br /><br /> <br /></div><br /><br /><p><strong>El value provider</strong></p><br /><br /><p>Crear un value provider es “tan sencillo” como implementar la interfaz IValueProvider. De todos modos debemos conocer un poco como funcionan los model binders a los que debemos proporcionar los valores.</p><br /><br /><p>Los value providers vienen a ser como un “diccionario enorme” que los model binders consultan cuando quieren obtener un dato. Debemos saber cómo (“con que clave”) nos va a pedir el model binder los datos y como debemos dárselos. En un post mío de hace tiempo <a href="http://geeks.ms/blogs/etomas/archive/2010/05/10/asp-net-mvc-el-defaultmodelbinder.aspx" target="_blank">ya comenté como funciona el DefaultModelBinder</a>, y allí cuento también la interacción entre el DefaultModelBiner y los value providers. </p><br /><br /><p>En fin, que todo ese rollete es para comentaros que muchas veces en lugar de implementar IValueProvider desde cero, es mucho mejor derivar de alguna de las clases que ya hay hechas, y hay una en concreto que nos viene al pelo<a href="http://msdn.microsoft.com/es-es/library/ee703471.aspx" target="_blank">: DictionaryValueProvider<TValue></a>. Esta clase implementa un value provider cuya fuente de datos es un diccionario cuyos valores son de tipo TValue. Y que es la sesión en el fondo sinó un gran diccionario cuyos valores son de tipo object?</p><br /><br /><p>Así pues creamos nuestra clase que derive de DictionaryValueProvider y lo único que tenemos que hacer es pasarle a nuestra clase base el diccionario que debe usar, que construimos a partir de la sesión:</p><br /><br /><div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: 'Courier New', courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"><br /> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px" id="codeSnippet"><span style="color: #0000ff">class</span> SessionValueProvider : DictionaryValueProvider<<span style="color: #0000ff">object</span>><br />{<br /> <span style="color: #0000ff">public</span> SessionValueProvider(HttpSessionStateBase session)<br /> : <span style="color: #0000ff">base</span>(CreateDictionary(session), CultureInfo.CurrentCulture)<br /> {<br /><br /> }<br /><br /> <span style="color: #0000ff">private</span> <span style="color: #0000ff">static</span> IDictionary<<span style="color: #0000ff">string</span>, <span style="color: #0000ff">object</span>> CreateDictionary(HttpSessionStateBase session)<br /> {<br /> var entries = session.Keys.Cast<<span style="color: #0000ff">string</span>>().ToDictionary(key => key, key => session[key]);<br /> <span style="color: #0000ff">return</span> entries;<br /> }<br />}</pre><br /><br /> <br /></div><br /><br /><p>Trivial no? El método CreateDictionary simplemente crea un IDictionary a partir de los datos de la sesión.</p><br /><br /><p><strong>Y listos!</strong> Hemos terminado, No necesitamos hacer nada más para que el binding de objetos de sesión funcione. El requisito para que el binding se efectúe es el de siempre: que el nombre del parámetro en la acción del controlador tenga el mismo nombre que la clave de sesión:</p><br /><br /><div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: 'Courier New', courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"><br /> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px" id="codeSnippet"><span style="color: #0000ff">public</span> <span style="color: #0000ff">class</span> SessionController : Controller<br />{<br /> <span style="color: #0000ff">public</span> ActionResult Put()<br /> {<br /> Session[<span style="color: #006080">"ufo"</span>] = <span style="color: #006080">"String en sessión"</span>;<br /> Session[<span style="color: #006080">"complex"</span>] = <span style="color: #0000ff">new</span> Foo()<br /> {<br /> Cadena = <span style="color: #006080">"Una cadena en objeto complejo"</span>,<br /> Entero = 100,<br /> Lista = <span style="color: #0000ff">new</span> List<<span style="color: #0000ff">int</span>>() {1, 1, 3, 5, 8, 13}<br /> };<br /> <span style="color: #0000ff">return</span> View();<br /> }<br /> <span style="color: #0000ff">public</span> ActionResult Get(<span style="color: #0000ff">string</span> ufo)<br /> {<br /> ViewData[<span style="color: #006080">"data"</span>] = ufo;<br /> <span style="color: #0000ff">return</span> View();<br /> }<br /><br /> <span style="color: #0000ff">public</span> ActionResult GetClass(Foo complex)<br /> {<br /> <span style="color: #0000ff">return</span> View(complex);<br /> }<br />}</pre><br /><br /> <br /></div><br /><br /><p>En este controlador la acción Put coloca dos datos en la sesión: una cadena con clave “ufo” y un objeto de una clase llamada Foo, con clave “complex”. Las dos acciones restantes (Get y GetClass) usan el binding y obtienen los datos de la sesión.</p><br /><br /><p><strong>Ventajas de usar el binding para obtener los valores</strong></p><br /><br /><p>La ventaja principal de usar el binding para obtener los valores en lugar de acceder a la sesión directamente es que desacopla el código de las acciones de la sesión de HTTP. En definitiva, si quiero probar si la acción Get funciona correctamente, p.ej. usando un unit test, me basta con pasarle una cadena cualquiera. Si en el código de la acción accediese a la sesión debería tener acceso a la sesión (desde la prueba) y rellenarla.</p><br /><br /><p>Espero que os sea útil!</p><br /><br /><p>Un saludo!</p><br /><br /><p>PD: Teneis un proyecto en VS2010 con MVC2 con el código de dicho post en mi skydrive: <a title="http://cid-6521c259e9b1bec6.office.live.com/self.aspx/BurbujasNet/ZipsPosts/MvcSessionBinding.zip" href="http://cid-6521c259e9b1bec6.office.live.com/self.aspx/BurbujasNet/ZipsPosts/MvcSessionBinding.zip">http://cid-6521c259e9b1bec6.office.live.com/self.aspx/BurbujasNet/ZipsPosts/MvcSessionBinding.zip</a></p><br /><br /><p>PD2: Como siempre: esto es un crosspost desde <a href="http://geeks.ms/members/etomas/default.aspx" target="_blank">mi blog bn geeks.ms</a>. Pásate por allí mejor!</p> eiximenishttp://www.blogger.com/profile/02568454383791432108noreply@blogger.com0tag:blogger.com,1999:blog-897874347676011387.post-16855445656387181072010-12-21T15:01:00.001+01:002010-12-21T15:01:00.123+01:00Opinión: Var o no var… esa es la cuestión.<p>Hola a todos! Desde hace algunos días estoy usando <a href="http://www.jetbrains.com/resharper/" target="_blank">Resharper</a>. La verdad no era, como decirlo, muy proclive para instalármelo, ya que había tenido no muy buenas experiencas con <a href="http://www.devexpress.com/Products/Visual_Studio_Add-in/Coding_Assistance/" target="_blank"><em>CodeRush</em></a><em>. </em>Seguramente no eran culpa de CodeRush sinó mías, pero bueno… Al final me lo instalé y debo decir que estoy gratamente sorprendido: Es una auténtica maravilla.</p> <p>Una cosa interesante de Resharper es que te hace <em>sugerencias</em> (que puedes desactivar si quieres, por supuesto) sobre como <em>codificar mejor</em>. Y una de las sugerencias es usar var <strong>siempre que se pueda</strong>:</p> <p><a href="http://lh6.ggpht.com/_1JRa_Jfnm20/TRCzF8ouojI/AAAAAAAAEGc/3jRIzPvlPys/s1600-h/image2.png"><img style="background-image: none; border-right-width: 0px; margin: ; padding-left: 0px; padding-right: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px; padding-top: 0px" title="image" border="0" alt="image" src="http://lh3.ggpht.com/_1JRa_Jfnm20/TRCzG2WnfVI/AAAAAAAAEGg/Qjdv4hdxT-c/image_thumb.png?imgmax=800" width="244" height="60" /></a></p> <p>Fijaos que incluso en un caso <strong>trivial</strong> como int i=0; nos recomienda que usemos var.</p> <blockquote> <p><strong>Nota: Primero una pequeña aclaración sobre var: </strong>Por si acaso… Recordad que var <strong>no es tipado dinámico</strong>, ni late-binding ni nada parecido a esto. Var simplemente le indica al <em>compilador</em> que infiera él el tipo de la variable. Pero la variable <em>tiene</em> un tipo concreto <em>y lo tiene en tiempo de compilación</em>. Por lo tanto olvidad todos vuestros prejuicios (si los tenéis) sobre tipos dinámicos.</p> </blockquote> <p>Hay tres corrientes de opinión al respecto de cuando usar var: Hay gente que opina que debe usarse <em>sólo</em> cuando es necesario (cuando se trabaja con objetos anónimos). </p> <p>Otros opinan que cuando el tipo ya aparece en la lína de código puede usarse var. Es decir, admiten esto:</p> <div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: 'Courier New', courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px" id="codeSnippet">var dict = <span style="color: #0000ff">new</span> Dictionary<<span style="color: #0000ff">int</span>, <span style="color: #0000ff">string</span>>();</pre><br /><br /> <br /></div><br /><br /><p>Porque el tipo de la variable ya aparece en el new. Pero no admiten lo siguiente:</p><br /><br /><div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: 'Courier New', courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"><br /> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px" id="codeSnippet">var result = stockManager.GetStocks();</pre><br /><br /> <br /></div><br /><br /><p>Porque, viendo el código: como se puede saber el tipo de <em>result</em>? (Debes irte a ver que devuelve el método GetStocks).</p><br /><br /><p>Por último el tercer grupo de opinión está a favor de usar var siempre. Incluso en los casos más triviales.</p><br /><br /><p>Por curiosidad:</p><br /><br /><ol><br /> <li>La msdn se situa en el primer grupo de opinión (literalmente dice “<em>the use of var does have at least the potential to make your code more difficult to understand for other developers. For that reason, the C# documentation generally uses var only when it is required” - <a title="http://msdn.microsoft.com/en-us/library/bb384061.aspx" href="http://msdn.microsoft.com/en-us/library/bb384061.aspx">http://msdn.microsoft.com/en-us/library/bb384061.aspx</a></em>). </li><br /><br /> <li>Resharper se sitúa en el tercer grupo, como hemos visto </li><br /><br /> <li>Yo me situaba en el segundo grupo de opinión. </li><br /></ol><br /><br /><p><strong>¿Que aporta usar siempre var?</strong></p><br /><br /><p>Que Resharper me estuviese continuamente insistiendo en usar <em>var</em> me hizo pensar en el <em>por que</em> de esa razón. Así que lo que hice fue probar y hacerle caso. Y empecé a usar <em>var</em> en todos los sitios. A ver que ocurría.</p><br /><br /><p>Y ocurrió una cosa interesante…</p><br /><br /><p>Al usar <em>var</em> continuamente pierdes el <em>tipo</em> de la variable de forma visual (es decir no sabes de que tipo es sólo viendo esa línea de código) y entonces te das cuenta de una cosa: que muchos nombres de variables <strong>aportan muy poca información</strong> sobre que hace realmente la variable. Los detractores de var dicen que puede complicar la lectura de código… pero que es más dificil de entender, esto:</p><br /><br /><div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: 'Courier New', courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"><br /> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px" id="codeSnippet">Player plr = GetCurrentPlayer();<br />Location l = plr.GetCustomLocation();<br />// Varias líneas de código hablando de <span style="color: #006080">"l"</span> y <span style="color: #006080">"plr"</span></pre><br /><br /> <br /></div><br /><br /><p>o esto:</p><br /><br /><div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: 'Courier New', courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"><br /> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px" id="codeSnippet">var player = GetCurrentPlayer();<br />var location = player.GetCustomLocation();<br /><span style="color: #008000">// Varias líneas de código hablando de "location" y "player"</span><br /></pre><br /><br /> <br /></div><br /><br /><p>El tema está en que la variable la declaras en una línea, y la usas en varias más. Evidentemente, si no usas var, la línea que declara la variable te da más información (exactamente el tipo de la variable), información que pierdes si usas var. Pero, como te das cuenta que estás <em>perdiendo</em> esta información, lo que haces es usar lo que resta de la línea para aumentar la claridad. Y que es lo que queda? El nombre de la variable.</p><br /><br /><p>Al usar var en todos los sitios <em>te fuerzas</em> a poner nombres más declarativos a tus variables, que expresen lo que la variable hace. Y todos sabemos que esa <strong>es una muy buena práctica</strong>. Y a veces no la seguimos, y una de las razones es que al tener una línea tipo:</p><br /><br /><div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: 'Courier New', courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"><br /> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px" id="codeSnippet">Location l = <span style="color: #0000ff">new</span> Location();</pre><br /><br /> <br /></div><br /><br /><p>Mentalmente piensas: <em><<Ya se ve que “l” es una Location. Ya queda claro.>></em></p><br /><br /><p>Pero no es cierto, porque al cabo de unas cuantas líneas te aparece una “l” y tienes que recordad que era una <em>Location</em>.</p><br /><br /><p>Mientras que si usas var, cuando escribes la línea tiendes a usar nombres más descriptivos, porque escribir:</p><br /><br /><div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: 'Courier New', courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"><br /> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px" id="codeSnippet">var l = <span style="color: #0000ff">new</span> Location();</pre><br /><br /> <br /></div><br /><br /><p>Hace como daño a la vista :)</p><br /><br /><p>Así que la sugerencia de usar <em>var </em>siempre, personalmente no me parece muy desacertada, y creo que voy a tomar esa opción de ahora en adelante.</p><br /><br /><p>Pero… Y vosotros? ¿Que opináis al respecto?</p><br /><br /><p>Un saludo!</p><br /><br /><p><strong>PD:</strong>  También creo que hay otra razón para que Resharper nos guie a usar siempre var y es que el código con var es más fácil de refactorizar (menos cambios) que el que usa declaraciones explícitas.</p><br /><br /><p>PD2: Como no… otro crosspost desde <a href="http://geeks.ms/blogs/etomas/default.aspx" target="_blank">mi blog de geeks.ms</a>!</p> eiximenishttp://www.blogger.com/profile/02568454383791432108noreply@blogger.com0tag:blogger.com,1999:blog-897874347676011387.post-40422070310135550272010-12-16T14:21:00.001+01:002010-12-16T14:21:15.439+01:00ASP.NET: Obtener el ID del usuario actual<p>Buenas!</p> <p>No se vosotros, pero yo cuando desarrollo mis aplicaciones, si uso FKs de la otabla de usuarios, las hago en base al ID del usuario, nunca en base a su nombre. Así pues, saber el ID del usuario actualmente autenticado en mi aplicación es algo fundamental.</p> <p>Primero, para saber el <strong>nombre</strong> del usuario autenticado podemos usar:</p> <div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: 'Courier New', courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px" id="codeSnippet"><span style="color: #0000ff">string</span> userName = HttpContext.Current.User.Identity.Name;</pre><br /><br /> <br /></div><br /><br /><br /><br /><p>El tema está en que las mentes pensantes que parieron el sistema de proveedores de autenticación en ASP.NET no tuvieron a bien a poner un campo para guardar el ID.</p><br /><br /><p>Por suerte obtenerlo es trivial:</p><br /><br /><div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: 'Courier New', courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"><br /> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px" id="codeSnippet"><span style="color: #0000ff">int</span> i = (<span style="color: #0000ff">int</span>)Membership.GetUser().ProviderUserKey;<br /></pre><br /><br /> <br /></div><br /><br /><p><strong><u>Ojo con ese código:</u></strong> Yo uso un membership provider propio, ya que mi base de datos usa ints para los IDs de usuarios. Si usáis el membership provider que viene por defecto en ASP.NET, el cast lo debéis hacer a Guid y no int.</p><br /><br /><p>Bueno todo muy bonito, pero antes que descorchéis el cava: eso <strong>hace una llamada a la base de datos</strong>. Además se trae <em>todos los campos</em> del registro correspondiente de la tabla de usuarios (que si usáis el membership provider que viene por defecto tiene la hostia y pico). O sea que cuidado con usar eso a mansalva… :)</p><br /><br /><p><strong>Eeeerrr… ¿se puede SIN necesidad de acceder a la base de datos?</strong></p><br /><br /><p>Bueno… esa es la gran pregunta, no nos vamos a engañar ;-) Hay varias maneras de poder acceder al ID del usuario <em>sin</em> hacer un round-trip a la base de datos, pero a si a bote pronto se me ocurren dos:</p><br /><br /><ol><br /> <li>Guardarlo en una variable de sesión: Podemos guardar el ID en una variable de sesión y consultarla cuando la necesitamos. Para una escalabilidad máxima podéis no usar <em>sticky sessions</em> y en el Session_Start guardar dicha variable con el ID. Si no usáis sticky sessions un mismo usuario puede iniciar sesión en varios IIS a la vez, pero en nuestro caso no es problemático (simplemente se consultará el ID del usuario cada vez que inicie sesión). Eso sí, estoy asumiendo que <strong>no</strong> guardáis nada más en la sesión (es decir que funcionalmente <em>no</em> dependéis de la sesión). </li><br /><br /> <li>Guardarlo en la cookie de autenticación del usuario. Esto no es, ni de lejos tan sencillo como el punto anterior, pero ya que hemos llegado hasta aquí… </li><br /></ol><br /><br /><p><strong>Modificar la cookie de autenticación</strong></p><br /><br /><p>Primero debemos hacer que cuando se cree la cookie de autenticación se añada el ID del usuario. En mi caso, como siempre, uso ASP.NET MVC, así que modificaré el código de AccountController (que es el que genera VS). Para aplicaciones webforms ese código debe colocarse cuando se va a hacer login del usuario.</p><br /><br /><p>En el código por defecto que genera VS para autenticar un usuario se usa:</p><br /><br /><div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: 'Courier New', courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"><br /> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px" id="codeSnippet"><span style="color: #0000ff">public</span> <span style="color: #0000ff">void</span> SignIn(<span style="color: #0000ff">string</span> userName, <span style="color: #0000ff">bool</span> createPersistentCookie)<br />{<br /> <span style="color: #0000ff">if</span> (String.IsNullOrEmpty(userName)) <span style="color: #0000ff">throw</span> <span style="color: #0000ff">new</span> ArgumentException(<span style="color: #006080">"Value cannot be null or empty."</span>, <span style="color: #006080">"userName"</span>);<br /><br /> FormsAuthentication.SetAuthCookie(userName, createPersistentCookie);<br />}</pre><br /><br /> <br /></div><br /><br /><p>Este código está en la clase <em>FormsAuthenticationService</em> (dentro de AccountsModel.cs) y no tiene ningún secreto: lo que hace es crear la cookie de autenticación de ASP.NET.</p><br /><br /><p>En nuestro caso vamos a modificar ese código por el siguiente:</p><br /><br /><div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: 'Courier New', courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"><br /> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px" id="codeSnippet"><span style="color: #0000ff">public</span> <span style="color: #0000ff">void</span> SignIn(<span style="color: #0000ff">string</span> userName, <span style="color: #0000ff">bool</span> createPersistentCookie)<br />{<br /> <span style="color: #0000ff">if</span> (String.IsNullOrEmpty(userName)) <span style="color: #0000ff">throw</span> <span style="color: #0000ff">new</span> ArgumentException(<span style="color: #006080">"Value cannot be null or empty."</span>, <span style="color: #006080">"userName"</span>);<br /> FormsAuthentication.SetAuthCookie(userName, createPersistentCookie);<br /> <span style="color: #0000ff">int</span> id = 100; <span style="color: #008000">// Aquí va el ID del usuario que pillaríamos de la BBDD</span><br /> <span style="color: #0000ff">string</span> userData = <font color="#0000ff">id.ToString();<span style="color: #0000ff"></span></font><br /> FormsAuthenticationTicket ticket = <span style="color: #0000ff">new</span> FormsAuthenticationTicket(1, userName, DateTime.Now, DateTime.Now.AddMinutes(30), createPersistentCookie, userData); <br /> <span style="color: #0000ff">string</span> encTicket = FormsAuthentication.Encrypt(ticket); <br /> HttpCookie faCookie = <span style="color: #0000ff">new</span> HttpCookie(FormsAuthentication.FormsCookieName, encTicket); <br /> HttpContext.Current.Response.Cookies.Add(faCookie);<br />}</pre><br /><br /> <br /></div><br /><br /><p>Lo que hacemos es crear una cookie, con datos adicionales (el ID del usuario).</p><br /><br /><p>Ahora lo que nos toca es la otra parte: reemplazar el valor de HttpContext.Current.User.Identity por uno propio que tenga el ID. Para ello usamos el evento Post<em>Authenticate_Request</em>:</p><br /><br /><div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: 'Courier New', courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"><br /> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px" id="codeSnippet"><span style="color: #0000ff">protected</span> <span style="color: #0000ff">void</span> Application_PostAuthenticateRequest(<span style="color: #0000ff">object</span> sender, EventArgs e)<br />{<br /> HttpCookie authCookie = Request.Cookies[FormsAuthentication.FormsCookieName];<br /> <span style="color: #0000ff">if</span> (authCookie != <span style="color: #0000ff">null</span>)<br /> {<br /> FormsAuthenticationTicket authTicket = FormsAuthentication.Decrypt(authCookie.Value);<br /> CustomIdentity identity = <span style="color: #0000ff">new</span> CustomIdentity(authTicket.Name, authTicket.UserData);<br /> GenericPrincipal newUser = <span style="color: #0000ff">new</span> GenericPrincipal(identity, <span style="color: #0000ff">new</span> <span style="color: #0000ff">string</span>[] {});<br /> Context.User = newUser;<br /> }<br />}</pre><br /><br /> <br /></div><br /><br /><p>Recogemos la cookie de autenticación, desencriptamos el ticket de autenticación por forms y con los datos (el nombre y el UserData) creamos un objeto de tipo CustomIdentity, clase nuestra que nos implementa IIdentity. Luego la incrustamos dentro de un GenericPrincipal y lo establecemos a la propiedad User del HttpContext.</p><br /><br /><p><strong>Nota:</strong> El segundo parámetro del constructor de GenericPrincipal es el array de roles a los que pertenece el usuario. En mi caso no uso roles, así que le asigno un array vacío.</p><br /><br /><p>La clase CustomIdentity es tal y como sigue:</p><br /><br /><div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: 'Courier New', courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"><br /> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px" id="codeSnippet"><span style="color: #0000ff">class</span> CustomIdentity : IIdentity<br /> {<br /><br /> <span style="color: #0000ff">public</span> CustomIdentity(<span style="color: #0000ff">string</span> name, <span style="color: #0000ff">string</span> id)<br /> {<br /> IsAuthenticated = <span style="color: #0000ff">true</span>;<br /> Name = name;<br /> Id = Int32.Parse(id);<br /> AuthenticationType = <span style="color: #006080">"Forms"</span>;<br /> }<br /><br /> <span style="color: #0000ff">public</span> <span style="color: #0000ff">string</span> AuthenticationType { get; <span style="color: #0000ff">private</span> set; }<br /> <span style="color: #0000ff">public</span> <span style="color: #0000ff">bool</span> IsAuthenticated { get; <span style="color: #0000ff">private</span> set; }<br /> <span style="color: #0000ff">public</span> <span style="color: #0000ff">string</span> Name { get; <span style="color: #0000ff">private</span> set;}<br /> <span style="color: #0000ff">public</span> <span style="color: #0000ff">int</span> Id { get; <span style="color: #0000ff">private</span> set; }<br /> }</pre><br /><br /> <br /></div><br /><br /><p>De esta manera, ahora podemos al Id del usuario, desde un controlador:</p><br /><br /><div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: 'Courier New', courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"><br /> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px" id="codeSnippet">CustomIdentity ci = (CustomIdentity)ControllerContext.HttpContext.User.Identity;<br /><span style="color: #0000ff">int</span> IdUsuario = ci.Id;</pre><br /><br /> <br /></div><br /><br /><p>Un misterio con el que me he encontrado es que el código de PostAuthenticateRequest si se pone en AuthenticateRequest (que parece que debería funcionar igual), se queja diciendo que la clase “CustomIdentity” no es serializable. No tengo muy claro porque ocurre eso y eso si que parece ser propio de MVC. Aquí hay más información al respecto: <a title="http://stackoverflow.com/questions/1884030/implementing-a-custom-identity-and-iprincipal-in-mvc" href="http://stackoverflow.com/questions/1884030/implementing-a-custom-identity-and-iprincipal-in-mvc">http://stackoverflow.com/questions/1884030/implementing-a-custom-identity-and-iprincipal-in-mvc</a></p><br /><br /><p>Y Listos! </p><br /><br /><p>Con esto podemos acceder al ID de nuestros usuarios sin necesidad de usar para nada la base de datos. Además, dado que estamos usando el sistema de autenticación de ASP.NET (no hacemos nada <em>raro</em>), nos siguen funcionando los filtros de autenticación como [Authorize].</p><br /><br /><p>Un saludo!</p><br /><br /><p><strong>Referencia:</strong> <a title="http://stackoverflow.com/questions/1064271/asp-net-mvc-set-custom-iidentity-or-iprincipal" href="http://stackoverflow.com/questions/1064271/asp-net-mvc-set-custom-iidentity-or-iprincipal">http://stackoverflow.com/questions/1064271/asp-net-mvc-set-custom-iidentity-or-iprincipal</a></p><br /><br /><p>PD: Eso es (como siempre) un crosspost desde <a href="http://geeks.ms/blogs/etomas/" target="_blank">mi blog en geeks.ms</a>!</p> eiximenishttp://www.blogger.com/profile/02568454383791432108noreply@blogger.com0tag:blogger.com,1999:blog-897874347676011387.post-62057654576205979772010-11-23T11:41:00.001+01:002010-11-23T11:41:15.910+01:00Trick: Enviar datos en JSON usando POST<p>Muy buenas!</p> <p>Una de las preguntas que mucha gente se formula cuando empieza a hacer cosillas con ajax y jQuery es <em>¿Como enviar datos codificados en JSON usando POST</em>? </p> <p>La verdad es que es muy sencillo, aunque jQuery no proporciona ninguna función <em>por defecto</em> que haga esto. Vamos a ver tres aproximaciones, las dos primeras incorrectas pero que nos acercarán para llegar al final a <strike>la</strike> una forma correcta de hacerlo.</p> <p><strong>Aproximación 1: Usando $.post</strong></p> <p>jQuery tiene una función específica para enviar datos usando post, llamada <a href="http://api.jquery.com/jQuery.post/">$.post</a>. Así podriamos pensar que el siguiente método funcionaria:</p> <div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: 'Courier New', courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px" id="codeSnippet"><span style="color: #0000ff">function</span> JsonPost(data, url, handler)<br />{<br /> $.post(url, data, handler);<br />}</pre><br /><br /> <br /></div><br /><br /><p>Y luego podríamos llamarlo de la siguiente manera:</p><br /><br /><div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: 'Courier New', courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"><br /> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px" id="codeSnippet"><span style="color: #008000">// Envia un objeto con propiedades Col y Row a /Map/ViewPort y</span><br /><span style="color: #008000">// llama a fillMap con el resultado devuelto</span><br />JsonPost({ Col: c, Row: r}, <span style="color: #006080">"/Map/Viewport"</span>, <span style="color: #0000ff">function</span>(data) {<br /> fillMap(data);<br />});</pre><br /><br /> <br /></div><br /><br /><p>Pero eso no realiza una petición JSON. Si usamos, p.ej. Firebug para analizar la petición vemos que lo que se envía al controlador es:</p><br /><br /><p><a href="http://lh5.ggpht.com/_1JRa_Jfnm20/TOuaO2UmFgI/AAAAAAAAEFg/OT2wPe5J0fw/s1600-h/image2.png"><img style="background-image: none; border-right-width: 0px; margin: ; padding-left: 0px; padding-right: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px; padding-top: 0px" title="image" border="0" alt="image" src="http://lh3.ggpht.com/_1JRa_Jfnm20/TOuaPrMZ6xI/AAAAAAAAEFk/v-DT2AlCcco/image_thumb.png?imgmax=800" width="244" height="118" /></a></p><br /><br /><p>Si os fijáis los datos no están codificados en JSON, sinó en el formato “estándard” (param=value&param=value&…)</p><br /><br /><p>Si se lee la documentación de $.post() se ve que acepta un último parámetro <em>datatype</em>, así que podríamos pensar que poniendo dicho parámetro a “json” funcionará, pero no. El parámetro “datatype” indica <strong>el tipo de datos esperado de vuelta</strong>, no el que se envia.</p><br /><br /><p>Resumiendo: $.post() <strong>siempre envía los datos en el formato <em>tradicional</em>.</strong></p><br /><br /><p><strong>Aproximación 2: Usando $.post() y convertir los datos a Json</strong></p><br /><br /><p>De acuerdo, hemos visto que $.post() nos transforma nuestro objeto javascript en la cadena <em>tradicional</em> de param=value&param=value&… Pero si a $.post() se le pasa una cadena la manda <em>tal cual</em>, por lo que si transformamos el objeto javascript a una cadena JSON parece que todo funcionará.</p><br /><br /><p>Para transformar un objeto javascript a notación JSON podemos usar el plugin <a href="http://code.google.com/p/jquery-json/">jQuery-Json</a>. Puede haber otros plugins por ahí, pero uséis el que uséis, <strong>aseguraros que soporta <a href="http://www.west-wind.com/weblog/posts/729630.aspx">native json</a></strong>. Native json es una funcionalidad que los nuevos navegadores traen y que se basa en un método llamado JSON.stringify que transforma el objeto pasado a una cadena json. Evidentemente siempre será mucho más rápido si el propio navegador puede hacer esto de forma nativa que si es código javascript que construye la cadena. El plugin jQuery-json usa JSON.stringify si está disponible y sólo en caso de que no exista usa código javascript.</p><br /><br /><p>Así pues podríamos modificar nuestra JsonPost para que quede como:</p><br /><br /><div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: 'Courier New', courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"><br /> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px" id="codeSnippet"><span style="color: #0000ff">function</span> JsonPost(data, url, handler)<br />{<br /> $.post(url,$.toJSON(data), handler);<br />}</pre><br /><br /> <br /></div><br /><br /><p>El método $.toJSON es el método proporcionado por el plugin. Ahora sí que estamos usando JSON para enviar los datos:</p><br /><br /><p><a href="http://lh3.ggpht.com/_1JRa_Jfnm20/TOuaQMPXu_I/AAAAAAAAEFo/i3lb3etkEXU/s1600-h/image11.png"><img style="background-image: none; border-right-width: 0px; margin: ; padding-left: 0px; padding-right: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px; padding-top: 0px" title="image" border="0" alt="image" src="http://lh4.ggpht.com/_1JRa_Jfnm20/TOuaRgtihkI/AAAAAAAAEFs/qFkyMSntNGU/image_thumb3.png?imgmax=800" width="244" height="89" /></a></p><br /><br /><p>Ok. Un parentesis: Recordad que MVC2 <strong>no tiene soporte directo para realizar binding de datos enviados en JSON</strong>. Aunque <a href="http://geeks.ms/blogs/etomas/archive/2010/06/01/asp-net-mvc-custom-model-binders-vs-valueproviders-y-un-ejemplo-con-json.aspx">es muy fácil crearse un Value Provider propio que de soporte a JSON en MVC2</a>. Y recordad que MVC3 ya viene con el soporte por defecto de JSON.</p><br /><br /><p>Bien, si usáis un value provider para JSON que os hayais encontrado por “ahí afuera” lo más probable es que no os funcione, es decir que en el controlador no recibáis los datos. Por que? Pues por lo que está marcado en rojo en la imagen antrior: aunque estamos enviando los datos en formato json, el content-type no es correcto. El content-type de JSON es <em>application/json </em>y este es el content-type que suelen mirar los value providers de JSON.</p><br /><br /><p><strong>Aproximación 3: Usando $.ajax()</strong></p><br /><br /><p>Bien, ya que $.post() no permite especificar el content-type, la tercera y última aproximación es usar <a href="http://api.jquery.com/jQuery.ajax/">$.ajax()</a>, que es la función más personalizable que tiene jQuery para hacer peticiones ajax.</p><br /><br /><p>De hecho básicamente lo único que tenemos que cambiar respecto la aproximación anterior es el content-type:</p><br /><br /><div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: 'Courier New', courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"><br /> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px" id="codeSnippet"><span style="color: #0000ff">function</span> JsonPost(data, url, handler)<br />{<br /> $.ajax(<br /> {<br /> url: url,<br /> type: <span style="color: #006080">"POST"</span>,<br /> success: handler,<br /> data: $.toJSON(data),<br /> contentType: <span style="color: #006080">"application/json"</span><br /> });<br />}</pre><br /><br /> <br /></div><br /><br /><p>Y lo que nos muestra Firebug de la petición:</p><br /><br /><p><a href="http://lh3.ggpht.com/_1JRa_Jfnm20/TOuaSEpDdtI/AAAAAAAAEFw/pAJPJA033TA/s1600-h/image%5B3%5D.png"><img style="background-image: none; border-right-width: 0px; margin: ; padding-left: 0px; padding-right: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px; padding-top: 0px" title="image" border="0" alt="image" src="http://lh5.ggpht.com/_1JRa_Jfnm20/TOuaS_BGUpI/AAAAAAAAEF0/RXfxHnCIwlY/image_thumb%5B1%5D.png?imgmax=800" width="244" height="83" /></a></p><br /><br /><p>Fijaos que ahora el propio Firebug reconoce que la petición es en JSON y me muestra los datos en JSON.</p><br /><br /><p>Espero que os sea útil! </p><br /><br /><p>Un saludo!</p><br /><br /><p>PD: Como no… otro crosspost desde <a href="http://geeks.ms/blogs/etomas/default.aspx">mi blog en geeks.ms</a>!</p> eiximenishttp://www.blogger.com/profile/02568454383791432108noreply@blogger.com0tag:blogger.com,1999:blog-897874347676011387.post-71321764978334761052010-11-19T12:24:00.001+01:002010-11-19T12:24:01.053+01:00[Evento] Hasta donde se puede llegar con ASP.NET MVC?<p>Muy buenas!</p> <p>El jueves <strong>2 de diciembre</strong> voy a realizar un WebCast para la gente del <a href="http://lleida.dotnetclubs.com/">Lleida DotNetClub</a> sobre ASP.NET MVC.</p> <p>La idea es empezar desde cero a desarrollar una aplicación ASP.NET MVC y ver hasta donde llegamos. Iremos explorando el framework y viendo paso a paso sus características: controladores, vistas, modelos, vistas parciales, ajax, helpers, inyección de dependencias,…</p> <p>La idea es que sea 100% Visual Studio, nada de powerpoints que de esto ya se encuentra mucho en la web!</p> <p>La página de registro está en <a title="https://msevents.microsoft.com/CUI/WebCastEventDetails.aspx?EventID=1032471331&EventCategory=4&culture=es-ES&CountryCode=ES" href="https://msevents.microsoft.com/CUI/WebCastEventDetails.aspx?EventID=1032471331&EventCategory=4&culture=es-ES&CountryCode=ES">https://msevents.microsoft.com/CUI/WebCastEventDetails.aspx?EventID=1032471331&EventCategory=4&culture=es-ES&CountryCode=ES</a></p> <p>Así que si estás interesado en ver ASP.NET MVC, ya sabes: te esperamos! ;-)</p> <p>Saludos y gracias a la gente del Lleida DotNetClub por invitarme a dar la charla! :)</p> <p><strong>PD:</strong> Huelga decir que si tienes cualquier comentario sobre <em>algo</em> específico que te gustaría ver en este Webcast coméntalo por aquí y voy a ver como se puede encajar! De lo que se trata es que este Webcast sea interesante <em>para vosotros</em>!</p> <p>PD2: Pues sí… Eso es oooooooootro crosspost de <a href="http://geeks.ms/blogs/etomas/default.aspx">mi blog de geeks.ms</a></p> eiximenishttp://www.blogger.com/profile/02568454383791432108noreply@blogger.com0tag:blogger.com,1999:blog-897874347676011387.post-47841481443149853992010-11-18T14:53:00.001+01:002010-11-18T14:53:17.578+01:00C# Básico: Covarianza en genéricos<p>Muy buenas! Hacía tiempo que no escribía nada de la <a href="http://geeks.ms/blogs/etomas/archive/tags/c_2300_+basico/default.aspx">serie C# Básico</a>. En esta serie voy tratando temas (sin ningún orden en particular) que considero que son fundamentos más o menos elementales del lenguaje. No es un tutorial al uso, cada post es independiente del resto y como digo no están ordenados por nada en particular.</p> <p>El post de hoy nace a raíz de una pregunta que vi en los foros de msdn (<a title="http://social.msdn.microsoft.com/Forums/es-ES/vcses/thread/daf808ed-a0aa-4e1e-88ed-64ee60cce918" href="http://social.msdn.microsoft.com/Forums/es-ES/vcses/thread/daf808ed-a0aa-4e1e-88ed-64ee60cce918">http://social.msdn.microsoft.com/Forums/es-ES/vcses/thread/daf808ed-a0aa-4e1e-88ed-64ee60cce918</a>), donde un usuario preguntaba porque el intentar convertir una List<LogVehiculos> a List<Log> le daba error teniendo en cuenta que LogVehiculos derivaba de Log.</p> <p>Mi respuesta fue que en C# 3.5 los genéricos no son covariantes, y este post es para explicarlo todo un poco más :)</p> <p><strong>Antes que nada</strong>, covarianza y contravarianza son dos palabrejas muy molonas para explicar dos conceptos que son muy básicos pero que tienen implicaciones muy profundas. El <strong>mejor artículo en español que he leído sobre covarianza y contravarianza</strong> es el del <em>Doctor</em> (maestros hay algunos, doctores muchos menos) <strong><a href="http://miguelkatrib.sys-con.com/">Miguel Katrib</a></strong> que salió publicado en la <a href="http://www.dotnetmania.com/">DotNetMania</a> número 62 y titulado “La danza de las varianzas”. Es un artículo que debe leerse con atención pero sin duda de lo mejorcito que he leído nunca. Este post no entrará ni mucho menos en la profundidad de dicho artículo, así que si os interesa el tema, ya sabeis: haceros con dicha DotNetMania. </p> <p>En este post nos vamos a centrar sólamente en la covarianza.</p> <p><strong>Covarianza</strong></p> <p>Llamamos covarianza a algo muy simple: Cuando permitimos sustituir un tipo D por otro tipo B. Para que eso sea posible debe cumplirse una condición: Que no haya nada que pueda hacerse con B y NO pueda hacerse con D.</p> <p>Vamos a suponer que tenemos una clase Animal, de la cual deriva la clase Perro:</p> <div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: 'Courier New', courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px" id="codeSnippet"><span style="color: #0000ff">class</span> Animal<br />{<br /> <span style="color: #0000ff">public</span> <span style="color: #0000ff">void</span> Comer() { ... }<br /> <span style="color: #0000ff">public</span> <span style="color: #0000ff">void</span> Dormir() { ... }<br />}<br /><br /><span style="color: #0000ff">class</span> Perro : Animal<br />{<br /> <span style="color: #0000ff">public</span> <span style="color: #0000ff">void</span> VigilarCasa() { ... }<br />}</pre><br /><br /> <br /></div><br /><br /><p>Si tenemos un método cualquiera que devuelva un perro, nosotros podemos convertir el resultado a un animal:</p><br /><br /><div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: 'Courier New', courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"><br /> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px" id="codeSnippet">Perro ComprarPerro() { ... }<br /><span style="color: #008000">// entonces eso es válido:</span><br />Animal animal = ComprarPerro();</pre><br /><br /> <br /></div><br /><br /><p><em>Eso</em> es covarianza: el poder sustituir la clase derivada (Perro) que devuelve el método con la clase base (Animal). C# soporta covarianza entre una clase derivada y su clase base (como hacen de hecho <em>todos</em> los lenguajes orientados a objetos).</p><br /><br /><p>Tiene lógica, porque fijaos que <strong>no</strong> hay nada que pueda hacerse con un Animal (B) que no pueda hacerse con un Perro (D): Dado que Perro deriva de Animal hereda todos sus métodos y propiedades.</p><br /><br /><p>Pero la covarianza se da también en más casos y algunos de ellos están soportados en C#. Veamos…</p><br /><br /><p><strong>Covarianza en delegados</strong></p><br /><br /><p>Se trata de poder asignar a un delegado que devuelve un Animal un método que devuelve un Perro:</p><br /><br /><div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: 'Courier New', courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"><br /> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px" id="codeSnippet"><span style="color: #0000ff">delegate</span> Animal AnimalDelegate();<br /><span style="color: #0000ff">class</span> Program<br />{<br /> <span style="color: #0000ff">static</span> Perro ObtenerPerro() { <span style="color: #0000ff">return</span> <span style="color: #0000ff">new</span> Perro(); }<br /> <span style="color: #0000ff">static</span> Animal ObtenerAnimal() { <span style="color: #0000ff">return</span> <span style="color: #0000ff">new</span> Animal(); }<br /> <span style="color: #0000ff">static</span> <span style="color: #0000ff">void</span> Main(<span style="color: #0000ff">string</span>[] args)<br /> {<br /> Animal animal = ObtenerPerro();<br /> AnimalDelegate ad = <span style="color: #0000ff">new</span> AnimalDelegate(ObtenerAnimal);<br /> AnimalDelegate ad2 = <span style="color: #0000ff">new</span> AnimalDelegate(ObtenerPerro); <br /> }<br />}</pre><br /><br /> <br /></div><br /><br /><p>Fijaos en la segunda declaración (ad2): Aunque el delegate está declarado para métodos que devuelven un Animal podemos usar este delegate con métodos que devuelvan un Perro. Por eso decimos que los delegates son covariantes en C#.</p><br /><br /><p><strong>Covarianza en arrays</strong></p><br /><br /><p>El siguiente código en C# funciona y es totalmente válido:</p><br /><br /><div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: 'Courier New', courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"><br /> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px" id="codeSnippet">Animal[] animales = <span style="color: #0000ff">new</span> Perro[100];</pre><br /><br /> <br /></div><br /><br /><p>Es decir podemos asignar un array de Perros a un array de Animales. De nuevo los arrays son covariantes en C#. Esta decisión se tomó en su día para, bueno… luego hablaremos más sobre ella :)</p><br /><br /><p><strong>Covarianza en genéricos</strong></p><br /><br /><p>El siguiente código <strong>no compila</strong> en C#:</p><br /><br /><div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: 'Courier New', courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"><br /> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px" id="codeSnippet"><span style="color: #008000">// error CS0029: Cannot implicitly convert type 'System.Collections.Generic.List<ConsoleApplication8.Perro>' to 'System.Collections.Generic.List<ConsoleApplication8.Animal>'</span><br />List<Animal> animales = <span style="color: #0000ff">new</span> List<Perro>();<br /></pre><br /><br /> <br /></div><br /><br /><p>Es por ello que decimos que los <strong>genéricos NO son covariantes en C#</strong>.</p><br /><br /><p>Y ahora viene la pregunta… ¿por que?</p><br /><br /><p>Bien, recordad que si yo quiero sustituir un tipo D por otro tipo B eso significa que en un objeto de tipo D <em>debo poder hacer cualquier cosa que haga en un objeto de tipo B</em>. Es decir, si hay algo, llamémosle f(), que pueda hacer para un objeto de tipo B que no pueda hacer con un objeto de tipo D, no puedo aplicar covarianza… Ya que entonces podría hacer D.f() que no sería válido (recordad que f() es válido para B y no para D).</p><br /><br /><p>Cojamos el caso de <em>List<Animal></em> y <em>List<Perro> </em>(recordad que Perro deriva de Animal). La pregunta es… hay alqo que podemos hacer con List<Animal> y que NO podamos hacer con List<Perro>? Veamos…</p><br /><br /><ul><br /> <li>Con List<Animal> puedo contar cuantos animales hay. Con List<Perro> también. </li><br /><br /> <li>Con List<Animal> puedo obtener todos los Animales que hay. Con List<Perro> puedo obtener los Perros, pero dado que Perro deriva de Animal, si obtengo un Perro estoy obteniendo un Animal (primer ejemplo que hemos visto). Así pues ningún problema. </li><br /><br /> <li>Con List<Animal> puedo añadir un Animal. Con List<Perro> puedo añadir… un Perro. Ojo que eso es importante: A List<Animal> puedo añadirle <strong>cualquier </strong>Animal… puede ser un Perro, puede ser un Gato. A List<Perro> no puedo añadirle cualquier animal, <em>debe</em> ser un Perro forzosamente. </li><br /></ul><br /><br /><p>Por lo tanto ya hemos encontrado que se puede hacer con List<Animal> que <em>no</em> pueda hacerse con List<Perro>: Añadir un Gato.</p><br /><br /><p>Si C# nos dejara aplicar covarianza entonces eso sería válido:</p><br /><br /><div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: 'Courier New', courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"><br /> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px" id="codeSnippet">List<Animal> animales = <span style="color: #0000ff">new</span> List<Perro>();<br />animales.Add(<span style="color: #0000ff">new</span> Gato()); <span style="color: #008000">// EEehhh... estoy añadiendo un Gato a una lista de Perros?</span><br /></pre><br /><br /> <br /></div><br /><br /><p>Por lo tanto, para evitar eso y asegurar que las listas de perros sólo tendrán perros el compilador no nos deja hacer esa conversión: Los genéricos <strong>no</strong> son covariantes.</p><br /><br /><p><strong>Y los arrays?</strong> Recordáis que los arrays sí son covariantes. El siguiente código <strong>es válido</strong> y legal:</p><br /><br /><div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: 'Courier New', courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"><br /> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px" id="codeSnippet">Animal[] animal = <span style="color: #0000ff">new</span> Perro[100];<br />animal[0] = <span style="color: #0000ff">new</span> Gato(); <span style="color: #008000">// Un Gato en una jauría de Perros!</span><br /></pre><br /><br /> <br /></div><br /><br /><p>Si ejecutas el siguiente código obtendrás una <a href="http://msdn.microsoft.com/en-us/library/system.arraytypemismatchexception.aspx">ArrayTypeMismatchException</a> en tiempo de ejecución. Es decir el código compila pero luego rebienta.</p><br /><br /><p>Alguien podría decir que hubiesen aplicado eso mismo a las Listas… dejar que fuesen covariantes y luego rebentar en tiempo de ejecución si añado un Gato a una List<Perro>. Porque no lo han hecho así? Pues porque repetir errores no es nunca una buena solución. Los arrays jamás debieron haber sido covariantes. Si los crearon así fue para dar soporte a lenguajes tipo Java dentro del CLR (Java tiene arrays covariantes). Y así estamos: un error de diseño de Java propagado a .NET. Fijaos que eso obliga a que cada vez que añadimos un elemento en un array el CLR en tiempo de ejecución deba comprobar que el elemento <em>realmente</em> es del tipo del array. Viva la eficiencia!</p><br /><br /><p><strong>Y con todo eso… llegó el Framework 4</strong></p><br /><br /><p>Bien… Ahora analicemos el siguiente código:</p><br /><br /><div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: 'Courier New', courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"><br /> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px" id="codeSnippet"><span style="color: #0000ff">static</span> IEnumerable<Perro> JauriaDePerros()<br />{<br /> <span style="color: #0000ff">return</span> <span style="color: #0000ff">new</span> List<Perro>();<br />}<br /><span style="color: #0000ff">static</span> <span style="color: #0000ff">void</span> Main(<span style="color: #0000ff">string</span>[] args)<br />{<br /> IEnumerable<Animal> perritos = JauriaDePerros();<br />}</pre><br /><br /> <br /></div><br /><br /><p>Ya os lo avanzo: el siguiente código <strong>no compila con el Framework 3.5</strong>. Recordad: los genéricos no son covariantes y hemos visto la razón. Pero <em>tiene</em> sentido en este caso? Hay algo que pueda hacer con un IEnumerable<Animal> y que no pueda hacer con IEnumerable<Perro>? Veamos…</p><br /><br /><ol><br /> <li>Con un IEnumerable<Animal> puedo obtener todos los Animales. Con un IEnumerable<Perro> puedo obtener todos los Perros, pero como hemos visto ya, los Perros los puedo ver como Animales. </li><br /></ol><br /><br /><p>Y ya está. No puedo hacer nada más con un IEnumerable<> salvo obtener sus elementos. Entonces porque no compila el código en C#? Pues bien, porque pagan justos por pecadores: En el framework 3.5 los genéricos no son covariantes. Nunca, aunque <em>por lógica</em> pudiesen serlo.</p><br /><br /><p>Para tener una solución a estos casos donde la covarianza tiene sentido, debemos usar el Framework 4 (VS2010). Una de las novedades que incorpora C# en esta versión es precisamente esta: covarianza de genéricos <em>en según que casos</em>.</p><br /><br /><p>Veamos: la covarianza en genéricos <em>es segura cuando el parámetro genérico se usa sólamente de salida</em>. Es decir cuando <em>ningún método acepta ningún parámetro del tipo genérico, como mucho sólo lo devuelven</em>. El problema en el caso de List<> estaba en que podía añadir un Gato a una lista de Perros. Y eso es posible porque uno de los métodos de la clase List<T> es Add(T item). Es decir el tipo genérico se usa como valor de entrada a los métodos. En cambio con IEnumerable<T> hemos visto que no hay ningún problema: En un IEnumerable<T> sólo puedo obtener sus elementos, pero no puedo añadirle elementos nuevos. No hay ningún método que reciba un parámetro del tipo genérico. Como mucho hay métodos que devuelven ojetos del tipo genérico. En este caso la covarianza es segura.</p><br /><br /><p>Para indicar en C# 4.0 que una clase genérica es covariante respecto a su tipo genérico, usamos la palabra clave <em>out. </em>P.ej. IEnumerable<T> en C# 4.0 está definido como:</p><br /><br /><div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: 'Courier New', courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"><br /> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px" id="codeSnippet"><span style="color: #0000ff">public</span> <span style="color: #0000ff">interface</span> IEnumerable<<span style="color: #0000ff">out</span> T> : IEnumerable<br />{<br /> <span style="color: #008000">// Métodos...</span><br />}</pre><br /><br /> <br /></div><br /><br /><p>Fijaos en el uso de <em>out</em> para indicarle al compilador: Este tipo es covariante respecto al tipo genérico T. Entonces <strong>este código que en VS2008 no compilaba, es válido en C# 4.0</strong>:</p><br /><br /><div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: 'Courier New', courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"><br /> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px" id="codeSnippet"><span style="color: #0000ff">static</span> IEnumerable<Perro> JauriaDePerros()<br />{<br /> <span style="color: #0000ff">return</span> <span style="color: #0000ff">new</span> List<Perro>();<br />}<br /><span style="color: #0000ff">static</span> <span style="color: #0000ff">void</span> Main(<span style="color: #0000ff">string</span>[] args)<br />{<br /> IEnumerable<Animal> perritos = JauriaDePerros();<br />}</pre><br /><br /> <br /></div><br /><br /><p>Por supuesto, esto sigue sin compilar:</p><br /><br /><div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: 'Courier New', courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"><br /> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px" id="codeSnippet"><span style="color: #0000ff">static</span> List<Perro> JauriaDePerros()<br />{<br /> <span style="color: #0000ff">return</span> <span style="color: #0000ff">new</span> List<Perro>();<br />}<br /><span style="color: #0000ff">static</span> <span style="color: #0000ff">void</span> Main(<span style="color: #0000ff">string</span>[] args)<br />{<br /> List<Animal> perritos = JauriaDePerros();<br />}</pre><br /><br /> <br /></div><br /><br /><p>Ya que List<T> no es covariante respecto al tipo genérico T (lógico, si lo fuese podría añadir un Gato a una lista de Perros).</p><br /><br /><p>En cambio eso <strong>si que es correcto en VS2010</strong>:</p><br /><br /><div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: 'Courier New', courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"><br /> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px" id="codeSnippet"><span style="color: #0000ff">static</span> List<Perro> JauriaDePerros()<br />{<br /> <span style="color: #0000ff">return</span> <span style="color: #0000ff">new</span> List<Perro>();<br />}<br /><span style="color: #0000ff">static</span> <span style="color: #0000ff">void</span> Main(<span style="color: #0000ff">string</span>[] args)<br />{<br /> IEnumerable<Animal> perritos = JauriaDePerros();<br />}</pre><br /><br /> <br /></div><br /><br /><p>Aunque el método JauriaDePerros() devuelve una List<Perro>, el código funciona porque:</p><br /><br /><ol><br /> <li>List<T> implementa IEnumerable<T> </li><br /><br /> <li>IEnumerable<T> es covariante respecto a T </li><br /></ol><br /><br /><p>En el fondo, fijaos que no hay problema: con <em>perritos</em> lo único que puede hacerse es obtener sus elementos, así que de nuevo no hay peligro de que añada un Gato a <em>perritos</em>.</p><br /><br /><p><strong>Declaración de mis clases genéricas covariantes</strong></p><br /><br /><p>Si yo creo una clase que quiera que sea covariante con su tipo genérico, simplemente debo usar <em>out</em>. La única restricción es que <strong>ningún método de mi clase podrá aceptar un parámetro del tipo genérico</strong>:</p><br /><br /><div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: 'Courier New', courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"><br /> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px" id="codeSnippet"><span style="color: #0000ff">interface</span> Foo<<span style="color: #0000ff">out</span> T><br />{<br /> T Bar() { ... }<br /> <span style="color: #0000ff">void</span> Baz(T t) { ... }<br />}</pre><br /><br /> <br /></div><br /><br /><p>Este código no compila (<em>error CS1961: Invalid variance: The type parameter 'T' must be contravariantly valid on 'ConsoleApplication5.Foo<T>.Baz(T)'. 'T' is covariant.</em>). Ese mensaje de error largote lo único que quiere decir es que T es covariante, y por lo tanto no podemos aceptar parámetros de tipo T.</p><br /><br /><p>Finalmente tened presente que sólo las interfaces pueden declarar que su tipo genérico es covariante (las clases no).</p><br /><br /><p>Bueno… dejémoslo aquí. Hay otro termino ligado a la covarianza que es la contravarianza, aunque no es tan <em>común</em> como la covarianza y quizá algún día hablemos de ella :)</p><br /><br /><p>Un saludo y recordaros lo que digo siempre en los posts de esta serie: Si tenéis temas sobre el lenguaje C# que queráis tratar, hacédmelo saber y haré lo que pueda!!!</p><br /><br /><p>PD: Para variar, eso es un crosspost desde <a href="http://geeks.ms/blogs/etomas/">mi blog en geeks.ms</a>! Pásate por allí que somos más!</p> eiximenishttp://www.blogger.com/profile/02568454383791432108noreply@blogger.com0tag:blogger.com,1999:blog-897874347676011387.post-83773362695499148912010-11-12T12:44:00.001+01:002010-11-12T12:44:56.769+01:00Saca tus scripts de tu código HTML<p>Buenas! En el post anterior os comenté el soporte de <a href="http://geeks.ms/blogs/etomas/archive/2010/11/09/unobtrusive-ajax-en-mvc3.aspx">Unobtrusive Ajax en ASP.NET MVC3</a>. Hoy quiero mostraros que esa técnica <strong>ni</strong> es exclusiva de MVC3, <strong>ni</strong>  requiere HTML5 para nada. En fin, que podéis empezar a usarla ya, con independencia de la tecnología que uséis. Lo que contaré en este artículo no es nada “revolucionario” ni una “técnica nueva”…</p> <p>De hecho, el ejemplo va a ser una página HTML, nada de ASP.NET :)</p> <p>Veamos, la técnica de Unobtrusive Javascript, se refiere a <strong>no tener mezclado código javascript con código de marcado HTML</strong>. Es decir, <strong>no</strong> queremos algo como:</p> <div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: 'Courier New', courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px" id="codeSnippet"><span style="color: #0000ff"><</span><span style="color: #800000">input</span> <span style="color: #ff0000">type</span><span style="color: #0000ff">="text"</span> <span style="color: #ff0000">id</span><span style="color: #0000ff">="txtName"</span> <span style="color: #ff0000">onkeypress</span><span style="color: #0000ff">="checkKey();"</span> <span style="color: #0000ff">/></span></pre><br /><br /> <br /></div><br /><br /><p>Aquí estamos mezclando código HTML con el código javascript (la llamada checkKey en el <em>onkeypress</em>).</p><br /><br /><p>Imaginemos que queremos que nuestros <strong>textboxes sólo acepten números</strong>. Y recordad que el objetivo es no tener código javascript mezclado con nuestro código HTML.</p><br /><br /><p>Eso lo podemos conseguir fácilmente, ya con jQuery:</p><br /><br /><div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: 'Courier New', courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"><br /> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px" id="codeSnippet"><html xmlns=<span style="color: #006080">"http://www.w3.org/1999/xhtml"</span>><br /><head><br /> <title>Demo Unobtrusive Javascript</title><br /> <script src=<span style="color: #006080">"jquery-1.4.1.js"</span> type=<span style="color: #006080">"text/javascript"</span>></script><br /></head><br /><body><br /> <script type=<span style="color: #006080">"text/javascript"</span>><br /> $(document).ready(<span style="color: #0000ff">function</span> () {<br /> $(<span style="color: #006080">'input:text'</span>).keypress(<span style="color: #0000ff">function</span> (<span style="color: #0000ff">event</span>) {<br /> <span style="color: #0000ff">if</span> (<span style="color: #0000ff">event</span>.keyCode < 47 || <span style="color: #0000ff">event</span>.keyCode > 58) {<br /> <span style="color: #0000ff">event</span>.preventDefault();<br /> }<br /> });<br /> });<br /> </script><br /><br /> Introduce sólo números: <br /><br /> <input type=<span style="color: #006080">"text"</span> /><br /></body><br /></html></pre><br /><br /> <br /></div><br /><br /><p>Incluso, si no queréis que haya el tag <script> con todo el código, podemos moverlo a un .js separado y usarlo desde nuestra página HTML que entonces quedaría como:</p><br /><br /><div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: 'Courier New', courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"><br /> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px" id="codeSnippet"><html xmlns=<span style="color: #006080">"http://www.w3.org/1999/xhtml"</span>><br /><head><br /> <title>Demo Unobtrusive Javascript</title><br /> <script src=<span style="color: #006080">"jquery-1.4.1.js"</span> type=<span style="color: #006080">"text/javascript"</span>></script><br /> <script src=<span style="color: #006080">"myscript.js"</span> type=<span style="color: #006080">"text/javascript"</span>></script><br /></head><br /><body><br /> Introduce sólo números: <br /><br /> <input type=<span style="color: #006080">"text"</span> /><br /></body><br /></html></pre><br /><br /> <br /></div><br /><br /><p>Por lo tanto vemos que con jQuery es muy fácil asignar comportamiento a objetos DOM, sin necesidad de andar con los handlers onXXXX.</p><br /><br /><p>Ahora bien, el código jQuery selecciona <strong>todos</strong> los <input type=”text”>, que passa si sólo quiero seleccionar <em>algunos?</em> Como le indico a mi código jQuery que sólo algunos textboxes son numéricos?</p><br /><br /><p>Una solución es <em>invertarnos</em> un atributo que indique que elementos queremos como numéricos. De esta manera p.ej. la página HTML queda como:</p><br /><br /><div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: 'Courier New', courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"><br /> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px" id="codeSnippet"><html xmlns=<span style="color: #006080">"http://www.w3.org/1999/xhtml"</span>><br /><head><br /> <title>Demo Unobtrusive Javascript</title><br /> <script src=<span style="color: #006080">"jquery-1.4.1.js"</span> type=<span style="color: #006080">"text/javascript"</span>></script><br /> <script src=<span style="color: #006080">"myscript.js"</span> type=<span style="color: #006080">"text/javascript"</span>></script><br /></head><br /><body><br /> Introduce sólo números: <br /><br /> <input type=<span style="color: #006080">"text"</span> datatype=<span style="color: #006080">"numeric"</span> /> <br /><br /> Aquí puedes introducir lo que quieras: <br /><br /> <input type=<span style="color: #006080">"text"</span> /><br /></body><br /></html></pre><br /><br /> <br /></div><br /><br /><p>Fijaos en el “datatype=”numeric” que es el atributo que me va a servir para decidir que textboxes son numéricos.</p><br /><br /><p>Y el código de myscript.js queda como:</p><br /><br /><div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: 'Courier New', courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"><br /> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px" id="codeSnippet">$(document).ready(<span style="color: #0000ff">function</span> () {<br /> $(<span style="color: #006080">'input[datatype=numeric]'</span>).keypress(<span style="color: #0000ff">function</span> (<span style="color: #0000ff">event</span>) {<br /> <span style="color: #0000ff">if</span> (<span style="color: #0000ff">event</span>.keyCode < 47 || <span style="color: #0000ff">event</span>.keyCode > 58) {<br /> <span style="color: #0000ff">event</span>.preventDefault();<br /> }<br /> });<br />});</pre><br /><br /> <br /></div><br /><br /><p>Y listos, simplemente incluyendo “myscript.js” en cualquier página ya podemos declarar que un textbox es numérico <em>simplemente</em> poniendo el atributo datatype=”numeric”.</p><br /><br /><p>Ahora, si alguien hace otra librería javascript para textboxes numéricos <strong>si también usa este atributo para indicarlos </strong>(ahí está el quid de la cuestión) simplemente cambiando el <script> para que en lugar de ir a myscript.js vaya a la nueva librería, ya tengo todo el cambio hecho… es decir, me he independizado del framework javascript que use.</p><br /><br /><p><strong>Y por ahí por donde entra HTML5?</strong> Pues bien, como eso de crearnos nuestros propios atributos está bien pero genera HTML que podríamos llamar <em>inválido</em> (en el sentido de que estos atributos no forman parte de HTML), para HTML5 han decidido simplemente que todos estos atributos “inventados” empiecen por <em>data-</em>.</p><br /><br /><p>Lo “único” que dice al respecto HTML5 es: “<em>Hey, si tienes que invertarte un atributo para lo que sea, haz que su nombre empiece por data-. Todos los atributos que empiecen por data- son atributos inventados por alquien y deben ser ignorados a todos los efectos (salvo para quien lo haya inventado que hará con él lo que le plazca, claro</em>). Ok, <a href="http://www.w3.org/TR/2009/WD-html5-20090423/dom.html">también añade una API específica (element.dataset) para leer esos atributos</a> (pero eso de momento no nos importa ya que no está soportada por la mayoría de navegadores).</p><br /><br /><p>Por lo tanto, si en lugar de que mi atributo se llame datatype, hago que le llame data-datatype (p.ej. cualquier nombre que empiece por data-) ya lo tengo todo <em>HTML5 compliant!</em></p><br /><br /><p>De hecho podéis hacer la prueba en <a title="http://validator.w3.org/check" href="http://validator.w3.org/check">http://validator.w3.org/check</a>. Entráis el código HTML de la página y lo validáis contra:</p><br /><br /><ul><br /> <li>HTML5 usando el atributo datatype=”numeric” y os dará <strong>error</strong> (Attribute not allowed) </li><br /><br /> <li>HTML5 usando el atributo data-datatype=”numeric” y os validará correctamente. </li><br /><br /> <li>Cualquier otra versión de HTML y os dará error en ambos casos. </li><br /></ul><br /><br /><p>Y listos! Por lo tanto fijaos que <strong>desde ya</strong> podeis empezar a aplicar técnicas de “Unobtrusive Javascript”: no necesitáis HTML5 para nada, ni MVC3 ni nada y la <em>recompensa</em> es un HTML mucho más claro y sencillo de ver!</p><br /><br /><p>Mi opinión es que, gracias a que HTML5 ha definido un <em>espacio de nombres</em> (data-) para los <em>atrbutos inventados</em> empezaremos a ver, cada ves más, librerías de javascript que usarán esos atributos, y seguramente algunos de ellos terminarán siendo estándares de facto (si yo hago una librería de javascript para validación. pues intentaré usar los mismos atributos data- que use la librería que sea <em>líder</em> en aquel momento, para compatibilizarme con ella).</p><br /><br /><p>Por cierto, si vais a usar muchos atributos data- en vuestras páginas web, echadle un vistazo a este plugin de jQuery: <a href="http://plugins.jquery.com/project/html5-dataset">HTML5 Dataset</a>.</p><br /><br /><p>Un saludo!</p><br /><br /><p><strong>Nota: </strong>El código de ese artículo lo he probado con IE9 y Firefox 3.6.10.</p><br /><br /><p>PD: Esto es un crosspost de <a href="http://geeks.ms/blogs/etomas/default.aspx">mi blog en geeks.ms</a> (como no…)</p> eiximenishttp://www.blogger.com/profile/02568454383791432108noreply@blogger.com0tag:blogger.com,1999:blog-897874347676011387.post-39475699049041882832010-11-09T11:56:00.001+01:002010-11-09T11:56:44.506+01:00Unobtrusive Ajax en MVC3<p>Buenas! Una de las novedades más interesantes de MVC3 es el soporte para eso que se llama <em>Unobtrusive </em>Ajax. La verdad es que no encuentro una buena traducción para Unobtrusive (discreto no me convence).</p> <p>La idea del Unobtrusive Ajax es <strong>evitar mezclar código script con código HTML</strong>. De la misma manera que CSS nos permite separar completamente el código HTML de su representación, con Unobtrusive Ajax vamos a poder separar el código javascript del código HTML.</p> <p>Pero mejor, veamoslo con un ejemplo, ultra sencillo :)</p> <p>Imaginad que tengo una vista con este contenido:</p> <div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: 'Courier New', courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px" id="codeSnippet"><h2>Normal Ajax</h2><br /><% <span style="color: #0000ff">using</span> (Ajax.BeginForm(<span style="color: #006080">"PostData"</span>, <span style="color: #0000ff">new</span> AjaxOptions() { HttpMethod=<span style="color: #006080">"Post"</span>, UpdateTargetId=<span style="color: #006080">"datadiv"</span>})) { %><br /><br /> <label <span style="color: #0000ff">for</span>=<span style="color: #006080">"name"</span>>Name: </label><br /> <input type=<span style="color: #006080">"text"</span> name=<span style="color: #006080">"name"</span> id=<span style="color: #006080">"name"</span>/><br /> <input type=<span style="color: #006080">"submit"</span> value=<span style="color: #006080">"Send"</span> /><br /><% } %><br /><hr /><br />Aquí irá el resultado: <p /><br /><div id=<span style="color: #006080">"datadiv"</span>><br /></div></pre><br /><br /> <br /></div><br /><br /><p>Esta vista genera un <form> con un campo de texto y envía los datos a una acción llamada “PostData” e incrusta el resultado de dicha acción (que será una vista parcial) en el div cuyo id es “datadiv”.</p><br /><br /><p>Este es el código HTML generado por esta vista en MVC2:</p><br /><br /><div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: 'Courier New', courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"><br /> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px" id="codeSnippet"><span style="color: #0000ff"><</span><span style="color: #800000">h2</span><span style="color: #0000ff">></span>Normal Ajax<span style="color: #0000ff"></</span><span style="color: #800000">h2</span><span style="color: #0000ff">></span><br /><span style="color: #0000ff"><</span><span style="color: #800000">form</span> <span style="color: #ff0000">action</span><span style="color: #0000ff">="/Home/PostData"</span> <span style="color: #ff0000">method</span><span style="color: #0000ff">="post"</span> <br /> <span style="color: #ff0000">onclick</span><span style="color: #0000ff">="Sys.Mvc.AsyncForm.handleClick(this, new Sys.UI.DomEvent(event));"</span> <br /> <span style="color: #ff0000">onsubmit</span><span style="color: #0000ff">="Sys.Mvc.AsyncForm.handleSubmit(this, new Sys.UI.DomEvent(event), { insertionMode: Sys.Mvc.InsertionMode.replace, httpMethod: &#39;Post&#39;, updateTargetId: &#39;datadiv&#39; });"</span><span style="color: #0000ff">></span><br /> <span style="color: #0000ff"><</span><span style="color: #800000">label</span> <span style="color: #ff0000">for</span><span style="color: #0000ff">="name"</span><span style="color: #0000ff">></span>Name: <span style="color: #0000ff"></</span><span style="color: #800000">label</span><span style="color: #0000ff">></span><br /> <span style="color: #0000ff"><</span><span style="color: #800000">input</span> <span style="color: #ff0000">type</span><span style="color: #0000ff">="text"</span> <span style="color: #ff0000">name</span><span style="color: #0000ff">="name"</span> <span style="color: #ff0000">id</span><span style="color: #0000ff">="name"</span><span style="color: #0000ff">/></span><br /> <span style="color: #0000ff"><</span><span style="color: #800000">input</span> <span style="color: #ff0000">type</span><span style="color: #0000ff">="submit"</span> <span style="color: #ff0000">value</span><span style="color: #0000ff">="Send"</span> <span style="color: #0000ff">/></span><br /><span style="color: #0000ff"></</span><span style="color: #800000">form</span><span style="color: #0000ff">></span><br /><span style="color: #0000ff"><</span><span style="color: #800000">hr</span> <span style="color: #0000ff">/></span><br />Aquí irá el resultado: <span style="color: #0000ff"><</span><span style="color: #800000">p</span> <span style="color: #0000ff">/></span><br /><span style="color: #0000ff"><</span><span style="color: #800000">div</span> <span style="color: #ff0000">id</span><span style="color: #0000ff">="datadiv"</span><span style="color: #0000ff">></span><br /><span style="color: #0000ff"></</span><span style="color: #800000">div</span><span style="color: #0000ff">></span></pre><br /><br /> <br /></div><br /><br /><p>Fijaos en que en el tag <form> se le ha incrustado código javascript para gestionar el <em>onclick</em> y el <em>onsubmit</em> (para poder realizar el envío via ajax).</p><br /><br /><p>Bien… y <strong>esta misma vista (idéntica) que código genera en MVC3?</strong> Pues el siguiente:</p><br /><br /><div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: 'Courier New', courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"><br /> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px" id="codeSnippet"><span style="color: #0000ff"><</span><span style="color: #800000">h2</span><span style="color: #0000ff">></span>Normal Ajax<span style="color: #0000ff"></</span><span style="color: #800000">h2</span><span style="color: #0000ff">></span><br /><span style="color: #0000ff"><</span><span style="color: #800000">form</span> <span style="color: #ff0000">action</span><span style="color: #0000ff">="/Home/PostData"</span> <br /> <span style="color: #ff0000">data-ajax</span><span style="color: #0000ff">="true"</span> <span style="color: #ff0000">data-ajax-method</span><span style="color: #0000ff">="Post"</span> <span style="color: #ff0000">data-ajax-mode</span><span style="color: #0000ff">="replace"</span> <br /> <span style="color: #ff0000">data-ajax-update</span><span style="color: #0000ff">="#datadiv"</span> <span style="color: #ff0000">method</span><span style="color: #0000ff">="post"</span><span style="color: #0000ff">></span><br /><br /> <span style="color: #0000ff"><</span><span style="color: #800000">label</span> <span style="color: #ff0000">for</span><span style="color: #0000ff">="name"</span><span style="color: #0000ff">></span>Name: <span style="color: #0000ff"></</span><span style="color: #800000">label</span><span style="color: #0000ff">></span><br /> <span style="color: #0000ff"><</span><span style="color: #800000">input</span> <span style="color: #ff0000">type</span><span style="color: #0000ff">="text"</span> <span style="color: #ff0000">name</span><span style="color: #0000ff">="name"</span> <span style="color: #ff0000">id</span><span style="color: #0000ff">="name"</span><span style="color: #0000ff">/></span><br /> <span style="color: #0000ff"><</span><span style="color: #800000">input</span> <span style="color: #ff0000">type</span><span style="color: #0000ff">="submit"</span> <span style="color: #ff0000">value</span><span style="color: #0000ff">="Send"</span> <span style="color: #0000ff">/></span><br /><span style="color: #0000ff"></</span><span style="color: #800000">form</span><span style="color: #0000ff">></span><br /><br /><span style="color: #0000ff"><</span><span style="color: #800000">hr</span> <span style="color: #0000ff">/></span><br />Aquí irá el resultado: <span style="color: #0000ff"><</span><span style="color: #800000">p</span> <span style="color: #0000ff">/></span><br /><span style="color: #0000ff"><</span><span style="color: #800000">div</span> <span style="color: #ff0000">id</span><span style="color: #0000ff">="datadiv"</span><span style="color: #0000ff">></span><br /><span style="color: #0000ff"></</span><span style="color: #800000">div</span><span style="color: #0000ff">></span></pre><br /><br /> <br /></div><br /><br /><p>Fijaos que diferencia… No hay nada de javascript mezclado en el código. Todo es HTML. Simplemente al tag <form> se le añaden unos cuantos atributos (los que empiezan por data-ajax) que indican como se debe comportarse este formulario a nivel de Ajax.</p><br /><br /><p>Y quien realiza “la magia”? Pues quien va a ser… nuestra amada jQuery, junto con una extensión de Microsoft (el fichero <em>jquery.unobtrusive-ajax.js</em>)! Para que esto funciona teneis que añadir tanto jQuery como la extensión de MS (yo los pongo en la master):</p><br /><br /><div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: 'Courier New', courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"><br /> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px" id="codeSnippet"><script src=<span style="color: #006080">"<%: Url.Content("</span>~/Scripts/jquery-1.4.1.js<span style="color: #006080">") %>"</span> type=<span style="color: #006080">"text/javascript"</span>></script><br /><script src=<span style="color: #006080">"<%: Url.Content("</span>~/Scripts/jquery.unobtrusive-ajax.js<span style="color: #006080">") %>"</span> type=<span style="color: #006080">"text/javascript"</span>></script></pre><br /><br /> <br /></div><br /><br /><p>Esta adaptación de los helpers en MVC3 para soportar esta característica es a lo que nos referimos cuando decimos que “ASP.NET MVC3 da soporte para <em>Unobtrusive</em> Ajax”, y es una doble gran noticia. Digo doble porque por un lado nos permite seguir usando los helpers con la garantía de que vamos a generar código “limpio” de javascript y por otro lado <strong>el helper de Ajax usa ¡por fin! jQuery</strong>. A diferencia de MVC2 donde el Helper Ajax usaba la Ajax Library de Microsoft. De hecho, aunque en los templates de proyecto se sigue poniendo, si me aceptas un consejo: bórrala y no la uses. Puedes borrarla con total tranquilidad porque en MVC3 ningún helper la usa.</p><br /><br /><p>Unobtrusive Ajax viene habilitado por defecto en los nuevos proyectos MVC3 pero lo podéis deshabilitar (y entonces generar el mismo código que en MVC2, usando la Microsoft Ajax Library). Podeis deshabilitarlo a nivel de vista o para todo el proyecto.</p><br /><br /><p>Para deshabilitarlo a nivel de vista, basta con incluir:</p><br /><br /><div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: 'Courier New', courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"><br /> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px" id="codeSnippet"><% HtmlHelper.UnobtrusiveJavaScriptEnabled = <span style="color: #0000ff">false</span>; %></pre><br /><br /> <br /></div><br /><br /><p>Para deshabilitarlo para todo el proyecto, puedes incluir ese mismo código en el global.asax.cs o bien usar web.config:</p><br /><br /><div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: 'Courier New', courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"><br /> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px" id="codeSnippet"><span style="color: #0000ff"><</span><span style="color: #800000">configuration</span><span style="color: #0000ff">></span><br /> <span style="color: #0000ff"><</span><span style="color: #800000">appSettings</span><span style="color: #0000ff">></span><br /> <span style="color: #0000ff"><</span><span style="color: #800000">add</span> <span style="color: #ff0000">key</span><span style="color: #0000ff">="UnobtrusiveJavaScriptEnabled"</span> <span style="color: #ff0000">value</span><span style="color: #0000ff">="false"</span><span style="color: #0000ff">/></span><br /> <span style="color: #0000ff"></</span><span style="color: #800000">appSettings</span><span style="color: #0000ff">></span><br /><span style="color: #0000ff"></</span><span style="color: #800000">configuration</span><span style="color: #0000ff">></span> </pre><br /><br /> <br /></div><br /><br /><p>Lo mismo para habilitarlo. Si <strong>no</strong> aparece la entrada <em>UnobtrusiveJavaScriptEnabled</em> en el <appSettings> el valor por defecto es <strong>false</strong>. Es por eso que si haces un upgrade de un proyecto de MVC2 a MVC3, no tendrás esta entrada en el web.config y por eso Unobtrusive Ajax estará deshabilitado!</p><br /><br /><p>Un saludo!</p><br /><br /><p>PD: El hecho de que los atributos que se usan para que Unobtrusive Ajax funcione empiecen por “data-“ es porque HTML5 reserva estos atributos “para usos propios de los scripts del site”, tal y como podéis leer en la <a href="http://dev.w3.org/html5/spec/elements.html#custom-data-attribute">especificación de Custom Data Attributes</a>.</p><br /><br /><p>PD2: Para variar… otro crosspost desde <a href="http://geeks.ms/blogs/etomas/default.aspx">mi blog en geeks.ms</a>! ;-)</p> eiximenishttp://www.blogger.com/profile/02568454383791432108noreply@blogger.com0tag:blogger.com,1999:blog-897874347676011387.post-66449992141962011132010-11-08T11:29:00.001+01:002010-11-08T11:29:47.866+01:00How to: Obtener controles de un formulario con generics II (Linq al rescate).<p>Ayer Lluis escribía este gran post: <em><a href="http://geeks.ms/blogs/lfranco/archive/2010/11/05/how-to-obtener-controles-de-un-formulario-con-generics.aspx">How to: Obtener controles de un formulario con generics</a></em>. Como bien dice es una pregunta… recurrente en todos los sitios :)</p> <p>Lo bueno de eso del desarrollo es que para todo hay varias soluciones, así que aquí os propongo otra, pero usando Linq. Personalmente me encanta Linq, supongo que es porqué siempre me han fascinado los lenguajes funcionales…</p> <p>Antes que nada tenemos que solucionar un temilla: Linq funciona sobre IEnumerable<T> pero la propiedad Controls de un Control devuelve un objeto de tipo ControlCollection (otra de esas n-mil clases que no tienen sentido alguno y que existen sólo porque no teníamos generics en la versión 1 del framework). Así que el primer paso es obtener un IEnumerable<Control> a partir de una ControlCollection. Con un método extensor eso es trivial:</p> <div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: 'Courier New', courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px" id="codeSnippet"><span style="color: #0000ff">public</span> <span style="color: #0000ff">static</span> IEnumerable<Control> AsEnumerable (<span style="color: #0000ff">this</span> Control.ControlCollection @<span style="color: #0000ff">this</span>)<br />{<br /> <span style="color: #0000ff">foreach</span> (var control <span style="color: #0000ff">in</span> @<span style="color: #0000ff">this</span>) <span style="color: #0000ff">yield</span> <span style="color: #0000ff">return</span> (Control)control;<br />}</pre><br /><br /> <br /></div><br /><br /><p>Ale… listos, con eso <em>transformamos</em> la CollectionControl en un IEnumerable<Control> y tenemos acceso a todo el potencial de Linq… Y como queda el método para obtener todos los controles de un tipo? Pues así:</p><br /><br /><div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: 'Courier New', courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"><br /> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px" id="codeSnippet"><span style="color: #0000ff">public</span> <span style="color: #0000ff">static</span> IEnumerable<T> GetAllControls<T>(<span style="color: #0000ff">this</span> Control @<span style="color: #0000ff">this</span>) <span style="color: #0000ff">where</span> T : Control<br />{<br /> <span style="color: #0000ff">return</span> @<span style="color: #0000ff">this</span>.Controls.AsEnumerable().Where(x => x.GetType() == <span style="color: #0000ff">typeof</span>(T)).<br /> Select(y=>(T)y).<br /> Union(@<span style="color: #0000ff">this</span>.Controls.AsEnumerable().SelectMany(x => GetAllControls<T>(x)).<br /> Select(y=>(T)y));<br />}</pre><br /><br /> <br /></div><br /><br /><p>¡No me diréis que no es precioso: no hay bucles, no hay ifs… Os he dicho que me encanta Linq? ;-)</p><br /><br /><p>Hay una <em>pequeña</em> diferencia entre la versión de Lluís y esta mía: la versión de Lluís usa una List<Control> en la que añade todas las referencias (copia las referencias a la lista. Ojo: las referencias, no los controles). Esa versión que usa Linq, no copia las referencias en ningún sitio, sinó que <em>simplemente</em> itera sobre la coleccion original: no crea listas internas, ni nada… Ese es el poder de Linq!</p><br /><br /><p>Es cierto que al usar IEnumerable<T> como valor de retorno, perdemos el método ForEach() (puesto que IEnumerable<T> no lo tiene y Linq no lo proporciona), pero hacer un método ForEach es trivial:</p><br /><br /><div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: 'Courier New', courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"><br /> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px" id="codeSnippet"><span style="color: #0000ff">public</span> <span style="color: #0000ff">static</span> <span style="color: #0000ff">void</span> ForEach<T>(<span style="color: #0000ff">this</span> IEnumerable<T> @<span style="color: #0000ff">this</span>, Action<T> action)<br />{<br /> <span style="color: #0000ff">foreach</span> (T t <span style="color: #0000ff">in</span> @<span style="color: #0000ff">this</span>)<br /> {<br /> action(t);<br /> }<br />}</pre><br /><br /> <br /></div><br /><br /><p>De todos modos, si os preguntáis <strong>porque Linq no ofrece un método ForEach</strong> quizá deberíais leeros este post de Octavio: <a href="http://geeks.ms/blogs/ohernandez/archive/2008/09/13/observaciones-con-respecto-al-m-233-todo-extensor-foreach-lt-t-gt.aspx">Observaciones con respecto a un método extensor ForEach<T></a>.</p><br /><br /><p>Un  saludo a todos y gracias a Lluís por darme “la excusa” de hacer otro post! ;-)</p><br /><br /><p>PD: Eso es (como no) un crosspost desde <a href="http://geeks.ms/blogs/etomas/default.aspx">mi blog de geeks.ms</a>.</p> eiximenishttp://www.blogger.com/profile/02568454383791432108noreply@blogger.com0tag:blogger.com,1999:blog-897874347676011387.post-48206019511210335272010-11-03T10:54:00.001+01:002010-11-03T10:54:30.172+01:00Opinión: De si Silverlight está muerto, agonizante o mejor que nunca<p>Andamos todos revolucionados estos dias, a raíz de unas declaraciones de <a href="http://www.microsoft.com/presspass/exec/bobmuglia/">Bob Muglia</a> donde decía “<a href="http://www.zdnet.com/blog/microsoft/microsoft-our-strategy-with-silverlight-has-shifted/7834">Our strategy with Silverlight has shifted</a>”. Eso unido al énfasis que se dio a HTML5 en el keynote del PDC y la no mención en absoluto de nada referente a Silverlight han disparado los rumores.</p> <p>Así pues… ¿está Silverlight muerto, agonizante o por el contrario está mejor que nunca? Primero un <em>disclaimer</em>: este es un post de opinión, todo lo que yo afirmo tajantemente en este post son cosas que <em>yo</em> creo. No tengo ni el conocimiento ni la razón absoluta, y además en MS nunca se sabe, así que como suele decirse: al final el tiempo dará y quitará razones :)</p> <p>La estrategia <em>inicial</em> de Microsoft para posicionar Silverlight fue darle el nicho de aplicaciones web: Con Silverlight tus aplicaciones web tendrán una experiencia de usuario mejor, decían. En estos tiempos, comparar Silverlight con Flash era habitual, aunque Microsoft insistía en que no eran comparables: que Silverlight era para <em>aplicaciones completas</em> y no para pequeños adornos. Cuando hacía tiempo que Flash había asumido que no sustituiría a HTML, parecía ser que Silverlight quería asumir el relevo.</p> <p>Pero con Silverlight 3 todo cambió: la posibilidad de ejecutar aplicaciones out-of-browser, la interoperabilidad COM y mejoras en el acceso a dispositivos locales, convertían a SL3 en algo distinto. Estaba claro que Silverlight llegaba a sitios donde HTML no sueña con llegar nunca. La batalla que inicialmente se libraba en el navegador ahora se trasladaba también al escritorio, y el rival era Adobe AIR. Habéis visto <a href="http://seesmic.com/seesmic_desktop/sd2/">Seesmic Desktop 2</a>? Es un excelente cliente de Twitter, para Windows y Mac, es una aplicación de escritorio… y está hecha con Silverlight.</p> <p><strong>¿Y donde estamos ahora?</strong></p> <p>El otro día en twitter <em>conversaba</em> con <a href="http://geeks.ms/blogs/amezcua">Alejandro Mezcua</a> (<a href="http://twitter.com/byteabyte">@byteabyte</a>) y Dani Mora (<a href="http://twitter.com/lodani">@LoDani</a>) sobre el futuro de Silverlight. Yo argumentaba que <strong>no creo ni mucho menos que Silverlight</strong> esté muerto. De hecho le veo un gran futuro a Silverlight en aplicaciones de escritorio y en aplicaciones para Windows Phone 7 (bueno, aquí depende de como le vaya a WP7, claro). Pero <strong>no le veo futuro para aplicaciones web</strong>. Quiere decir esto que Silverlight va a desaparecer del browser? No, tiene su nicho en sites específicos donde HTML no llega: p. ej. nada mejor que Silverlight para streaming de video. Sí, sí… HTML5 va a soportar vídeo, pero no soportará DRM ni bitrates variables. Si necesitas esto, necesitarás <em>algo externo</em> que te lo de, y ahí entra Silverlight. Pero para la creación <em>de aplicaciones web</em>, no lo veo, y diría que Microsoft tampoco :)</p> <p>¿Y RIA? La clave de RIA es lo que significa la I. Dani me comentaba que para él, la I de RIA era “aquel entorno que el administrador de sistemas podía controlar”. Entonces <em>no </em>estamos hablando de internet… llamésmole intranet o alguna otra cosa, pero no internet. Si quieres desarrollar una aplicación <em>para internet</em> no hay mejor opción que HTML. Y los tiempos han cambiado mucho: de acuerdo que hace tiempo desarrollar con javascript era poco menos que un infierno, pero ahora tenemos a <a href="http://jquery.com/">jQuery</a> que convierte eso en un juego de niños. Antes Ajax era doloroso y entraba con vaselina: ahora con un par de líneas puedes usar Ajax sin preocuparte de que navegadores se usan ni nada parecido. Y sí: hay <a href="http://jquerymobile.com/">jQuery para dispositivos</a> móviles. Aunque de todos modos la experiencia parece demostrar que en los móviles la gente prefiere aplicaciones nativas antes que webs.</p> <p>Así que si quieres estar presente en aplicaciones web dentro de poco tiempo, <em>aprende ya HTML5</em>. No hagas caso de los que dicen que no está terminado, que no saldrá hasta dentro de no se cuantos años: son verdades a medias. Es cierto que la especificación no está lista, pero muchas partes ya estan terminadas, y ya están siendo implementadas en los navegadores. No ha salido hace poco la noticia de que <a href="http://test.w3.org/html/tests/reporting/report.htm">IE9 era el navegador más compatible con HTML5</a>? Como podría ser si no hubiese salido nada de HTML5? Dentro de poco ya comenzaremos a ver sites desarrollados en HTML5. El momento de lanzarse no es dentro de un, dos o tres años: es ahora.</p> <p>Pero seguiremos viendo Silverlight en el navegador? Pues creo que sí: en la gran internet en aquellos sitios <em>junto</em> a HTML, donde se requieran aspectos que HTML5 no va asumir y en intranets (o entornos controlados) también supongo que podrán verse… Aunque en estos entornos, porque obligar al usuario a usar un navegador? Las capacidades out of browser de Silverlight hacen que el usuario tenga la sensación de usar una aplicación nativa, con toda la seguridad del sandbox de Silverlight y la misma facilidad de actualización que si una aplicación web se tratara.</p> <p>Y fuera del navegador? Pues sin duda. Veremos cada vez más aplicaciones de escritorio desarrolladas en Silverlight, y tener todas sus ventajas: actualizaciones automáticas, seguridad y multiplataforma (Windows, Mac). Y ojalá Microsoft se lance en serio a Linux y haga su implementación de Silverlight (y no lo deje medio abandonado con <a href="http://www.mono-project.com/Moonlight">Moonlight</a>).</p> <p>De hecho, para resumir este post, si hay alguien que deba sentirse amenazado por Silverlight no es ni mucho menos HTML5… en todo caso, si hemos de buscar a alguien, se trataría de WPF. Cada versión de Silverlight acorta distancias con <em>su hermano mayor</em> y no es descabellado pensar si algún dia llegarán a <em>unirse</em>.</p> <p>Y a vosotros que os parece?</p> <p>Un saludo!</p> <p>PD: Como siempre… esto es un crosspost desde <a href="http://geeks.ms/blogs/etomas/default.aspx">mi blog en geeks.ms</a>.</p> eiximenishttp://www.blogger.com/profile/02568454383791432108noreply@blogger.com0