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