domingo, 31 de mayo de 2009

C# - JqModal – Contenido dinámico utilizando “User Controls”

 

Como bien habrán observado la documentación provista por JqModal, este no posee demasiadas alternativas a la hora de desplegar contenido dinámico dentro el popup. Observaran  en la documentación que puede cargarse contenido proveniente de páginas mediante la utilización del parámetro “ajax” de jqModal, pero en todos los ejemplos solo es contenido estático proveniente de paginas htm.

Una alternativa que he evaluado es hacer uso de páginas .aspx como contenido del popup, pero esto tuvo que ser descartado ya que no era soportado por de forma eficiente. Se realizaron pruebas pero si mucho éxito, si bien el contenido se desplegaba sin errores, este destruía los bordes y títulos del popup, además el contenido no era completamente renderizado, sino solo una parte del mismo.

Es por ellos que se opto la utilización de “User Control” para darle dinamismo al contenido, además de poder hacer uso de controles asp.net.

En el ejemplo provisto podrán notar como en la región de tag <div> que define al popup, se hace uso de un .ascx, como contenido.

Notaran además que se hizo uso del control updatePanel dentro del “User Control”, el objetivo del mismo era evitar que toda la página se recargue al presionar un botón de asp.net dentro del popup de jqModal. Este efecto se produce porque en realidad el user control es renderizado dentro de la página que lo contiene, por eso forma parte de la misma.

Se debe aclarar que en las pruebas además de hacer uso de un popup de jqModal, debía cargarse contenido dinámico en la propia página, y es en esta internación en donde se encontraron problemas.

Estructura del ejemplo:

 

Prueba 1 – Conflicto entre popup jqModal y llamda $.get

La primera implementación realizada hacia uso dialogo desplegable cuyo contenido es proporcionado por un User Control, pero a su vez la pagina principal renderiza una grilla que es cargada dinámicamente mediante la funcionalidad de jQuery $.get.

El ejemplo de esta situación lo podrán apreciar en la carpeta de proyecto web de nombre “jqModal1”.

Deberán iniciar con la pagina “Principa.aspx”, en esta observaran un link que desplegara el popup, y un botón que por medio de una llamada de $.get, desplegara una tabla. En el cuadro de dialogo cuentan con un botón que les permite cargar una grilla en su interior.

El popup funciona de maravilla siempre y cuando no se realice la carga de la tabla proveniente de la función $.get, una vez que esta es mostrada en pantalla, el popup se desplegara, pero los eventos del los controles de asp.net dejaran de funcionar, mostrando un error de javascript que dice:

Sys.WebForms.PageRequestManagerServerErrorException: The state information is invalid for this page and might be corrupted.

Este problema se debe a que la invocación por medio del uso de $.get, no solo trae el contenido de la grilla, sino que además obtiene el viewstate de la pagina a la que llama, esto hace que entre en conflicto con el viewstate que se encuentra actualmente en la pagina original, provocando problemas en las llamadas de los controles de asp.net. Básicamente lo que provoca es que los dos viewstate entren en conflicto y deje en estado invalido la información de la página, o sea exactamente lo que informa el mensaje del error.

Prueba 2 – Resolución del conflicto popup jqModel y llamada $.ajax

Para resolver el conflicto provocado por la llamada $.get y el viewstate, además de tener en cuenta que solo era necesario renderizar contenido html, el cual confecciona la grilla, fue por lo cual se utilizo la llamada $.ajax.

Esta implementación la observaran bajo la carpeta “jqModal2”, deberán poner como página de inicio a “Principal.aspx”, contenida en esta carpeta.

Observaran que la estructura es idéntica, salvo que en esta se hace uso del $.ajax, el cual realiza la invocación a web servicio, que se encuentra en el codebehind de Grid.aspx.

La clase del servicio ha sido declarada con el atributo [ScriptService],  mientras que el servicio se la agregaron los atributos [WebMethod, ScriptMethod]

[WebMethod, ScriptMethod]
public static string GetTable(int reloadcounter)
{
}

La invocación al servicio alojado en la página se realiza de la siguiente forma:

$.ajax({
    type: "POST",
    url: "Grid.aspx/GetTable",
    data: "{'reloadcounter':" + reloadnumber + "}",
    contentType: "application/json; charset=utf-8",
    dataType: "json",
    success: function(result) {
        loadGrid(result);
    },
    error: function(XMLHttpRequest, textStatus, errorThrown) {
        alert(textStatus + ": " + XMLHttpRequest.responseText);
    }
});
function loadGrid(result) {
    $("#grid-information").html(result.d);
}

Este servicio será el encargado de retornar la tabla pero debe remarcarse que en esta oportunidad el resultado devuelto es bastante más controlado, pues uno en la implantación del servicio decide que string retornar, o sea en este caso no se obtiene el viewstate que interfiere en los eventos.

Notaran que ya no se produce el error de la primera prueba, pudiendo hacer uso del botón de asp.net del popup sin inconvenientes.

