lunes, 28 de febrero de 2011

A veces veo “Cookies”

Explicar ahora mismo como funcionan las cookies en ASP.NET me parecería volver a escribir sobre algo que ya está suficientemente explotado en Internet. Sin embargo, trabajando hoy en un proyecto que hace un uso intensivo de las mismas, me he encontrado con un par de situaciones, cuando menos curiosas, que creo si podrían aportar algo nuevo al debate y explicación de cookies en ASP.NET.

En el siguiente enlace se explica casi todos los temas relacionados con cookies en ASP.NET, http://msdn.microsoft.com/es-es/library/ms178194.aspx. Una de los puntos explicar cómo acceder a una cookie en el objeto Request, es decir, saber si hemos recibido del cliente una determinada cookie.

        If Not Request.Cookies.Item("panicoenlaxbox") Is Nothing Then

            ' La cookie existe

        End If


El anterior código no tiene ningún problema y funciona satisfactoriamente, pero cuál es mi sorpresa cuando veo que para preguntar si hemos establecido el valor de una cookie durante el ciclo de vida de nuestra página, el código de comprobación cambia radicalmente. Es decir, un código como el siguiente no funciona como yo pienso cabría esperar:

        If Not Response.Cookies.Item("panicoenlaxbox") Is Nothing Then

            ' Si utilizamos Response, nunca una cookie será Nothing...

            ' Ahora debemos comprobar la propiedad Value de la cookie

        End If

 
Lo que pasa es que si utilizamos Response, la propiedad Item nunca nos devolverá Nothing, luego siempre hay que comprobar, no ya la existencia de la cookie, sino el valor de la cookie.

        If Not String.IsNullOrEmpty(Response.Cookies.Item("panicoenlaxbox").Value) Then

            ' La cookie existe

        End If

 
Ahora que parece que ya hemos solucionado esta dicotomía en la forma de preguntar por la existencia de una cookie, nos encontramos con un comportamiento aún más extraño y que yo, y a menos que alguien me lo explique, no termino de entender. El comportamiento “extraño” consiste en que simplemente por el mero hecho de acceder a la propiedad Item de la colección Response.Cookies, la cookie por la preguntamos es creado automáticamente con el valor “” para su propiedad Value, tanto en la colección Response como en la colección Request. Veamos un ejemplo que no quiero quedar como un mentiroso:

Inicialmente tenemos esta situación en lo relativo a cookies.

clip_image002

Ahora simplemente escribimos el siguiente código:

        If Not String.IsNullOrEmpty(Response.Cookies.Item("panicoenlaxbox").Value) Then

            ' La cookie existe

        End If

 

Y vemos como todo cambia “mágicamente”.

clip_image004

De hecho, esto se traduce en la siguiente cabecera enviada al navegador cliente.

clip_image006

Otro punto interesante en lo relativo al uso de las cookies es que cuando agregamos una cookie, no se comprueba que el nombre sea único en la colección, y de este modo podemos tener 2 cookies con el mismo nombre. Por ejemplo:

        Dim cookie As New HttpCookie("panicoenlaxbox", "primer pánico")

        Response.Cookies.Add(cookie)

        cookie = New HttpCookie("panicoenlaxbox", "segundo pánico")

        Response.Cookies.Add(cookie)

 

clip_image008

Si ahora preguntamos por el valor de la cookie “panicoenlaxbox” el valor devuelto por la colección es siempre el primer elemento de la colección, luego será “primer pánico” en Response y “” en Request.

clip_image010

Y con sus correspondientes 2 cabeceras!!

clip_image012

La conclusión es que mientras que en el código de la página .aspx cuando preguntábamos por “panicoenlaxbox” nos devolvía “primer pánico” (la primera aparición en la colección), en el siguiente PostBack de la página nos devolverá “segundo pánico” puesto que de las 2 cabeceras Set-Cookie ha prevalecido la segunda.

clip_image014

La verdad es que después de todo esto, sólo me queda el consuelo que el método Remove(NombreCookie) elimina todas las cookies con el mismo nombre en la colección, así que si entras en barrena y tienes que borrar cookies al menos puede estar seguro de haber dejado limpia la colección ;-)

Un saludo!

martes, 22 de febrero de 2011

HotFixs para Visual Studio 2010

Si trabajas con Visual Studio 2010, quizás hayas encontrado algún problema con el scroll en el menú contextual del proyecto.

Tanto el problema como la solución están explicados en http://geeks.ms/blogs/jorge/archive/2010/10/15/visual-studio-2010-hotfix-para-resolver-el-problema-del-scroll-en-el-men-250-contextual.aspx

