June 2009 - Artículos

Hola a todos! En este post (continuación del post anterior ASP.NET MVC y Ajax) voy a comentaros algunas cosillas más sobre ASP.NET MVC y ajax utilizando jQuery. En este caso vamos a ver como implementar un clásico de Ajax: las combos encadenadas.

Vamos a hacer el manido ejemplo de continentes y paises: seleccionas un continente en la combo y via Ajax se rellena la combo de paises. Vereis que sencillo es crear combos encadenadas con ASP.NET MVC y jQuery.

Lo primero es crear una vista que tenga las dos combos que queramos (yo como siempre modifico la vista inicial (Views/Home/Index.aspx).

El código que he puesto es:

<h1>Ajax y MVC... tan fácil como parece!</h1>
Escoja continente y deje que el poder de Ajax le muestre algunos paises...

<select id="cmbContinente">
    <option value="---" selected="selected">Escoja continente...</option>
    <option value="eur">Europa</option>
    <option value="ame">America</option>
    <option value="asi">Asia</option>
    <option value="afr">Africa</option>
</select>
<select id="cmbPaises"></select>

Excelente… ya tenemos las dos combos. Ahora empieza lo interesante: cuando se seleccione un elemento de la combo de continentes vamos  a realizar una petición ajax al controlador, para que nos devuelva que países se corresponden al continente seleccionado…

Como siempre jQuery viene en nuestra ayuda:

<script type="text/javascript">
    $(document).ready(function() {
        $("#cmbContinente").change(function() {
            fillCombo("cmbPaises", $("#cmbContinente").val());
        });
    });
</script>

Si visteis mi post anterior este código ya no os sonará tan extraño. Si no lo habeis visto, o bien no os acordáis ahí va un rápido resumen: con $(document) obtengo el manejador jQuery al documento actual, y llamo a su método ready. El método ready se ejecuta cuando el documento está “listo” (esto es todo el árbol DOM inicializado), y como parámetro espera una función. En mi caso le paso una función anónima con el código a ejecutar.

Lo que hace la función anónima es muy sencillo. Recordad que en jQuery la expresión $(“#xx”) obtiene un manejador jQuery al elemento DOM cuyo id sea “xx”. Así pues obtengo el manejador jQuery de la combo cuyo id es “cmbContinente” y me suscribo a su evento change. Cuando se usa jQuery generalmente no nos suscribimos a los eventos de los elementos DOM usando la sintaxis clásica onXXX, sinó que usamos las propias funciones de jQuery. En mi caso uso la función change() que se correspondería el evento onChange. La razón de actuar así es que jQuery intenta ofrecer un modelo de eventos unificado entre distintos navegadores. Si veis el código, simplemente se le pasa al método change una función anónima con el código a ejecutar cuando se lance el evento change: una llamada a una función fillCombo  que definiremos luego. A esta fillCombo le pasamos el id de la combo que vamos a actualizar y el valor seleccionado de la combo de continentes. Fijaos que de nuevo usamos jQuery para saber el valor seleccionado de la combo: obtenemos un manejador jQuery a la combo y llamamos al método val().

Bien, vamos ahora a ver el código de la función fillCombo. Esta función coje dos parámetros updateId y value (el id de la combo a rellenar y el valor seleccionado de la primera combo):

<script type="text/javascript">
    function fillCombo(updateId, value) {
        $.getJSON("<%= Url.Action("PaisesPorContinente") %>" 
+ "/" + value, function(data) { $("#"+updateId).empty(); $.each(data, function(i, item) { $("#"+updateId).append("<option id='"
+ item.IdPais +"'>" + item.Nombre
+ "</option>"); }); }); } </script>

Veamos que hace exactamente fillCombo:

  • Usa la función getJSON de jQuery. Esta función realiza una llamada a la URL indicada (en mi caso una llamada a la acción PaisesPorContinente), usando AJAX y espera que dicha función devuelva un resultado en formato JSON. Además parsea el resultado JSON y lo convierte en un objeto javascript. Para los que no sepáis que es JSON os diré que es un lenguaje para representar objetos (tal y como puede ser XML) pero mucho más compacto, y además más “compatible” con javascript. Para más información pasaros por json.org.
  • La función getJSON admite dos parámetros: el primero es la URL y el segundo una función a ejecutar cuando se reciba el resultado… Para variar le pasamos una función anónima :)
  • Y la función anónima qué hace?
    • Pues bueno, primero vacía la combo indicada (llama al método empty() que lo que hace es vaciar el árbol DOM de un determinado elemento, por lo que aplicado a un <select> le elimina todas sus <option>).
    • Despues usa el método each de jQuery para iterar sobre la respuesta (asume que lo que se recibe es un array JSON). El método each recibe dos parámetros: el primero es la variable sobre la que iteramos y el segundo la función a ejecutar por cada elemento. En mi caso otra función anónima que usa el método append para añadir una etiqueta <option> a la combo (accediendo a las propiedades IdPais y Nombre de cada elemento del array JSON).

Bueno… hemos terminado la parte de cliente… veamos que debemos hacer en el servidor.

En primer lugar debemos crear la acción PaisesPorContinente en nuestro controlador: Una acción es un método público del controlador, y el nombre del método define el nombre de la acción, aunque esto se puede modificar con el uso del atributo ActionNameAttribute:

[ActionName("PaisesPorContinente")]
public ActionResult GetPaisesPorContinente(string id)
{
    var  paises = new PaisesModel().GetPaisesPorContinente(id);
    return new JsonResult() { Data = paises };
}

Accedemos al modelo y le pedimos que nos devuelva los paises del continente según el id indicado. Luego debemos devolver el resultado de la petición, que ahora no es código html, sinó código JSON. Por suerte la gente del framework MVC sabe que JSON se usa constantemente en AJAX, así que han implementado un serializador para convertir objetos de .NET en objetos JSON. Para devolver un objeto serializado en JSON simplemente devolved un JsonResult con la propiedad Data establecida al objeto que quereis devolver… y listos.

El código de mi modelo es muy simple:

public class Pais
{
    public string Continente { get; set; }
    public string Nombre { get; set; }
    public string IdPais { get; set; }
}

public class PaisesModel
{
    private List<Pais> paises;
    public PaisesModel()
    {
        this.paises = new List<Pais>();
        this.paises.Add(new Pais()
        {
            Continente = "eur",
            IdPais = "es",
            Nombre = "España"
        });
        // Más y más paises añadidos...
    }

    public IEnumerable<Pais> GetPaisesPorContinente
(string continente) { return this.paises.FindAll
(x => x.Continente == continente); } }

Simplemente devuelve una lista de objetos de la clase Pais. Fijaos como la clase Pais tiene las propiedades IdPais y Nombre que usábamos dentro de fillCombo! Y ya hemos terminado! Si quereis ver el proyecto completo, os lo podeis descargar de aquí.

Qué… fácil, no???

Un saludo a todos! ;-)

