A pensar en paralelización: Parallel Extensions por Gustavo Lombardo

29. julio 2010

Si deseamos que nuestro software siga beneficiándose de las capacidades futuras de hoy y mañana de los procesadores debemos empezar a cambiar nuestra manera de desarrollar. La razón de esto es que las aplicaciones de negocio van tener que ejecutarse en máquinas multi-core o con varios procesadores. Hasta hace pocos años una máquina con varios procesadores estaba prácticamente reservada a la supercomputación, pero hoy en día ya son comunes en cualquier PC de escritorio.

La programación concurrente y el paralelismo no son una materia trivial, la sincronización entre procesos no lo es y dividir el trabajo, asignarlo a varios procesadores, recoger los resultados y mezclarlos para reconstruir la solución final tampoco.

En el pasado, la paralelización requería manipulación de bajo nivel de los subprocesos y bloqueos. Hoy en día, los desarrolladores no tendrían que luchar contra esta complejidad, porque sería dar un paso atrás en productividad. Su tarea es seguir dedicando sus esfuerzos a lo que mejor saben hacer que es desarrollar aplicaciones de alto nivel que resuelven problemas de negocio sin controlar la ejecución concurrente de sus procesos.

Para ello, debemos acostumbrarnos a usar el término task en lugar del thread como seguramente la mayoría de nosotros veníamos haciendo y aprovechar las nuevas capacidades que ofrece las APIs de .NET Framework 4, así como también las del Visual Studio 2010.

.NET Framework 4, proporciona un nuevo runtime, nuevos tipos de biblioteca de clases y nuevas herramientas de diagnóstico para la programación paralela y concurrente. Es aquí donde hace su aparición Parallel Extensions, simplificando el desarrollo en paralelo, de modo de poder escribir código paralelo eficaz, específico y escalable de forma natural sin tener que trabajar directamente con subprocesos. Sin embargo, no todo código se presta para la paralelización por ejemplo, si un bucle realiza solo una cantidad reducida de trabajo en cada iteración o no se ejecuta para un gran número de iteraciones, la sobrecarga de la paralelización puede dar lugar a una ejecución más lenta del código. Además, al igual que cualquier código multiproceso, la paralelización hace que la ejecución del programa sea más compleja.

Parallel Extensions se compone de dos partes: Parallel Linq (PLINQ) y Task Parallel Library (TPL). También consta de un conjunto de estructuras de datos de coordinación (CDS) utilizada para sincronizar y coordinar la ejecución de tareas concurrentes. La siguiente ilustración proporciona información general de alto nivel de la arquitectura de programación paralela en .NET Framework 4.

IC389193

A continuación explicaré cada uno de sus componentes. PLINQ implementa el conjunto completo de operadores de consulta estándar de LINQ  e incluye otros adicionales para las operaciones paralelas. Combina la simplicidad y legibilidad de la sintaxis de LINQ con la eficacia de la programación paralela. En muchos escenarios, PLINQ puede aumentar significativamente la velocidad de las consultas LINQ  to Objects utilizando todos los núcleos disponibles en el equipo host de una forma más eficaz. Para hacer esto divide el origen de datos en segmentos y, a continuación, ejecuta la consulta en cada segmento en subprocesos de trabajo independientes en paralelo en varios procesadores. La clase System.Linq.ParallelEnumerable expone casi toda la funcionalidad de PLINQ.  Les mostraré un ejemplo en el que invoco el método de extensión ParallelEnumerableAsParallel() para indicarle que la query debe ejecutarse en forma paralela.

for (var i = 0; i < data.Length; i++)
{
    data[i] = i;
}
 
data[1000] = -1;
data[14000] = -2;
data[15000] = -3;
data[676000] = -4;
data[8024540] = -5;
data[9908000] = -6;
 
var negativos = from valor in data.AsParallel()
                where valor < 0
                select valor;

 

Al ejecutarse la query en paralelo los resultados de cada subproceso de trabajo deben volver a combinarse en el subproceso principal por ejemplo para la inserción en una lista. El tipo de combinación que  PLINQ realiza depende de los operadores que se encuentran en la consulta. Con el método WithParallelMergeOptions(), puede indicarse a PLINQ una sugerencia de que tipo de combinación se va a realizar.

La biblioteca TPL es un conjunto de API que tiene como propósito lograr que los desarrolladores aumenten la productividad simplificando el proceso de agregar paralelismo y simultaneidad a las aplicaciones, utilizando eficazmente todos los procesadores que están disponibles. Además, se encarga de la división del trabajo, la programación de los subprocesos y otros detalles de bajo nivel.

