viernes, 29 de junio de 2012

Cuidado con los redondeos porque puedes perder algún céntimo

Hoy voy a hablar sobre uno de esos fallos que te puede volver loco toda una mañana e incluso hacerte perder los papeles, sobre todo al final cuando te das cuenta del fallo.

En esta ocasión la cosa va de redondeos de importes de facturas así que lo primero que haremos es buscar como se redondea los valores en euors. Según la sección 3, artículo 11 de la ley 46/1998, de 17 de diciembre (consultar aquí), sobre la introducción del euro

Uno. En los importes monetarios que se hayan de abonar o contabilizar, cuando se lleve a cabo una operación de redondeo después de una conversión a la unidad euro, deberán redondearse por exceso o por defecto al céntimo más próximo. Los importes monetarios que se hayan de abonar o contabilizar y se conviertan a la unidad monetaria peseta deberán redondearse por exceso o por defecto a la peseta más próxima. En caso de que al aplicar el tipo de conversión se obtenga una cantidad cuya última cifra sea exactamente la mitad de un céntimo o de una peseta, el redondeo se efectuará a la cifra superior.

O sea que debemos redondear a dos decimales al céntimo más próximo y en caso que el tercer decimal sea 5 debemos redondear al alza. En base a esto haremos unas pruebas para ver el resultado
decimal d1;
decimal d2;

d1 = 123.472m;
d2 = Math.Round(d1, 2);
Print(d1, d2);

d1 = 123.478m;
d2 = Math.Round(d1, 2);
Print(d1, d2);

d1 = 123.475m;
d2 = Math.Round(d1, 2);
Print(d1, d2);
La salida de este programa es la siguiente
Valor: 123,472
Valor redondeado: 123,47
---
Valor: 123,478
Valor redondeado: 123,48
---
Valor: 123,475
Valor redondeado: 123,48
Como vemos todo funciona correctamente pero vamos a usar el valor 123,465 que según la ley debería ser redondeado a 123,47.
d1 = 123.465m;
d2 = Math.Round(d1, 2);
Print(d1, d2);
Y la salida sería
Valor: 123,465
Valor redondeado: 123,46
Si nos fijamos, vemos que el valor obtenido no es el que esperamos, y con este error nos tropezamos el otro día. Estabamos convencidos que el código estaba bien, ya que la función Math.Round hace el redondeando al alza en caso de ser un cinco. Tras mucho darle vueltas al asunto y algun que otro "me cago en todo", nos dio por leernos con detalle la ayuda del método Round.

El valor devuelto por el método Math.Round es "el número más próximo a d con una precisión igual a decimals. Si d se encuentra entre dos números, uno de los cuales es par y el otro es impar, se devuelve el número par. Si la precisión de d es menor que decimals, se devuelve d sin modificar."

Vaya, ¿y esto?, para mi que el método Math.Round no hacía esto, pero por lo que parece esto lo ha hecho de siempre así que la memoria nos ha gastado una mala pasada. Para que el redondeo sea el esperado debemos usar otra sobrecarga de dicho método, en la que podemos indicar que tipo de redondeo queremos usar, quedando nuestro código así
d1 = 123.465m;
d2 = Math.Round(d1, 2, MidpointRounding.AwayFromZero);
Print(d1, d2);
Obteniendo la siguiente salida
Valor: 123,465
Valor redondeado: 123,47
Así que, cuidado con los redondeos no sea que nos llevemos un susto en la facturación a final de año.

Happy coding!

miércoles, 20 de junio de 2012

Validar el formato de una imagen (no me mientas con el formato) con C#

De verdad que hay veces en la que es mejor estarse callados porque si hablas puedes abrir La caja de Pandora y al final terminarás muy pero que muy mal.