[Ejemplo]
[Documento]

viernes, 28 de noviembre de 2008

NHibernate, ejemplo práctico

Para abrir el blog no elegí mejor tema que el grandioso framework de persistencia, de cual puedo estar hablando sino es NHibernate.

La idea principal de esta publicación será ni más ni menos que proporcionar un ejemplo integral de varias características que mucha falta hacen y que tanto cuesta aprender al comenzar con esta herramienta.

Este artículo no tiene intención de ser detallista ni explicar puntualmente cada aspecto implementado, para ello podrá encontrar artículos mas destacados como por ejemplo el excelente articulo publicado en el Blog de Dario Quintana.

La idea final es simplemente proporcionar un ejemplo que permita ver varias características funcionando juntas.

Aspectos implementados

Entre las características implementadas encontraran los siguientes puntos:

  • Modelo completamente tipado
  • Relaciones many-to-one, one-to-many y many-to-many
  • Persistencia de enumerados
  • Persistencia de campos nText (type="StringClob")
  • Herencia table per class hierarchy <subclass>, table per subclass <joined-subclass>
  • Herencia table per class hierarchy <subclass>, utilizando una formula en el <discriminator>.
  • Consultas hql entre relaciones many-to-many

[C#]
 

Diagramas del Dominio

Diagrama del dominio de la Institución

Diagrama del dominio de reserva de libros

Diagramas de persistencia en SQL Server 2005

Diagrama de Base de Datos de la Institución

Diagrama de Base de Datos de las Reservas de libros

Persistencia de Enumerados

Un punto mas que importante para brinda consistencia al modelo, es la utilización de enumerados en al definición del dominio de la aplicación.

En la clase ClaseEntity, se observara un atributo de nombre FormaPago definido como un enumerado, por supuesto en la definición de su mapper (Clase.hbm.xml) encontraran la definición de de la propiedad

<property type="NHib.Infrastructure.GenericEnumMapper`1[[NHib.Domain.Entities.enumFormaPago, NHib.Domain]], NHib.Infrastructure" name="FormaPago" column="FormaPago" />

Se observa claramente la utilización de un mapper de enumerado (el cual esta alojado dentro del NHib.Infrastructure), el cual permitirá recuperar el número asignado al ítem del enumerado asignado.

Persistencia de campos nText

Un tema interesante para probar es la persistencia de atributos espaciales, como es el caso de comentarios, los cuales deben contener texto que escapa a un simple descripción. Es en estos casos que se utilizara en nText, existiendo en NHibernate una forma especial de definir este tipo de mapeo.

En la clase LibroEntity, es justamente donde se hace uso de este, en el atributo Comentarios definido en el mapper como:

<property column="Comentario" type="StringClob" name="Comentario" />

Es en la clase de test TestLibro.cs -> LargoComentarios(), donde se realiza una prueba de su correcta persistencia, comprobando que no se trunca el valor del atributo

Herencia

En la aplicación encontraran tres tipos de herencia implementada, utilizando:

Tabla para todas las clases

Este primer tipo de implementación lo ubicaran en la entidad LibroEntity, en esta se remarca la definición de los atributos discriminator-value el cual toma un valor numérico cuyo tipo es definido en el tag <discriminator>

Se aclara que el tipo de dato por defecto definido es del tipo de datos string, esto a simple vista pareciera no tener mayor implicancia, pero al momento de utilizar un <discriminator> con valores numéricos, todas las clases deberán tener definido un discriminator-value, aunque la clase principal no sea utilizada.

En el ejemplo la definción del tag <class>, al estar utilizando la interface ILibroEntity, debe definir un discriminator-value, con un valor que no es utilizado por el modelo como diferenciador en la herencia de clases, esto debe hace con el simple objetivo de evitar un error de tipos de datos.

Tabla para cada subclase

La implementación se podrá observar en la clase ReservaEntity, a diferencia del ejemplo anterior que utiliza una sola clase, no existe el discriminator-value, y es por medio de la relación uno a uno de las tablas que se mapean como se obtiene el tipo de objeto.

En este caso se hará uso de un tag de mapeo: <joined-subclass>, la cual definirá cual es el atributo relación entre las tablas, siendo esta la razón de utilizar <key column="" />

Herencia utilizando formulas en el discriminator

Esta será utilizada en los casos en donde se pretenda generalizar entidades que pueden ser agrupadas desde otro nivel de abstracción que requiere el modelo analizado.

En el ejemplo sucedió con los convenios, (definido en la entidad ConvenioEntity) el cual describe la existencia de varios tipos de talleres y excursiones, que se pueden asociar a una institución.

Pero el modelo solo necesitaba diferenciar entre convenios del tipo taller y excursión, para esto necesita agrupar distintos valores en un discriminator.

Además para aumentar la complejidad el atributo esta definido con un carácter mientras que el mapeo hará uso de un valor numérico.

Es por medio del atributo formula que se transformara el valor del discriminator de una valor del tipo carácter a uno numérico.

Se debe aclarar que este tipo de definiciones presentan un inconveniente, solo podrán recuperarse los distintos tipos de subclases definidas, pero no podrán persistirse, ya que es imposible por medio de la definición de la formula saber que atributo de tipo especificar, esto deberá realizarse implícitamente en la definición de la clase a persistir.

Es por esta razón que se notara la definición de un enumerado dentro de las subclases que permita diferenciar las distintas clases concretas de Convenios. La redefinición del constructor permite forzar la especificación al momento de crear una nueva subtipo de Convenio.

El framework necesita la especificación de un constructor sin parámetros, pero para evitar su utilización es que se define como protected. Al igual que la definición del atributo Tipo (también protected) que permitirá ser utilizado solamente por el mapper, permitiendo especificar el tipo concreto a persistir.

Además se debe entender que por tratarse de un campo del tipo char, se realiza por medio del “case” la asignación del valor correcto, si hubiera sido numérico se podría haber persistido el enumerado directamente como el caso visto en a persistencia de enumerados.

Consultas hql entre many-to-many

Si bien la definición del mapeo de la navegación entre entidades resulta de suma utilidad, hay ocasiones en donde se requiere obtener algún tipo de filtro aplicado al otro extremo de la relación, es en este punto donde se hará uso de las consultas HQL, y en ellas el operador JOIN.

Para visualizar un ejemplo de su implementación se podrán visitar varios puntos donde fueron utilizados, destacando:

  • ProfesorRepository -> GetAlumnos(): Implementa un join en donde se recorren dos relaciones muchos a muchos. Esta podrá ser testeada desde TestProfesores -> RecuperoAlumnos()
  • AlumnoRepository -> GetByClase(): Define varias sobrecargas con diferente filtros que son aplicados a un join.

Eliminar en cascada

Como es de suponer esta facilidad de actualizar la cascada de elementos relacionados es de muchísima importancia, pero durante la utilización de la navegación entre entidades me encontré con un pequeño detalle que quería remarcar.

Se trata de la utilización de la propiedad inverse="true", esta es de suma importancia cuando se esta utilizando navegación bidireccional.

De no especificarse esta propiedad al momento de producirse, por ejemplo, la eliminación de un objeto padre, sus hijos serán actualizados y al no encontrase la entidad superior generará una excepción que indicara la imposibilidad de insertar null en el campo de la tabla.

Esto podría resolverse fácilmente marcando el campo de relación para que permita null en su contenido, pero no todos los tipos de relaciones en el modelo pueden hacer esto.

Un ejemplo del uso de esta propiedad podrá encontrase en el achivo de mapeo de la entidad Institución (Institucion.hbm.xml), y la prueba de la eliminacion en cascada se encuentra en el test: TestInstituciones -> EliminoInstitucion()

Nota: Si quieren probar como funciona esta propiedad elimínenla del archivo de mapeo Institucion.hbm.xml, de la relación con el Alumno por ejemplo, e intenten correr el test: TestInstituciones -> EliminoInstitucion(), para ver los resultados.

También podrán marcar el campos "Institución" de la tabla "Alumnos", observarán como se soluciona el problema.

Implementación

En la aplicación encontraran que esta formada por varias capas entre ellas la de Aplicación y Presentación, pero estas no están implementadas, pues no era necesario para el objetivo de aprender NHibernate, simplemente con la utilización del proyecto de Test se pudo comprobar el correcto funcionamiento del framewok de persistencia.

Se debe remarcar que el dominio debe ser tomado algo inventado, no perteneciente a un dominio existente, pensado simplemente para lograr el objetivo principal de verificar la funcionalidad de NHibernate.

Si bien el código es sumamente útil con tan solo visualizar el código de los test, así como también el de los mapper, es posible ejecutarlo si se conecta apropiadamente a una base de datos. Para ellos, como en la mayoría de las aplicaciones, simplemente se debe modificar el app.config del proyecto de pruebas, apuntándolo a la DB que corresponda.

Para crear la estructura de la base de datos se encontrar un proyecto con los script de creación de la estructura, o en caso contrario podrán hacer uso de los archivos .mdf y .ldf de la base de datos, adjuntándolos por medio de la opciones de attach del SQL Server 2005.

Para la ejecución de los test hice uso de UnitRun, de esta forma podía ejecutarlos uno a uno en modo debug e ir analizando como se comporta cada uno.

Conclusión

Espero antes que nada haber aportado un granito mas de arena a la investigación de este potentísimo framework, apuntando principalmente aspectos un poco mas avanzados de los cuales es complicado encontrar ejemplo cerrados que los integre.

Quedan varios puntos todavía por probar, como ser el mapeo a store procedure, paginado, etc

Espero les sea de utilidad y cualquier duda, modificación o error que encuentren serán bienvenidos.