José F. Romaniello

Las aventuras y desventuras de un codificador.

Este es el primer post de una serie que voy a escribir, explicando distintos patrones de Presentación para aplicaciones de escritorio, los cuales en general permiten la separación de responsabilidades. Dicha separación nos permitirá escribir tests de los diferentes artefactos.

En esta entrada voy a hablar puntualmente del patrón Passive View definido por Martin Fowler y voy a mostrar una implementación para un caso de uso sencillo. Hacia al final del artículo he dejado el link para descargar el código completo del ejemplo.

En este patrón el View expone una serie de métodos los cuales permiten al Presenter manejar cada uno de sus widgets (controles) y  sus respectivas propiedades. Una característica importante de este patrón es que el View no conoce ningún Modelo.

Imaginemos un caso de uso para editar los datos de un cliente, al iniciar la pantalla tenemos que mostrar los datos actuales del cliente, luego el usuario realizará modificaciones y guardará o cancelará.

Esta sería nuestra pantalla:

image

La interfaz del artefacto View se podría definir de la siguiente manera:

public interface IEditarClienteView
{
    string Nombre { get; set; }
    string Apellido { get; set; }
    string CodigoPais { get; set; }

    void CargarPaisParaSeleccion(string codigoIso, string nombre);

    event EventHandler GuardarClick;
    event EventHandler CancelarClick;
    
    void Close();
    void Show();
}

El Presenter sería algo así:

public class EditarClientePresenter
{
    private readonly IEditarClienteView editarClienteView;
    private readonly IRepositorioClientes repositorioClientes;
    private readonly IRepositorioPaises repositorioPaises;
    private int clienteEnEdicion;

    public EditarClientePresenter(IEditarClienteView editarClienteView, 
        IRepositorioClientes repositorioClientes, 
        IRepositorioPaises repositorioPaises)
    {
        this.editarClienteView = editarClienteView;
        this.repositorioClientes = repositorioClientes;
        this.repositorioPaises = repositorioPaises;
        editarClienteView.GuardarClick += EditarClienteViewGuardarClick;
        editarClienteView.CancelarClick += EditarClienteViewCancelarClick;
    }

    public void Iniciar(int idCliente)
    {
        clienteEnEdicion = idCliente;
        foreach (var pais in repositorioPaises.ObtenerOrdenadosPorNombre())
        {
            editarClienteView.CargarPaisParaSeleccion(pais.CodigoIso, pais.Nombre);
        }

        var cliente = repositorioClientes.Obtener(idCliente);
        
        editarClienteView.Nombre = cliente.Nombre;
        editarClienteView.Apellido = cliente.Apellido;
        editarClienteView.CodigoPais = cliente.Pais.CodigoIso;

        editarClienteView.Show();
    }

    private void EditarClienteViewCancelarClick(object sender, EventArgs e)
    {
        editarClienteView.Close();
    }

    private void EditarClienteViewGuardarClick(object sender, EventArgs e)
    {
        var cliente = repositorioClientes.Obtener(clienteEnEdicion);
        cliente.Nombre = editarClienteView.Nombre;
        cliente.Apellido= editarClienteView.Apellido;
        cliente.Pais = repositorioPaises.Obtener(editarClienteView.CodigoPais);
    }
}

Cabe mencionar que este presenter, así como la interfaz del view, nace a partir de una serie de tests que fui escribiendo al principio. Esta técnica se denomina comúnmente TDD, aunque sus derivados como BDD también son válidos. Esta es la serie de tests que definieron mi implementación:

using System;
using Moq;
using NUnit.Framework;
using PassiveView.Dominio;
using PassiveView.Repositorios;
using SharpTestsEx;

namespace PassiveView.Tests
{
    [TestFixture]
    public class EditarClientePresenterTests
    {
        public IRepositorioPaises CrearDobleDeRepositorioPaises()
        {
            var repositorioPaises = new Mock<IRepositorioPaises>();
            repositorioPaises.Setup(r => r.ObtenerOrdenadosPorNombre())
                .Returns(new[]
                             {
                                 new Pais {CodigoIso = "AR", Nombre = "Argentina"},
                                 new Pais {CodigoIso = "UY", Nombre = "Uruguay"}
                             });

            return repositorioPaises.Object;
        }

