XAML Sorpresa

WP8SDK: Saca la cartera

Para subirse al tren del nuevo Windows Phone 8 y correr su SDK toca pasar por caja. No, el SDK es gratis como siempre, también el VS 2012 Express que incluye, pero algo tienes que poner tú, ¿no? 

  1. Necesitas un Win8 Pro (no vale Std) para que tire el emulador que usa HyperV. Pero el Pro vale sólo 30 € en España.
  2. De paso, si no tienes cuenta de la Store, ahora tienes 7 días para conseguirla por $8.
    Te cobran $99 pero te devuelven la diferencia en 30-45 días, como explican en el DevCenter.

Total, que Microsoft quiere tu dinero y por eso ha ajustado muuucho sus precios. Pero las cantidades son tan bajas que espero que a nadie le suponga un impedimento para lanzarse a por el SDK. El principal gasto, que es el Win8, tiene muchas más ventajas: aparte de las visuales y las mejora de rendimiento, te permite también desarrollar apps para la Windows 8 Store.

Lo que te puede costar más es el PC: tiene que soportar virtualización, mira este post de Rafa Serna sobre cómo verificar los requisitos con la tool coreinfo.

Así que mucha suerte.

Un placer.

[Entity Framework] ON DELETE CASCADE por defecto nunca más

Voy a ser muy conciso: Entity Framework aplica ON DELETE CASCADE por defecto a las relaciones requeridas. Esto es una mala decisión para un framework de uso general, porque a) muchos desarrolladores no lo saben, lo que puede conducir a acciones peligrosas, y b) es fácil olvidarse de desactivarlo. Sí, desactivarlo, porque esta mala decisión tiene una cara positiva: es muy fácil de quitar, sólo una línea para retirar una convención.

Por suerte, usar SQL Compact Edition como BD durante las primeras etapas de un desarrollo me suele avisar de este olvido. Otro día podemos hablar más de esta técnica. La cuestión hoy es ¿cómo me avisa? Muy sencillo: porque hay patrones de ON DELETE CASCADE que SqlCe no soporta, y da un error, aunque no muy descriptivo. Vamos a verlo con un sencillo ejemplo.

Montar una aplicación EF + SqlCe en 5 pasos

Vamos a crear un modelo con 3 entidades: Cliente, Ticket (que tiene un cliente) y Pago (que tiene un Cliente y un Ticket). Sí, ya sé que es redundante y no es 3NF, pero así tiene que ser. Además, todas esas relaciones serán requeridas.

1 Crear un nuevo proyecto de consola (también puede ser WPF o MVC, lo que más os guste).

2 Añadir el paquete de nuget EntityFramework.SqlServerCompact (¿Que no sabéis? Pues no sigáis leyendo. Yo sólo quiero lectores vagos, como buenos informáticos).

3 Crear el modelo. Como buenos vagos, podéis copiar y pegar esto (aunque he metido un compiler error… sólo por fastidiar a los vagos, que me caen mal):

public class DemoDb : DbContext {
    public DbSet<Cliente> Clientes { get; set; }
    public DbSet<Ticket> Tickets { get; set; }
    public DbSet<Pago> Pagos { get; set; }
}

public class Cliente {
    public int Id { get; set; }
    public string Nombre { get; set; }
}

public class Ticket {
    public int Id { get; set; }
    public string Numero { get; set; }

    public int ClienteId { get; set; }
    public Cliente Cliente { get; set; }

    public ICollection<Pago> Pagos { get; set; }
}

public class Pago {
    public int Id { get; set; }
    public DateTimo Fecha { get; set; }
    public decimal Importe { get; set; }

    public int ClienteId { get; set; }
    public Cliente Cliente { get; set; }
    
    public int TicketId { get; set; }
    public Ticket Ticket { get; set; }
}

(Por cierto, el artículo no es de WinRT, pero ¿a que los números de los pasos son muy Metro?)

4  Añadimos algo de código al Main para que conecte con (y cree) la base de datos:

static void Main(string[] args) {
    using(var db = new DemoDb()) {
        Console.WriteLine("Hay {0} clientes", db.Clientes.Count());
    }
}

5 El paso final: Ejecutar. EF creará una nueva base de datos TuNamespace.DemoDb.sdf… pero se producirá un error:

The referential relationship will result in a cyclical reference that is not allowed. [ Constraint name = FK_Pagoes_Tickets_TicketId ]

Dejando a un lado el Pagoes (aunque en realidad a mí no me importa que las tablas se llamen así. Total, eso sólo lo ven los DBA), ¿alguien entiende este error? “Cyclical reference” ¿dónde? Evidentemente no hay ningún ciclo, pero lo que le pasa a SqlCe es que no soporta todos los ON DELETE CASCADE que estamos definiendo. Como dijimos, cada una de las relaciones requeridas de antes es por defecto ON DELETE CASCADE, y SqlCe 4.0 no soporta los dos caminos de eliminación que se están generando: porque si eliminamos un cliente, los pagos se intentarán eliminar por 2 caminos:

Cliente –> Pago

Cliente –> Ticket –> Pago

No le doy mayor importancia a esta decisión en el diseño de SqlCe 4.0 (que sí podría tenerla), y reitero que esto sólo sucede en SqlCe, es decir, cualquier SQL Server desde el Express en adelante no tienen esta restricción ni por lo tanto daría este error: crearía la base de datos sin problemas. Sin problemas pero con los peligrosos ON DELETE CASCADE. ¿Qué pasa si un usuario avanzado (léase el programador) elimina un cliente? Que todos sus tickets y pagos se borran. Sin más aviso. Y sin vaselina.

Solución

La solución para ambos problemas (a saber, para los despistados, uno, poder generar la base de datos en SqlCe, y dos, protegernos de eliminaciones accidentales de datos en el resto de motores) es eliminar la convención de que cada relación requerida sea ON DELETE CASCADE. Esta convención es una de las muchas que vienen activadas por defecto en nuestro modelo de DbContext, y quitarla es tan sencillo como añadir esto (en nuestro caso en la clase DemoDb):

protected override void OnModelCreating(DbModelBuilder modelBuilder) {
    modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
}

Si ahora volvemos a ejecutar el proyecto, SqlCe creará la base de datos sin más error. Bueno, no, porque antes falló a mitad de su creación por lo que antes hay que eliminar ese archivo (por defecto, SqlCe crea su base de datos en \Bin\Debug con extensión .sdf).

De ahora en adelante, un intento de eliminar un cliente que tenga Tickets y/o Pagos dará un error. Y si queremos activar ON DELETE CASCADE para alguna relación en particular, después de sopesarlo y con todas sus consecuencias, podemos hacerlo con la API fluida de definición del modelo, método WillCascadeOnDelete.

Conclusión

Otro día podemos hablar de la conveniencia de usar SqlCe en las etapas más tempranas del desarrollo de una aplicación. A mí, entre otras cosas, me sirve como recordatorio de eliminar esta convención. También podéis poneros una alarma en el móvil, pero recordad que “OneToManyCascadeDeleteConvention is evil”.

[WPF] Desnudando el TabControl: ajustar las pestañas en una fila

Después de bastante tiempo sin ponerme ante esta página, hoy he encontrado algo de tiempo y una excusa adecuada en las opciones de personalización que nos ofrece el control TabControl de WPF.

El hecho es que si añadimos más pestañas de las que caben en el control, se crea una segunda línea de pestañas para alojarlas:

image

Este comportamiento que en general es suficiente, en ocasiones puede no ser lo que deseamos para nuestra aplicación. En mi caso, necesitaba que al reducir el ancho de la ventana no se cree una segunda fila, sino que se estrechen las pestañas para ajustarse al ancho disponible.

Desnudando el TabControl

El modelo de extensibilidad de WPF permite atacar este problema con sencillez. Para esto, debemos conocer la composición del TabControl: internamente, utiliza un control TabPanel para mostrar las pestañas, y un ContentPresenter para el contenido de la pestaña seleccionada. El mencionado control TabPanel proporciona un diseño muy específico: es similar a un WrapPanel, ya que cuando los controles no caben saltan a la siguiente línea, pero con la particularidad de ajustar el contenido para abarcar toda la fila. Podemos ver el TabControl por dentro sustituyendo su Template por esta sencilla plantilla:

        <TabControl Margin="10" Height="80">
            <TabControl.Template>
                <ControlTemplate TargetType="TabControl">
                    <DockPanel>
                        <TabPanel DockPanel.Dock="Top"
                                  IsItemsHost="True"/>
                        <Border Background="{TemplateBinding Background}" 
                                BorderThickness="{TemplateBinding BorderThickness}" 
                                BorderBrush="{TemplateBinding BorderBrush}">
                            <ContentPresenter Name="PART_SelectedContentHost" 
                                              Margin="{TemplateBinding Padding}" 
                                              ContentSource="SelectedContent" />
                        </Border>
                    </DockPanel>
                </ControlTemplate>
            </TabControl.Template>
            <TabItem Header="Tab 1">

Si lo probamos, ahora el TabControl se dibujará de forma ligeramente distinta:

image

Aunque a grandes rasgos es el mismo diseño, por la simplicidad de la plantilla utilizada se ha descuadrado ligeramente el diseño. No es esto lo que me interesa, sino identificar que el responsable de dibujar las pestañas es el control TabPanel, lo que nos va a permitir sustituirlo por otro y conseguir otros comportamientos.

Usando un WrapPanel

Simplemente con sustituir la etiqueta TabPanel por WrapPanel (el resto de propiedades se mantiene igual), el diseño del control cambia automáticamente:

image

Evidentemente el diseño no ha mejorado, pero vemos lo sencillo que es realizar cambios una vez que nos hemos hecho con la plantilla del control.

Usando un StackPanel

Vamos a probar a usar un StackPanel horizontal. Cambiamos el WrapPanel por:

<StackPanel DockPanel.Dock="Top" Orientation="Horizontal"
            IsItemsHost="True"/>

Y obtenemos:

image

La última pestaña, Tab 7, no cabe y no hay forma de hacerla visible si no es ampliando el tamaño de la ventana. Pero veamos cómo podemos añadir la posibilidad de desplazarnos a esas pestañas ocultas.

Usando un StackPanel y un ScrollViewer

Vamos a incluir el StackPanel dentro de un ScrollViewer:

<ScrollViewer DockPanel.Dock="Top" 
                HorizontalScrollBarVisibility="Auto" 
                VerticalScrollBarVisibility="Disabled">
    <StackPanel Orientation="Horizontal"
                IsItemsHost="True"/>
</ScrollViewer>

Aunque el diseño no es muy atractivo, ya tenemos la posibilidad de desplazarnos a los elementos no visibles:

image

Este diseño puede potenciarse:

  • Para pantallas táctiles, ocultando la barra horizontal (Hidden) y activando el PanningMode="HorizontalOnly" que permitirá desplazarnos arrastrando las pestañas con el dedo.
  • Incluyendo botones de desplazamiento incrustados junto a las pestañas, lo que puede conseguirse cambiando el Template del ScrollViewer. Más información en este buen post de Olaf Rabbachin.

Pero no era esto lo que yo pretendía: mi objetivo era ajustar al ancho disponible. Lo primero que pienso es un Grid.

Por qué no uso un Grid

Evidentemente, para conseguir ese ajuste podemos crear un Grid con 7 columnas de tamaño proporcional (Star – *) y situar cada pestaña en una de ellas. El problema es que para esto necesitamos definir la propiedad adjunta Grid.Column en cada cabecera de pestaña, lo que no es sencillo. Bueno, el concepto de sencillez es diferente para cada uno, pero los buenos informáticos somos vagos (eso me digo para consolarme ;) e intentamos conseguir más con menos.

Si recopilamos, hasta ahora no hemos tenido que establecer ninguna propiedad en los ítems para conseguir los diseños, por lo que vamos a intentar otra aproximación con un control de uso poco frecuente.

Usando un UniformGrid

El control UniformGrid es un Grid simplificado donde definimos sólo el número de filas y columnas, y se creará una cuadrícula con filas y columnas del mismo tamaño. Además, los elementos no hay que asignarlos a cada fila o columna, sino que se asignan por orden: el primer control a la primera celda, el siguiente a la segunda, así hasta completar la fila, luego la segunda fila y así hasta que no queden más ítems. Por lo cual podemos usar como contenedor sólo esto:

<UniformGrid DockPanel.Dock="Top" Columns="7" Rows="1" IsItemsHost="True"/>

Con el UniformGrid tenemos el ajuste que buscábamos:

image

image

El ajuste y los bordes puede mejorarse si se sigue más fielmente la Template original del TabControl. Yo he procurado simplificar las plantillas para que sea más fácil seguir los ejemplos, sacrificando este nivel de detalle.

Mi versión final

Aunque la solución que hemos introducido con UniformGrid tiene un defecto: necesita conocer el número de pestañas para asignar Columns=”7”. Pero vamos a solucionar esto usando un sencillo truco: si definimos Rows=”1” pero no definimos el número de columnas, se crearán tantas columnas como pestañas existan. Así que sólo tenemos que eliminar la propiedad Columns, y finalmente nos queda:

        <TabControl Margin="10" Height="80">
            <TabControl.Template>
                <ControlTemplate TargetType="TabControl">
                    <DockPanel SnapsToDevicePixels="true" ClipToBounds="true">
                        <UniformGrid DockPanel.Dock="Top" Rows="1" IsItemsHost="True"/>
                        <Border Background="{TemplateBinding Background}" 
                                BorderThickness="{TemplateBinding BorderThickness}" 
                                BorderBrush="{TemplateBinding BorderBrush}">
                            <ContentPresenter Name="PART_SelectedContentHost" 
                                              SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" 
                                              Margin="{TemplateBinding Padding}" 
                                              ContentSource="SelectedContent" />
                        </Border>
                    </DockPanel>
                </ControlTemplate>
            </TabControl.Template>
            <TabItem Header="Tab 1">
                <TextBlock Text="With UniformGrid"/>
            </TabItem>
            <TabItem Header="Tab 2"/>
            <TabItem Header="Tab 3"/>
            <TabItem Header="Tab 4"/>
            <TabItem Header="Tab 5"/>
            <TabItem Header="Tab 6"/>
            <TabItem Header="Tab 7"/>
        </TabControl>

El control UniformGrid está sólo disponible en WPF, pero es muy sencillo de implementar en Silverlight y otras plataformas XAML, como muestra Jeff Wilcox en este post.

Un placer.

De cómo en AspNetMVC prevalece QueryString sobre el propio Modelo

Imaginemos una página MVC muy sencilla: un buscador con dos datos, el texto a buscar y una casilla para indicar si se quiere coincidencia estricta de mayúsculas o no. La búsqueda se ejecuta mediante un botón y los resultados se muestran en la misma página tras un roundtrip completo al servidor, sin AJAX. Para construir esta página, vamos a elaborar sus tres piezas M.V.C.

Modelo

Una clase ViewModel con los parámetros de la búsqueda y con una lista de resultados.

public class BuscarViewModel
{
    public string Texto { get; set; }
    public bool EnMayusculas { get; set; }
    public IEnumerable<dynamic> Resultados { get; set; }
}

Controlador

Una acción Buscar de tipo GET que recibe los parámetros de la búsqueda. Por simplicidad, uso el mismo ViewModel como parámetro de entrada, para aprovechar el trabajo del ModelBinder de MVC. La acción rellena el valor de Resultados, en teoría considerando los parámetros de búsqueda, aunque en este caso he usado valores de ejemplo.

Imaginemos también una peculiar regla de negocio que nos exige que la casilla “Coincidir mayúsculas” se devuelva siempre desmarcada, incluso si ha sido marcada en la búsqueda anterior. Para ello, en el modelo recibido la establecemos a falso.

public ActionResult Buscar(BuscarViewModel buscarViewModel)
{
    buscarViewModel.EnMayusculas = false;                         //Siempre se pone a falso
    var ejemplo = new {Texto = "Ejemplo" };
    buscarViewModel.Resultados = Enumerable.Repeat(ejemplo, 50);  //Ficticio
    return View(buscarViewModel);
}

Esta acción responde a las siguientes URI (suponiendo un controlador EntidadController):

  • /Entidad/Buscar
  • /Entidad/Buscar?Texto=abc&EnMayusculas=true

Vista

Una vista muy sencilla podría ser:

@model MvcCheckBoxForFail.Models.BuscarViewModel
@{
    ViewBag.Title = "Buscar";
    var grid = new WebGrid(Model.Resultados);
}

<h2>Búsqueda</h2>
@using (Html.BeginForm("Buscar", "Entidad", FormMethod.Get)) {
    <fieldset>
        <legend>Buscar @Model.Texto en mayúsculas @Model.EnMayusculas</legend>

        <div>
            Texto
            @Html.EditorFor(model => model.Texto)
            Mayúsculas
            @Html.EditorFor(model => model.EnMayusculas)
            <input type="submit" value="Buscar" />
        </div>

        @grid.GetHtml()

    </fieldset>
}

Sus elementos principales son:

  • Un WebGrid nativo de MVC para mostrar los resultados con una sencilla paginación en servidor.
  • Un Form con método GET.
  • Los campos para el texto y para forzar la coincidencia de mayúsculas, generados ambos con EditorFor.

Problemática

Si probamos esta sencilla página, nos encontraremos con un curioso comportamiento: no sigue la regla de que la casilla de forzar mayúsculas debe salir siempre desactivada. Por el contrario, mantiene el valor usado para la búsqueda. Pero nosotros hemos asignado false claramente en el controlador, por lo que ¿quién es el responsable de que se muestre la casilla activada? A este nivel, el culpable es el método EditorFor, que no usa el valor que trae el modelo (sí, de ahí el resaltado amarillo, soy muy malo para mantener el suspense). Más adelante depuraremos mejor las responsabilidades.

Antes de eso, probamos con otras opciones. Por ejemplo, en lugar de EditorFor, usaremos CheckBoxFor:

@Html.CheckBoxFor(model => model.EnMayusculas)
El comportamiento, como era de esperar, es idéntico. Así que vamos un paso más allá y usamos el método CheckBox:
@Html.CheckBox("EnMayusculas", Model.EnMayusculas)

Para nuestra sorpresa, el comportamiento sigue siendo el mismo, aún cuando Model.EnMayusculas es siempre falso. Pero es más, si escribimos:

@Html.CheckBox("EnMayusculas", false)

Incluso indicando el valor de false explícitamente, el input se renderiza marcado (checked) en ciertas ocasiones. ¿Cómo es esto posible?

Primera explicación

En primer lugar, ya hemos identificado en qué casos se activa la casilla: cuando estaba marcada al hacer la búsqueda, es decir, cuando se recibe en la QueryString un EnMayusculas=true.

