Buenas prácticas aplicando MVVM: Converters (parte 2)

jueves, 19 de junio de 2014

Buenas prácticas MVVM

En este post continuamos con los converters, vamos a ver casos más concretos que en el post anterior, donde podemos utilizar converters que nos ayuden a liberar al view model de conocer como se representan los datos en la vista.

Fechas

Según mi experiencia, el formato en el que se representa un valor de fecha no es responsabilidad del view model, este solo debe tener el dato de tipo DateTime. Como se va a representar la fecha es responsabilidad de la vista.

¿Esto en que nos beneficia?, va a convertir nuestro view model en más reutilizable. Imaginemos que tenemos una ventana de listado y otra de detalle, probablemente nos interese en el listado mostrar la fecha de cada elemento en formato corto pero sin embargo en la vista de detalle puede interesarnos mostrar la fecha en formato largo. Este puede ser un ejemplo de un view model reutilizado en dos vistas y en cada una se muestra la misma fecha en formatos diferentes.

El converter de fecha podría ser algo parecido a este

public class DateConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, string language)
    {
        string formatDate = "M/d/yyyy";

        if (parameter !=null)
            formatDate = parameter.ToString();

        DateTime date = (DateTime)value;

        return value is DateTime ? date.ToString(formatDate):null;
    }

    public object ConvertBack(object value, Type targetType, object parameter, string language)
    {
        string strValue = value as string;
        DateTime resultDateTime;
        if (DateTime.TryParse(strValue, out resultDateTime))
        {
            return resultDateTime;
        }
        return DependencyProperty.UnsetValue;
    }
}

Donde puede recibir por parámetro el formato de fecha a convertir.

Podríamos utilizarlo sin pasar un formato de fecha y nos formateará al formato por defecto o especificando un formato de fecha especifico, lo que nos va a permitir mostrar el dato de fecha en diferentes vistas con diferentes formatos.
    
<TextBlock Text="{Binding BindedText,Converter={StaticResource DateConverter,ConverterParameter='dddd, MMMM dd, yyyy'}}"/>

Si quisiéramos representar la fecha en un DatePicker, como la propiedad Date de este control es de tipo DateTimeOffset, necesitaríamos otro converter diferente. 

public class DateTimeToDateTimeOffsetConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, string language)
    {
        try
        {
            DateTime date = (DateTime)value;
            return new DateTimeOffset(date);
        }
        catch (Exception ex)
        {
            return DateTimeOffset.MinValue;
        }
    }

    public object ConvertBack(object value, Type targetType, object parameter, string language)
    {
        try
        {
            DateTimeOffset dto = (DateTimeOffset)value;
            return dto.DateTime;
        }
        catch (Exception ex)
        {
            return DateTime.MinValue;
        }
    }
}

Mediante estos converters y con el mismo view model, podemos dejar en manos de la vista o las vistas, decidir como van a representar los datos.

Podríamos representar por ejemplo el mismo valor de tipo fecha de un view model de las siguientes formas.

Date Converters

Precios

El tratamiento con los precios es un poco más complejo. Hay que diferenciar entre el formato en el que presentamos el precio y conversiones entre diferentes monedas.

Imaginemos que lo que estamos haciendo es una aplicación de un eCommerce y en el proceso de compra en el último paso, damos la opción de ver los importes en la moneda que el usuario desee. Por un lado tendremos que convertir el precio de cada producto, los gastos de envío, impuestos etc.. y toda esta lógica sería lógica de presentación y por lo tanto corresponde al view model hacer todas estas conversiones  porque estamos realizando conversiones entre valores sin decidir como se van a presentar. Así podremos  realizar test unitarios del view model para ver si realiza bien las conversiones. Sin embargo la forma en que vamos a presentar estos precios corresponde a la vista y no al view model. Podríamos decidir que los precios se muestren como un string en un Label o un TextBlock en cuyo caso posiblemente lo ideal sea que aparezca en el formato del país de la moneda. Por ejemplo si tenemos un precio de 50000,99 dolares, seguramente queramos presentarlo como $50,000.99 en formato americano y para esto nos podemos apoyar de nuevo en un converter y liberar de esta responsabilidad al view model.

El converter podría ser parecido a este

public class CurrencyConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, string language)
    {
        CultureInfo culture;

        try
        {
            if (string.IsNullOrEmpty(language.ToString()))
                culture = new CultureInfo(Windows.Globalization.Language.CurrentInputMethodLanguageTag);
            else
                culture = new CultureInfo(language.ToString());

            decimal price = (decimal)value;

            return price.ToString("C2", culture);
        }
        catch
        {
            return null;
        }
    }

    public object ConvertBack(object value, Type targetType, object parameter, string language)
    {
        decimal resultDateTime;
        CultureInfo culture;
        
        if (string.IsNullOrEmpty(language.ToString()))
            culture = new CultureInfo(Windows.Globalization.Language.CurrentInputMethodLanguageTag);
        else
            culture = new CultureInfo(language.ToString());

        if (decimal.TryParse(value.ToString(), NumberStyles.Currency, culture, out resultDateTime))
        {
            return resultDateTime;
        }
        return DependencyProperty.UnsetValue;
    }
}

Donde puede recibir por parámetro la cultura en la que se tiene que basar para formatear el precio. Podríamos utilizarlo sin pasar un nombre de cultura y en ese caso utilizar una cultura por defecto o la cultura actual del sistema, también podríamos indicar mediante el parametro language la cultura a la que deseamos que se formatee.
    
<TextBlock Text="{Binding Price,Converter={StaticResource CurrencyConverter ,ConverterLanguage="{Binding OrderCulture}"}}"/>

Al igual que el converter de fechas este planteamiento nos va a permitir mostrar el dato de un precio en diferentes vistas con diferentes formatos o en mostrarlo en diferentes tipos de controles, porque el dato enlazado siempre es un decimal que no depende de la vista.

Resumen

Hemos visto como podemos plantear el enlace de la vista a dos tipos datos de un view model como son fechas y precios, de forma que mediante converters no acoplemos el view model a la vista, de forma que este no decida como se representa la información, permitiendo así poder tener un view model más reutilizable.

Libros Relacionados

MVVM Unleashed
Advanced MVVM

No hay comentarios:

Publicar un comentario