Por otro lado, a mi personalmente, me pone enfermo el problema de “Out Of Memory” cuando estás copiando en el editor de Visual Studio 2010. Pues gracias al bruno también tenemos hotfix http://geeks.ms/blogs/elbruno/archive/2011/02/22/vs2010-hotfix-para-evitar-el-out-of-memory-en-el-editor-de-workflow-foundation.aspx

Un saludo.

viernes, 18 de febrero de 2011

¿DataRow o DataRowView como origen de datos?

Hoy me ha pasado que tenía la necesidad de devolver un DataRow desde un método en vez de un DataTable. El motivo principal es que el método sólo devuelve 1 registro así que pensé que sería mejor devolver un DataRow que un DataTable.

La sorpresa ha aparecido cuando he enlazado a datos un repetidor con éste registro. Para hacerlo tampoco he hecho nada excepcional, simplemente agregar a mi página .aspx un control de origen de datos (en mi caso un ObjectDataSource) y un control Repeater enlazado al ObjectDataSource a través de la propiedad DataSourceID. Pues bien, las expresiones de tipo Eval(“NombreCampo”) han dejado de funcionar con el mensaje de error “DataBinding: 'System.Data.DataRow' no contiene una propiedad con el nombre 'NombreCampo.”

Siendo así, me han asaltado automáticamente una las siguientes preguntas:

  • ¿Se puede enlazar un control enlazado a datos con un DataRow?
  • ¿Se puede enlazar un control enlazado a datos con un DataView?

La respuesta no va a ser complicada, pero antes de resolverla simplemente me gustaría apuntalar algunos conceptos que, aunque quizás sean obvios, no quisiera dejar de exponer.

  • Un DataTable no pertenece siempre a un DataSet, luego la propiedad <NuestroDataTable>.DataSet podría ser Nothing.
  • Un DataRow no pertenece siempre a un DataTable, luego la propiedad <NuestroDataRow>.Table podría ser Nothing.
  • Si enlazamos un control enlazado a datos con un DataTable, en realidad el control enlazado a datos, iterará por su vista por defecto, esto es <NuestroDataTable>.DefaultView que devuelve objetos del tipo DataRowView (no DataRow).
  • Se puede navegar desde un DataRowView a su vista con la propiedad DataView, y desde ahí a su DataTable con la propiedad Table y de ahí a su DataSet con la propiedad DataSet.
  • Para enlazar un control de origen de datos (por ejemplo un Repeater) por código, no se puede establecer su propiedad DataSource a un objeto que no implemente IListSource o IEnumerable, luego no se podrá enlazar con un DataRow o un DataRowView.

Los problemas aparecieron justo a propósito de este último punto, porque mi control ObjectDataSource devolvía un DataRow y puesto que el enlace no es por código sino desde el marcado de la página .aspx, la verdad es que inicialmente, el Repeater no se quejaba. De hecho, te confirmo que si es desde el marcado, un DataRow o DataRowView puede ser perfectamente un origen de datos para un control enlazado a datos (otra cosa es que como veremos más adelante un DataRow no es un objeto muy “agradecido” en lo relativo a éste enlace).

Si el enlace es con un DataTable:

Eval("NombreCampo")

Valor

Container

RepeaterItem

Container.DataItem

DataRowView

DataBinder.GetDataItem(Container)

DataRowView

DataBinder.Eval(Container.DataItem, " NombreCampo ")

Valor

DataBinder.Eval(DataBinder.GetDataItem(Container), " NombreCampo ")

Valor


Si el enlace es con un DataRow:

Eval("NombreCampo")

{"DataBinding: 'System.Data.DataRow' no contiene una propiedad con el nombre 'NombreCampo '."}

Container

RepeaterItem

Container.DataItem

DataRow

DataBinder.GetDataItem(Container)

DataRow

DataBinder.Eval(Container.DataItem, "NombreCampo")

{"DataBinding: 'System.Data.DataRow' no contiene una propiedad con el nombre 'NombreCampo '."}

DataBinder.Eval(DataBinder.GetDataItem(Container), "NombreCampo")

{"DataBinding: 'System.Data.DataRow' no contiene una propiedad con el nombre 'NombreCampo'."}

CType(Container.DataItem, Data.DataRow).Item("NombreCampo ")

Valor


Si el enlace es con un DataRowView:

Eval("NombreCampo")

Valor

Container

RepeaterItem

Container.DataItem

DataRowView

DataBinder.GetDataItem(Container)

DataRowView

DataBinder.Eval(Container.DataItem, "NombreCampo")

Valor

DataBinder.Eval(DataBinder.GetDataItem(Container), "NombreCampo")