NOTA: En la QueryString se recibirá siempre un EnMayusculas=false, independientemente de que se marque o no la casilla. Esto es debido al hidden que genera CheckBoxFor. Para más información sobre este comportamiento, ver esta respuesta de Jeremy.

¿Qué se deduce de aquí? Pues que al usar los métodos del helper Html para renderizar un control (no sucede sólo con el CheckBox, probadlo con otros), si el nombre indicado existe en la QueryString recibida, se usará ese valor independientemente de su valor en el modelo o del valor estricto que le pasemos al helper. Hay más información sobre este comportamiento en esta incidencia respondida por RanjiniM de forma contundente: “Este es el comportamiento esperado en ASP.NET MVC”.

Soluciones

Ante esto, pueden buscarse distintas soluciones. Yo voy a plantear las dos más extremas.

La primera es dejar de usar el helper CheckBox e insertar un input en HTML directamente. Incluso podemos elaborar nuestro propio helper que evite este comportamiento.

Pero antes veamos la segunda solución, que es la que propone RanjiniM en la respuesta anterior: excluir la propiedad en cuestión del uso del ModelBinder. La firma de la acción incluirá un nuevo atributo, quedando así en nuestro controlador:

public ActionResult Buscar([Bind(Exclude="EnMayusculas")] BuscarViewModel buscarViewModel)

El atributo se aplica al parámetro, no al método completo, y define la propiedad (o propiedades, separadas por comas) para las que queremos evitar el comportamiento descrito. Una vez establecido este atributo, el código original (usando CheckBoxFor) funciona correctamente, y la casilla sale desmarcada en todos los casos.

ModelBinder y ValueProvider

Con la ayuda de Luis Ruiz Pavón y de Eduard Tomàs he revisado cómo puede afectar el orden de definición de los ValueProvider a esta incidencia, pero mis pobres conocimientos no me han permitido llegar a una conclusión.

Yo entiendo que tanto el ModelBinder como los ValueProvider se utilizan a la hora de generar el modelo, es decir, de construir la instancia que se pasa como argumento a la acción Buscar. Pero una vez generada esa instancia de BuscarViewModel, no consigo entender por qué vuelve a prevalecer el valor de QueryString sobre lo que pone en el modelo. Si los valores de QueryString ya se han trasladado al modelo, lo lógico después es trabajar con el modelo, que puede haber sufrido cambios como en nuestro supuesto.

La intención de toda esta exposición es doble: en primer lugar, servir de ayuda a quien pueda encontrarse esta misma incidencia; y por otro lado, tratar de comprender mejor la justificación de este comportamiento, que según afirma RanjiniM no es un bug sino el comportamiento esperado (a no ser que estemos disfrazando un bug de feature como tantas veces ;) Por eso agradezco vuestros comentarios y opiniones al respecto.

Un placer.

Aclarando CQRS [traducción]

Artículo original de Udi Dahan, 2009. Traducido por Andrea Magnorsky, Carlos Peix y Pablo Núñez.

Después de ver cómo la comunidad ha interpretado CQRS (Separación de Responsabilidades con Comandos y Consultas) creo que ha llegado la hora de hacer algunas aclaraciones. Algunos lo han relacionado con Event Sourcing. La mayoría han superpuesto sus ideas previas de  arquitecturas de capas sobre el nuevo patrón. Aquí espero identificar CQRS en sí mismo, y describir en qué puntos puede conectarse con otros patrones.

¿Por qué CQRS?

Antes de describir los detalles de CQRS necesitamos entender sus dos principales potencias conductoras: la colaboración y la obsolencia.

Colaboración se refiere a circunstancias bajo las cuales múltiples actores usarán o modificarán los mismos datos (tengan la intención de colaborar entre ellos o no). A menudo hay reglas que indican qué usuario puede realizar qué tipo de modificación y modificaciones que podrían ser aceptables en un caso pueden no serlo en otros. Expondremos algunos ejemplos en breve. Los actores pueden ser humanos, como los usuarios normales, o automáticos, como otro software.

Obsolencia se refiere al hecho de que en un entorno colaborativo, una vez que los datos han sido mostrados al usuario esos mismos datos pueden haber sido modificados por otro actor, en otras palabras la información puede potencialmente ser obsoleta. Cualquier sistema que haga uso de una caché está sirviendo datos obsoletos, a menudo por una cuestión de eficiencia. Lo que esto significa es que no podemos confiar completamente en las decisiones de nuestros usuarios, ya que podrían estar tomadas en base a información desactualizada . O sea, el hecho que la información es obsoleta no es completamente obvia.

Las arquitecturas de capas estándar no tratan explícitamente con ninguno de estos problemas (colaboración y obsolencia ). Mientras que compartirlo todo en una misma base de datos podría ser un paso en la dirección correcta para tratar la Colaboración,  la obsolescencia es normalmente más acusado en esas arquitecturas por el uso de cachés cuya intención es mejorar el rendimiento.

Una imagen como referencia

He dado algunas charlas sobre CQRS usando este diagrama para explicarlo:

Las cajas llamadas AC son Componentes Autónomos. Describiremos qué los hace autónomos cuando discutamos los comandos. Pero antes de entrar en lo complicado, vamos a empezar por las consultas (Query en el gráfico).

Consultas

Si los datos que vamos a mostrar a los usuarios estarán obsoletos de todas maneras, ¿es realmente necesario ir a la base de datos maestra para obtenerlos de allí? ¿Por qué transformar esas estructuras en 3ª forma normal a objetos del dominio si nosotros sólo queremos datos, y no comportamientos para cumplir las reglas? ¿Por qué transformar esos objetos del dominio en DTOs (Objetos de Transferencia de Datos) para enviarlos a través de una red, y quién dice que: esa red va a estar exactamente ahí? ¿Por qué transformar esos DTOs en modelos para la vista (view models).

Resumiendo, parece que estuviéramos haciendo demasiado trabajo innecesario basado en el supuesto de que reutilizar código que ya ha sido escrito será más fácil que simplemente resolver el problema que tenemos entre manos. Así que vamos a probar otra alternativa.

¿Qué tal si creamos un almacén de datos adicional cuyos datos puedan estar un poco desactualizados respecto a la base de datos maestra? Es decir, los datos que mostramos al usuario van a ser obsoletos de una forma u otra, así que por qué no reflejar esta obsolescencia en el propio almacén de datos. Más adelante veremos una aproximación para mantener este almacén de datos más o menos sincronizado.

Ahora bien, ¿cuál debería ser la estructura correcta para este almacén de datos? ¿Qué tal el propio modelo de la vista? Una tabla para cada vista. Entonces nuestro cliente podría hacer simplemente SELECT * FROM MiTablaDeVista (o posiblemente pasarle un ID en una cláusula Where), y enlazar el resultado con la pantalla. Podría ser así de simple. Podrías envolverlo con una fina fachada si sientes la necesidad, o con procedimientos almacenados(Stored Procedures) , o usar AutoMapper para que mapee desde un DataReader a tu clase modelo de la vista. El hecho es que la estructura modelo de la vista ya es buena para transmitir, así que no necesitas transformarla en nada más.

Podrías incluso considerar mover ese almacén de datos a tu capa web. Así tendría tanta seguridad como una caché de memoria en la capa web. Con darle permisos al servidor web para que sólo haga SELECT en esas tablas será suficiente.

Almacén de datos de las consultas

Pero usar una base de datos convencional como almacén de datos al que consultar no es la única opción. Considera que el esquema de las consultas es básicamente idéntico al modelo de la vista. No hay relaciones entre las diferentes clases modelo de las vistas, así que no se debería necesitar ninguna relación entre las tablas en ese almacén de datos.

En ese caso ¿necesitas realmente una base de datos relacional?

La respuesta es no, pero por razones prácticas y debido a la inercia de las organizaciones, probablemente sea tu mejor opción (por ahora).

Escalando las consultas

Ahora que las consultas son ejecutadas desde un almacén de datos distinto de la base de datos maestra, y no podemos asumir que los datos servidos estén actualizados al 100%, puedes fácilmente añadir más instancias a esos almacenes sin preocuparte de que no contengan exactamente los mismos datos. El mismo mecanismo que actualiza una instancia puede actualizar muchas instancias, como veremos más adelante.

Esto te ofrece una escalabilidad horizontal barata para tus consultas. Y al no estar haciendo casi ninguna transformación, la latencia por consulta disminuye también. El código sencillo es código rápido.

Modificaciones de datos

Como nuestros usuarios están tomando decisiones basadas en datos obsoletos, necesitamos ser más exigentes respecto a qué dejamos pasar. Pongamos un escenario para explicar por qué:

Supongamos que tenemos un servicio de atención telefónica donde un operador está al teléfono con un cliente. Este usuario está viendo los detalles del cliente en su pantalla y quiere hacerlo un cliente ‘preferido’, además de modificar su dirección, cambiar su título Srta. por Sra., cambiar su apellido, e indicar que ahora está casada. Lo que el usuario no sabe es que tras abrir la pantalla, un evento procedente del departamento de facturación indica que este mismo cliente no paga sus facturas: es moroso. En este punto, nuestro usuario confirma sus cambios.

¿Deberíamos aceptar esos cambios?

Bien, nosotros deberíamos aceptar algunos de ellos, pero no el cambio a ‘preferido’ ya que el cliente es moroso. Pero escribir este tipo de comprobaciones no es fácil: necesitamos comprobar las diferencias entre los datos, inferir qué significan los cambios, cuales están relacionados entre sí (cambio de apellido y título) y cuales no, identificar contra qué datos comprobar (no sólo comparando con los datos que el usuario leyó, sino con el estado actual de la base de datos) y entonces decidir si rechazar o aceptar.

