miércoles, 29 de junio de 2011

Cómo NO complicarnos la vida con jQuery

Estoy liado con un rediseño de la capa de presentación de una aplicación web y está claro que voy a utilizar “intensamente” jQuery.

jQuery es maravilloso, eso no lo voy a descubrir yo, pero me pasa que cuando lo utilizo tiendo a acumular cantidades ingentes de código javascript para realizar todo tipo de tareas y al final, mi inocente código se convierte en todo un mini-programa dentro de mi aplicación… o peor, dentro de una sola página.

¡Basta! ¡Hay que poner puertas al campo! Esa es mi intención de hoy. Ver cómo puedo contener la cantidad de código y además escribir un código más eficiente y sobre todo, independiente en la medida lo posible de la estructura DOM de mi página (que ya estoy harto de que cualquier cambio en el diseño de la página, lleve al traste con la mitad de mi código jQuery).

Mi primera recomendación (o bien auto-imposición) va a ser lograr la independencia del diseño y de la programación. Es decir, que cambios en la estructura del DOM no supongan, o al menos no en exceso, cambios en mi código. Para ello, primero veremos cómo hacer “mal” algo y después cómo hacerlo bien.

Haciéndolo mal…

    <div id="padre">

        <div>

            Red</div>

        <div>

            Yellow</div>

        <div>

            Olive</div>

        <div>

            Blue</div>

    </div>

    <script type="text/javascript">

        $().ready(function () {

            $("#padre div").click(function (e) {

                $(this).parent().css("background-color", $(this).html());

            });

        });       

    </script>

 

Este super-programa lo que hace es que cuando se pulsa en un div hijo, establece el color del padre al color que dice el hijo. Lo cierto es que funciona, pero vamos a criticarlo y veremos como no ha resultado el mejor de mis programas ;-)