pd: Por cierto, si vais a trabajar con combos y jQuery os recomiendo que le echeis un vistazo al plugin de jQuery para selectboxes, que añade funciones específicas para trabajar con selects (sí, sí… jQuery puede extenderse con plugins que le añaden todavía más funcionalidades!).

con 15 comment(s)
Archivado en:

Hola a todos amigos! ;-)

El comentario de Gabriel en este post de mi blog (http://geeks.ms/blogs/etomas/archive/2009/04/02/asp-net-mvc-controles-chart-y-ajax.aspx) me ha motivado a escribir la siguiente entrada.

Él preguntaba sobre si los controles Ajax de ASP.NET, como p.ej. UpdatePanel se podían usar bajo el framework MVC. No conozco mucho los controles de la Ajax Library porque personalmente no me interesan demasiado, aunque apuesto que la mayoría usan viewstate así que me imagino que no deben poder usarse bajo MVC…

… por otro lado sobreentiendo que la duda de Gabriel, va un poco más allá y quiere saber como usar Ajax con el framework MVC. Pues para verlo muy someramente aquí va esta entrada.

El soporte para Ajax del framework MVC no es que sea espectacular: las vistas tienen una propiedad Ajax que permite acceder al objeto AjaxHelper, que contiene varios métodos para permitir usar Ajax en el framework MVC. P.ej. si quieres poner un link que al pulsarlo cargue el div cuyo id sea "ajaxpanel” puedes hacer:

<%=Ajax.ActionLink("Pulsa aquí", "view1", new AjaxOptions() 
{ UpdateTargetId="ajaxpanel"}) %>

Al pulsar sobre el enlace, el framework MVC ejecutará la acción “view1” (del controlador actual) y la vista que esta acción devuelva será incrustada dentro del elemento DOM cuyo id sea ajaxpanel.

No está mal, pero tiene dos pegas:

  1. De serie no nos viene nada más: si queremos usar imágenes que sean enlaces Ajax, o botones, o una combo, debemos hacerlo nosotros.
  2. Usa la librería propia de Ajax de MS (MicrosoftAjax.js y MicrosoftMvcAjax.js)… No tengo nada personal en contra de esta librería, pero no veo muy claro a que viene: ¿teniendo jQuery, para que reinventar la rueda?

Yo personalmente uso sólo jQuery para mi soporte Ajax… ¿Por qué? Pues porqué me da todo lo que necesito… y más. Además de que es cross-browser y no sólo ofrece temas para Ajax, sino para muchas otras cosas. Si estás desarrollando web y no usas jQuery cuando empieces te preguntarás como has podido estar sin ella todo este tiempo.

Usar jQuery implica que no usamos las funciones del AjaxHelper, pero bueno no importa demasiado: rehacerlas usando jQuery no cuesta nada… ah! Y además jQuery ya viene con el framework MVC (en la carpeta /scripts).

En esta demo, veremos como hacer en un momento una vista con dos botones: al pulsar cada uno de ellos se cargará via Ajax una vista y se incrustará dentro de un div.

1. Inicio: Modificación de la vista master

Creamos una nueva aplicación ASP.NET MVC. Ello nos creará la aplicación inicial, con varias vistas (login, home, registro) para empezar a trabajar. Vamos a modificar directamente la vista inicial (Views/Home/Index.aspx).

Lo primero a hacer es incluir la carga de jQuery en nuestra vista. En mi caso modifico la vista master, para que jQuery esté incluida “de serie” en todas mis vistas… yo la uso a mansalva, y creedme: vais a terminar haciendo lo mismo :)