Valor

CType(Container.DataItem, Data.DataRowView).Item("NombreCampo")

Valor

 
Las conclusiones que extraigo de todo esto son las siguientes:

  • jamás enlazaré un control de origen de datos con un DataRow
  • sólo a veces enlazaré un control de origen de datos con un DataRowView
  • casi siempre enlazaré un control de origen de datos con un DataTable

De todas formas, si te quieres aclarar un poco más con la sintaxis de Container, DataBinder, etc, puedes visitar este otro post en el que se habló sobre ello.

Un saludo!

jueves, 10 de febrero de 2011

Más diferencias entre sitio web o aplicación web

En un posterior post “Sitio web o aplicación web” enumeramos muchas de las diferencias que hay entre un sitio web y una aplicación web.

Hoy quisiera matizar dos de estas diferencias que me ha causado más de un dolor de cabeza, la primera es el distinto ámbito de visibilidad que se dan a los métodos de las páginas .aspx, y el segundo es como publicar correctamente ficheros de informe de Crystal Reports.

En un sitio web, el ámbito predeterminado para un nuevo método “manejador de un evento” que se crea al seleccionar un control y evento es Protected. Sin embargo, en una aplicación web el ámbito predeterminado es Private. En principio no hay mayor problema y todo funciona correctamente.

Sitio web

Private Sub Button1_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles Button1.Click

Aplicación web

Private Sub Button1_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles Button1.Click

Sin embargo, si tenemos la necesidad de enlazar el evento del botón desde el marcado en vez de con la instrucción Handles, nos encontramos con que en una aplicación web no es posible debido a que el método no es accesible desde el marcado por su visibilidad.

<asp:Button ID="Button1" runat="server" Text="Button" OnClick="Button1_Click" />

La solución es sencilla y pasa por cambiar Private por Protected, pero quedas avisado!

El otro problema que también quiero esclarecer es como publicar correctamente un fichero .rpt de Crystal Reports.

En un sitio web (y al no existe un fichero de proyecto .vbproj), todos los ficheros son incluidos automáticamente en el resultado de la publicación/precompilación. Sin embargo, en una aplicación web (y al existir un fichero de proyecto), todos los ficheros tienen propiedades que permiten especificar el comportamiento durante la publicación. Es decir, sólo en una aplicación web tendremos disponibles las siguientes propiedades por cada fichero.

clip_image001

Si precompilamos un sitio web, los ficheros .rpt son también compilados y en vez del original fichero .rpt nos encontramos en la salida con un fichero .rpt de marcador con el siguiente texto “Este es un fichero marcador generado por la herramienta de precompilación y que no se debería elimina”. Conclusión, nuestros informes no funcionarán cuando se publiquen.

La solución pasa por después de precompilado, borrar estos ficheros .rpt y sustituirlos por los originales que funcionarán felizmente en nuestro servidor.

Desconozco si hay más extensiones con las que suceda este problema, pero prometo ir actualizando este post según las vaya encontrado.

Por otro lado, si hablamos de una aplicación web, el compilador no incluirá en la salida los ficheros .rpt (bien, antes iban mal y ahora directamente ni están). Esto también pasa con ficheros .pdf, .docx, etc. Es decir, la aplicación web no los considerará “outputeables” (al loro con la palabra!) a no ser que se lo indiquemos.

La solución pasa por establecer por cada fichero que no se incluya, el valor “Siempre” para la propiedad “Copiar en el directorio de resultados”.

Un saludo!.

sábado, 22 de enero de 2011

Clases y métodos parciales

Una clase parcial permite dividir la definición de una clase en múltiples ficheros.
Después y en tiempo de compilación, los distintos ficheros en los que está definida la clase se unirán para formar una sola clase igual que si se hubiera definido inicialmente en un solo fichero.

La magia de combinar distintos ficheros de una misma clase se consigue a través de la palabra clave Partial (en realidad, no hace falta que cada definición parcial de la clase esté en un fichero distinto).

Lo cierto es que Visual Studio es bastante flexible con la definición de clases parciales y podemos ver cualquiera de los siguientes escenarios:

  • Un solo fichero con una sola clase con la palabra Partial (es decir, no es parcial pero aunque se indique no hay ningún error).
  • Dos ficheros con dos clases, ambas con la palabra clave Partial.
  • Dos ficheros con dos clases y sólo una de ellas con la palabra clave Partial.

Por otro lado, los métodos parciales son métodos donde su firma o prototipo está declarado en un fichero y su implementación está en otro fichero.