Para seleccionar los hijos utilizo la expresión $(“#padre div”). Pues bien, como alguien (léase yo mismo o el diseñador de turno) quiera introducir más div debajo de #padre voy a tener un problema porque va a seleccionar todos (los que inicialmente eran hijos y también los que metimos después). La solución podría pasar por utilizar la expresión $(“#padre > div”), que seleccionará solo los hijos (no así los descendientes que no sean hijos) de #padre. Pero aun así, ¿Qué pasa si los nuevos div son realmente hijos (están justo debajo de #padre) y no quiero seleccionarlos?. Podríamos estar un rato largo discutiendo sobre esto, pero para abreviar mi solución pasa por agregar clases CSS de programación. ¡Uff!, suena raro, pero realmente lo que quiero decir es que utilizaré clases CSS para la selección con jQuery, pero serán clases que no tendrán un reflejo en la apariencia del documento (dicho de otro modo, no fueron pensadas para dar diseño, sino solamente para seleccionar elementos a través de jQuery). Vamos a ver cómo queda:

    <div id="padre">

        <div>

            Yo sólo pasaba por aquí, ¡no me selecciones!</div>

        <div class="slct_Hijo">

            Red</div>

        <div class="slct_Hijo">

            Yellow</div>

        <div class="slct_Hijo">

            Olive</div>

        <div class="slct_Hijo">

            Blue</div>

        <div>

            Yo soy el último de la fila, ¡nadie se acuerda de mí!

        </div>

    </div>

    <script type="text/javascript">

        $().ready(function () {

            $("#padre .slct_Hijo").click(function (e) {

                $(this).parent().css("background-color", $(this).html());

            });

        });       

    </script>

 

* Lo de slct_Nombre es simplemente una convención, en mi caso slct es la abreviatura de select

Ahora, el diseñador puede hacer lo que quiera y no habrá que cambiar el código (incluso metió 2 div hijos que quedaron excluidos de la selección). Igualmente tenemos un mismo problema con $(this).parent() ¿Qué pasa si cambia el padre?. Por otro lado, lo de tomar el color desde el contenido del div hijo tampoco es muy científico (imagínate que el diseñador decide meter un span o cualquier otra cosa dentro del hijo, para darle más vidilla al asunto?. Sin más dilaciones, solución final o cómo hacerlo bien:

    <div class="slct_Padre">

        <div>

            <div>

                Yo sólo pasaba por aquí, ¡no me selecciones!</div>

            <div class="slct_Hijo">

                Mi color es el <span class="slct_Color">Red</span></div>

            <div class="slct_Hijo">

                Yo sin embargo soy <span class="slct_Color">Yellow</span></div>

            <div class="slct_Hijo">

                El más bonito soy yo, que soy <span class="slct_Color">Olive</span></div>

            <div class="slct_Hijo">

                El color de cielo es <span class="slct_Color">Blue</span></div>

            <div>

                Yo soy el último de la fila, ¡nadie se acuerda de mí!

            </div>

        </div>

    </div>

 

    <script type="text/javascript">

        $().ready(function () {

            $(".slct_Padre .slct_Hijo").click(function (e) {

                $(this).closest(".slct_Padre").css("background-color", $(this).find(".slct_Color").html());

            });

        });       

    </script>

 

Por último, también es importante ver que utilizo .slct_Padre .slct_Hijo en vez de div.slct_Padre div.slct_Hijo. Si me está leyendo algún purista, podrá decir que el segundo selector es más óptimo (le costara menos) para jQuery. A cambio, yo le diría que si en vez de div, el diseñador decide que hablamos de p, pues ya tengo el lío. La moraleja de todo esto es que yo opino que el selector de jQuery tiene que ser lo más generalista posible (excepto en operaciones críticas donde quizás arañar un par de ciclos de CPU al navegador sea la diferencia entre la vida y la muerte).

A partir de aquí, hay otras consideraciones a tener en cuenta pero eso lo dejo para un siguiente post que tengo ya horneándose en la cocina.

Un saludo!

lunes, 13 de junio de 2011

Timeout en TransactionScope

Trabajando con TransactionScope, ¿Recuerdas “ha venido para quedarse”? nos han surgido problemas relacionados con el timeout de las transacciones.

Lo cierto es que no está muy claro cuál es el timeout por defecto de una nueva transacción, cual es el máximo valor que puede alcanzar una transacción, etc.

Lo primero a tener en cuenta es que podemos incluir la siguiente sección a nuestro fichero web.config para controlar el timeout por defecto de nuevas transacciones.

<configuration>

<system.transactions>

<defaultSettings timeout="00:01:00"/>

</system.transactions>

</configuration>

 

El atributo timeout determina el valor predeterminado para el timeout de una nueva transacción.

A nivel de machine.config, podemos también especificar el máximo valor de timeout disponible, maxTimeout (esto está para que los administradores de sistemas nos hagan la vida imposible a los pobres desarrolladores… bueno, y también para controlar a desarrolladores locos…). Ninguna transacción podrá tener un timeout superior al valor establecido (o al valor por defecto). Puedes saber exactamente qué fichero machine.config te está afectando consultando System.Runtime.InteropServices.RuntimeEnvironment.SystemConfigurationFile)

<configuration>

<system.transactions>

<machineSettings maxTimeout="00:10:00"/>

</system.transactions>

</configuration>

 

En caso de no existir ninguna de estas secciones, los valores predeterminados son de 1 minuto para la propiedad timeout y 10 minutos para la propiedad maxTimeout.

La referencia sobre estas secciones las puedes encontrar aquí.

En tiempo de ejecución, puedes ver que valores exactos tienes consultado las propiedades TransactionManager.DefaultTimeout y TransactionManager.MaximumTimeout.

Otra consideración es aclarar como la transacción toma el valor del timeout en función de las opciones y constructor que elijamos al crear nuestro nuevo objeto TransactionScope.

El siguiente código tomará el valor de timeout por defecto para una nueva conexión (el valor de TransactionManager.DefaultTimeout):

Using tran As New Transactions.TransactionScope

tran.Complete()

End Using

 

Sin embargo, este otro código tomará el valor 0, que significa infinito (aunque recuerda que nunca podrá irse más allá del valor de maxTimeout establecido en el machine.config).

' options se inicializa con Timeout igual a 00:00:00, luego infinito

Dim options As New TransactionOptions

Using tran As New TransactionScope(TransactionScopeOption.Required, options)

tran.Complete()

End Using

 

Un saludo!

domingo, 12 de junio de 2011

NText en Sql Server Ce 4.0

Hoy me han preguntado en el blog como poder grabar un texto de más de 4000 caracteres en una base de datos Sql Server Ce 4.0.

Como me dice el autor del comentario, no tenemos disponible el tipo de datos nvarchar(max), así que la única solución que veo es declarar el campo ntext (que aunque después podría darnos guerra, por lo menos podremos grabar nuestra “super-cadena” de más de 4000 caracteres…)

Pues bien, tengo buenas y malas noticias.

Las malas son que utilizando System.Data.Common (factoría de proveedores) yo no encuentro como hacerlo, es decir, la equivalencia del tipo ntext de CE es System.Data.DbType.String si estamos trabajando con System.Data.Common. Y en este caso, el tamaño máximo de la cadena asignada es 4000. Si intentamos poner una cadena de tamaño superior, no falla pero trunca el valor al grabarlo en la base de datos. De hecho, aquí hay alguien que pregunta por esto mismo, pero como la solución (si es que la hay) está colgada en experts-exchange.com y es de pago, pues me voy a quedar con las ganas y voy a posponer este problema, rezando porque ningún cliente tenga la necesidad de grabar más de 4000 caracteres en las aplicaciones que nos piden en la empresa ;-)