        private static IRepositorioClientes CrearDobleRepositorioClientes()
        {
            var repositorio = new Mock<IRepositorioClientes>();
            var cliente = new Cliente
                                    {
                                        Nombre = "Jose",
                                        Apellido = "Romaniello",
                                        Pais = new Pais {CodigoIso = "AR", Nombre = "Argentina"}
                                    };
            repositorio.Setup(r => r.Obtener(1)).Returns(cliente);
            return repositorio.Object;
        }

        [Test]
        public void AlIniciarCargarPaises()
        {
            var view = new Mock<IEditarClienteView>();

            var presenter = new EditarClientePresenter(
                view.Object,
                CrearDobleRepositorioClientes(),
                CrearDobleDeRepositorioPaises());

            presenter.Iniciar(1);

            view.Verify(v => v.CargarPaisParaSeleccion("AR", "Argentina"));
            view.Verify(v => v.CargarPaisParaSeleccion("UY", "Uruguay"));
        }

        [Test]
        public void CuandoInicioCargoLosDatosDelCliente()
        {
            var view = new Mock<IEditarClienteView>();
            view.SetupAllProperties();

            var presenter = new EditarClientePresenter(
                view.Object,
                CrearDobleRepositorioClientes(),
                CrearDobleDeRepositorioPaises());

            presenter.Iniciar(1);

            view.Object.Satisfy(v => v.Apellido == "Romaniello"
                                     && v.Nombre == "Jose"
                                     && v.CodigoPais == "AR");
        }

        [Test]
        public void CuandoInicioMostrarForm()
        {
            var view = new Mock<IEditarClienteView>();
            view.SetupAllProperties();

            var presenter = new EditarClientePresenter(
                view.Object,
                CrearDobleRepositorioClientes(),
                CrearDobleDeRepositorioPaises());

            presenter.Iniciar(1);

            view.Verify(v => v.Show());
        }

        [Test]
        public void CuandoGuardoCargarLosValoresDeLaPantalla()
        {
            var view = new Mock<IEditarClienteView>();
            view.SetupAllProperties();

            var repositorioClientes = CrearDobleRepositorioClientes();
            var cliente = repositorioClientes.Obtener(1);

            var presenter = new EditarClientePresenter(
                view.Object,
                repositorioClientes,
                CrearDobleDeRepositorioPaises());

            presenter.Iniciar(1);

            view.Object.Nombre = "Pedro";

            view.Raise(v => v.GuardarClick += null, EventArgs.Empty);

            cliente.Nombre.Should().Be.EqualTo("Pedro");

        }
        [Test]
        public void CuandoCanceloCierroElForm()
        {
            var view = new Mock<IEditarClienteView>();
            view.SetupAllProperties();

            
            var presenter = new EditarClientePresenter(
                view.Object,
                CrearDobleRepositorioClientes(),
                CrearDobleDeRepositorioPaises());

            view.Raise(v => v.CancelarClick += null, EventArgs.Empty);

            view.Verify(v => v.Close());
        }


    }
}

La implementación del view es algo que he hecho al final, y es la siguiente:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows.Forms;

namespace PassiveView
{

    public partial class FormEditarCliente : Form, IEditarClienteView
    {
        private readonly BindingList<KeyValuePair<string, string>>  paises 
                = new BindingList<KeyValuePair<string, string>>();

        public FormEditarCliente()
        {
            InitializeComponent();

            cmbPaises.DataSource = paises;
            cmbPaises.DisplayMember = "Value";
            cmbPaises.ValueMember = "Key";
        }

        public string Nombre
        {
            get { return tNombre.Text; }
            set { tNombre.Text = value; }
        }

        public string Apellido
        {
            get { return tApellido.Text; }
            set { tApellido.Text = value; }
        }

        public string CodigoPais
        {
            get { return cmbPaises.SelectedValue.ToString(); }
            set { cmbPaises.SelectedValue = value; }
        }

        public void CargarPaisParaSeleccion(string codigoIso, string nombre)
        {
            paises.Add(new KeyValuePair<string, string>(codigoIso, nombre));
        }

        public event EventHandler GuardarClick
        {
            add { btAceptar.Click += value; }
            remove { btAceptar.Click -= value; }
        }

        public event EventHandler CancelarClick
        {
            add { btCancelar.Click += value; }
            remove { btCancelar.Click -= value; }
        }
    }
}

Este código esta escrito en el código behind del formulario.

Conclusiones

Este patrón tiene la ventaja de lograr una muy buena separación entre Model View y Presenter, nuestro código de view es relativamente sencillo y el view hace muy pocas cosas, por lo tanto podríamos decir que el patrón logra muy bien su objetivo.