Desafortunadamente para nuestros usuarios, tendemos a rechazarlo todo si alguna parte está mal. Así que nuestros usuarios tendrán que actualizar sus pantallas para obtener los datos actualizados, y volver a escribir todos los cambios anteriores, esperando que esta vez no les gritemos por culpa de un conflicto de concurrencia optimista(Optimistic concurrency).

Conforme nuestras entidades se hacen más grandes, con más campos, también hay más actores trabajando con esas mismas entidades, y mayor es la probabilidad de que alguien toque algún atributo de ellas en un momento dado, incrementando el número de conflictos de concurrencia.

Si solo hubiera una forma de que nuestros usuarios nos proporcionen sus modificaciones de datos con un correcto nivel de granularidad y clara intención. En eso precisamente consisten los comandos.

Comandos

Un elemento clave de CQRS es repensar el diseño de la interfaz de usuario para permitirnos capturar la intención de nuestros usuarios, por ejemplo marcar un cliente como preferido es para el usuario una unidad de trabajo distinta de indicar que el cliente se ha mudado o que se ha casado. Usando un interfaz de usuario tipo Excel para modificar los datos no captura la intención, como vimos antes.

Podríamos incluso considerar que el usuario pudiera enviar un nuevo comando aun antes de haber recibido confirmación del anterior. Podríamos tener un pequeño asistente en el lateral mostrando al usuario sus comandos pendientes, retirándolos asíncronamente al recibir confirmación del servidor, o marcándolos con una X si fallan. El usuario podría entonces hacer doble clic sobre la tarea fallida para encontrar información sobre qué ha sucedido.

Noten que el cliente envía comandos al servidor, no los publica. La publicación está reservada para eventos que establecen un hecho: indicar que algo ha sucedido; y que el publicador no se preocupa sobre qué harán los receptores de ese evento al respecto.

Comandos y Validación

Pensando en qué podría hacer que un comando fallara, un aspecto que surge es la validación. La validación es diferente de las reglas de negocio ya que establece una afirmación independiente del contexto acerca del comando. Un comando es válido o no lo es. Las reglas de negocio, por otro lado, son dependientes del contexto.

En el ejemplo que vimos antes, los datos que nuestro operador guardaba eran válidos, sólo el evento recibido previamente desde facturación provocaba que el comando fuera rechazado. Si ese evento de facturación no hubiese llegado, los datos se habrían aceptado.

Incluso siendo un comando válido, todavía puede haber razones para rechazarlo.

De hecho, la validación puede ser realizada en el cliente, comprobando que se han rellenado todos los campos requeridos para ese comando, que los rangos de números y fecha se cumplen y ese tipo de cosas. El servidor todavía debería validar todos los comandos recibidos y no confiar en que los clientes hagan la validación.

Replanteo de UI (Interfaz de Usuario) y comandos a la luz de la validación

El cliente puede usar el almacén de datos para consultas al validar comandos. Por ejemplo, antes de enviar el comando de que el cliente se ha mudado, podría comprobar que el nombre de la calle existe en el almacén para consultas.

En este punto, podríamos replantear el interfaz de usuario (UI) y poner una caja de texto con autocompletado para el nombre de la calle, asegurando así que la calle que pasaremos en el comando será válida. Pero podríamos ir aún más allá. ¿Por qué no pasar el ID de la calle en lugar de su nombre? Hagamos que el comando represente la calle no como una cadena sino como un ID (int, guid, lo que se quiera).

En el servidor, la única razón de que tal comando falle sería debido a un problema de concurrencia: que alguien haya eliminado esa calle y que todavía no se haya aplicado esa eliminación en el almacén para consultas, un conjunto bastante excepcional de circunstancias.

Razones por las que comandos válidos fallan y qué hacer al respecto

Así tenemos un cliente que se porta bien, que está enviando comandos válidos, y un servidor que todavía puede decidir si rechazarlos. A menudo las circunstancias de rechazo tienen que ver con los cambios de estado realizados por otros actores, que resultan relevantes en el procesamento de ese comando.

En el ejemplo anterior de CRM, se debía sólo a que el evento de facturación llegara primero. Pero “primero” podría ser un milisegundo antes que nuestro comando. ¿Qué pasaría si nuestro usuario presionara el botón un milisegundo antes? ¿Debería eso realmente cambiar los resultados desde el punto de vista del negocio? ¿No esperaríamos que nuestro sistema se comportase de la misma forma cuando lo observamos desde afuera?

Así que, si el evento de facturación llegara el segundo, ¿no se debería volver a poner el cliente preferido como normal? Y más aún: ¿no se debería notificar al cliente, por ejemplo enviándole un email? En ese caso, ¿por qué no establecer ese comportamiento incluso cuando el evento de facturación llegue primero? Y ya que tenemos configurado un modelo de notificaciones, ¿necesitamos realmente devolver un error al operador? Es decir, dado que él no puede hacer otra cosa más que notificarlo al cliente.

Por lo cual si no estamos devolviendo errores al cliente (quien ya nos está enviando comandos válidos), quizá todo lo que necesitamos hacer en el cliente cuando enviamos un comando es decir al usuario “gracias, recibirá confirmación vía email en breve”. Ni siquiera necesitamos un asistente en el UI para mostrarnos los comandos pendientes.

Comandos y Autonomía

Podemos ver que en este modelo los comandos no necesitan ser procesados inmediatamente, pueden ser encolados. Cuando se ejecuten realmente es cuestión de un acuerdo de servicio (Service-Level Agreement, SLA) y no es significativo desde el punto de vista de la arquitectura. Esta es una de las cosas que hace que el nodo que procesa los comandos sea autónomo desde la perspectiva del tiempo de ejecución: no se necesita una conexión siempre activa con el cliente.

Es más, no necesitaríamos acceder al almacén para consultas para procesar comandos, cualquier estado necesario sería gestionado por el componente autónomo, eso es parte del significado de autonomía.

Otra cuestión es el caso de fallos en el procesamiento de mensajes por culpa de caídas de la base de datos o por conflictos bloqueantes. No hay razón para devolver esos errores al cliente, podemos deshacer (rollback) e intentarlo de nuevo. Cuando el administrador recupere la base de datos, todos los mensajes en cola de espera serán procesados con éxito y nuestros usuarios recibirán confirmación.

El sistema como un todo es bastante más robusto ante las condiciones de error.

Dado que ya no se lanzan consultas contra esta base de datos, es capaz de mantener más filas/páginas en memoria para servir comandos, mejorando el rendimiento. Cuando tanto los comandos como las consultas eran servidos por las mismas tablas, el servidor de base de datos estaba siempre haciendo malabares con las filas de ambos.

Componentes Autónomos

En la figura superior vemos todos los comandos dirigidos hacia el mismo AC (componente autónomo), pero podríamos lógicamente procesar cada comando en un AC separado, con su propia cola. Esto nos permite visualizar qué cola es más larga, lo cual nos muestra de una forma muy obvia qué parte del sistema es un cuello de botella. Esta información, que es interesante para el desarrollador, es indispensable para los administradores de sistemas.

Dado que los comandos esperan en colas, podemos agregar más nodos procesadores a esas colas (si estamos usando NServiceBus, podemos emplear el distributor), para de esta manera escalar sólo la parte del sistema que es lenta. No hay necesidad de gastar servidores en las otras peticiones.

Capas de servicio

Nuestros objetos de procesamiento de comandos en los distintos componentes autónomos realmente conforman nuestra capa de servicio. La razón de que no se represente explícitamente esta capa en CQRS es porque realmente no está ahí, al menos no como una colección lógica identificable de objetos relacionados, y he aquí porque:

En la aproximación de la arquitectura en n capas (o 3-capas), no hay reglas en cuanto a las dependencias entre los objetos dentro de una capa, o más bien se supone que están permitidas. Sin embargo, al mirar a la capa de servicio a través del prisma de la orientación a comandos, lo que vemos son objetos manejando diferentes tipos de comandos. Cada comando es independiente de los otros, así que ¿por qué deberíamos permitir que los objetos que los manejan dependan unos de otros?

Las dependencias son cosas que deberían ser evitadas, a menos que haya una buena razón para ellas.

Mantener independientes entre sí estos objetos de manejo de comandos nos permitirá evolucionar nuestro sistema más fácilmente, un comando cada vez, no necesitando ni siquiera detener el sistema completo, dado que la nueva versión es compatible hacia atrás con la anterior.

Por lo tanto, hay que mantener cada manejador de comando en su propio proyecto VS, o quizás incluso en su propia solución, alejando así a los desarrolladores de la tentación de introducir dependencias en nombre de la reutilización (es una falacia). Si después decidieras, como un aspecto del despliegue, que quieres poner todos juntos en el mismo proceso alimentándose de la misma cola, puedes combinar con ILMerge esos ensamblados y alojarlos juntos, pero entendiendo que puedes estar perdiendo muchos de los beneficios de tus componentes autónomos.

¿Qué hay del modelo del dominio?

Aunque en el diagrama anterior podías ver el modelo del dominio bajo los componentes autónomos de procesamiento de comandos, realmente sólo es un detalle de implementación. No hay nada que establezca que todos los comandos deben ser procesados por el mismo modelo del dominio. Podría decirse que puedes tener algunos comandos procesados con secuencias transaccionales (transaction scripts), otros usando modulo de tabla (table module o active record), así como otros usando el modelo del dominio. Event sourcing sería otra posible implementación.