Las buenas noticias son que si utilizamos el proveedor nativo de CE (en vez de la factoría de proveedores) y un SqlCeParameter, sí que podremos grabar más de 4000 caracteres en un campo de tipo ntext.

Por ejemplo, tengo en mi disco un fichero LIBRO.TXT (que literalmente tiene dentro un libro con “chorrocientos mil” caracteres) y no hay problema en guardarlo de este modo…

        Dim cnn As New System.Data.SqlServerCe.SqlCeConnection

        cnn.ConnectionString = "Data Source=|DataDirectory|\MIDB.sdf"

        cnn.Open()

        Dim cmd As New System.Data.SqlServerCe.SqlCeCommand

        cmd.CommandText = "UPDATE Ficheros SET Comentario = @Comentario"

        Dim prm As New System.Data.SqlServerCe.SqlCeParameter

        prm.ParameterName = "@Comentario"

        prm.SqlDbType = Data.SqlDbType.NText

        prm.Value = System.IO.File.ReadAllText("C:\LIBRO.txt")

        cmd.Parameters.Add(prm)

        cmd.Connection = cnn

        cmd.ExecuteNonQuery()

        cmd.Dispose()

        cnn.Dispose()

 

Espero haber contestado con esto a la pregunta del blog, pero en cualquier caso es otro tip a sumar a esa gran decisión de ¿Sql Server CE 4.0 en ASP.NET Sí o No?

Un saludo!.

Enumeraciones binarias

Las enumeraciones son un gran recurso que utilizo casi a diario en mis programas. De hecho, quizás las utilice en exceso, pero una vez me he acostumbrado a trabajar con ellas, lo cierto es que ya no puedo vivir sin ellas.

Un aspecto de las enumeraciones que no es muy utilizado pero que reporta grandes beneficios son las denominadas “enumeraciones binarias”. Por ejemplo, piensa que tenemos una enumeración “Persona” que tiene los siguientes valores: Alto, Bajo, Rubio, Moreno, Delgado, Gordo, Inteligente y Tonto. Todo el mundo sabe cómo asignar un valor sencillo de la enumeración a una variable, por ejemplo:

    Public Enum Persona As Integer

        Alto

        Bajo

        Rubio

        Moreno

        Delgado

        Gordo

        Inteligente

        Tonto

    End Enum

 

    Sub Main()

        Dim manolo As Persona = Persona.Alto

        If manolo = Persona.Alto Then

            Console.WriteLine("Manolo es alto")

        End If

    End Sub

 

Sin embargo ¿Cómo podemos guardar en la misma variable que Manolo, además de Alto también es Rubio y Delgado? Para resolver esto tenemos que utilizan enumeraciones binarias.

Definición

Una enumeración binaria tiene que cumplir las siguientes características:

·         El primer valor de la enumeración tiene que ser 1 (no 0, como es por defecto).

·         El resto de valores de la enumeración tienen que ser la potencia de 2.

·         Tenemos que incluir el adorno Flags.

De este modo, la anterior enumeración quedaría como sigue:

    <Flags()> _

    Public Enum Persona As Integer

        Alto = 1

        Bajo = 2

        Rubio = 4

        Moreno = 8

        Delgado = 32

        Gordo = 64

    End Enum

 

A partir de aquí tenemos que trabajar con operadores lógicos para asignar, consultar o retirar valores de una variable del tipo “Persona”. Los operadores disponibles son:

·         And, conjunción lógica.

·         Or, disyunción lógica.

·         Not, negación lógica.

·         Xor, exclusión lógica.

Agregar valores

Se agregan (suman) valores con el operador Or.

Dim manolo As Persona

manolo = Persona.Alto ' Manolo sólo es Alto (1)

manolo = Persona.Alto Or Persona.Rubio 'Manolo es Alto y Rubio. (5)

manolo = manolo Or Persona.Delgado 'Manolo es además de Alto y Rubio, también Delgado. (37)

 

Si nos fijamos, la primera asignación asigna el valor 1 (Alto) a la variable Manolo.