Toda esta historia comienza con un simple FileUpload que permite al usuario subir una imagen. En principio esto no debería dar ningún problema, es algo que se encuentra perfectamente documento en Internet y en menos de 2 minutos lo tienes montado en tu aplicación. El problema surge cuando a alguien se le ocurre hacer la siguiente pregunta, ¿qué pasa si no suben una imagen, o sea, si algún avispado coge un ejecutable y le cambia la extensión a por ejemplo, jpg? La verdad es que mis conocimientos sobre seguridad no llegan a tanto y no se cuales serías las implicaciones reales sobre tal acción pero con semejante pregunta ya tenemos el lío montado así que, toca hacer un sistema para comprobar que la imagen que el usuario ha subido es realmente la imagen que dice ser.

Lo primero es preguntarnos cuales son los formatos gráficos que vamos a admitir, lo cual, es una mala pregunta porque la respuesta fue, todos. Eso es imposible así que, vamos a limitarlo a 3 o 4, así que la cosa quedó en los más utilizados, esto es, jpeg, gif, png y bmp.

Para esto podríamos hacer uso del objeto Image que nos da .NET, pero alguien (y no soy yo) se le metieron en la cabeza estás dos cosas
  • Usar el objeto Image es poco eficiente.
  • Referenciar a System.Drawing en la capa de negocio es poco elegante.
De todas formas aquí está como podría ser este código (por si alguien quiere usarlo)
public bool validateImage(byte[] bytes)
{
  try 
  {
    Stream stream = new MemoryStream(bytes);
    using(Image img = Image.FromStream(stream))
    {
      if (img.RawFormat.Equals(ImageFormat.Bmp) ||
          img.RawFormat.Equals(ImageFormat.Gif) ||
          img.RawFormat.Equals(ImageFormat.Jpeg) ||
          img.RawFormat.Equals(ImageFormat.Png))
        return true;
    }
    return false;
  } 
  catch
  {
    return false;
  }
}
Tras revisar un poco de documentación sobre estos formatos (la wikipedia es un lugar fantástico) llegamos a esta conclusión.
  • JPEG: Los primeros 4 bytes son FF D8 FF E0 (aunque parece que lo realmente seguro son los dos primeros bytes, ya que por ejemplo algunas cámaras Canon ponen FF E1 en el tercer y cuatro byte).
  • GIF: Los primeros 6 bytes son siempre "GIF87a" o "GIF89a".
  • PNG: Los primeros 8 bytes son 89 50 4E 47 0D 0A 1A 0A.
  • BMP: Los primeros 2 bytes son 42 4D.
Con esta información ya nos podemos poner manos a la obra. Para realizar las validaciones implementamos una pequeña factoría de validadores de imágenes ya que después de esto vinieron más cosas, y al final lo mejor fue encapsular todo el comportamiento específico de cada imagen en un clase (como se debería hacer siempre, ¿no?).
Nuestra interface para la validación del formato
public interface IImageValidator
{
  byte[] Content { get; set; }
  bool IsValid();
}
Nuestra factoría de interfaces
public static IImageValidator Create(string extension, byte[] content)
{
  switch (extension.ToUpper())
  {
    case "BMP":
      return new BMPImageValidator() { Content = content };
    case "GIF":
      return new GIFImageValidator() { Content = content };
    case "JPG":
    case "JPEG":
      return new JPEGImageValidator() { Content = content };
    case "PNG":
      return new PNGImageValidator() { Content = content };
    default:
      throw new Exception(string.Format("Format '{0}' not supported", extension));
  }
}
Y por último como sería el validador para el caso de un fichero jpg
public class JPEGImageValidator : IImageValidator
{
  #region IImageValidator

  public byte[] Content { get; set; }

  public bool IsValid()
  {
    byte[] magicNumber = { 0xFF, 0xD8 };   
    return (Content.Take(2).ToArray() == magicNumber);
  }

  #endregion
}
Para usar el validador tan solo debemos hacer lo siguiente
byte[] imageJPG = System.IO.File.ReadAllBytes("test-jpg.jpg");
IImageValidator validator = ImageValidatorFactory.Create("jpg", imageJPG);
bool isValid = validator.IsValid();
Para la prueba estoy cargando la imagen desde un fichero. Si queremos usarlo en un entorno web (MVC) podemos hacer lo siguiente
string extension = string.Empty;
if (file.FileName.LastIndexOf('.') > 0)
  extension = file.FileName.Substring(file.FileName.LastIndexOf('.') + 1);       
MemoryStream memoryStream = new MemoryStream();
file.InputStream.CopyTo(memoryStream);
byte[] content = memoryStream.ToArray();

IImageValidator validator = ImageValidatorFactory.Create(extension, content);
bool isValid = validator.IsValid();
Respecto a la eficiencia de los métodos, he modificado el método validateImage para que en vez de validar un byte[] también valide un Stream, para ahorrarnos esta conversión y así ser más justos en la comparación. Los resultados son realmente demoledores

  • validateImage (byte[]): 5601 ms.
  • validateImage (stream): 5157 ms.
  • ImageValidatorFactory: 4 ms.

Así que realmente el uso de la clase Image es un factor a tener en cuenta en caso que trabajemos de manera masiva con imágenes.

Viendo estos números puede que quede justificado hacer nuestros propios validadores de imágenes aunque para otras tareas puede que no quede tan justificado sobre todo teniendo en cuenta los mil y un detalles que hay que tener en cuenta cuando trabajamos con este tipo de ficheros a bajo nivel.

Happy coding!

miércoles, 13 de junio de 2012

Introducción a Knockout (VI) - Insertando datos con ASP.NET MVC4 WebApi

En el anterior artículo de knockout vimos como podíamos llamadas AJAX para cargar nuestra UI. Hoy vamos a ver como podemos hacer el mantenimiento de nuestras entidades también de una forma sencilla. Lo primero será extender nuestro repositorio de datos para añadir cierta lógica para el almacenamiento de nuestros datos.

IRepository
public interface IBookRepository
{
  IEnumerable<Book> Get();
  IEnumerable<Book> GetList(string search);
  Book Add(Book item);
  void Remove(int id);
}
BookRepository
public class BookRepository : IBookRepository
{
    private List<Book> products = new List<Book>();
    private int _nextId = 1;

    public BookRepository()
    {
        Add(new Book
        {
            Id = 1,
            Title = "Introducing Microsoft Sql Server 2012"
        });
        Add(new Book
        {
            Id = 2,
            Title = "Introducing Windows Server 2008 R2"
        });
        Add(new Book
        {
            Id = 3,
            Title = "Visual Studio 2010"
        });
        Add(new Book
        {
            Id = 4,
            Title = "Programming Windows Phone 7"
        });
        Add(new Book
        {
            Id = 5,
            Title = "Visual Studio 2008"
        });
        Add(new Book
        {
            Id = 6,
            Title = "Microsoft .NET 4.0"
        });
        Add(new Book
        {
            Id = 7,
            Title = "ASP.NET 4.0"
        });
    }

    public IEnumerable<Book> Get()
    {
        return products;
    }

    public IEnumerable<Book> GetList(string search)
    {
        return products.Where(m => m.Title.Contains(search));
    }

    public Book Add(Book item)
    {
        item.Id = _nextId++;
        products.Add(item);
        return item;
    }

    public void Remove(int id)
    {
        products.RemoveAll(p => p.Id == id);
    }
}
BookController
public class BookController : ApiController
{
    static IBookRepository repository = new BookRepository();

    // GET /api/book
    public IEnumerable Get(string search)
    {
        return (string.IsNullOrEmpty(search) ? repository.Get() : repository.GetList(search));
    }

    // POST /api/book/
    public Book Post(Book model)
    {
        return repository.Add(new Book() { Title = model.Title });
    }

    // DELETE /api/values/5
    public void Delete(int id)
    {
        repository.Remove(id);
    }
}

Bien, con esto ya podemos irnos a la parte de cliente para ver como haríamos. La verdad es que es bastante fácil y lo único que debemos hacer es extraer los valores de las propiedades de nuestro modelo para formar un objeto JSON que pasaremos vía POST a nuestra API.

De esa manera nuestra interfaz y nuestro modelo quedarían así
<h2>Ejemplo de uso de Knockout</h2>
<h3>Insertar un libro</h3>
<div>
  Título del libro <input type="text" data-bind="value: title" /><input type="button" data-bind="click: onsave" value="Save" />
</div>

<h3>Listado de libros</h3>
<div id="search">
  <input type="text" data-bind="value: search" />
  <input type="button" data-bind="click: onsearch" value="Search" />
</div>

<!-- ko if: books().length == 0 -->
<div data-bind="if: books().length == 0">
  No hay resultados que mostrar
</div>
<!-- /ko -->

<!-- ko ifnot: books().length == 0 -->
<ul data-bind="foreach: books">
  <li ><span data-bind="text: $data.Title"></span> | <span data-bind="click: $parent.ondelete" style="cursor: pointer">[x]</span></li>
</ul>
<!-- /ko -->
Vemos que hemos añadido una pequeña sección para añadir el título de un nuevo libro, mientras que el resto de la interfaz permanece igual respecto al ejemplo del artículo anterior.
<script type="text/javascript" language="javascript">
    function ViewModel() {
     var self = this;

     self.title = ko.observable('');
            self.search = ko.observable('');
     self.books = ko.observableArray([]);

     self.onsave = function () {
      var title = self.title();
   
      if (title == '')
      {
       alert('El título es obligatorio');
       return; 
      }
   
      $.ajax({
       type: 'POST',
       dataType: "json",
       contextType: "application/json; charset=utf-8",
       url: "@Url.Action("book", "api")",
       data: { Title: title },
       success: function (pReturn) {
        self.books.push(pReturn);
       }
      });
     }

     self.onsearch = function () {
      var search = self.search();
      $.ajax({
       type: 'GET',
       dataType: "json",
       contextType: "application/json; charset=utf-8",
       url: "@Url.Action("book", "api")" + "/get/" + search,
       data: { },
       success: function (pReturn) {
        self.books(pReturn);
       }
      });
     }

     self.ondelete = function (e) {   
      $.ajax({
       type: 'DELETE',
       dataType: "json",
       contextType: "application/json; charset=utf-8",
       url: "@Url.Action("book", "api")",
       data: { id: e.Id },
       success: function (pReturn) {
        self.books.remove(e);
       }
      });
     }

     self.onsearch();
    }
    ko.applyBindings(new ViewModel()); 
</script>
Vemos que el método onsave extraemos el valor de la propiedad observable que luego usaremos para formar el JSON que pasaremos en la llamada. Para modelos más complejos podríamos serializar con el método toJSON todo el modelo de la siguiente manera:
self.onsave = function () { 
        var data = ko.toJSON(self);
   
 if (data.title == '')
 {
  alert('El título es obligatorio');
  return; 
 }
   
 $.ajax({
  type: 'POST',
  dataType: "json",
  contextType: "application/json; charset=utf-8",
  url: "@Url.Action("book", "api")",  
                data: data,
  success: function (pReturn) {
   self.books.push(pReturn);
  }
 });
}
OJO, porque este ejemplo "no funciona" porque en la serialización a JSON del modelo también se está incluyendo la propiedad books, que incluye la lista de libros que se está mostrando y esto provoca que el mapeo del modelo se haga incorrectamente en el método POST de la API. Lo he comentado para que sirva de referencia en modelos más complejos.

Otra opción es usar dos modelos diferentes en la misma vista, pero eso lo dejaremos para otro artículo que este con tanto código se ha extendido un poco.

Happy coding!