El concepto principal de Parallel Extensions es una tarea, que es una pequeña unidad de código, de forma independiente. Se encarga de dividir el trabajo y lanzar un número óptimo de threads basándose en el número de CPUs o cores que tenemos.

Yendo aun mas allá, sin llegar al nivel de granularidad de una task, podemos trabajar a más alto nivel aún con la clase estática Parallel y escribir código como el siguiente:

static void Main()
{
    Parallel.For(from, to, i=> )
    {
    });
}
 

Mediante un ciclo como el anterior, dejamos que el runtime de Parallel Extensions se encargue de paralelizar el trabajo creando las tasks y encargándose de  todo el proceso. A continuación les mostraré las mejoras de rendimiento al paralelizar un programa muy sencillo. El método SumaRaicesNesimaDeX devuelve la suma de las raíces n-énesima de todos los enteros entre 1 y un 10 millones donde n es una variable recibida como parámetro. La forma de resolución de la raíz contiene pasos de procesamiento adicionales que aumentan el tiempo de ejecución total de la consulta. Además, en el main utilizo la clase Stopwatch para determinar cuantos milisegundos tarda el programa en ejecutarse.

static void Main()
{
    var tiempo = Stopwatch.StartNew();
    
    for (var i = 2; i < 20; i++)
    {
        var resultado = SumaRaicesNesimaDeX(i);
        Console.WriteLine("Raiz {0} = {1} ", i, resultado);
    }
 
    Console.WriteLine(tiempo.ElapsedMilliseconds);
    Console.ReadLine();
}
 
public static double SumaRaicesNesimaDeX(int n)
{
    double resultado = 0;
 
    for (var x = 1; x < 10000000; x++)
    {
        //Raíz n-ésima de x
        resultado += Math.Exp(Math.Log(x) / n);
    }
 
    return resultado;
}
 

El programa lo ejecute en AMD Phenom II X4 810 de 64 bits con 4 GB de memoria RAM y tardó aproximadamente 16 segundos.

Ahora si remplazo el ciclo:

for (var i = 2; i < 20; i++)
{
    var resultado = SumaRaicesNesimaDeX(i);
    Console.WriteLine("Raiz {0} = {1} ", i, resultado);
}

 

por el siguiente código añadiéndole paralelismo:

Parallel.For(2, 20, (i) =>
{
    var resultado = SumaRaicesNesimaDeX(i);
    Console.WriteLine("Raiz {0} = {1} ", i, resultado);
});

Mediante pequeñas modificaciones del código como son un índice de inicio y de fin y la llamada a través de un delegate, la ejecución del programa solo tarda aproximadamente 4 segundos y medio.

Visual Studio 2010 incluye nuevas herramientas de profiling y debugging para la ejecución concurrente. Un ejemplo de esto es el visualizador de concurrencia. Esta herramienta ayuda  a analizar nuestras aplicaciones secuenciales para descubrir las oportunidades de paralelismo. El visualizador de concurrencia incluye visualización y herramientas de reporte. Hay tres puntos de vista principales: utilización de CPU, subprocesos y núcleos.

imageEl eje X muestra el tiempo transcurrido desde el inicio de la traza hasta el final de la actividad de la aplicación. El eje Y muestra el número de núcleos de procesadores lógicos en el sistema. La zona verde representa el número medio de cores lógicos que se analiza en la aplicación en un momento dado en la ejecución del profiling. El resto de los núcleos por otros procesos que se ejecutan en el sistema (que se muestra en amarillo).

Si queremos paralelizar nuestra aplicación, debemos buscar áreas de ejecución que presentan largas regiones verdes a nivel de un solo núcleo en el eje Y o regiones donde no hay mucha utilización de la CPU, donde el verde no demuestra ni es considerablemente menos de 1 en promedio. Ambas circunstancias podrían indicar una oportunidad para la paralelización. En segundo lugar, si estamos tratando de afinar la aplicación paralela, esta vista le permite confirmar el grado de paralelismo que existe cuando la aplicación se ejecuta.

Este cambio de paradigma nos exige una nueva manera de pensar. Los threads son una abstracción demasiado débil. Debemos empezar a pensar en tareas no en threads y en relaciones entre esas tareas y su concurrencia en lugar de sincronización de threads. Seremos los desarrolladores quienes de un modo u otro tendremos que empezar a pensar en paralelización.

.NET, Entity Framework, Visual Studio

Enviando JSON a una acción en ASP.NET MVC por Fernando Abuin