La segunda asignación asigna el valor 1 (Alto) y el valor 4 (Rubio) a la variable Manolo. De ese modo, Manolo vale 5 (la suma de Alto y Rubio).

Por último, la tercera asignación asigna el valor que ya tuviera Manolo (que podría ser cualquiera pero en nuestro caso es 4 – Alto y Rubio) más el valor 35 (Delgado). Finalmente, Manolo vale 37 (la suma de Alto y Rubio y Delgado).

En lo relativo a la suma de valores, da igual que sumemos varias veces un valor, no por ello Manolo será 2 veces alto!

Retirar valores

Se retiran (restan) valores con el operador Xor.

manolo = Persona.Alto Or Persona.Rubio

manolo = manolo Xor Persona.Rubio 'Manolo ya NO es Rubio (1)

 

Ahora, Manolo ya es no Rubio y su valor es 1, luego es Alto.

Con la resta de valores (Xor) hay que tener mucho cuidado, ya que si intenta restar un valor que actualmente no está asignado, en vez de restar lo que hará es sumar el valor. Por ejemplo:

manolo = Persona.Alto Or Persona.Rubio

manolo = manolo Xor Persona.Rubio 'Manolo ya NO es Rubio (1)

' Manolo no es Moreno, pero intentamos retirar (Xor) el valor Moreno, y en vez de retirarlo lo que hacemos es agregarlo

manolo = manolo Xor Persona.Moreno 'Ahora Manolo es Alto y Moreno (9)

 

Para resolver esto, antes de retirar (restar) un valor hay que asegurarse de que el valor existe realmente. Por ejemplo:

manolo = Persona.Alto Or Persona.Rubio

' Sólo si Manolo es Moreno, le retiramos (restamos) Manolo

If manolo And Persona.Rubio Then 'Comprobar si Manolo es Moreno

manolo = manolo Xor Persona.Moreno

End If

 

Consultar valores

Para consultar valores podemos llevar a cabo las siguientes instrucciones:

manolo = Persona.Bajo Or Persona.Moreno

If manolo = Persona.Bajo Then

Console.WriteLine("Manolo es SÓLO Bajo, nada más")

End If

If manolo And Persona.Bajo Then

Console.WriteLine("Manolo es Bajo")

End If

If manolo = (Persona.Bajo Or Persona.Moreno) Then

Console.WriteLine("Manolo es Bajo y Moreno")

End If

 

Un saludo!.

martes, 7 de junio de 2011

Más experiencias con Sql Server CE 4.0 y ASP.NET

Siguiendo en la línea de los últimos posts, hoy voy a hablar sobre… ¡sorpresa! ¡SQL Server CE 4.0! ;-)

Simplemente son unos tips que me han ocurrido hoy y quería compartirlos.

En SQL Server (la versión de los niños mayores) es perfectamente válida este sentencia:

UPDATE Tabla SET Nombre = Valor FROM Tabla

Sin embargo, en CE la misma consulta nos arroja el siguiente error:

Error al analizar la consulta. [ Token line number = 1,Token line offset = 60,Token in error = FROM ]

Esto quiere decir que a CE no le gusta el FROM, así que la consulta anterior quedaría:

UPDATE Tabla SET Nombre = Valor

Por otro lado y aunque no falla, la sentencia SELECT @@IDENTITY no devuelve ningún valor (al menos a mí no me lo devuelve), así que si quiero saber cual es el último valor de identidad insertado hay que lanzar una consulta como la siguiente:

SELECT autoinc_next - 1 FROM information_schema.columns WHERE table_name = 'MITABLA’ AND autoinc_seed > 0

Ya os iré contando mis venturas y desventuras con CE 4.0 en ASP.NET.

Un saludo!

jueves, 2 de junio de 2011

Ficheros bloqueados de SQL Server CE 4.0 en proyecto ASP.NET

Como sabréis si habéis leído anteriores post de mi blog, estamos inmersos en un proyecto de sitio web ASP.NET con SQL Server Compact Edition 4.0. Después de haber resuelto como desplegar el proyecto en un hosting compartido que no tiene instalado el runtime de SQL Server CE (utilizando también de forma opcional el espacio de nombres System.Data.Common y su factoría de proveedores), ahora nos encontramos con un nuevo problema para el que, con franqueza, no hemos encontrado una solución definitiva.

El problema está es que cuando ejecutamos una página que tiene acceso al fichero .sdf del proyecto (la base de datos de SQL CE), el proceso w3wp.exe (el worker process de ASP.NET) carga el runtime de SQL CE y cuando queremos sobreescribir el directorio \bin de nuevo en el servidor (porque por ejemplo estamos actualizando nuestra aplicación o estamos actualizando la propia versión de SQL CE – cuando llegue el momento y suba de versión, etc.), ciertos ficheros de ese runtime de SQL CE están bloqueados y no permiten la sobreescritura.