Como desventaja podríamos decir, que en este caso de uso sencillo se puede observar que estamos desaprovechando las características de databinding que winforms nos ofrece, haciendo que la interfaz de nuestra View sea muy pesada (muchos métodos) y el mapeo entre propiedades y controles puede llegar a ser tedioso.

El código completo de este sencillo ejemplo puede ser descargado de aquí.

| More

Sometime ago I noticed that Visual Studio has a very nice feature that allows you to nest any file in any other. From the point of view of the Solution Explorer any file can be child of any other, that's the way that generated code work for WPF, Winforms, and even ASP.Net files.

But there is not an easy way to use this feature arbitrary on any type of file, or classes and classes, so why not build a VSPackage?

Well, this is the result of few hours of thinking.

Step 1 : Select the files to nest

First child, next the root is the preferred way.

1-SelectFiles

 

Step 2 : Nest in!

2-NestIn

 

Step 3 : Select the root or use the suggested

3-SelectTheRoot-OrUseTheDefault

 

done!

4-You-Are-Done

 

FAQ

Do I need this extension to see the files nested?
You don’t, this extension use a pretty standard feature of visual studio. The extension is only for doing the nest.

Where do I get this thing?
Download the bits right from the Visual Studio gallery!
The source code is here.

| More

I will show you in this post some internals of my HqlAddin project. The scenario is that I wanted to add highlighting for specific some specific things when editing hbm.xml files, and inside the query tag:

image

The first thing we need is a Format like this:

/// <summary>
/// Defines the string editor format for the hbm editor.
/// </summary>
[Export(typeof(EditorFormatDefinition))]
[ClassificationType(ClassificationTypeNames = Name)]
[Name(Name)] //The name of the Format
[UserVisible(true)] //this should be visible to the end user
[Order(Before = Priority.Default)] //set the priority to be after the default classifiers
internal sealed class StringFormat  : ClassificationFormatDefinition
{

    public const string Name = "StringFormat";

    /// <summary>
    /// Defines the visual format for the "StringClassifier" classification type
    /// </summary>
    public StringFormat()
    {
        DisplayName = Name;
        ForegroundColor = Colors.DarkRed;
    }
}

Pretty easy, this class define a format named StringFormat (which is internal to my extension), the format has a custom ForegroundColor. Nothing more, the other formats are exactly like this.

The next step is to write a IClassifierProvider :

[Export(typeof (IClassifierProvider))]
[ContentType("xml")]
internal sealed class StringClassifierProvider : IClassifierProvider
{
    [Import] private IClassificationTypeRegistryService classificationRegistry;

    #region IClassifierProvider Members

    public IClassifier GetClassifier(ITextBuffer textBuffer)
    {
        return textBuffer
            .Properties
            .GetOrCreateSingletonProperty(() => new StringClassifier(classificationRegistry));
    }

    #endregion
}

This classifier provider adds a Stringclassifier to the textbuffer for the content type “xml”. There are others content types that you can use like “code”, or the very basic “text”, on the other hand you can create your own Content Types for other file extensions.

Given the fact that all my classifier are based on regular expressions, I wrote a RegexBasedClassifier:

internal abstract class RegexBasedClassifier : IClassifier
{
    private readonly IClassificationTypeRegistryService classificationRegistry;

    protected RegexBasedClassifier(IClassificationTypeRegistryService classificationRegistry)
    {
        this.classificationRegistry = classificationRegistry;
    }

    public abstract string FormatterName { get; }

    public abstract IEnumerable<Regex> Regexs { get; }


    public IList<ClassificationSpan> GetClassificationSpans(SnapshotSpan span)
    {
        if (!span.IsInQueryTag()) return Enumerable.Empty<ClassificationSpan>().ToList();
        var startline = span.Start.GetContainingLine();
        var endline = (span.End - 1).GetContainingLine();
        var text = span.Snapshot.GetText(new SnapshotSpan(startline.Start, endline.End));

        return (from regex in Regexs
               from match in regex.Matches(text).OfType<Match>()
               select CreateSpan(span, match))
               .ToList();
    }

    private ClassificationSpan CreateSpan(SnapshotSpan span, Match match)
    {
        var snapshotSpan = new SnapshotSpan(span.Snapshot, 
                                            span.Start.Position + match.Index, 
                                            match.Length);
        return new ClassificationSpan(
            snapshotSpan, 
            classificationRegistry.GetClassificationType(FormatterName));
    }

    public event EventHandler<ClassificationChangedEventArgs> ClassificationChanged;
}