22. abril 2010

 

Descargar Código Fuente

El otro día estaba intentando enviar datos a una acción utilizando el AJAX de jQuery. En la mayoría de los casos, podrán utilizar este método y enviar datos a una acción sin problemas, ya que el default binder de MVC se encarga de interpretar los datos.

Mi problema surgió cuando intenté enviar datos más complejos. En el controlador tenía el siguiente método:

   1:          [AcceptVerbs(HttpVerbs.Post)]
   2:          public virtual JsonResult Actualizar(Producto producto)
   3:          {
   4:              //HACER ALGO 
   5:          }

Este método sólo acepta un POST, y tiene un argumento de tipo Producto. Veamos cómo está compuesta esta clase:

   1:      public class Producto
   2:      {
   3:          public int Id { get; set; }
   4:          public string Nombre { get; set; }
   5:          public IEnumerable<Promocion> Promociones { get; set; }
   6:   
   7:          public class Promocion
   8:          {
   9:              public string Id { get; set; }
  10:              public double Descuento { get; set; }
  11:              public DateTime FechaDesde { get; set; }
  12:              public DateTime FechaHasta { get; set; }
  13:          }
  14:      }

 

Para poder enviar esto utilizando jQuery y AJAX, lo más sencillo sería hacer:

   1:      var data = {
   2:          "Id": 1,
   3:          "Nombre": "Cualquier Nombre",
   4:          "Promociones": [
   5:         {
   6:             "Id": 1,
   7:             "Descuento": 0.15,
   8:             "FechaDesde": new Date(año, mes, día),
   9:             "FechaHasta": new Date(año, mes, día)
  10:         },
  11:         {
  12:             "Id": 2,
  13:             "Descuento": 0.05,
  14:             "FechaDesde": new Date(año, mes, día),
  15:             "FechaHasta": new Date(año, mes, día)
  16:         }
  17:       ]
  18:      };
  19:   
  20:      $.ajax({
  21:          type: "POST",
  22:          dataType: "json", // Tipo de dato que devuelve el server
  23:          url: 'Controller/Action',
  24:          data: data, // Datos a enviar
  25:          success: function(serverResponse) {
  26:              // Realizar algo cuando la llamada es exitosa
  27:          },
  28:          error: function() {
  29:              // Realizar algo cuando la llamada falla
  30:          }
  31:      });
 

Lo que estoy haciendo es crear un objeto con los miembro Id, Nombre y Promociones y asignándolo a la variable data.

Sin embargo, el miembro Promociones es un objeto de tipo Array. Por default, la función $.ajax() intentará convertir cualquier dato a un string. Como Promociones es un array, al intentar convertirlo a un string, terminará enviando al servidor lo siguiente:

IDDivision=6&IDBonificacion=LB6&Tipo=A&Vigencias=[object+Object]&Vigencias=[object+Object]

Es decir, jQuery serializa los valores de un mismo array con el mismo Key. Por ejemplo, {foo:["bar1", "bar2"]} se convierte en '&foo=bar1&foo=bar2'.

Sin embargo, como el valor de cada elemento de Promociones es otro objeto, lo que termina enviando es el .toString() del objeto, el cual es "[object Object]" justamente. El Default Model Binder de ASP.NET MVC, intentará hacer el bind al objeto Producto, pero debido a que jQuery serializo incorrectamente el objeto, el miembro Promociones terminará siendo null, en lugar de los datos que estábamos intentando enviar realmente.

Para resolver este problema, podemos convertir los objetos javascript a JSON. Para hacer esto, utilicé JSON for jQuery (link), pero se puede utilizar cualquier cosa que tome un objeto javascript y lo transforme a JSON. Al enviar los datos al server, debemos asegurarnos que el content type sea del tipo application/json, que es lo que estoy haciendo en la línea 5 del siguiente código:

 

   1:      var jsonString = $.toJSON(data);
   2:      $.ajax({
   3:          type: "POST",
   4:          dataType: "json", // Tipo de dato que devuelve el server
   5:          contentType: 'application/json', // Tipo de datos que envío
   6:          url: 'Controller/Action',
   7:          data: jsonString, // Datos a enviar
   8:          success: function(serverResponse) {
   9:              // Realizar algo cuando la llamada es exitosa
  10:          },
  11:          error: function() {
  12:              // Realizar algo cuando la llamada falla
  13:          }
  14:      });

Fíjense que antes de hacer la llamada a ajax(), estoy transformando la variable data a JSON, y pasando su resultado a la función $.ajax(), en lugar del objeto. La variable jsonString entonces quedará en:

'{"Id": 1, "Nombre": "Cualquier Nombre", "Promociones": [{ "Id": 1, "Descuento": 0.15, "FechaDesde": "2010-01-01T03:00:00.000Z", "FechaHasta": "2010-05-31T03:00:00.000Z" }, { "Id": 2, "Descuento": 0.05, "FechaDesde": "2010-06-02T03:00:00.000Z", "FechaHasta": "2010-08-01T03:00:00.000Z" } ] }'

Si prestan atención, podrán darse cuenta que el JSON string es exactamente igual que la declaración de la variable data, definida más arriba. La única diferencia es que jsonString es literalmente un string, y data es un objeto javascript.

Ahora, lo único que queda resolver es el Model Binding del lado del servidor. Lamentablemente, ASP.NET MVC 2 no interpreta JSON correctamente, por lo tanto vamos a tener que hacer algo para hacerlo funcionar.

El enfoque más común sería hacer un Custom Model Binder. Sin embargo, esto implica que en cada acción que recibe JSON, debemos indicar explícitamente a MVC que utilice nuestro Model Binder. Si se trata de una sola acción, no habría ningún problema, pero si hay muchas acciones que utilizan el Model Binder, y todas reciben distintos tipos de datos, se va a volver muy tediosa la implementación.

Por suerte, en MVC 2, tenemos algo llamado Value Providers. Mientras que los Model Binders son utilizados para bindear datos que recibe el servidor, Value Providers proveen una abstracción para los datos en sí.

Para resolver mi problema, creé un Custom Value Provider que recibe datos JSON y lo serializa a un diccionario, en lugar de al objeto. Luego, éste diccionario es pasado al Default Model Binder de MVC, el cual realiza el bind al objeto final, e incluso realiza cualquier validación que ustedes hayan indicado.

El Custom Value Provider es el siguiente:

   1:  public class JsonValueProviderFactory : ValueProviderFactory
   2:  {
   3:      public override IValueProvider GetValueProvider
   4:                                           (ControllerContext controllerContext)
   5:      {
   6:          object jsonData = GetDeserializedJson(controllerContext);
   7:   
   8:          if (jsonData == null)
   9:              return null;
  10:   
  11:          var dictionary = new Dictionary<string, object>
  12:                                             (StringComparer.OrdinalIgnoreCase);
  13:   
  14:          FlattenToDictionary(dictionary, string.Empty, jsonData);
  15:   
  16:          return new DictionaryValueProvider<object>
  17:                                       (dictionary, CultureInfo.CurrentCulture);
  18:      }
  19:   
  20:      private static object GetDeserializedJson
  21:                                           (ControllerContext controllerContext)
  22:      {
  23:          if (!controllerContext.HttpContext.Request.ContentType
  24:            .StartsWith("application/json", StringComparison.OrdinalIgnoreCase))
  25:              return null;
  26:   
  27:          string bodyText = new StreamReader
  28:                (controllerContext.HttpContext.Request.InputStream).ReadToEnd();
  29:   
  30:          if (string.IsNullOrEmpty(bodyText))
  31:              return null;
  32:   
  33:          var serializer = new JavaScriptSerializer();
  34:   
  35:          return serializer.DeserializeObject(bodyText); 
  36:      }
  37:   
  38:      private static void FlattenToDictionary(
  39:            IDictionary<string, object> dictionary, string prefix, object value)
  40:      {
  41:          var dictionaryValue = value as IDictionary<string, object>;
  42:   
  43:          if (dictionaryValue != null)
  44:          {
  45:              foreach (KeyValuePair<string, object> entry in dictionaryValue)
  46:              {
  47:                  string propertyKey;
  48:   
  49:                  if (!string.IsNullOrEmpty(prefix))
  50:                      propertyKey = prefix + "." + entry.Key;
  51:                  else
  52:                      propertyKey = entry.Key;
  53:   
  54:                  FlattenToDictionary(dictionary, propertyKey, entry.Value);
  55:              }
  56:          }
  57:          else
  58:          {
  59:              var listValue = value as IList;
  60:              if (listValue != null)
  61:              {
  62:                  for (int i = 0; i < listValue.Count; i++)
  63:                      FlattenToDictionary(dictionary,
  64:                                          prefix + "[" + i + "]", listValue[i]);
  65:              }
  66:              else
  67:                  dictionary[prefix] = value;
  68:          }
  69:      }
  70:  }