La vista master está en Views/Shared/Site.Master. Si la abrís vereis que tiene un poco de código. Podeis obviarlo, simplemente añadid una etiqueta <script> dentro del <head>:

<head runat="server">
    <title><asp:ContentPlaceHolder ID="TitleContent" runat="server" />
</
title> <link href="../../Content/Site.css"
rel="stylesheet" type="text/css" /> <%-- Incluimos jQuery --%> <script type="text/javascript" src="../../Scripts/jquery-1.3.2.js">
</
script>
</head>

(En este caso incluyo jquery-1.3.2.js que es la versión que viene junto con el framework MVC).

2. Modificación de la vista inicial

Como comenté en el punto anterior vamos a trabajar modificando directamente la vista inicial (Views/Home/Index.aspx). Para ello vamos a añadir simplemente dos botones y un <div> vacío que será nuestro contenedor ajax:

<%@ Page Language="C#" MasterPageFile="~/Views/Shared/Site.Master" 
Inherits="System.Web.Mvc.ViewPage" %> <asp:Content ID="indexTitle" ContentPlaceHolderID="TitleContent"
runat="server"> Home Page </asp:Content> <asp:Content ID="indexContent" ContentPlaceHolderID="MainContent" runat="server"> <h2>Pulsa los botones para refrescar el div usando ajax</h2> <input type="button" id="view1" value="Vista 1" /> <input type="button" id="view2" value="Vista 2"/> <%-- Este es el div que vamos a modificar via ajax --%> <div id="ajaxpanel" style="border-color:red; border-style:solid;
border-width:thin"></div> </asp:Content>

He puesto un border rojo al div para que vea (sí, reconozco que el diseño no es mi fuerte :p).

2.1 Crear las llamadas Ajax al pulsar los botones

Ahora vamos a añadir código javascript para que cuando se pulsen los botones se hagan las llamadas ajax… Si ya estás corriendo a meter un onClick en cada <input> tranquilo: bienvenido al mundo de jQuery :)

Este es el código javascript que puedes colocar en tu página (justo antes del tag <h2>, dentro del asp:Content IndexContent):

<script type="text/javascript">
    $(document).ready(function() {
        $("#view1").click(function() {
            $("#ajaxpanel").load("<%=Url.Action("View1") %>");
        });
        $("#view2").click(function() {
            $("#ajaxpanel").load("<%= Url.Action("View2") %>");
        });
    });
</script>

Vamos a comentar rápidamente este código… aunque es muy simple muestra dos conceptos clave de jQuery: los selectores y la potencia de sus funciones incorporadas.

El $ es el simbolo “jQuery” por excelencia. $(document) me devuelve un manejador al documento actual. La función ready() espera como parámetro otra función que se ejecutará cuando el documento esté cargado y todos los objetos DOM existan.

En mi caso le paso a la función ready una función anónima que hace lo que a mi me interesa que se haga cuando el documento esté cargado: crear funciones gestoras de los click de los botones para que se llame via Ajax a otras URLs.

El código $("#view1") es un selector: los selectores son una de las claves de jQuery, ya que permiten obtener un manejador jQuery a uno o varios objetos DOM para realizar tareas con ellos. Aquí estoy usando uno de los más simples, el # que devuelve un manejador al objeto DOM cuyo ID sea la cadena que hay después de #. Así $(“#view1”) me devuelve un manejador al objeto DOM cuyo id sea “view1”, que en mi caso es el primer boton.