De nuevo y en tiempo de compilación, para aquellos métodos parciales para los que no se ha escrito una implementación, el compilador eliminará tanto el prototipo o firma del método, como todas las llamadas al mismo en nuestro código.

Un método parcial cumple las siguientes características:

  • Sólo se pueden declarar en clases parciales
  • Sólo pueden ser procedimientos (Sub)
  • Su ámbito tiene que ser privado
  • Sólo se usa la palabra Partial en el prototipo.
  • Los nombres de la implementación deben coincidir con los parámetros del prototipo.

Los usos más habituales de las clases parciales son:

  • Mejorar la legibilidad de clases muy grandes al separar la clase en varios ficheros.
  • Si se está trabajando con un software de control de código fuente, al separar la clase en varios ficheros se permite que varios desarrolladores trabajen en la misma clase y así evitar conflictos con la protección y desprotección de un solo fichero.
  • Con código auto-generado, el desarrollador puede escribir código personalizado para extender una clase en un fichero aparte con la seguridad de que no será machacado posteriormente si se vuelve a autogenerar el código.
  • También es posible separar conceptualmente los métodos de una clase (como si fueran regiones) o decidir que cuando se implementa una interfaz, ésta se implementa en un fichero aparte.

Por supuesto, y antes de que el ávido lector lo pregunte, una clase parcial no puede estar escrita en distintos lenguajes ni puede estar definida en distintos ensamblados, por lo que no se puede “anexar” una clase parcial a una clase de un ensamblado de terceros (para extender este tipo de clases podríamos utilizar los métodos extensores, pero esto será en otro post, que ya está escrito!)

Un error que suele suceder cuando hablamos de métodos parciales, es que se confunde un método parcial con un evento, y aunque tiene un parecido razonable, hay diferencias entre ellos que nos enseñan que no son conceptos intercambiables.

  • Un método parcial tiene que ser implementado en la propia clase, mientras que la suscripción a un evento puede ser realiza en cualquier clase.
  • La suscripción a un evento sucede siempre en tiempo de ejecución (siendo además posible anular esa suscripción, por ejemplo con RemoveHandler), mientras que con un método parcial el enlace es siempre en tiempo de compilación.
  • Un evento permite múltiples suscripciones y múltiples suscriptores.
  • Un evento permite distintos ámbitos de visibilidad (público, privado, etc.) mientras que un método parcial es siempre privado.

Hay que entender un método parcial como una “oportunidad” que, normalmente un código auto-generado, nos ofrece para personalizar su comportamiento. Con esto quiero decir que fuera del contexto de código autogenerado, un método parcial no parece tener mucho sentido (sobre todo porque el ámbito del método tiene que ser privado, así que un cliente de nuestra clase no podría hacer nada con este método).

No me gustaría finalizar este post sin algunos ejemplos que ilustrarán el uso de lo aquí expuesto:

' Clase parcial

Partial Public Class Persona

    Property Nombre As String

 

    Public Sub Saludar(ByVal saludo As String)

        ' Llamada a un método parcial

        ' Si durante la compilación, VS no encuentra una implementación

        '  para el método PrevioSaludar elíminará ésta llamada a PrevioSaludar

        PrevioSaludar(saludo)

        ' Fijarse que la propiedad Apellidos está en otro fichero

        Console.WriteLine(String.Format("Saludos {0} {1}", Nombre, Apellidos))

    End Sub

 

    ' Prototipo del método parcial PrevioSaludar

    ' NO puede tener código, sólo la firma

    Partial Private Sub PrevioSaludar(ByVal saludo As String)

    End Sub

 

End Class

 

' Clase parcial

Partial Class Persona

    Property Apellidos As String

End Class

 

Si ejecutamos ahora el siguiente código (y puesto que no hemos implementado el método parcial), la salida será la siguiente::

    Sub Main()

        Dim yo As New Persona With {.Nombre = "Sergio", .Apellidos = "León"}       

        yo.Saludar("Hola Mundo!")

        Console.ReadLine()

    End Sub

 

 

clip_image001

 

Sin embargo, si ahora implementamos el método parcial, tendremos ésta otra salida:

' Clase parcial

Partial Class Persona

    Property Apellidos As String

 

    ' Implementación de un método parcial

    ' Fijarse que NO lleva la palabra Partial

    ' Además, el nombre de los parámetros es igual que el prototipo

    Private Sub PrevioSaludar(ByVal saludo As String)

        Console.WriteLine(String.Format("Saludo parcial {0}", saludo))

    End Sub

End Class

 

clip_image002

Donde podemos ver que se ha llamado a la implementación del método parcial.

Un saludo!

viernes, 21 de enero de 2011

Cheatsheet