Y luego, debemos agregar la siguiente línea en el Global.ajax de nuestra aplicación:

   1:  protected void Application_Start()
   2:  {
   3:          RegisterRoutes(RouteTable.Routes);
   4:          ValueProviderFactories.Factories.Add(new JsonValueProviderFactory());
   5:  }

Listo! Ahora al enviar JSON al servidor, nuestro Value Provider interceptará el request, y serializará los datos de tipo JSON a un diccionario que pueda ser interpretado por el Default Model Binder. Lo único que debemos recordar hacer es setear el ContentType a 'application/json', y éstos requests serán procesados por nuestro Value Provider.

Ahora, veamos un poco qué está haciendo el Value Provider.
Al único método que tenemos que hacerle override de la clase ValueProviderFactory es a GetValueProvider(). Lo que hace es deserializar JSON y transformarlo en un objeto .NET, luego lo serializa al diccionario y devuelve un DictionaryValueProvider, el cual es utilizado más tarde por el default model binder.

El método GetDeserializedJson() hace exactamente lo que dice, deserializa JSON. Hay que tener cuidado de pasarle un string que contenga JSON, porque puede arrojar una excepción si se pasa JSON invalido. Probablemente sería conveniente encerrar a la llamada DeserializeObject en un try/catch, y devolver null cuando falle.

Finalmente, el método FlattenToDictionary() es un método recursivo que recorre el objeto deserializado, que para este ejemplo es el siguiente:

clip_image002

Como verán, el objeto deserializado es un Dictionary<string, object>. Para Id y Nombre, el tipo de dato de Value es int y string respectivamente. Para Promociones, en cambio, es un Array de object, y cada elemento de ese Array es un Dictionary<string,object>. Lo que hace FlattenToDictionary() es recorrer todos estos elementos para 'aplanarlo' (flatten) a un diccionario. El método nos devuelve esto:

clip_image004

Si leyeron el post de Andres Stang probablemente esto les resulte familiar. Lo que hace el método es recorrer el objeto, y construir el Key de cada uno de los valores que contiene para poder insertarlo en un diccionario, donde todos los valores de cada elemento sea un tipo de dato primitivo. Este diccionario luego es interpretado por el default model binder. Es decir, lo que estamos haciendo es transformar JSON a un formato que pueda ser interpretado por el default model binder.

Les dejo el código fuente con una aplicación de ejemplo para que prueben:

JsonToMVCAction.rar (153,28 kb)

.NET, ASP.NET, Desarrollo Web, JQuery , , ,

Lanzamiento Virtual “La eficiencia en tus manos” por Daniel Laco

16. abril 2010

Hola a todos, quiero recordarles que este 21 de Abril será el evento virtual de lanzamiento de 11 importantes productos y donde muchos MVPs estaran como speakers y como expertos para resolver tus dudas.

clip_image003_010b7c0b-5450-4bae-8599-79e3ef3ca26b

 clip_image001_0d130959-9982-4736-8319-5fff7ce34ca4

 

 

clip_image002_f403aaf7-c2c3-4e8d-beb7-f7bb8c027891

Regístrate ya!

.NET, ASP.NET, Entity Framework, General, Sharepoint, Visual Studio

Que hay de nuevo en VS2010? por Daniel Laco

4. marzo 2010

Ya se viene la liberación de la versión final de Visual Studio 2010 y .NET 4.0 y uno siempre quiere tener una vista rápida de los cambios, agregados, etc.

En http://msdn.microsoft.com/en-us/library/bb386063(VS.100).aspx pueden encontrar un listado de todos los temas nuevos de esta plataforma.

 

.NET, ADO.NET, ASP.NET, Entity Framework, Visual Studio, WCF, WinForms , , , ,

Introducción al Validation Application Block por Maxi Guillén - Daniel Laco

27. diciembre 2007

 

Enterprise Library Validation Application Block

Uno de los temas siempre mas controversiales en del desarrollo, es el proceso de validación, ya sea en al entrada de datos, en el manejo de las reglas de negocio o en la parte de persistencia de los datos.

Con el Validation Application Block (VAP) contenido en la Enterprise Library podemos resolver de un modo elegante y personalizable toda la problemática de validación en nuestras aplicaciones.

El VAP brinda un entorno común para definir reglas de validación para los objetos del negocio e interfaces de usuario. Facilita varios tipos de clases de validadores que incluyen código para validar tipos de datos de .net. Y pueden desarrollarse validadores personalizados para reutilizar.