Una vez tengo un manejador de jQuery puedo hacer barbaridad de cosas con él (que afectarán al objeto (u objetos) DOM a los que apunte dicho manejador). En mi caso llamo a la función click que espera como parámetro otra función. La función click lo que hace es ejecutar la función que se pase como parámetro cuando se lance el evento click del elemento DOM subyacente… Y que le paso como parámetro a la función click? Pues otra función anónima con el código a ejecutar cuando se lance el evento. ¿Y qué codigo es este? Pues usar un selector para obtener un manejador al objeto cuyo ID sea “ajaxpanel” (que es el <div>) y llamar el método load. El método load usa Ajax para cargar la URL especificada y incrustar el resultado dentro del DOM del manejador usado. Es decir, en nuestro caso dentro del div.

Ya casi estamos…

3. Crear las acciones en el controlador

En ASP.NET MVC las URLs se mapean a acciones de los controladores, no a archivos físicos .aspx. Si os fijais en el codigo de la vista se ha usado <%= Url.Action(“xxx”) %> para pasar la URL al método load de jQuery. Esta llamada a URL.Action obtiene la URL de la acción indicada del controlador actual. En mi caso he llamado a las acciones “View1” y “View2”.

Dado que estamos en una vista gestionada por el controlador Home, debemos crear las acciones en el controlador HomeController. La forma más simple de crear una acción es definir un método público en dicho controlador. Así, pues definimos los métodos View1 y View2 en HomeController (que está dentro de la carpeta Controllers):

public ActionResult View1()
{
    return PartialView("View1");
}
public ActionResult View2()
{
    return PartialView("View2");
}

En este caso las dos acciones son bastante tontas: el controlador no hace nada salvo retornar dos vistas, llamadas View1 y View2. El método PartialView se usa cuando lo que se va a devolver es una vista parcial, esto es una vista que no es un HTML completo sino una parte.

Solo nos queda una pequeña cosa… crear las dos vistas!

4. Crear las dos vistas parciales

Vamos a crear las dos vistas que necesitamos. Dado que estamos creando vistas del controlador Home, debemos crearlas dentro de Views/Home. Para ello hacemos click con el botón derecho en la carpeta Home en el Solution Explorer y seleccionamos la opción Add->View. Esto nos despliega el cuadro de nueva vista de ASP.NET MVC.

image

Damos nombre a la vista (View1) y marcamos la checkbox de “Create a partial view”. Con ello nos va a crear un archivo .ascx, en lugar de un .aspx (que sería una página completa).

Yo he añadido el siguiente código a mi vista View1:

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl" %>
<h1>Esta es la vista1</h1>

Ya veis… poca cosa, no?

Repetid el proceso para crear la View2 y… ya habeis terminado.

Ejecutad el proyecto y os aparecerá la página principal, con los dos botones. Pulsad uno u otro y observad como el div se rellena via Ajax con el código de cada vista.

En la última imagen os dejo una captura con el firebug donde se ve la última petición ajax:

image

Esto ha sido una muy breve introducción… pero espero que os haya servido para que os entre el gusanillo de ASP.NET MVC!!! ;-)

Saludos!

con 18 comment(s)
Archivado en:

Hola... qué tal?

Imagina que en algún proyecto que estés haciendo, quieres ofrecer una combo para seleccionar colores. De acuerdo, ya se que hay otros métodos para hacer que el usuario seleccione un color, como usar el ColorDialog, pero a lo mejor te interesa que el usuario sólo pueda escoger colores de una lista predeterminada...

Por suerte en .NET hacer que una combo dibuje sus elementos como nosotros queremos, es realmente simple... ¿quieres tener una combo como esta?

image

Pues, ya verás que fácil... apenas cinco líneas de código, y será tuya...

Paso 1: Añadir la combo al formulario

Añade una ComboBox normal a tu proyecto, y establece las propiedades DropDownStyle a DropDownList (para que el usuario no pueda teclear valores nuevos) y DrawMode a OwnerDrawFixed.

La propiedad DrawMode es la clave: Puede tener tres valores, que son "Normal" (el sistema operativo se encarga de dibujar la combo), "OwnerDrawFixed" (nosotros nos encargamos de dibujar cada elemento) y "OwnerDrawVariable" (nosotros nos encargamos de dibujar cada elemento y además cada elemento puede tener un tamaño distinto).

Paso 2: Código para dibujar cada elemento

Crea una función gestora para el evento DrawItem: la combo lanza este evento cada vez que debe dibujar un elemento. La función gestora recibe un objeto DrawItemEventArgs que contiene toda la información que necesitamos para poder "dibujar" el elemento: de que elemento se trata, el contexto gráfico a usar y las coordenadas que ocupa el rectángulo del elemento dentro del contexto gráfico de la combo...

Las primeras líneas de la función gestora serían las siguientes:

ComboBox cmb = sender as ComboBox;
if (cmb == null) return;
if (e.Index < 0) return;
if (!(cmb.Items[e.Index] is Color)) return;
Color color = (Color)cmb.Items[e.Index];