Otra cosa a entender referente al modelo del dominio es que ya no se usa para dar servicio a las consultas. Así que la cuestión es: ¿por qué necesitas tener tantas relaciones entre entidades en tu modelo del dominio?

(Es posible que desee disponer de un momento para asimilar esto.)

¿Realmente necesitamos una colección de órdenes en la entidad cliente? ¿En qué comando necesitaríamos navegar por esa colección? De hecho, ¿qué tipo de comando necesitaría una relación uno-a-muchos? Y si es así para las uno-a-muchos, las muchos-a-muchos definitivamente estarían fuera también. Es decir, la mayoría de los comandos sólo contienen uno o dos ID en el mejor de los casos.

Cualquier operación de agregación, que podría haber sido calculada iterando por las entidades hijas, podrá ser precalculada y almacenada como propiedades en la entidad padre. Siguiendo este proceso a través de todas las entidades en nuestro dominio dará lugar a entidades aisladas necesitando nada más que un par de propiedades para los ID de sus entidades relacionadas (hijos conteniendo el ID del padre, como en las bases de datos).

De esta forma, los comandos podrían ser completamente procesados por una sola entidad: viola, una entidad raíz que es un límite de consistencia.

Persistencia para el procesamiento de comandos

Ya que la base de datos usada para procesamiento de comandos no se usa para consultas, y que la mayoría de (si no todos) los comandos contienen el ID de las filas a las que van a afectar, ¿realmente necesitamos tener una columna para cada propiedad de objetos del dominio? ¿No podríamos serializar la entidad del dominio y ponerla en una sola columna, y tener otra columna conteniendo el ID? Esto suena bastante parecido a un almacenamiento basado en claves tal como está disponible en los varios proveedores en la nube. En tal caso, ¿necesitas realmente un mapeador objeto-relacional (ORM) para persistir en este tipo de almacenamiento?

También podrías extraer una propiedad adicional para cada dato del que quieras que la base de datos asegure la unicidad.

No estoy sugiriendo que hagas esto en todos los casos, más bien trato de hacer que te replantees algunas presunciones básicas.

Permíteme repetirlo

La forma en que se procesan los comandos es un detalle de la implementación de CQRS.

Manteniendo el almacén para consultas actualizado

Después de que el componente autónomo de procesamiento de comandos ha decidido aceptar uno, y tras modificar su almacén de persistencia en consecuencia, publicará un evento notificándolo al resto del mundo.Este evento a menudo es la “forma pasada” del comando:

HacerClientePreferidoCommand -> ClienteHaSidoHechoPreferidoEvent

La publicación del evento se hace en la misma transacción en que se procesa el comando y se hacen los cambios en la base de datos. De este modo, cualquier tipo de error al confirmar la transacción (commit) dará lugar a que el evento no sea enviado. Esto es algo que debería ser gestionado por defecto por el bus de mensajes, y si se utiliza MSMQ como transporte subyacente, requiere del uso de colas transaccionales.

El componente autónomo que recibe esos eventos y actualiza el almacén de datos para consultas es bastante simple, traduciendo de la estructura del evento a la estructura de persistencia del modelo de la vista. Sugiero tener un manejador de eventos por cada clase modelo de la vista (es decir, por tabla).

Aquí tenemos la imagen con todas las piezas de nuevo:

Contextos limitados

Aunque CQRS afecta a muchas piezas de la arquitectura del software, todavía no está en la cima de la cadena alimenticia. CQRS, si se usa, será aplicado dentro de un contexto limitado (DDD) o de un componente de negocio (SOA): una pieza cohesiva del dominio del problema. Los eventos publicados por un BC serán escuchados por otros BC, y cada uno de ellos actualizará sus almacenes (de comandos y de consultas) como sea necesario.

Los interfaces de usuario de CQRS que encontramos en cada BC pueden ser “unificados” en una única aplicación, ofreciendo a los usuarios una única vista compuesta con todas las partes del dominio del problema. Las librerías de composición de UI serán de gran ayuda para estos casos.

Resumen

CQRS versa sobre cómo alcanzar una arquitectura apropiada para aplicaciones multiusuario colaborativas. Explícitamente considera factores como la obsolescencia de datos y la volatilidad, y explota esas características para crear construcciones más simples y escalables.

Uno no puede disfrutar verdaderamente los beneficios de CQRS sin considerar el interfaz de usuario, sin hacer que este capture explícitamente la intención del usuario. Cuando se considera la validación en el lado del cliente, la estructura de comandos debe ser de alguna forma reajustada. Pensando más profundamente en el orden en que los comandos y eventos son procesados puede conducir a patrones de notificación que hagan innecesaria la devolución de errores.

Mientras que el resultado de aplicar CQRS a un proyecto dado es una base de código más facil de mantener y eficiente, esta simplicidad y escalabilidad requieren de la comprensión detallada de los requisitos del negocio y no son el resultado de ninguna “buena práctica” de carácter técnico. Si acaso, podemos ver una plétora de enfoques a problemas aparentemente similares siendo empleados conjuntamente: lectura con cursores frente a modelos de dominio, mensajes unidireccionales frente a llamadas asíncronas.

Aunque este artículo supera las 3000 palabras (un récord para este blog), reconozco que no profundiza lo suficiente en la materia (toma unos 3 días de los 5 de mi curso Advanced Distributed Systems Design cubrirlo todo en suficiente detalle). En cualquier caso, espero que te ofrezca un entendimiento de por qué CQRS es como es y quizá te abra los ojos a otras formas de mirar al diseño de sistemas distribuidos.

Las preguntas y los comentarios serán muy bienvenidos.

[Udi Dahan, 2009]

[ASP.NET MVC] Detectando DEBUG en código y en Razor

Últimamente he aumentado bastante mi productividad usando un mecanismo muy sencillo: implementando código sólo para depuración que me ayuda en las sesiones de depuración, y ahora no me estoy refiriendo a trazas ni logs. Me refiero a cosas tan sencillas como:

  • Iniciar sesión automáticamente con un usuario y contraseña dados (suponiendo, como en mi proyecto, que se prohíbe por requisito que el navegador recuerde al usuario).
  • Rellenar valores de mi conveniencia en un formulario.

Como estas cosas, aunque las implemente, no quiero que ni por lo más remoto puedan llegar a producción, uso la constante de compilación DEBUG que indica si estamos ejecutando en Debug o en Release (y si tenemos más configuraciones, podremos indicar si la queremos declarar o no). Como mi código en producción irá como Release, no corro el riesgo de ejecutar mis atajos para depuración.

En un proyecto web MVC, según dónde nos encontremos, la forma de detectar si estamos en Debug o Release es distinta como vamos a ver a continuación.

Directivas de precompilación y DEBUG

Las directivas de precompilación de C# permiten que el compilador considere un código u otro en función de unas constantes, como DEBUG en nuestro caso. Por ejemplo, algo como:

#if DEBUG
        string username = "pablo";
#else
        string username = null;
#endif

Hace que los programas generados en Debug y en Release sean distintos: uno llevará una asignación a “pablo” y el otro a null. Ojo que no existe doble declaración de la variable: en un caso se declarará una y el propio compilador ignorará la otra línea, no sólo no se ejecuta, es que ni se compila.

Usando estas directivas no corro el riesgo de olvidarme de quitar este código de depuración cuando genere mi versión para producción: se usará el código que asigna null.

Un código similar he usado en mi MembershipProvider (no pongo el ejemplo porque mi caso es muy particular y dependiente de un motor de bases de datos muy particular) para iniciar una sesión concreta siempre en Debug, mientras que en Release el usuario debe iniciar sesión.

Las directivas de precompilación tienen más usos, como usar un mismo código base para diferentes targets, como Silverlight, Windows Phone… con algo como:

#if SILVERLIGHT

Lo cual nos acerca a aquello de un código, tres pantallas. Aunque no es el tema de hoy.

Detectando modo Debug en Razor

Pensando bien, uno interpreta que esto se puede aplicar al código C# incrustado en Razor. Yo he intentado usarlo para el segundo punto planteado al inicio de este artículo, y de forma natural uno piensa que puede hacer:

@{
#if DEBUG
        string titulo = "pablo";
#else
        string titulo = null;
#endif
}

Cuidado, esto no funciona. Y tiene sentido que no lo haga: este código dentro de Razor no es compilado al publicar el proyecto, sino que es incrustado en las vistas, y luego las vistas son compiladas por IIS según necesidad. Por lo que #if DEBUG siempre devuelve true. No sirve.

Pero hay otra forma de detectar que estamos en Debug desde Razor, y aunque no es exactamente igual, en la mayoría de los casos será equivalente y nos servirá. Se trata de la propiedad:

Context.IsDebuggingEnabled

Que nos ofrecen las páginas Razor a través de su HttpContext. De esta forma sí podemos hacer:

@{
    string titulo;
    if(Context.IsDebuggingEnabled)
        titulo = "pablo";
    else
        titulo = null;
}

Con lo que podemos usar esa variable @titulo como valor del elemento input correspondiente para que al cargar la página tengamos un valor inicial. Ojo, esto se podría (debería) hacer en el modelo (viewmodel) usado en la página, pero entonces podríamos usar directivas de precompilación y no tendría gracia el uso de IsDebuggingEnabled, así que he forzado un poco el ejemplo para mostrar este caso.

Conclusión