Se pueden crear grupos de validadores llamados Rule Set (1 a N validators) mediante parámetros de atributos. Es una forma de validar objetos complejos agrupando validadores de diferentes tipos y aplicándolos a campos, propiedades y a nested objects.

Las validaciones se pueden definir o bien a nivel de atributos en las clases o en un archivo de configuración.


Se puede utilizar con las siguientes tecnologías:
ASP .NET
Windows Forms
Ajax
WCF
WPF Requerimientos:
.Net Framework 2, 3 o 3.5

Para utilizar ValidationBlock no es necesario referenciar toda la Enterpise Library, sólo debemos referenciar las siguientes bibliotecas:
Microsoft.Practices.EnterpriseLibrary.Comon.dll
Microsoft.Practices.EnterpriseLibrary.Validation
Microsoft.Practices.ObjectBuilder.dll
Microsoft.Practices.EnterpriseLibrary.Integration.WinForms (solo en la capa de presentación)

Se utilizan los siguientes name space:
using Microsoft.Practices.EnterpriseLibrary.Validation;
using Microsoft.Practices.EnterpriseLibrary.Validation.Validators;
using Microsoft.Practices.EnterpriseLibrary.Validation.Integration.WinForms; (solo en la capa de presentación)

Validadores predefinidos
Validation Application Block incluye un conjunto de validadores predefinidos que comúnmente se pueden utilizar en las aplicaciones. Cada validador está asociado a un tipo específico. Algunos de estos validadores son:

  • ContainsCharacterValidator
  • NotNullValidator
  • ObjectValidator
  • DomainValidator
  • StringLengthValidator
Ejemplo de utilización de atributos:
public class Customer
{
    // validator attributes with field as target
    [NotNullValidator]
    [StringLengthValidator(1, 50)]
    public string Name;
 
    // validator attribute with property as target
    [DateRangeValidator("2007-10-27T00:00:00", "2008-10-27T00:00:00")]
    public DateTime DateJoined { get { return this.dateJoined; } }
    
    // validator attribute with method as target
    // assume that ShippingAddress is defined elsewhere and has validation
    [ObjectValidator]
    public ShippingAddress GetPrimaryShippingAddress()    {….}
    // validator attribute with method as target (applied to an enumeration)
    [ObjectCollectionValidator(typeof(ShippingAddress))]
    public IEnumerable<ShippingAddress> GetAlternateShippingAddresses()    {….}
}

Validadores compuestos Se pueden crear Validadores compuestos AND u OR (AndCompositeValidator y OrCompositeValidator), agrupa validaciones en forma lógica, es decir en el caso de And todos los validadores deben devolver verdadero para que el objeto sea válido. Las formas para crear validadores compuestos son:

  • Usar la herramienta de configuración
  • Usar atributos
  • Mediante código

Ejemplo por código:

Validator validator = new AndCompositeValidator(new NotNullValidator(), new StringLengthValidator(4, 8));
string password = "123";
ValidationResults results = validator.Validate(password);
bool esValido = results.IsValid;


Ejemplo por atributos:

public class cliente
{
    private string nombre;
    [ValidatorComposition(CompositionType.And)]
    [NotNullValidator(MessageTemplate = ValidatorMsgs.NotNullMsg), 
     StringLengthValidator(1, 50, MessageTemplate = ValidatorMsgs.StringRange50)]
    public string Nombre
    {
        Get{ return nombre; }
        Set{ nombre = value }
    }
}


Atributos modificadores de validación

Permiten modificar el comportamiento de los atributos de validación. Los atributos modificadores son:

  • ValidatorComposition = [CompositionType.And/CompositionType.Or], permite hacer override del comportamiento por defecto (AND) cuando hay más de un atributo de validación.
  • IgnoreNulls, los demás validadores no serán llamados si el valor que se está verificando es nulo.


Propiedades de los atributos de validación

Property name Description
MessageTemplate Descripcion que aparecerá en la propiedad Message del resultado, si se integra con winforms es lo mostrará el ErrorProvider en la intefaz de usuario.
MessageTemplateResourceName This property defines the name of the resource for message templates.
MessageTemplateResourceType This property defines the name of the type used for the message template resource.
Negate Modifica el comportamiento del validador para que falle la validación si se cumple la condición. Al contrario del comportamiento común que consiste en fallar la validación si no se cumple la condición
Tag Se usa para permitir clasificar los resultados
Ruleset Nombre de la regla a la que pertenece el validador


Ejemplo de uso con una clase:

public partial class Estacion : IDisposable
{
    private int _idEstacion;
    private string _nombre;
        private string _ubicacion;
    private string _telefono;
    private IList<OperacionCabina> _operacionCabinaList;
 
    public int IdEstacion
    {
        get { return this._idEstacion; }
    }
 
    [NotNullValidator(MessageTemplate = ValidatorMsgs.NotNullMsg), 
    StringLengthValidator(1, 50, MessageTemplate = ValidatorMsgs.StringRange50)]
    public string Nombre
    {
        get { return this._nombre; }
        set
        {
            this._nombre = value; 
        }
    }…
 


Validación Self
Consiste en implementar la lógica de validación dentro de la misma clase. Se rotula la clase con [HasSelfValidation] y con [SelfValidation] a cada método de la clase que se llamará cuando se efectúe la validación.

Por ejemplo:

[HasSelfValidation]
public class TemperatureRange
{ 
    private int min; 
    private int max;
 
    // ...
 
    [SelfValidation]             
    public void CheckTemperature(ValidationResults results) 
    { 
         if (max < min) 
           results.AddResult(new ValidationResult("Max less than min", this, null, null, null));
    } 
} 


Si se quiere que la validación sea heredable hay que marcar con HasSelfValidation tanto a la clase padre como a sus hijos y el método de validación debe ser público.


Validación de objetos

Puede realizarse mediante un llamado al método Validate de la clase estática Validation, el resultado se obtiene mediante la clase ValidationResults

Ejemplo de validación de una instancia de la clase Estacion:

Estacion estacion = Global.Data.GetObject<Estacion>();
Validation results = Validation.Validate(estacion);
Bool esValido = results.IsValid;


Puede utilizarse también la clase ValidationFactory para crear una instancia del validador.

Customer myCustomer = new Customer( /* ... */ );
Validator<Customer> goldCustomerValidator =   ValidationFactory.CreateValidator<Customer>("GoldCustomer");
ValidationResults results = goldCustomerValidator.Validate(myCustomer);


Resultados de una validación

El método de validación retornará el resultado en una instancia de la clase ValidationResults que es una colección de objetos ValidationResult los cuales tienen las siguientes propiedades:

Property

Description

Key

Nombre que identifica la ubicación del validador. Ej:”Nombre” significa que se colocó un validador en la propiedad nombre de un objeto Cliente.

Object

Puntero al objeto que fue validado

Message

Describe la falla de la validación

Tag

Valor ingresado por código que describe el resultado, se utilize para clasificar o filtrar

Target

Validador que falló



Herencia

Las reglas de validación se aplican a través de la jerarquía de clases. Si una clase hereda un miembro de de otra y no lo sobrecarga se aplican los validadores de la clase de la cual hereda. Si una clase hereda un miembro y lo sobrecarga, entonces no se aplican las validaciones especificadas en la clase de la cual hereda.

Crear un nuevo validador

Se pueden crear clases validadoras, pueden validar tipos de datos de nuevas formas o pueden validar datos complejos como instancias de la clase Cliente.

Para esto se puede heredar de las clases:

  • Validator<T>, para crear validadores fuertemente tipados.
  • Validator

Crear un nuevo atributo perzonalizado

Se pueden crear atributos personalizados heredando de la clase ValidatorAttribute.

Integración con WinForms
Validation Application Block tiene la capacidad de trabajar en conjunto con el objeto ErrorProvider para mostrar el resultado de las validaciones directamente en los controles con el clásico ícono rojo.
Esto se realiza soltando un control ErrorProvider en el Form deseado y luego soltando un control ValidatorProvider (Que debe ser obtenido partir de la biblioteca de Microsoft.Practices.EnterpriseLibrary.Validation.Integration.WinForms.
Cada control ValidatorProvider que se suelte sobre el formulario adiciona 3 nuevas propiedades para cada control del formulario que pueden ser accedidas desde la grilla de propiedades. Estas propiedades son: PerformValidation, SourcePropertyName y ValidatedProperty.
Para que el formulario valide automáticamente los controles debemos efectuar los siguientes pasos:

  1.  
    1. ErrorProvider: seleccionar el control ErrorProvider deseado del formulario.
    2. Enabled: debe ser igual a True
    3. RulesetName: debe ser igual a la regla deseada si no se utilizó este parámetro en los atributos se puede dejar vacío.
    4. SourceTypeName: Se especifica el camino de namespace hasta la clase contiene las validaciones seguido por el nombre de la biblioteca donde se encuentre.
    5. Ej: SourceTypeName = Entidades.Cliente, Entidades
    6. SpecificationSource: declara desde donde se obtienen las reglas de validación, puede ser desde atributos, archivo de configuración o ambos.
    1. PerformValidation: boleano, true para que el control sea validado con el ErrorProvider.
    2. SourcePropertyName: nombre del campo que tiene definida una validación. Ej. SourcePropertyName=Nombre, si definimos por ejemplo una validación mediante el atributo [NotNullValidator] en la propiedad Nombre de la clase Cliente
    3. ValidatedProperty: nombre de la propiedad del control desde la cual se obtendrá el valor para efectuar la validación. Por ejemplo si el control fuera un TextBox usaríamos ValidatedProperty = Text.
  2. 1. Configurar las propiedades del Control ErrorProvider: 2. Configurar las siguientes propiedades de los controles que se deseen que participen de las validaciones:


Ejemplo de una clase Validator y una clase ValidatorAttribute Personalizado

 [ConfigurationElementType(typeof(CustomValidatorData))]
 public class PercentageValidator: Validator<decimal>
 {
        public PercentageValidator(NameValueCollection attributes) : base(null, null)
        {
        }
 
        public PercentageValidator() : this(null)
        {
        }
 
        protected override void DoValidate(decimal objectToValidate, object currentTarget, 
        string key, ValidationResults validationResults)
        {
            if (objectToValidate < 0 || objectToValidate > 100)
            {
                validationResults.AddResult(new 
                 ValidationResult("El valor debe estar entre 0 y 100.", currentTarget, "Decimal", "", this));
            }
        }
 
        protected override string DefaultMessageTemplate
        {
            get { return "Valor inválido"; }
        }
 }
 
 public class PercentageValidatorAttribute : ValidatorAttribute
 {
        public PercentageValidatorAttribute()
        {
        }
 
        protected override Validator DoCreateValidator(Type targetType)
        {
            return new PercentageValidator();
        }
 }
 
 public class ObjetoTest
 {
        private string nombre;
        private decimal valor;
 
        [PercentageValidator()] //Mi Custom Validator
        public decimal Valor
        {
            get { return valor; }
            set { valor = value; }
        }
 
        [NotNullValidator,StringLengthValidator(1,50)]
        public string Nombre
        {
            get { return nombre; }
            set { nombre = value; }
        }
 }


Como se dijo anteriormente, Application Validation Block permite manejar las reglas de validación mediante el archivo de configuración. El archivo puede ser editado mediante la herramienta “Edit Enterprise Library Configuration”, por ejemplo agregamos una sección de Validation Block y luego se selecciona las clases a validar a partir de ensamblados. Especificamos para cada clase los miembros que serán validados y seleccionamos los Validators deseados y especificamos sus parámetros. También podemos especificar las clases que usen SelfValidation.


Este es un ejemplo de un archivo de configuración, con la definición de validaciones.

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <configSections>
    <section name="validation" type="Microsoft.Practices./>
      EnterpriseLibrary.Validation.Configuration.
      ValidationSettings,
      Microsoft.Practices.EnterpriseLibrary.Validation"
  </configSections>
  <validation>
    <type name="EntLib3.Subscriber" defaultRule="default">
      <rule name="default">
        <properties>
          <property name="Name">
            <add name="NameNotNull" type="Microsoft.Practices. />
                 EnterpriseLibrary.
                 Validation.Validators.NotNullValidator,
                 Microsoft.Practices.
                 EnterpriseLibrary.Validation"
            <add name="NameLength" type="Microsoft.Practices.
                 EnterpriseLibrary.
                 Validation.Validators.StringLengthValidator,
                 Microsoft.
                 Practices.EnterpriseLibrary.Validation"
                 lowerBound="1"
                 upperBound="200" lowerBoundType="Inclusive"
                 upperBoundType="Inclusive"/>
          </property>
          <property name="EmailAddress">
            <add name="EmailAddressNotNull" type="Microsoft. />
                Practices.
                 EnterpriseLibrary.Validation.Validators.
                 NotNullValidator,
                 Microsoft.Practices.EnterpriseLibrary.
                 Validation"
            <add name="EmailAddressLength" type="Microsoft.
                 Practices.
                 EnterpriseLibrary.Validation.Validators.
                 StringLengthValidator,
                 Microsoft.Practices.EnterpriseLibrary.
                 Validation"
                 lowerBound="1"
                 upperBound="200" lowerBoundType="Inclusive"
                 upperBoundType="Inclusive"/>
          </property>          
        </properties>
      </rule>
    </type>
  </validation>
</configuration>

 

 

.NET ,