This base class check if the current line is inside a QueryTag (IsInQueryTag call), then it find the matches for all the regex defined in the derived class, and if it found a match then it create a ClassificationSpan with the FormatterName defined in the derived class.

So the implementations of the StringClassifier is:

internal class StringClassifier : RegexBasedClassifier
{
    public StringClassifier(IClassificationTypeRegistryService classificationRegistry)
        : base(classificationRegistry)
    {
    }

    public override string FormatterName
    {
        get { return StringFormat.Name; }
    }

    public override IEnumerable<Regex> Regexs
    {
        get { yield return new Regex(@"(\'[^\'\\]*(?:\\.[^\'\\]*)*\'*)"); }
    }
}

And the KeywordClassifier is this:

internal class KeywordClassifier : RegexBasedClassifier
{
    private readonly string[] keywords = new[]
            {
                "join", "from", "select",
                "new", "where", "and",
                "or", "band", "between",
                "not", "join", "left",
                "inner", "fetch", "in",
                "group", "by", "sum", "count",
                "coalesce", "null", "is", "like"
            };

    public KeywordClassifier(IClassificationTypeRegistryService classificationRegistry)
        : base(classificationRegistry)
    {
    }

    public override string FormatterName
    {
        get { return KeywordFormat.Name; }
    }

    public override IEnumerable<Regex> Regexs
    {
        get
        {
            return from keyword in keywords
                   select new Regex(string.Format(@"\b({0})\b", keyword));
        }
    }
}

You can look at the tests for these classes in the HqlAddin.Test project. Thats all, I hope you find it useful!

| More

He estado dedicando un buen tiempo a mi extensión para Visual Studio, llamada HqlAddin. Al trabajar una extensión que va a ser utilizada otro sistema (en este caso Visual Studio) me ha ocurrido que en ciertas ocasiones no se como se va a comportar dicho sistema. Si bien es cierto que dicho comportamiento debería estar documentado, ya sea de forma escrita o en UnitTests, en la vida real no siempre sucede eso. Es por ello que Reflector Pro me ha sido de gran utilidad a la hora de desarrollar nuevas funcionalidades, como también a la hora de resolver errores.

Utilizar Reflector Pro, es muy sencillo, luego de abrir el proyecto tenemos que ir al menú Reflector y clickear en “Choose Assemblies to Debug”:

2010-10-11_1952

Es probable que si es la primera vez que lo utilizamos nos solicite desactivar “Just My Code”.

Luego tenemos que seleccionar el ensamblado sobre el cual nos gustaría adentrarnos:

image

Y eso es todo…. una vez que le damos aceptar, Reflector descompilara el ensamblado que hayamos seleccionado y a partir de este momento podemos depurar dentro del ensamblado con F11.. paso a paso.

Algunas aclaraciones:

  • Si el ensamblado que seleccionamos, en su directorio tiene un archivo PDB, esto quiere decir que nosotros ya tenemos los símbolos de depuración. Aquí pueden suceder dos cosas:
    • Si nosotros tenemos el código fuente de ese ensamblado (en el estado en que estaba cuando se compilo), utilizar Reflector para este caso es innecesario. Pero igual se puede.
    • Si nosotros no tenemos el código fuente de ese ensamblado, lo mejor sería borrar el PDB del directorio en el que estaba, o simplemente dejar que reflector haga una copia a otro directorio y modifique temporalmente nuestras referencias. Esto luego se puede desactivar con un solo click.
  • Algunos ensamblados, de Visual Studio por ejemplo, están compilados de una manera que se llama “código optimizado”, si bien es posible seguir paso a paso la ejecución en este código, no es posible hacer cosas triviales como observar el valor de una variable.
  • Después de un tiempo de utilizar la herramienta, uno se da cuenta que hay algunas cosas que no están tan bien descompiladas, un caso típico son los bloques enumeradores:
public IEnumerable<int> EjemploEnumerador(string parametro)
{
    yield return 0;
    if (parametro == "a") yield return 1;
    yield return 32;
}

Hay que recordar que los bloques enumeradores son solo un truco del compilador, y por lo tanto, no esta tan fácil descompilarlo.

Estoy muy conforme con la herramienta y considero que es muy útil a la hora de trabajar con .Net.

Por último quería agradecer a RedGate por brindarme una licencia gratuita para utilizar en el desarrollo de mi proyecto OpenSource y a todas las empresas que siguen esta línea, tanto JetBrains, SharpCrafters, e inclusive el mismo Microsoft.

| More