Podemos hacer trucos en nuestro código que nos faciliten la tarea rutinaria de ejecutar una y otra vez una aplicación en desarrollo para llegar a la página que estamos implementando, saltando los pasos que un usuario debería recorrer. Esta simplificación puede aumentar considerablemente nuestra productividad, pero cuando lo hagamos, debemos seguir los cauces que existen para ello (detección de DEBUG con #if, propiedad IsDebuggingEnabled) para que al pasar la aplicación a producción no quede ni rastro de nuestros trucos. Y ojito también con lo que subimos al control de código fuente, a ver si otro compañero se va a encontrar con nuestros atajos y quizá no le vayan a hacer mucha gracia.

Un placer.

Primer contacto con Code Contracts (no es un tutorial)

Hola, me llamo Pablo y hoy he sufrido mi primer contacto forzoso con Code Contracts. Lo de forzoso no es porque no me guste ese proyecto, sino porque ha sido completamente involuntario. Pero antes de nada, ¿qué es Code Contracts?

Code Contracts

Code Contracts es un proyecto de Microsoft Research para incluir en nuestro código las precondiciones y poscondiciones que deben cumplirse antes y después de su ejecución, así como los invariantes que deben cumplirse siempre. Muchos no habrán usado estas palabrejas desde que dejaron la Universidad, otros ni eso, pero al programar todos estamos asumiendo premisas que deben cumplirse (por ejemplo, que un método debe llamarse antes que otro, o que una variable tenga un rango de valores concreto), sólo que no los reflejamos en ningún sitio, sólo están en nuestra cabeza. Ahí es donde interviene Code Contracts, permitiendo poner negro sobre blanco (pixel negro sobre pixel blanco, quiero decir) esas condiciones, y además verificando que se cumplan tanto durante la ejecución de nuestro programa como de nuestros tests.

Como idea es muy interesante, y permite incrementar la calidad de nuestro código, difuminando un poco la división entre programa y tests, ya que estas comprobaciones (equivalentes a los Assert de un test) están en el propio programa. También ayuda a la legibilidad del código y a su comprensión por parte de quien venga detrás (que normalmente somos nosotros mismos dentro de un tiempo). Y su uso no es nada difícil, como muestra un botón:

public void Bind(FrameworkElement bindingObject, Func<FrameworkElement, FrameworkElement> bindingObjectParentFunc)
{
    Contract.Requires<ArgumentNullException>(bindingObject != null, "Binding object cannot be null.");
    Contract.Requires<ArgumentNullException>(bindingObjectParentFunc != null, "Binding object function cannot be null.");
…

En lugar de comprobar si nos han pasado bien los atributos con un if, lo estamos declarando usando Contract, el punto de acceso principal a la librería Code Contracts. Se entiende fácil, ¿verdad? Pues si queréis saber más, os dejo aquí una presentación de un chico que parece que sabe de esto.

¿Pero qué te ha pasado hoy?

Pues nada, que estaba tratando de localizar un error en la versión que estamos preparando para WPF de SilverDiagram, y entre cambios de código y recompilaciones de librerías me he encontrado con este error:

image

El texto es bastante más extenso, pero qué os voy a contar de esas hermosas pilas de llamadas listadas en enormes MessageBox. Todo un símbolo, y perfecto para demostrar a nuestro jefe/usuarios lo difícil que es nuestra profesión.

La cuestión es que este error viene provocado por Code Contract, porque se da una situación curiosa: Tengo dos librerías, A y B. La librería B referencia a A (pero están en soluciones distintas, así que referencia el ensamblado A.dll, pongamos).

Yo estaba probando B sin problemas, tratando de corregir el error, haciendo cambios y pruebas normalmente. Cuando identifico que el error parece estar en A, abro su solución, lo corrijo y compilo para usar esta nueva A.dll. Esta compilación no se queja.

Compilo B con la nueva A.dll y de nuevo sin problema. Pero al ejecutar, el error, ah, el error: Must use the rewriter when using Contract.Requires<TException>.

Está claro que la culpa es de Code Contracts. Echo una búsqueda rápida, y encuentro en el blog de Derik Whittacker una coincidencia. Y hace especial hincapié en que la culpa no es de B, sino del proyecto A, a pesar de no haber dado errores en su compilación. Y el motivo es sencillo: no tengo instalado Code Contracts en mi ordenador.

Instalando… y solucionado

Así que tras cerrar los Visuales Estudios, descargo e instalo desde la web de Code Contracts (hay versión comercial y académica, pero no me preguntéis sobre eso). Una vez instalado, conseguimos dos cosas:

  1. En las Propiedades de los proyectos hay una nueva pestaña Code Contracts donde podemos definir el comportamiento de esta librería en nuestro proyecto.
  2. Al volver a compilar el proyecto, ya podemos usar A.dll con normalidad en otros proyectos (si estaba bien configurada la comprobación en tiempo de ejecución de las propiedades de Code Contracts, pero esto es harina de otro costal).

Espero que esto pueda servir de ayuda para quienes sufran el mismo problema, y también para que algunos nos empecemos a introducir en Code Contracts (…por mi primero).

Un placer.

Carencias de Sql CE 4.0 en Visual Studio 2010

Hoy domingo he estado preparando una demo introductoria a Entity Framework, con el objetivo de que fuera lo más sencilla posible. Para ello, no he querido trabajar ni en un proyecto web ni en una aplicación Windows, sino en un proyecto de consola, para no exigir ningún conocimiento previo en el destinatario. Y desde un principio había elegido basarme en SQL Server Compact Edition (Sql CE), y su última versión 4.0, para evitar la necesidad de instalar un motor de bases de datos y profundizar en su configuración, pero todavía manteniendo un proveedor nativo de .net y de Entity Framework. Todo sobre Visual Studio 2010. También debo adelantar que mi contacto previo con Sql CE es mínimo, sólo como registro de Elmah y poco más. Pero como hoy he aprendido algunos hechos, los quiero compartir en este artículo.

Aunque yo suelo trabajar con EF aplicando la metodología Code-First, de nuevo por simplicidad decido plantear la demo como Model-First, por aquello del interfaz gráfico y demás (que está sobrevalorado, donde se ponga un buen interfaz de código fluido…). Bueno, creo un archivo .edmx y defino en él 2 tablas con su propio Id autonumérico como clave primaria y una clave foránea entre ambas. Cuando pido Crear base de datos a partir del modelo en el menú contextual, me pide crear una conexión a base de datos, y me ofrece 3 proveedores: SQL Server, archivo mdf y Sql CE 3.5.

image

Decido continuar con mi idea de usar CE aunque sea 3.5 (ya veré después… sólo estoy preparando una demo). Creo un nuevo archivo sdf para la conexión y Visual Studio me genera un archivo edmx.sqlce con el script de base de datos para generar las tablas. Lo conecto al sdf y ejecuto sin problemas. Pero al incluir código para crear una entidad y ejecutarlo, obtengo el error:

Server-generated keys and server-generated values are not supported by SQL Server Compact.

Lo cual no es del todo cierto, bueno, casi lo era cuando se escribió ese mensaje de error, ya que Sql CE 3.5 no soporta autonuméricos generados en el servidor a través de Entity Framework, es decir, realmente es una carencia del provider y no del motor. Con esto, Sql CE 3.5 no me sirve para mi sencillo ejemplo, donde no voy a montar ningún generador de claves. Esta carencia ha sido subsanada en Sql CE 4.0, así que doy marcha atrás y me lanzo a la búsqueda de esta versión.

Hasta ahora, cuando lo había necesitado (para Code-First y para Elmah) había usado el paquete nuget. Pero esto no sirve para EDMX, ya que debe estar registrado el proveedor en Visual Studio. De ahí que me descargo las SQL CE Tools for Visual Studio SP1 (que ya lo tenía) desde este artículo de ScottGu (no sé si también estará en español, he buscado un poco y no lo he encontrado). Pero tras instalarlo, me siguen saliendo los mismos proveedores que antes, por lo que investigo un poco y descubro que el proveedor Sql CE 4.0 sólo se ofrece en el EDMX dentro de proyectos web, no en mi humilde proyecto de consola. De hecho si en un proyecto web creo el sdf de la versión 4.0 y añado una conexión a él en el Explorador de servidores de Visual Studio, al tratar de usarlo desde el EDMX no se incluye en el combo (sólo se incluyen las conexiones con Sql CE 3.5 o con Sql Server). Es realmente desagradable ver cómo un EDMX añadido a una librería de clases (que es lo aconsejable, extraer el modelo fuera del proyecto web) sólo permite usar Sql CE 3.5 mientras que en un EDMX dentro de un proyecto web sí se ofrece Sql CE 4.0:

image

Todo esto me parece una situación extraña y limitadora, ya que Sql CE 4.0 es una solución ideal para pequeñas aplicaciones o utilidades, o para la primera fase de algunas aplicaciones mayores, incluso para instalaciones pequeñas de esas mismas aplicaciones; y con esta carencia se dificulta su utilización práctica para quienes prefieren usar el EDMX en lugar de la aproximación Code-First. Sólo me queda añadir que esta restricción puede salvarse siguiendo este truco (en inglés), que implica modificar el EDMX a mano, y que puede servir a los más tenaces (los demás habrán desistido antes, tristemente).

Y para terminar tengo que dar las gracias a mis agazapados del Twitter, Marc Rubiño y Rodrigo Corral, por echarme una mano y compartir experiencias acerca de Sql CE (incluso siendo el día del Señor). Es un gustazo tener a gente así leyendo mis tonterías para ayudar. Otra vez gracias.

Un placer.

[ASP.NET MVC] Discriminar acciones según el nombre de los parámetros

El caso que voy a exponer hoy está “basado en hechos reales”, como las películas de Antena 3. Me encontré con la necesidad de ofrecer dos rutas como:

http://mi.com/Form/Show?url=http://geeks.ms

http://mi.com/Form/Show?name=Geeks

Es decir, para quien usa la página, nuestra acción Show puede recibir una URL o bien el nombre de una página que ya tengamos previamente guardada. Son las dos formas de acceder al servicio. Por poner algo más de contexto, puede imaginar el lector un servicio como Google Mobilizer (transformar una página para facilitar su lectura en dispositivos móviles) mezclado con un gestor de marcadores (bookmarks). El uso de la misma ruta en ambos casos es una decisión de diseño que no depende de nosotros.

Este diseño admite dos posibles planteamientos en MVC:

  • Una única acción con dos parámetros opcionales, url y name.
    • Según el parámetro dado, elegir un comportamiento u otro.
    • Decidir qué hacer si nos pasan los dos parámetros (por ejemplo, dar un error).
  • Dos acciones separadas, una con un parámetro url y otra con name.
    • Pro: podemos organizar mejor nuestro código (en cada acción lo suyo).
    • Pro: la decisión la hace MVC, no necesitamos discriminar según los parámetros recibidos. Queda más natural.
    • Contra: no es soportado de forma nativa en MVC 3, como veremos.

Como el código de ambas acciones no tiene apenas nada en común, me gustaba más la segunda opción, por lo que vamos a ver si podemos ir salvando los obstáculos que nos encontremos en el desarrollo del ejemplo. Para comenzar:

  1. Creamos un nuevo proyecto ASP.NET MVC 3 de nombre SelectingActionByArgumentsName.
  2. Usamos la plantilla Vacía (Empty) y motor de vistas Razor (View engine).
  3. Añadimos un nuevo controlador: clic derecho en la carpeta Controllers, y elegimos Agregar (Add) > Controller.
  4. Le llamamos FormController, y lo creamos vacío (Empty controller).

Ya tenemos el proyecto listo para incluir nuestras dos acciones. Bajo la acción Index que se genera siempre, añadimos las dos nuestras, devolviendo sólo un contenido (para simplificar):

public ActionResult Show(string url)
{
    return Content("Loaded from url");
}

public ActionResult Show(string name)
{
    return Content("Loaded from name");
}

Y encontramos el primer obstáculo:

C# no admite dos métodos con la misma firma en una clase.

Evidentemente, aunque el nombre de los parámetros sea distinto, son del mismo tipo, por lo que C# (no ASP.NET MVC, sino el compilador) nos dará un error. Contra esto, poco podemos hacer, hay que cambiar algo. Pero existe una solución sencilla: añadir un parámetro opcional. Esto hará que los métodos sean diferentes. Por ejemplo, vamos a añadir un parámetro opcional save a la acción que recibe una url:

http://mi.com/Form/Show?url=http://geeks.ms&saveAs=Geeks

Este parámetro indica que queremos que se guarde la dirección con ese nombre. De hecho, hemos aprovechado para añadir funcionalidad, pero en la práctica podríamos haber añadido un parámetro sin utilidad y no usarlo nunca (su única utilidad sería permitir que el compilador acepte nuestros dos métodos de forma válida). Y lo haremos opcional, por lo que la URL inicial seguirá funcionando. Así que cambiamos la primera acción por:

public ActionResult Show(string url, string saveAs = "")

Y ya tenemos el primer obstáculo salvado: el compilador no se queja. Ya podemos ejecutar. Pero si navegamos a (en xxx irá el puerto de vuestro servidor web):

http://localhost:xxx/Form/Show?name=Geeks

Recibimos un error ahora de ASP.NET MVC (segundo obstáculo):

The current request for action 'Show' on controller type 'FormController' is ambiguous between the following action methods:
System.Web.Mvc.ActionResult Show(System.String, System.String) on type SelectingActionByArgumentsName.Controllers.FormController
System.Web.Mvc.ActionResult Show(System.String) on type SelectingActionByArgumentsName.Controllers.FormController

ASP.NET MVC no sabe elegir qué acción utilizar. ¿Pero no hemos puesto name en nuestra URL? Pues usa la acción que tiene un argumento name, so tonto (no sé si vosotros también soléis insultar a la pantalla). Pero no: ASP.NET MVC no usa los nombres de los argumentos para elegir qué acción llamar durante el proceso de enrutado (por defecto, que como casi todo en MVC 3, esto puede cambiarse). Ojo, sí usa los nombres después para asignar los parámetros, pero eso es cuando ya ha elegido la acción; es para elegirla cuando no los considera. Sólo sus tipos. Y usando sólo los tipos, se encuentra con esa ambigüedad.

Pero como hemos dicho, casi todo en MVC 3 puede cambiarse, y además de diferentes formas. En este caso, vamos a usar una solución vista en StackOverflow, aunque modificada para hacerla más genérica, dirigida a cambiar el mecanismo de selección de acciones (sí, ese que hemos dicho que se basa en los tipos) para que considere también los nombres de los argumentos. Y además vamos a hacerlo mediante un atributo para que sólo afecte a las acciones donde lo necesitemos. Para esto, sólo debemos crear una clase que extienda de ActionMethodSelectorAttribute y sobreescribir el método IsValidForRequest, que es quien realizará la comprobación.

public class SelectByArgumentNamesAttribute : ActionMethodSelectorAttribute {
    public override bool IsValidForRequest(ControllerContext controllerContext, 
                                           MethodInfo methodInfo) {
        return methodInfo.GetParameters()
                .All(pi => pi.IsOptional 
                        || controllerContext.HttpContext.Request[pi.Name] != null);
    }
}

Un detalle aclaratorio: no tenemos que comprobar el nombre del método, ya que esto lo hace ASP.NET MVC antes de llamar a esta validación. Sólo tenemos que comprobar si, según los datos de la petición (Request), este método (methodInfo) es el apropiado o no. Por lo que comprobamos que todos los argumentos del método vienen en la Request (con su nombre), salvo que sean opcionales.

Ahora sólo tenemos que usar este atributo en las acciones conflictivas (en ambas). Por ejemplo:

[SelectByArgumentNames]
public ActionResult Show(string url, string saveAs = "") {
…

Tras usar el atributo en las dos acciones, ya podemos probar a navegar a las URL:

http://localhost:xxx/Form/Show?url=http://geeks.ms

http://localhost:xxx/Form/Show?name=Geeks

¿Pero qué sucede? Que todavía nos queda por salvar el tercer obstáculo, aunque no está relacionado con lo anterior. Si comprobáis, veréis que funciona correctamente con la primera dirección (con o sin el parámetro opcional) pero falla con la segunda, la que usa el parámetro name.

¿Por qué? Bueno, pues este es uno de esos problemas que nos invitan a creer en la magia y abandonar la programación para dedicarnos a echar las cartas, hasta que descubrimos de qué se trata: la culpa es del nombre elegido para el parámetro url, dado que siempre que preguntemos por Request[“url”] obtendremos un valor, nunca es null, aunque no hayamos pasado ningún parámetro con ese nombre en la URL, porque el valor obtenido es ¡el de la propia URL de la petición!

Por suerte, este obstáculo es el de más fácil solución: en lugar de buscar en Request, profundizamos un poco más hasta Request.QueryString, donde sólo están los parámetros que vienen en la QueryString, con lo que sólo habrá un url si se lo pasamos, y si no devolverá null. La solución pasa por cambiar:

controllerContext.HttpContext.Request[pi.Name] != null

por

controllerContext.HttpContext.Request.QueryString[pi.Name] != null

Y resuelto. He querido incluir este último obstáculo por 1. ser fiel a la realidad (recordad que el artículo está basado en hechos reales) y 2. por la moraleja, parafraseando: “el más mínimo desconocimiento de la herramienta puede hacerse pasar por magia”.

En conclusión, hemos visto cómo se puede cambiar de forma muy sencilla la forma en que ASP.NET MVC selecciona la acción a partir de la URL. Recordad que, igualmente, muchos otros comportamientos generales se pueden modificar y adaptar a los casos concretos, o incluso esto podíamos haberlo conseguido de otras formas. Y sobre todo, lo más importante: hay que conocer bien la herramienta con la que trabajamos para adaptarla, entenderla... y distinguirla de la magia.

Un placer.

Recibiendo un parámetro de tipo Array en un Controller de ASP.NET MVC

Implementando una acción en un controlador (Controller) de ASP.NET MVC he necesitado que uno de los parámetros fuera una lista o array de enteros, en concreto los Id a considerar para un pequeño informe. Por ponerlo negro sobre blanco, o mejor, pixel sobre pantalla:

public class InformeController : Controller
{
    //
    // GET: /Informe/Ventas?desde=6/1/2011&hasta=6/30/2011&centros=1,2,3

    public ActionResult Ventas(DateTime desde, DateTime hasta, int[] centros)
    {
        return Content("Centros: " + string.Join(" / ", centros));
    }
}

 

Para mi informe necesito un rango de fechas definido por desde y hasta, y una lista de centros definidos por sus Id. El paso de parámetros en la URL, mediante HTTP GET, tiene para mí muchas ventajas, entre ellas la facilidad para realizar pruebas.

A modo de test devuelvo una cadena con los centros separados por barras, para que se vea que no hay truco. En el comentario del propio código puede verse cómo sería una llamada, el interés está en el parámetro centros que recibe una lista separada por comas de los Id (dejando de lado que las fechas deben ir en inglés, pero esa es otra cuestión). La elección de la coma como separador es mía, aunque sigue una convención bastante extendida.

La cuestión es que esta acción tan sencilla e intuitiva no funciona puesto que en el argumento centros no se recibe el valor deseado en todos los casos.

  • En el ejemplo del comentario, con centros=1,2,3 en la QueryString, recibiremos null en centros. Mal.
  • En cambio, si pasamos centros=1 recibiremos un array con un entero de valor 1. Bien.

¿A qué se debe esto? Bien, tenemos que saber que estamos confiando en el ModelBinder de ASP.NET MVC para convertir los valores de la QueryString (nuestra URL) en los parámetros de nuestras acciones. Este ModelBinder tiene una funcionalidad muy completa con los tipos sencillos, pero poco más. Si necesitamos algo extra, como es nuestro caso, no nos ayuda.

Pero ASP.NET MVC tiene múltiples puntos de extensión, entre ellos la posibilidad de definir nuestros propios ModelBinder para cubrir los tipos que necesitemos. Así que vamos a definir uno para permitir la recepción de un array de enteros en un formato separado por comas. Sólo tenemos que extender de IModelBinder:

public class ArrayOfIntModelBinder : IModelBinder
{
    public object BindModel(ControllerContext controllerContext,

                            ModelBindingContext bindingContext)
    {
        return controllerContext.HttpContext.Request[bindingContext.ModelName]

            .Split(',')

            .Select(int.Parse)

            .ToArray();
    }
}

 

Esta interfaz sólo nos exige implementar el método BindModel que tiene la responsabilidad de transformar el trozo de la QueryString donde va nuestro parámetro (y que es un string, no lo olvidemos) en el valor destino, en nuestro caso un array de enteros. Recibe dos argumentos:

  • El contexto del controlador, que incluye la información de la petición (Request). De aquí obtenemos el valor del parámetro en la QueryString.
  • El contexto del modelo, en nuestro caso el argumento destino de la conversión. De aquí obtenemos el nombre del argumento buscado, que en nuestro ejemplo anterior sería “centros” pero que si usamos bindingContext.ModelName tenemos un Binder genérico que nos sirve para cualquier otro uso de array de enteros en otra acción (o incluso como otro parámetro en la misma).

El resto del método es sencillo: dividimos la lista de enteros usando la coma como separador, los convertimos a enteros con int.Parse y convertimos el IEnumerable resultante en un array. Pido perdón por mi concisión en el código, parte de la culpa la tiene Linq, otra buena parte Resharper, y otra yo mismo, qué pasa, si tenemos un lenguaje potente y hermoso como C# es para aprovecharlo, ¿no?

Bueno, nos queda la parte más importante, porque como ya imagináis esta clase por sí misma no hace nada si no le decimos a ASP.NET MVC que la utilice al procesar las peticiones. Para esto, basta con incluir en el Global.asax.cs una línea como:

 

ModelBinders.Binders[typeof(int[])] = new ArrayOfIntModelBinder();

 

Podemos ponerla en Application_Start(), y su mandato es sencillo de definir con palabras: “cuando encuentres un parámetro de tipo array de entero, lo procesas con esta instancia y no con el ModelBinder genérico.” Dicho y hecho, si todavía tenéis la URL anterior, volved a ejecutar el proyecto y refrescar esa página, podéis comprobar como ahora el parámetro centros devuelve todos los enteros que hayamos tenido a bien suministrarle en la URL.

Un placer.

Análisis de una aplicación web tipo Gmail

Durante la lectura de la hoja de ruta de MVC4 (en inglés), leo lo siguiente que pica mi curiosidad:

implementing a full-fledged single-page application (e.g. Gmail)

En primer lugar el hecho de que un documento de Microsoft cite a Gmail como ejemplo no es gratuito. Es bien merecido, ya que Gmail supuso una revolución en el concepto de página web y en su transición al concepto de aplicación web, en el sentido de sustituto de las aplicaciones de escritorio tradicionales (ya sabemos que antes ya había aplicaciones web, pero ¿cómo eran? E incluso ¿cómo son muchas ahora?). Pese a su antigüedad, podemos incluso atrevernos a hablar de Gmail como la primera aplicación HTML5, o de una precursora de estas (en realidad es una precursora en el uso de AJAX, pero ¿de dónde sale HTML5 si no?) Dicho esto con todas las precauciones tratándose de un desarrollo que comenzó hace ya varios años (hace sólo 2 años que dejó de ser beta, pero el proyecto se inició en 2004). También podemos señalar otro logro en el haber de Gmail, su contribución a que muchos usuarios asimilaran el concepto de la nube incluso antes de haberlo oído nombrar por primera vez.

En cualquier caso, su referencia en la hoja de ruta se refiere principalmente al modelo de aplicación: completa y en una única página. Es decir, lo tenemos todo disponible sin cambiar de página, con algunas características muy destacables:

  • Navegación: Es como un navegador dentro de un navegador: sólo cambia el contenido, los menús se conservan. De hecho incluso muestra su propio mensaje Cargando… sustituyendo al del navegador.
  • Enlaces: Aún simulando el comportamiento del navegador, se integra perfectamente con él: responde al botón atrás (y toda la gestión del historial), permite abrir un correo en una nueva página o ventana…
  • Caché local: Mantiene los correos descargados en la memoria del navegador, lo que nos permite una navegación muy rápida entre ellos.
  • Carga inicial: Todo esto tiene un coste: la carga inicial no es rápida, de ahí la barra de progreso. Pero aun así tampoco es lenta, podemos decir que es muy soportable.

Un día tengo que profundizar bien en cómo hace Gmail todo esto y lo compartiré con vosotros (aunque si alguien quiere ahorrarme el trabajo, soy todo oídos). Lo bueno de las aplicaciones web es que es fácil aprender de ellas puesto que tenemos su parte de cliente en nuestro navegador, y Gmail es evidentemente una aplicación de cliente: Javascript en estado puro. Lo cual no quiere decir que sea fácil de bichear.

En cualquier caso, y volviendo a MVC (perdón que abrevie, pero me cuesta citar un tercio de alfabeto cada vez, o sea ASP.NET MVC, que son 9 de las 27 letras del abecedario) es evidente que el tipo de aplicaciones cubierto hasta ahora por MVC no es el tipo Gmail, sino todo lo contrario: la típica aplicación web con páginas bien diferenciadas. Evidentemente se puede mejorar, sobre todo con jQuery, cada vez mejor integrado, y con la facilidad que ofrecen los controllers para implementar servicios web, pero sigue sin existir un soporte nativo para este tipo de aplicaciones (léase plantilla de proyecto, helpers, integración con librerías de javascript para tal fin…) ¿Es eso lo que plantean en la hoja de ruta? Evidentemente, los artefactos que planteo pueden desarrollarse sobre MVC3, de hecho muchos los habrán desarrollado en mayor o menor medida. Pero su eventual inclusión en MVC4 contribuiría a su estandarización y a la difusión de su uso.

En resumen, dentro de la gran bola de nieve que es HTML5, el tipo de aplicación en página única es uno de los objetivos que se persiguen con la especificación. Y no sólo depende de características en el navegador (como bien ha demostrado el propio Gmail hace tiempo, aunque sí puedan ayudar a simplificar y mucho su desarrollo); sino que requieren de una fuerte integración entre cliente y servidor. MVC nos ofrece una moderna y flexible plataforma en el servidor, ¿en su nueva versión extenderá sus ramas aún más hacia el cliente con una integración más estrecha con librerías de javascript para simplificar el desarrollo de este tipo de aplicaciones? Tengo que pensar que las extensiones de jQuery desarrolladas por Microsoft (templates, data binding) ya marcaban este camino. ¿Tan lejano está el día en que yo no necesite mirar cómo hace Gmail su magia porque tendremos una plataforma que nos lo proporcione a un alto nivel de abstracción?

Por supuesto, esto no son más que divagaciones mías centradas en tan sólo uno de los numerosos puntos que se marca la hoja de ruta de ASP.NET MVC 4, es una lectura muy recomendable y amena.

Hola Geeks.ms

Hoy estreno mi nuevo blog en geeks.ms, algo que no podía ni imaginar hace sólo unos meses. Pero un grupo fantástico de gente me ha puesto las pilas y me ha convencido de que con muy poquitos conocimientos pero muchas ganas de compartir se puede aportar mucho a la comunidad, y si nos estamos nutriendo siempre de las aportaciones de los demás en nuestro día a día, es de justicia que aportemos también algo, por poco que sea. Así que muchas gracias a Rodrigo por ofrecerme este espacio y a tantos amigos de Twitter que me mantienen despierto todo el día y parte de la noche con sus aportaciones de enorme calidad.

Me siento muy feliz de formar parte de esta gran comunidad y espero poder aportar todo lo que mi tiempo libre me permita. Bueno, aunque libre ya no es, porque ya está también ocupado ;-)

Y ahora un poco sobre mí: Aunque me inicié con Visual Basic 6 y ADO, mi mayor experiencia se centra en C#, WPF y Silverlight, con inmersiones más o menos profundas en WinForms, EF, ASP.NET MVC, SQL Server, TFS… y tantos productos relacionados. De ahí que la temática del blog pueda ir cambiando según el proyecto en el que esté trabajando.

Por cierto, no quiero decir con el título que geeks.ms sea el mundo, pero sí es cierto que agrupa una parte importante de nuestro mundo profesional y por eso estoy muy ilusionado por formar parte activa de él.

Nos leemos.