Escribir un post sobre qué es un cheatsheet parece un poco inservible, pero además de explicarte qué es una cheatsheet, también pretendo que sea un directorio de enlaces imperecedero donde volver cada cierto tiempo para encontrar este tipo de documento.

Un cheatsheet se traduce como hoja de trucos, de hecho cuando uno está jugando a un videojuego y aparentemente está haciendo trampa se le dice que es un “chetero”, luego que es un tipo que hace trampas. En cualquier caso, yo creo que la traducción más acertada sería una “chuleta” de toda la vida. Es decir, un pequeño documento de referencia para tener impreso junto a tu ordenador y de un vistazo localizar la funcionalidad deseada de un tema en concreto.

Este interés por las cheatsheets surgió cuando comencé a utilizar jQuery. Así, que ahí va la primera cheatsheet referente a jQuery http://woork.blogspot.com/2009/09/jquery-visual-cheat-sheet.html

Además, ahora me he bajado una cheatsheet de HTML5 http://woork.blogspot.com/2009/09/html-5-visual-cheat-sheet-by-woork.html

Una cheatsheet de Linq
http://www.aspnetresources.com/blog/linq_sqo__cheat_sheet.aspx

Otra cheatsheet muy interesante es sobre ASP.NET AJAX, http://weblogs.asp.net/scottgu/archive/2007/01/10/download-asp-net-ajax-pdf-cheat-sheets.aspx

Cheatsheet de CSS

http://www.smashingmagazine.com/2010/05/13/css-2-1-and-css-3-help-cheat-sheets-pdf/

Prometo ira actualizando este post a medida que vaya encontrando otras “chuletas” de interés manifiesto.

Un saludo!.

martes, 18 de enero de 2011

Anidar una aplicación ASP.NET dentro de otra

El otro día se me planteó el problema de publicar en un servidor de internet una aplicación dentro de una aplicación existente ya publicada en la raíz del mismo servidor. Es decir, imaginar que tenemos la aplicación “AplicacionRaiz” que es tan patética como 2 páginas .aspx con un diseño innovador (a través de un tema de ASP.NET) que establece el color de fondo en amarillo y el color de primer plano en azul.  Todo parece ir bien (excepto algunos usuarios que comienzan a sufrir ataques epilépticos después de navegar por nuestra aplicación) pero después tengo la necesidad de publicar “AplicacionAnidada” dentro de “AplicacionRaiz”, aun cuando “AplicacionAnidada” no tiene nada que ver con “AplicacionRaiz”.

Vemos primero lo bonita que es “AplicacionRaiz” ya publicada en nuestro servidor de internet.

clip_image001

Después nos ponemos a programar “AplicacionAnidada” y como el diseño anterior no causo mucho furor entre los usuarios, pensamos que mejor lo dejamos sin estilo y que un blanco nuclear llene de emoción nuestra vida.

clip_image002

Ahora publicamos nuestra “AplicacionAnidada” en el servidor web de producción dentro de la “AplicacionRaiz”, la cual no podemos esquivar puesto que está publicada en la raíz del web, C:\inetpub\wwwroot\.

Ahora exploramos “AplicacionAnidada” en el servidor web y vemos que pasa algo extraño.

clip_image004

Upss!, en “AplicacionAnidada” no recuerdo haber establecido ningún tema, de hecho no lo hice porque soy un pésimo diseñador, así que todo apunta a que “AplicacionAnidada” está heredando la configuración de “AplicacionRaiz”. Vamos, lo confirmo porque en la pantalla de error aparece como archivo de origen el fichero web.config de “AplicacionRaiz”. Pues bien, dicho esto tenemos una solución fantástica para decirle a “AplicacionAnidada” que NO herede nada de “AplicacionRaiz”, de hecho queremos que “AplicacionAnidada” sea una aplicación completamente distinta de “AplicacionRaiz”… de hecho lo son!

La solución pasa por decir en la aplicación padre (en nuestro caso “AplicacionRaiz”) que no propague su configuración a aplicaciones hijas, en nuestro “AplicacionAnidada”). Esto se consigue a través de la propiedad inheritInChildApplications del elemento location del fichero web.config.

El truco está en crear una sección location con path = “.” y con inheritChildApplications = “false” y meter dentro de esta localización el resto del fichero web.config (bueno todo no, porque la sección configSections y runtime tienen que estar fuera).

Por ejemplo:

<location path="." inheritInChildApplications="false">
El resto de tu fichero web.config iría aquí…
</ location>

Ahora al navegar a “AplicacionAnidada” ya volveremos a ver nuestro bonito blanco nuclear ;-)

clip_image005

Un saludo!.