Aquí simplemente estamos asegurándonos de que el elemento que vamos a dibujar sea un objeto Color. Para ello accedemos a la propiedad Index del objeto DrawItemEventArgs que recibimos que nos indica el índice del elemento que estamos dibujando.

Ahora solo nos queda dibujar el elemento: un rectángulo con los bordes negros y rellenado del color indicado, junto al nombre del color:

Brush brush = new SolidBrush(color);
e.Graphics.DrawRectangle(
Pens.Black,
new Rectangle(e.Bounds.Left + 2, e.Bounds.Top + 2, 19,
e.Bounds.Size.Height - 4));
e.Graphics.FillRectangle(brush,
new Rectangle(e.Bounds.Left + 3, e.Bounds.Top + 3, 18,
e.Bounds.Size.Height - 5));
e.Graphics.DrawString(color.Name, cmb.Font,
Brushes.Black, e.Bounds.Left + 25, e.Bounds.Top + 2);
brush.Dispose();

El primer DrawRectangle dibuja el borde del rectángulo, el FillRectangle pinta su interior y el DrawString escribe el nombre del color. Finalmente recordad que debemos hacer dispose de cualquier objeto GDI+ que usemos.

Como se puede observar, apenas cinco líneas de codigo GDI+.

Paso 3: Perfeccionando el tema...

La combo ya es 100% funcional, pero si la usais vereis que "no resalta" en azul el elemento seleccionado, tal y como hace por defecto la combobox... Evidentemente no lo hace porque no hemos puesto código para ello, pero tranquilos: son apenas tres líneas de código.

El propio objeto DrawItemEventArgs tiene todo lo que necesitamos para poder hacerlo:

  1. El método DrawBackground() que dibuja el fondo del elemento actual: en blanco en azul en función de si tiene el cursor del ratón encima o no.
  2. La propiedad ForeColor que nos devuelve el color con el que dibujar el elemento actual (por defecto es negro si no tiene el cursor del ratón encima o blanco en caso contrario).
  3. La propiedad BackColor que nos devuelve el color que se usa en el método DrawBackground.

El código final para dibujar nuestra combo es el siguiente:

private void cmbColor_DrawItem(object sender, 
DrawItemEventArgs e)
{
ComboBox cmb = sender as ComboBox;
if (cmb == null) return;
if (e.Index < 0) return;
if (!(cmb.Items[e.Index] is Color)) return;
Color color = (Color)cmb.Items[e.Index];
// Dibujamos el fondo
e.DrawBackground();
// Creamos los objetos GDI+
Brush brush = new SolidBrush(color);
Pen forePen = new Pen(e.ForeColor);
Brush foreBrush = new SolidBrush(e.ForeColor);
// Dibujamos el borde del rectángulo
e.Graphics.DrawRectangle(
forePen,
new Rectangle(e.Bounds.Left + 2, e.Bounds.Top + 2, 19,
e.Bounds.Size.Height - 4));
// Rellenamos el rectángulo con el Color seleccionado
// en la combo
e.Graphics.FillRectangle(brush,
new Rectangle(e.Bounds.Left + 3, e.Bounds.Top + 3, 18,
e.Bounds.Size.Height - 5));
// Dibujamos el nombre del color
e.Graphics.DrawString(color.Name, cmb.Font,
foreBrush, e.Bounds.Left + 25, e.Bounds.Top + 2);
// Eliminamos objetos GDI+
brush.Dispose();
forePen.Dispose();
foreBrush.Dispose();
}

Y listos!! Nuestra combo ya está lista para usar. Podeis meterle algunos colores para probar:

cmbColor1.Items.Add(Color.Black);
cmbColor1.Items.Add(Color.Blue);
cmbColor1.Items.Add(Color.Red);
cmbColor1.Items.Add(Color.White);
cmbColor1.Items.Add(Color.Pink);
cmbColor1.Items.Add(Color.Green);

Y este es el resultado final:

image

¿No está nada mal, eh?

Saludos a todos!

con 3 comment(s)
Archivado en: ,

A veces hay aspectos de C# que no pensamos hasta que nos encontramos con ellos… A mi me pasó con un código parecido a este:

class Program
{
    static void Main(string[] args)
    {
        Baz baz = new BazDerived();
        new Foo().Bar(baz);
        Console.ReadLine();
    }
}
class Foo
{
    public void Bar<T>(T t)
    {
        Console.WriteLine("Bar<T> typeof(T) = " + typeof(T).Name);
    }
    public void Bar(object o)
    {
        Console.WriteLine("Bar o.GetType() = " + o.GetType().Name);
    }
}
class Baz { }
class BazDerived : Baz { }

La pregunta seria: ¿cual es la salida por pantalla de este programa?