Si recordamos, tuvimos que copiar en el directorio \bin de nuestro sitio web todo el contenido de la carpeta C:\Program Files\Microsoft SQL Server Compact Edition\v4.0\Private (excepto el fichero System.Data.SqlServerCe.Entity.dll). Pues bien, cuando queremos volver a subir nuestra aplicación al hosting, obtenemos un “bonito” bloqueo (esto también ocurre cuando copiamos a través de ftp, pero para el ejemplo estoy copiando en un servidor de la oficina utilizando directamente una carpeta compartida).

Si vemos en el servidor que está ocurriendo con el programa Unlocker, podemos ver que el proceso w3wp.exe tiene “pillados” algunos ficheros del runtime de SQL CE.

Para desbloquearlos sólo hemos encontrado las siguientes formas (siempre sin utilizar Unlocker o parecidos que nos parecen un pelín agresivo y además resultan inviable en un entorno de hosting compartido donde no tenemos acceso físico a la máquina):

  • Reiniciar el servicio “Servicio de publicación World Wide Web”.
  • Reciclar el grupo de aplicaciones (AppPool) de nuestra aplicación.

De hecho, el kit de la cuestión es tirar abajo el proceso w3wp.exe, así que en líneas generales, mientras lo consigas cualquier método es válido.

Si estás pensando que modificando el fichero web.config también funcionará… pues va a ser que no. Esto es porque no hay que confundir el AppPool (grupo de aplicaciones), que es quién levanta el proceso w3wp.exe, con el AppDomain (dominio de la aplicación). Si modificas el fichero web.config lo que se reiniciará será el AppDomain (igual que si modificas el fichero global.asax, etc.). Puedes ver más información en el enlace siguiente http://stackoverflow.com/questions/302110/what-causes-an-application-pool-in-iis-to-recycle

Claro está que una solución sería no copiar el directorio \bin completo cada vez que publiquemos, pero con sinceridad quiero despreocuparme de ello y no tener que recordar cada vez que subo que tengo que “excluir” ciertas carpetas y ficheros de mi directorio \bin. De este modo, la única solución digna que hemos encontrado pasa por ser nosotros mismos quienes reiniciemos el grupo de aplicaciones de nuestra aplicación desde el mismo código de nuestra aplicación, y en ese preciso instante (y antes de que nadie pida una página con acceso nuestro fichero .sdf y vuelva a dejar pillado los ficheros), subir rápidamente… La verdad es que no muy científico pero ¡no vemos otra!

Para reiniciar el grupo de aplicaciones desde código, he encontrado los siguientes enlaces. Aún no he probado este método, pero estoy en ello y será “página obligada” en cualquier proyecto ASP.NET con SQL CE 4.0

http://www.codeproject.com/KB/aspnet/AppPoolRecycle.aspx

http://terrapinstation.wordpress.com/2008/06/12/restart-iis-application-pool-from-aspnet-page/

http://www.logue.com.ar/blog/2008/02/find-and-recycle-current-application-pool-programmatically-for-iis-6/

Por otro lado, también tengo que decir (y con profunda amargura) que me estoy encontrado otros problemas con SQL CE 4.0 que me están dejando un mal sabor de boca (ya sé que CE no es SQL Express, pero en mi opinión la gente de Microsoft podía haber hecho algo más por facilitar la vida a los desarrolladores…)

  • No soporta el uso de TransactionScope. Más info aquí. Básicamente CE no soporta transacciones anidadas, y eso y TransactionScope no se llevan bien…
  • No se puede cambiar el nombre de una tabla sino es a través del complemento SQL Server Compact ToolBox.
  • No se puede cambiar el nombre de un campo.
  • No se puede cambiar el orden de los campos.
  • No se puede modificar la identidad (identity) para un campo en una tabla ya creada.
  • No se puede conectar nuestra base de datos con ninguna versión de SSMS (Sql Server Management Studio).
    Cuidado con el complemento SQL Server Compact ToolBox porque sólo tiene un editor de SQL y se perderán los cambios si se genera alguna SQL automáticamente (clic derecho en tabla, por ejemplo). Además de que el editor no está muy logrado en lo relativo a deshacer, seleccionar, etc.

En los próximos días iré contando más experiencias con CE, pero por ahora estoy en ese momento de indecisión, que no sabría si me gusta o no me gusta…

Un saludo!