Pues… esta es la respuesta (para los que querais pensar un poco la he puesto en negro sobre negro… seleccionad el texto para verlo):

Bar<T> typeof(T) = Baz

La verdad es que tiene toda su lógica… lo que personalmente no me gusta nada es que este código compile sin generar ningún warning. ¿Que opinais vosotros?

Saludos!!!

con 4 comment(s)
Archivado en:

Imaginaos que teneis una solución con varios proyectos, y que estos compilan en un directorio concreto, llamésmole Q:\bin.

En otra solución teneis varios proyectos más, con referencias a los assemblies que estan en Q:\bin (no son referencias de proyecto porque estan en distintas soluciones).

Y ya puestos, imaginad también que estáis usando Code Contracts. Y cuando compilais los proyectos de la segunda solución visual studio se descuelga con un bonito error:

Unresolved assembly reference not allowed: assembly_que_esta_en_Q_bin.dll y el “fichero” que genera el error es ccrewrite.

La solución es ir al proyecto que no compila, abrir sus propiedades y en la pestaña Code Contracts añadir el directorio Q:\bin a la entrada “Lib Paths”:

image

Con esto el proyecto debería funcionar correctamente!

La solución gracias a http://social.msdn.microsoft.com/Forums/en-US/codecontracts/thread/7225dc8d-7005-4da7-8a39-688e5b766434

Saludos!

pd1: Que hace la entrada “Lib Paths” de la pestaña Code Contracts? Pues añade los directorios especificados al parámetro /libpaths: de ccrewrite

pd2: Que es ccrewrite? Es el MSIL rewriter de code contracts, el encargado de “mover” todas las precondiciones al principio de todo de nuestro método y de “mover” las postcondiciones justo al final de nuestro método.

con no comments
Archivado en:

Hola a todos… después de que Jorge (en http://geeks.ms/blogs/jorge/archive/2009/04/26/precondiciones-y-microsoft-code-contracts.aspx) yo mismo (en http://geeks.ms/blogs/etomas/archive/2009/05/04/pexcando-errores-en-nuestro-c-243-digo.aspx) comentasemos algo de Code Contracts, voy a comentar algunas cosillas más que me he encontrado con Code Contracts usándolos en un proyecto real.

Aunque están en fase “beta”, la tecnología está lo suficientemente madura para ser usada en proyectos “reales”, al menos teniendo en cuenta de que las builds donde suelen activarse todos los contratos son las builds de debug. En nuestro caso tenemos activados todos los contratos en debug y sólo las precondiciones en release.

Precondiciones

La última versión de Code Contracts salió el 20 de mayo, y una de las principales diferencias es que la excepción de violación de contrato (ContractException) ha dejado de ser pública para ser interna. Por ello en lugar de Contract.Requires() es mejor usar Contract.Requieres<TEx>() que lanza una excepción de tipo TEx en caso de que la condición no se cumpla:

public double Sqrt(double d)
{
    Contract.Requires<ArgumentException>(d >= 0, "d");
}

Si la precondición no se cumple la excepción que se generará será una ArgumentException en lugar de la interna ContractException.

Recordad que fundamentalmente las precondiciones vienen a decir que nosotros como desarrolladores de una clase, no nos hacemos responsables de lo que ocurra si el cliente no cumple el contrato… por lo tanto es de cajón que el cliente debe poder comprobar si su llamada cumple las precondiciones o no. O dicho de otro modo: las precondiciones de un método público deben ser todas ellas públicas. Esto no es correcto:

class Foo
{
    List<int> values;
    public Foo()
    {
        this.values = new List<int>();
    }
    public void Add(int value) 
    {
        Contract.Requires<ArgumentException>
            (!values.Contains(value));
        values.Add(value);
    }       
}

Como va a comprobar el cliente que su llamada a Add cumple el contrato, si no le damos ninguna manera de que pueda validar que el parámetro que pase no esté en la lista? Lo suyo es hacer algo así como:

class Foo
{
    List<int> values;
    public IEnumerable<int> Values {
        get { return this.values; }
    }
    public Foo()
    {
        this.values = new List<int>();
    }
    public void Add(int value) 
    {
        Contract.Requires<ArgumentException>
            (!Values.Contains(value));
        values.Add(value);
    }       
}

Interfaces

Métodos que implementen métodos de una interfaz NO pueden añadir precondiciones. Es decir, esto no compila si teneis los contratos activados:

interface IFoo
{
    double Sqrt(double d);
}

class Foo : IFoo
{
    public double Sqrt(double d)
    {
        Contract.Requires<ArgumentException>(d >= 0, "d");
        return Math.Sqrt(d);
    }
}

El compilador se quejará con el mensaje: Interface implementations (ConsoleApplication7.Foo.Sqrt(System.Double)) cannot add preconditions. La razón de esto es que las precondiciones deben añadirse a nivel de interfaz y no a nivel de implementación, por la razón de que la interfaz es en muchos casos lo único que vamos a hacer público.

Dado que C# no nos deja meter código en una interfaz, la sintaxis para definir las precondiciones de una interfaz es un poco… curiosa: consiste en declarar una clase que no hace nada salvo contener las precondiciones, y usar un par de atributos (ContractClass y ContractClassFor) para vincular esta clase “de contratos” con la interfaz:

[ContractClass(typeof(IFooContracts))]
interface IFoo
{
    double Sqrt(double d);
}
[ContractClassFor(typeof(IFoo))]
class IFooContracts : IFoo
{
    double IFoo.Sqrt(double d)
    {
        Contract.Requires<ArgumentException>(d >= 0, "d");
        return default(double);
    }
}
class Foo : IFoo
{
    public double Sqrt(double d)
    {
        return Math.Sqrt(d);
    }
}

La clase IFooContracts contiene sólo los contratos para la interfaz IFoo. El valor de retorno usado es ignorado (es sólo para que compile el código). Si ejecutais el código paso a paso, vereis que cuando haceis new Foo().Sqrt() se ejecutan primero los contratos definidos en IFooContracts.Sqrt y luego el código salta al método Foo.Sqrt.

Code Contracts requiere que la implementación de la interfaz sea explícita.

Invariantes

El invariante de un objeto es un conjunto de condiciones que se cumplen siempre a lo largo del ciclo de vida del objeto (excepto cuando es destruído). A nivel de contratos esto significa que son condiciones que se evalúan inmediatamente después de cualquier método público, y que todas ellas deben cumplirse. Si alguna de ellas falla, el invariante se considera incumplido y el contrato roto.

Los invariantes se declaran en un método de la clase decorado con el atributo ContractInvariantMethodAttribute y consisten en varias llamadas a Contract.Invariant con las condiciones que deben cumplirse:

class Foo
{
    public int Value { get; private set;}
    public void Inc()
    {
        this.Value++;
    }
    public void Dec()
    {
        this.Value--;
    }
    [ContractInvariantMethod]
    protected void ObjectInvariant()
    {
        Contract.Invariant(this.Value > 0);
    }
}

En este código hemos definido que el invariante de Foo es que el valor de la propiedad Value debe ser siempre mayor o igual a cero. Esta condición se evaluará al final de cualquier método público de Foo. Por lo tanto en el siguiente código:

Foo foo = new Foo();
foo.Inc();
foo.Dec();
foo.Dec();

La segunda llamada a Dec() provocará una excepción de contrato.

Métodos puros

Un método “Puro” es aquel que no tiene ningún “side-effect”, es decir no modifica el estado de ninguno de los objetos que recibe como parámetro (incluyendo this). Un método puro se puede llamar infinitas veces con los mismos parámetros y se obtendran siempre los mismos resultados.

El código que se ejecuta para evaluar los contratos debe ser código puro. La razón principal es que los contratos pueden desactivarse en función del tipo de build, por lo que añadir código que no sea puro puede causar que el comportamiento cambie en función de si los contratos están o no habilitados. Si llamamos a un  método NO PURO desde un contrato nos va a salir un warning. Fijaos en el siguiente código:

[ContractClass(typeof(IFooContracts))]
interface IFoo
{
    IEnumerable<int> Values { get;}
    void Bar(int value);
}
[ContractClassFor(typeof(IFoo))]
sealed class IFooContracts : IFoo
{
    private IEnumerable<int> values; // Para que compile
    IEnumerable<int> IFoo.Values { get { return values; } }
    void IFoo.Bar(int value)
    {
        Contract.Requires(CheckValue(value));
    }
    public bool CheckValue(int value)
    {
        return (value % 2) == 0 && !((IFoo)this).Values.Contains(value);
    }
}
class Foo : IFoo
{
    private readonly List<int> values;
    public IEnumerable<int> Values
    {
        get { return this.values; }
    }
    public Foo()
    {
        this.values = new List<int>();
    }
    public void Bar(int value)
    {
        this.values.Add(value);
    }
}

Antes que nada: que no os líe la variable privada IEnumerable<int> values declarada en IFooContracts: es simplemente para que compile el código, pero realmente nunca es usada… Realmente nunca se instancian (ni usan) objetos de las clases de contrato: en el contexto de ejecución del método CheckValue el valor de this NO es un objeto IFooContracts, si no un objeto Foo (mirad la imagen si no me creeis :p).

image

Bueno… que me desvío del tema. A lo que íbamos: La clase de contratos para  IFoo, define un método CheckValue que sirve para evaluar la precondición del método Bar. Si compilais os aparecerá un warning:

Detected call to impure method 'ConsoleApplication7.IFooContracts.CheckValue(System.Int32)' in a pure region in method 'ConsoleApplication7.IFooContracts.ConsoleApplication7.IFoo.Bar(System.Int32)'

Como sabe Code Contracts que mi método CheckValue no es puro? Pues simplemente porque yo no lo he declarado como tal. Para ello basta con decorarlo con el atributo Pure:

[Pure]
public bool CheckValue(int value)
{
    return (value % 2) == 0 && !((IFoo)this).Values.Contains(value);
}

Actualmente Code Contracts no comprueba que mi método que dice ser puro, lo sea de verdad… como en MS no se fían mucho de nosotros (los desarrolladores… ¿por que será? :p) están preparando mecanismos de validación de forma que cuando un método diga ser puro, lo sea efectivamente de verdad. A dia de hoy, tanto si poneis [Pure] como si no, funciona todo igual, así que el atributo Pure sirve para poco, al menos en lo que a ejecución se refiere. De todos modos creo que documentalmente es un atributo muy interesante: Indica que quien hizo el método lo hizo con la intención de que fuese puro, así que quizá debemos vigilar un poco las modificaciones que hagamos en este método…

Personalmente me hubiese encantado que Pure formase parte de C# a nivel de palabra reservada incluso… para que el compilador nos pudiese avisar si estamos modificando algún estado de algún objeto (o sea llamando a un método no-puro) desde un método puro. Pero si no nos quieren ni dar referencias const, mucho menos todavía nos van a dar esto… :(

Bueno… cierro el rollo aquí… han quedado bastantes cosillas de contracts pero espero que al menos esto os ayude y os anime a dar el paso de empezar a utilizarlos en vuestras aplicaciones, porque realmente creo que vale mucho la pena…

… y más cuando Sandcastle sea capaz de sacar la información de los contratos de cada clase en la documentación, tal y como parece ser intención de microsoft (http://social.msdn.microsoft.com/Forums/en-US/codecontracts/thread/cb5556e9-9dc9-45ed-8016-567294236af3).

Saludos!

con 2 comment(s)
Archivado en:

Que XmlSerializer es una clase curiosa es evidente, hay multitud de maneras de controlar la serialización de un objeto y varios trucos más o menos ocultos (os recomiendo el blog de jmservera que tiene algunos posts interesantes)…

… Lo que quiero comentaros ahora es un caso que me encontré el otro día (valeeee… ayer), en concreto con las auto-propiedades que se incorporaron en C# 3.0.

En la msdn se dice que XmlSerializer es capaz de serializar propiedades ICollection o IEnumerable que sean read-only (de hecho la regla CA2227 del análisis estático se basa en esto). Pues bien, esto es cierto si entendemos como read-only que no haya setter… ni tan siquiera privado.

Es decir, esto NO funciona:

class Program
{
    static void Main(string[] args)
    {
        XmlSerializer xml = new XmlSerializer(typeof(Foo));
        using (FileStream fs = new FileStream(@"C:\temp\foo.xml",
            FileMode.Open, FileAccess.ReadWrite, FileShare.None))
        {
            xml.Serialize(fs,new Foo());
            fs.Close();
        }

    }
}
public class Foo
{
    public Foo()
    {
        this.Data = new List<Bar>();
        this.Data.Add (new Bar());
        this.Data.Add (new Bar());
    }

    public List<Bar> Data { get; private set; }
    int OtherData;
}
public class Bar {}

Si intentamos serializar (o deserializar da igual) recibimos una System.InvalidOperationException (con un mensaje "No se puede generar una clase temporal (result=1). error CS0200: No se puede asignar la propiedad o el indizador 'ConsoleApplication6.Foo.Data' (es de sólo lectura)".

Si transformamos la clase Foo al estilo de C# 2.0 sin usar auto-propiedades, el código funciona, siempre que no pongamos el setter privado…

… como mínimo curioso, no?

Es evidente que cuando se hizo XmlSerializer nadie pensó en un setter de propiedad privado (ya que entonces lo usual era no poner setters privados en propiedades read-only), pero con la aparición de las auto-propiedades en C# 3.0 estos son cada vez más frecuentes… así que igual habría que actualizar XmlSerializer, porque que me obliguen a usar el estilo de C# 2.0 para poder serializar las propiedades de colección no es que me guste especialmente.

Y quizá, ya puestos a pedir, C# debería incorporar auto-propiedades read-only sin necesidad de poner el setter privado y que tuviesen la misma semántica que las variables readonly: sólo podrían ser inicializadas en el constructor. Esto permitiría también que el CLR realizara determinadas optimizaciones…

Saludos!! ;-)

con 5 comment(s)
Archivado en: