Mostrando entradas con la etiqueta csharp. Mostrar todas las entradas
Mostrando entradas con la etiqueta csharp. Mostrar todas las entradas

viernes, 2 de noviembre de 2012

C# 5 - Caller Information

Una de las novedades que trae C# en su versión 5.0 es el Caller Information que nos puede ser bastante útil para trazar nuestras aplicaciones. Ahora podemos obtener la siguiente información del método "llamante"
  • CallerFilePathAttribute: Ruta completa del fichero que contiene al "llamante". Hay que tener en cuenta que es la ruta en el momento de la compilación.
  • CallerLineNumberAttribute: Número de linea que contiene la llamada a nuestro método. Line number in the source file at which the method is called.
  • CallerMemberNameAttribute: Método o propiedad del "llamante".
Y como siempre, lo mejor es verlo todo con un ejemplo.
private void button_Click(object sender, EventArgs e)
{
    //...
    NuestroMetodo();
    //...
}

public void NuestroMetodo([CallerFilePath] string sourceFile = "",
    [CallerLineNumber] int lineNumer = 0,
    [CallerMemberName] string memberName = "") {

    string msg = String.Format("{0}: Llamada a nuestro método desde {1}.{3}: line {2} en {4}",
        DateTime.Now.ToString("yyyy-mm-dd HH:MM:ss.fff"),
        Path.GetFileNameWithoutExtension(sourceFile),
        lineNumer,
        memberName,
        sourceFile);

    Debug.WriteLine(msg);
}
Y el resultado de ejecutar este código es el siguiente
2012-41-02 19:11:52.219: Llamada a nuestro método desde Form1.button_Click: line 25 en C:\Users\Sergio\Documents\Visual Studio 2012\Projects\CallerInformationDemo\CallerInformationDemo\Form1.cs
Hay que tener en cuenta que los parámetros a los que le apliquemos los atributos deben ser opcionales. Si no lo hacemos así, el compilador de C# nos los requerirá en la llamada, y los valores que pasemos sobreescribiran los valores por defecto.

No se hasta que punto es práctico decorar todos nuestro métodos con estos atributos pero quizás para determinados momentos nos pueda resultar útil guardar esta información para posteriormente analizarla.

Happy coding!

Más información sobre estos nuevos atributos en la MSDN.
CallerFilePathAttribute
CallerLineNumberAttribute
CallerMemberNameAttribute

martes, 18 de septiembre de 2012

Utilizar un número indeterminado de parámetros en C#

Tal y como hace el método Format de la clase String hoy he necesitado hacer una función que utilice un número indeterminado, bueno en realidad tres o 4, de parámetros, pero no me apetecía crear 3, 4 o 5 métodos  iguales con llamadas de unos a otros. La solución es bien simple y para eso usaremos una característica que nos da C#. Para demostrar su funcionamiento haremos una función que sumará un número indeterminado de número de enteros
public int Suma(params int[] values)
{
 int suma = 0;
 for (int i = 0; i < values.Length; i++)
  suma += values[i];
 return suma;
}
Bastante fácil, ¿verdad?

Happy coding!

miércoles, 4 de julio de 2012

Entity Framework y el borrado de entidades (II) - usando lambda y árboles de expresión

Como ya explique en el artículo anterior la forma más natural de borrar un registro es a través de su clave primaria, aunque eso no siempre tiene porque ser así. Un par de ejemplos sobre esto puede ser
  • Borrar aquellos registros que tengan más de un año de antigüedad. Práctico en tablas de logs.
  • Borrar aquellos registros que tengan un estado determinado.
  • O simplemente querer borrar registros en base a una clave ajena, como por ejemplo, borrar todas los lineas de una factura.
En todos estos casos la solución planteada en el artículo anterior no nos valdría ya que asumimos que tan sólo vamos a borrar un registro.

Borrado de varias entidades
Basándonos en este ejemplo
public class Bar
{
  public int IdBar { get; set; }
  public string Description { get; set; }
  public int IdState { get; set; }
}
y queriendo borrar aquellos registros que estén en el estado 1 podríamos hacer lo siguiente
using (DataContext context = new DataContext())
{
  var list = context.Bar.Where(m => m.IdState == 1);
  foreach (Bar bar in list)
    context.Bar.Remove(bar);
  context.SaveChanges();
}
Si analizamos el código anterior veremos que para borrar n elementos, debemos hacer n + 1 accesos a la base de datos. Lanzaremos n consultas de borrado, y necesitaremos una más para recuperar los elementos a borrar.

Como últimamente me he vuelto un inconformista esta solución no me gusta porque para borrar 1000 elementos necesito hacer 1001 acceso a base de datos y eso me parece demasiada carga para un servidor, que en mi caso, ya anda demasiado justo de recursos.

La primera solución que se me ocurrió fue usar un método de este estilo
using (DataContext context = new DataContext())
{
  context.Database.ExecuteSqlCommand("DELETE FROM Bar WHERE IdState = @IdState", new 
object[] { new System.Data.SqlClient.SqlParameter("IdState", 1) });
}
Esta solución es bastante buena aunque me deja atado a una tabla y a un filtro en concreto, lo cual me obliga a estar estableciendo métodos por cada tabla donde desee borrar un conjunto de datos y si uso varios filtros a estar usando parámetros opcionales (que suelen afear el código bastante).

Para mi lo ideal sería en un sólo método poder resolver esta situación, y tras investigar un poco se puede hacer con algo de código extra. Dado que estoy usando Code First he creado un método Delete en el contexto con el siguiente aspecto
public void Delete<T>(Expression<Func<T, bool>> condition)
{
  string where = generateWhere(condition.Body);
  string query = String.Format("DELETE FROM {0} WHERE {1}", typeof(T).Name, where);
  ((IObjectContextAdapter)this).ObjectContext.ExecuteStoreCommand(query);
}
De esta manera puedo pasar que entidad estoy intentado borrar y los filtros que voy a usar en el borrado. Antes de continuar me gustaría aclarar que transformar un árbol de expresion a una condición SQL no es una tarea fácil y esta llena de pequeño matices que puede que hagan que no sea rentable realizar el esfuerzo. En mi caso las condiciones de borrado son muy simples y la mejora de rendimiento (que veremos al final) justificaban el pequeño esfuerzo que ha llevado el desarrollo de este método. Pero ojo, hacer un LINQ Provider no es una tarea que se pueda considerar trivial.

Bien, con esto podríamos llamar a nuestro método de borrado de la siguiente manera
using (DataContext context = new DataContext())
{
  context.Delete<Bar>(m => m.IdState == 1);
}
Si analizamos nuestra Expression de entrada vemos como tiene una propiedad Body donde está la/s condiciones que hemos pasado y que tenemos que transformar a una condición Where de SQL. En esta propiedad tenemos tanto la parte izquierda como la parte derecha de la condición, así como el operador que se está usando. En el método generateWhere analizaremos todas estas partes para devolver un Where válido.
private string generateWhere(dynamic operation)
{
  string left = generateToken(ExpressionTypeEnum.Left, operation.Left);
  string right = generateToken(ExpressionTypeEnum.Right, operation.Right);

  // TODO: Completar operaciones (por ahora con estas es suficiente)
  var ops = new Dictionary();
  ops.Add(ExpressionType.Equal, "=");
  ops.Add(ExpressionType.GreaterThan, ">");
  ops.Add(ExpressionType.GreaterThanOrEqual, ">=");
  ops.Add(ExpressionType.LessThan, "<");
  ops.Add(ExpressionType.LessThanOrEqual, "<=");

  ops.Add(ExpressionType.And, "AND");
  ops.Add(ExpressionType.AndAlso, "AND");
  ops.Add(ExpressionType.Or, "OR");
  ops.Add(ExpressionType.OrElse, "OR");

  return string.Format("{0} {1} {2}", left, ops[operation.NodeType], right);
}
En mi caso estoy tomando como base que la Expression es de tipo BinaryExpression aunque más exactamente es de tipo LogicalBinaryExpression (esto lo podemos comprobar en tiempo de ejecución). En otras expresiones este análisis de la expresión fallaría por no tener alomejor una propiedad Right.

Vemos que tanto para la parte izquierda como para la parte derecha llamamos al método generateToken que se encarga de generarnos una cadena con el valor contenido en esa parte, e incluso, de llamar recursivamente al método generateWhere en caso que estemos combinado filtros de esta manera
using (DataContext context = new DataContext())
{
  context.Delete<Bar>(m => m.IdState == 1 || m.IdState == 2);
}
El método generateToken tendría el siguiente aspecto
private string generateToken(ExpressionTypeEnum type, dynamic operation)
{
  string token = string.Empty;
  if (operation is BinaryExpression)
    token = generateWhere(operation);
  else
    token = (type == ExpressionTypeEnum.Left ? operation.Member.Name : operation.Value.ToString());
   
  return token;
}
Como único detalle hay que tener en cuenta que dependiendo de que parte de la expresión estemos analizando extraeremos el token de una u otra propiedad.

Con esto ya tenemos nuestro pequeño "parser" listo para ser usado en un entorno controlado y con condiciones simples. He detectados otras expresiones que poco a poco iré añadiendo. Estas expresiones son
  • UnaryExpression. Para filtros de tipo m => !m.IsActive. Donde IsActive es de tipo boolean.
  • PropertyExpression: Para filtros de tipo m=> m.IsActive. Donde IsActive es de tipo boolean.
  • MethodBinaryExpression: Para filtros de tipo m=>m.Date < DateTime.Now. Donde como es lógico Date es de tipo DateTime.
Para ver la mejora de rendimiento he realizado un borrado iterativo y un borrado con esté método usando el siguiente código
Iterativo
using (DataContext context = new DataContext())
{
  var list = context.Bar.Where(m => m.IdState == 1);
  foreach (Bar bar in list)
    context.Bar.Remove(bar);
  context.SaveChanges(); 
}
Directo
using (DataContext context = new DataContext())
{
  context.Delete<Bar>(m => m.IdState == 1);
}

He lanzado estos métodos borrado 10, 100, 1000 y 10000 elementos. Cada bloque lo he lanzado tres veces y he calculado la media para dar la duración de la duración. Con estas condiciones estos son los datos que he obtenido

Registros a borrar - Tiempo de ejecución en ms 

Conclusiones
Como vemos en la tabla para borrados de más de 1000 registros el borrado iterativo se puede vuelve un poco pesado, ya que nos lleva casi 4 segundos ejecutarlo (producto de tener que lanzar 1001 consultas a la base de datos), mientra que nuestro borrado directo no llega a los 12 ms. En condiciones más extremas (10.000 registros) el borrado iterativo llega a casi 2 minutos de ejecución mientras que el directo no nos pasa de un cuarto de segundo. Interesante, ¿verdad?

En la mayoría de los escenarios no merece la pena complicarnos la vida tanto ya que tan solo borraremos un par de registros y podemos optar por el método iterativo, ya que como vemos entre ambos métodos para 10 registros casi no tenemos diferencias de tiempos. Para borrados de más registros siempre podemos optar procedimientos almacenados o lanzar directamente nosotros la consultas desde nuestro programa, pero si queremos tener a nuestra disposición mucho filtros para borrar está opción nos puede valer.

Eso si, como ejercicio de programación ha estado realmente bien y me he permitido ver por encima como funcionan los árboles de expresión.
Happy coding!

martes, 3 de julio de 2012

Entity Framework y el borrado de entidades (I)

A la hora de borrar entidades en Entity Framework nos damos cuenta que podemos actuar de dos maneras diferentes sobre todo si tenemos en cuenta factores como el número de consultas que tenemos que lanzar contra nuestra base de datos.

Borrado de entidades por la clave primaria
Quizás es la forma más natural de realizar un borrado aunque no por ello es la única. Supongamos que tenemos la siguiente entidad POCO con la cual hemos montado nuestra base de datos a través de Code First.
public class Bar
{
  public int IdBar { get; set; }
  public string Description { get; set; }
  public int IdState { get; set; }
}
El código para borrar una entidad por su clave primera podría ser algo así
private void DeleteByID(int idBar)
{
  using (DataContext context = new DataContext()) {
    Bar bar = context.Bar.Where(m => m.IdBar == idBar).SingleOrDefault();
    context.Bar.Remove(bar);
    context.SaveChanges();
  }
}
¿Sencillo verdad? La verdad es que si, aunque si somos un poco curiosos o simplemente sabemos lo que estamos haciendo y usamos el SQL Server Profiler veremos que la ejecución de ese código tan simple genera estas dos consultas
exec sp_executesql N'SELECT TOP (2) 
[Extent1].[IdBar] AS [IdBar], 
[Extent1].[Description] AS [Description], 
[Extent1].[IdState] AS [IdState]
FROM [dbo].[Bar] AS [Extent1]
WHERE [Extent1].[IdBar] = @p__linq__0',N'@p__linq__0 int',@p__linq__0=1
exec sp_executesql N'delete [dbo].[Bar]
where ([IdBar] = @0)',N'@0 int',@0=1
O sea, que estamos lanzando dos consultas contra nuestra base de datos para simplemente hacer un borrado. Esto desde mi punto de vista es un poco absurdo, aunque menos mal que con un pequeño truco podemos resolver este pequeño inconveniente. El truco consiste en adjuntar a nuestro contexto una entidad con la clave que queramos borrar, luego la borramos y grabamos los cambios. Con esto se lanzará la consulta de borrado con la información de la clave que deseamos borrar. El código para esto es el siguiente
private void DeleteByID(int idBar)
{
  using (DataContext context = new DataContext())
  {
    Bar bar = new Bar() { IdBar = idBar };
    context.Bar.Attach(bar);
    context.Bar.Remove(bar);
    context.SaveChanges();
  }
}
Si usamos el SQL Profiler con este método veremos que tan solo se ejecuta esta consulta
exec sp_executesql N'delete [dbo].[Bar]
where ([IdBar] = @0)',N'@0 int',@0=1
Como vemos, con un pequeño cambio hemos podido resolver de una manera fácil el problema de rendimiento que podríamos tener si hacemos muchos borrados en nuestra aplicación.

En el próximo artículo trataré el borrado de registros cuando no estamos borrando por la clave primaria y donde una vez más el número de accesos a la base de datos puede ser un factor muy importante a tener en cuenta.

Happy codding!

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!

viernes, 11 de mayo de 2012

ASP.NET MVC4 Validando un campo que depende de otro

Parece que estoy destinado a tropezarme con algunos problemas cada vez que quiero validar algo en las clases modelo de mis vistas. Lla parte positiva es que uno sigue aprendiendo, así que, vamos al lio. Como siempre, supongamos esta vista y este modelo simplificados

Vista
<h2>Index</h2>
@using (Html.BeginForm())
{
 <div>Otros @Html.CheckBoxFor(m => m.Otros)</div>
 <div>Comentario @Html.TextBoxFor(m => m.Comentario)</div>
 <div>@Html.ValidationMessageFor(m => m.Comentario)</div>
 <input type="submit" value="Enviar" />
}
Modelo
public class IndexModel
{
 public bool Otros { get; set; }
 public string Comentario { get; set; }
}
Como siempre, nada raro. Se trata de validar el campo Comentario siempre y cuando el valor de campo Otros sea verdadero. Una vez más busqué algo que de serie me hiciera esta validación pero no encontré nada, así que una vez más me tocó hacer mi propio validador. Para crear un validador simplemente tenemos que heredar de la clase ValidationAttribute y sobreescribir el método IsValid. Pero tendremos que sobreescribir el método IsValid que tiene dos argumentos (object value y ValidationContext validationContext) ya que tenemos que acceder al contexto de validación para obtener el valor de una propiedad. El esqueleto básico del validador sería algo así
public class RequiredIfTrue : ValidationAttribute
{
  protected override ValidationResult IsValid(object value, ValidationContext validationContext)
  {
    return base.IsValid(value, validationContext);
  }
}
Bien, lo primero que haremos será definir una propiedad (TrueOrFalsePropertyName) en nuestro validador para indicar el nombre de la propiedad enlazada. En nuestro caso, es la propiedad que deberá tener el valor "verdadero" para seguir con la validación, o sea, la propiedad Otros. Luego, en el método IsValid, accederemos a esa propiedad, obtendremos su valor, y comprobaremos si tenemos o no que continuar con la validación. También haremos algunas validaciones como que el nombre de la propiedad es válido y que el tipo que devuelve es un boolean En estos casos, he optado por lanzar una excepción para detener el flujo del programa ya que considero que son fallos de "programación" y que estos fallos no llegarían al usuario final. Con estás premisas nuestro validador queda de la siguiente forma
public class RequiredIfTrue : ValidationAttribute
{
 public string TrueOrFalsePropertyName { get; set; }

 protected override ValidationResult IsValid(object value, ValidationContext validationContext)
 {
  if (string.IsNullOrEmpty(TrueOrFalsePropertyName))
   throw new Exception("No se ha especificado la propiedad 'TrueOrFalsePropertyName'");

  // Obtenemos la propiedad
  PropertyInfo property = validationContext.ObjectType.GetProperty(TrueOrFalsePropertyName);
  if (property == null) 
   throw new Exception(string.Format("No se encuentra la propiedad '{0}'", TrueOrFalsePropertyName));
  
  // Comprobamos que la propiedad sea de tipo System.Boolean
  if (property.PropertyType != typeof(System.Boolean))
   throw new Exception(string.Format("La propiedad '{0}' no es de tipo System.Boolean", TrueOrFalsePropertyName));

  bool pValue = (bool)property.GetValue(validationContext.ObjectInstance, null);

  // Comprobamos el valor
  if (pValue && (value == null || (string)value == string.Empty)) return new ValidationResult(ErrorMessage);
  
  return null;
 }
}
Y el funcionamiento del validador se puede ver en las siguientes imágenes.



Si no marcamos el campo otros da igual el valor


Si marcamos el campo Otros, el campo Comentario es obligatorio.


Todo correcto otra vez

Happy coding!

Nota: Y para estrenar mi cuenta en SkyDrive, se pueden descargar el código fuente de los siguientes enlaces.
MvcApplication1.rar
MvcApplication1.zip

viernes, 4 de mayo de 2012

ASP.NET MVC4 Validando que un campo sea true o checked

Parece que todo lo que me propongo hacer últimamente se convierte en una odisea, por muy fácil que pueda parecer al principio. Tras mi anterior aventura para validar que dos campos sean iguales en un formulario de registro esta vez le ha tocado el turno a la validación de un valor en un checkbox. Supongamos una vez más un modelo bastante simple

Modelo
public class IndexModel
{
  public bool AceptoCondiciones { get; set; }
}
Vista
@using (Html.BeginForm())
{   
  <div>@Html.CheckBoxFor(m => m.AceptoCondiciones) Acepto las condiciones de uso</div> 
  <div>@Html.ValidationMessageFor(m => m.AceptoCondiciones)</div>
 
  <input type="submit" value="Enviar" />
}
La idea es comprobar que el usuario acepta las condiciones de uso. Algo bastante típico en formularios de registro. La primera idea fue marcar la propiedad AceptoCondiciones con el DataAnnotation [Required], pero tras escribirlo (no hizo falta ni probarlo) me di cuenta que no iba a funcionar, ya que el checkbox siempre tiene un valor, o verdadero o falso (marcado o desmarcado) por lo que la regla Required no nos valdría.

Tras mirar y mirar por google y darme cuenta que ya llevaba bastante tiempo perdido no me quedo más remedio que lanzarme a hacer mi propio validador. Para hacer una validador propio tan solo debemos heredar de la clase ValidationAttribute y sobreescribir el método IsValid. Pensando en el futuro añadí una propiedad ValidValue que nos indica cual es el valor que damos por válido, no sea que alguna vez necesite tener desmarcado un check. Con esto el validador quedaría así
public class IsTrueOrFalseAttribute : ValidationAttribute
{
  /// 
  /// Valor permitido
  /// 
  public bool ValidValue { get; set; }

  public override bool IsValid(object value)
  {
    if (value == null || value.GetType() != typeof(bool)) return false;

    return ((bool)value == ValidValue ? true : false);
  }
}
Y añadimos el atributo al modelo
public class IndexModel
{
  [IsTrueOrFalse(ValidValue=true)]
  public bool AceptoCondiciones { get; set; }
}
Con esto esto ya podemos probar nuestro validador y ver que funciona de la manera esperada


Pero, siempre hay un pero, este validador presenta un problema y es que siempre irá al servidor para realizar la validación. En mi caso no hay problema porque en el formulario de registro siempre voy al servidor para comprobar algunas cosas, como el nombre de usuario, pero en otros escenarios puede ser que no tengamos que ir al servidor para nada, e ir para solo comprobar si un checkbox está marcado es innecesario.


Como me gusta investigar y aprender cosas nuevas, decidí hacer que mi validador también tuviera soporte para cliente. La verdad es que esta parte está bastante mal documentada pero básicamente lo primero que tenemos que hacer es que nuestro validador implemente la interfaz IClientValidatable. En nuestro caso el validador quedaría de la siguiente manera
public class IsTrueOrFalseAttribute : ValidationAttribute, IClientValidatable
{
  /// 
  /// Valor permitido
  /// 
  public bool ValidValue { get; set; }

  public override bool IsValid(object value)
  {
    if (value == null || value.GetType() != typeof(bool)) return false;

    return ((bool)value == ValidValue ? true : false);
  }

  IEnumerable<modelclientvalidationrule> IClientValidatable.GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
  {
    var rule = new ModelClientValidationRule
    {
        ErrorMessage = String.IsNullOrEmpty(ErrorMessage) ? FormatErrorMessage(metadata.PropertyName) : ErrorMessage,
        ValidationType = "istrueorfalse"
    };
    // Transformamos el valor boolean a un string en minúsculas para que no falle la transformación en cliente de string a boolean.
    rule.ValidationParameters["validvalue"] = (this.ValidValue ? "true" : "false");

    yield return rule;
  }
}
Y luego en un fichero .js debemos añadir el siguiente código
$.validator.addMethod("istrueorfalse", function (value, element, param) {
  // Comparamos el valor checked del elemento con el valor permitido pasado por parámetro
  return element.checked == Boolean(param.validvalue);
});

$.validator.unobtrusive.adapters.add('istrueorfalse', ['validvalue'], function (options) {
  options.rules['istrueorfalse'] = options.params;
  options.messages['istrueorfalse'] = options.message;
});
Si ejecutamos nuestro código veremos que realiza la validación sin la necesidad de ir a cliente.

Nota: Para activar las validaciones en cliente (pese a que en ASP.NET MVC4 ya viene por defecto) debemos asegurarnos que tenemos estas lineas en el web.config
<appSettings>
  <add key="ClientValidationEnabled" value="true" />
  <add key="UnobtrusiveJavaScriptEnabled" value="true" />
</appSettings>
También hay que comprobar que tenemos añadidas las referencias a los scripts jquery-1.6.2.js, jquery.validate.js, jquery.validate.unobtrusive.js y al fichero que creemos para añadir las lineas donde añadimos el validador y el adaptador.

Como vemos tampoco es extraordinariamente complicado validar nuestros datos en cliente así que, lo mejor que podemos hacer es hacerlo...

Happy coding!

miércoles, 2 de mayo de 2012

ASP.NET MVC4 Validando que dos campos sean iguales

Algo muy típico en el registro de usuarios es tener que introducir la contraseña dos veces para asegurarnos que la contraseña introducida es la misma. Lo normal sería poner esta validación como un DataAnnotation en la clase modelo con la que trabaje nuestra vista. Lo que vendría a ser algo así

Modelo
using System.ComponentModel.DataAnnotations;

public class IndexModel
{
  [Required(ErrorMessage = "La constraseña es obligatoria")]
  public string Password { get; set; }
  public string ConfirmPassword { get; set; }
}
Vista
@using (Html.BeginForm())
{
  <div class="editor-label">
    <label>Password</label>
    @Html.TextBoxFor(m => m.Password)
    @Html.ValidationMessage("Password")
  </div>
  <div class="editor-label">
    <label>Confirm password</label>
    @Html.TextBoxFor(m => m.ConfirmPassword)
    @Html.ValidationMessage("ConfirmPassword")
  </div>
  <input type="submit" value="Submit" />
}
Como vemos es un modelo muy simple en el que la contraseña es obligatoria y en el que tenemos que validar que ambas contraseñas introducidas sean iguales. Pues bien, lo primero que hice fue mirar en los DataAnnotations si había algún modificador para lo que yo estaba buscando, y cual fue mi sorpresa al ver que no había nada.

Lo siguiente que se me ocurrió fue hacer un validador personalizado, pero tras mirar como se hace me pareció que esto era demasiado complicado para lo que yo quería hacer y que seguro que algo tenía que haber en el framework para resolverlo.

Tras mucho indagar logré encontrarlo y en atributo en concreto se llama Compare (que original), pero en vez de estar definiddo en el namespace System.ComponentModel.DataAnnotations está definido en el namespace System.Web.Mvc. Así que nuevo modelo quedaría definido de la siguiente manera
using System.ComponentModel.DataAnnotations;
using System.Web.Mvc;

public class IndexModel
{
  [Required(ErrorMessage = "La constraseña es obligatoria")]
  [Compare("ConfirmPassword", ErrorMessage = "Las constraseñas no son iguales")]
  public string Password { get; set; }
  public string ConfirmPassword { get; set; }
}
Mucho más simple que hacer nosotros el validador.
Happy coding!


viernes, 27 de abril de 2012

Introducción al Sql Injection en MySql

Después de leer este comentario "Eso con MySql no pasaría. Esas cosas solo le pasan al Sql Server porque es de Microsoft..." en mi artículo sobre Introducción al Sql Injection en Sql Server no me he quedado más remedio que realizar el mismo experimento pero esta vez sobre MySql. Voy a dejar fuera del artículo como instalar las cosas que se necesitan para probar el código pero básicamente son
También nos lo podemos bajar todo junto de aquí.

Bien, con todo montado y funcionando lo primero que haremos será crear una tabla User similar a la anterior e insertar un registro. Podemos hacerlo usando las siguientes sentencias SQL
CREATE  TABLE `test`.`User` (
  `IdUser` INT NOT NULL AUTO_INCREMENT ,
  `Username` VARCHAR(50) NOT NULL ,
  `Password` VARCHAR(200) NOT NULL ,
  `Name` VARCHAR(50) NOT NULL ,
  PRIMARY KEY (`IdUser`) ,
  UNIQUE INDEX `IX_Username` (`Username` ASC) );

INSERT INTO `test`.`User` (`Username`, `Password`, `Name`) VALUES ('admin', 'admin', 'Administrador');
Y montaremos una aplicación con el mismo aspecto que la que usamos en el anterior artículo.


Tras esto pondremos este código en el manejador del evento Click del botón
try
{
 string connectionString = ConfigurationManager.ConnectionStrings["Foo"].ConnectionString;
 using (MySqlConnection connection = new MySqlConnection(connectionString))
 {
  connection.Open();
  string cmdText = "SELECT * FROM User WHERE Username='" + textUsername.Text + "' AND Password='" + textPassword.Text + "'";
  MySqlCommand command = new MySqlCommand(cmdText, connection);
  MySqlDataReader dataReader = command.ExecuteReader();
  if (dataReader.HasRows)
  {
   dataReader.Read();
   string name = dataReader["Name"].ToString();
   labelStatus.Text = "Bienvenido al sistema " + name;
  }
  else
   labelStatus.Text = "Nombre de usuario o contraseña incorrectos";
 }
}
catch (Exception ex)
{
 labelStatus.Text = ex.Message;
}
Bien, ahora es el momento de hacer las mismas pruebas que hicimos con el Sql Server.
Si ponemos el usuario y la contraseña de manera correcta


Y si la ponemos de manera incorrecta


Ahora toca hacer el Sql Injection, pero como no uso normalmente MySql no me queda más remedio que buscar como comentar una linea a partir de un carácter (o caracteres). La solución está aquí y es usando el carácter #. Con esto usaremos ' OR 1=1 #  como nombre de usuario y el resultado es el siguiente


Como vemos, hemos accedido al sistema simplemente conociendo un nombre de usuario, al igual que ocurría con el Sql Server. Para corregir este fallo de seguridad usaremos parámetros tal y como hicimos en el anterior artículo
try
{
    string connectionString = ConfigurationManager.ConnectionStrings["Foo"].ConnectionString;
    using (MySqlConnection connection = new MySqlConnection(connectionString))
    {
        connection.Open();
        string cmdText = "SELECT * FROM User WHERE Username=?Username AND Password=?Password";
        MySqlCommand command = new MySqlCommand(cmdText, connection);
        command.Parameters.Add(new MySqlParameter("?Username", textUsername.Text));
        command.Parameters.Add(new MySqlParameter("?Password", textPassword.Text));
        MySqlDataReader dataReader = command.ExecuteReader();
        if (dataReader.HasRows)
        {
            dataReader.Read();
            string name = dataReader["Name"].ToString();
            labelStatus.Text = "Bienvenido al sistema " + name;
        }
        else
            labelStatus.Text = "Nombre de usuario o contraseña incorrectos";
    }
}
catch (Exception ex)
{
    labelStatus.Text = ex.Message;
}
En este caso si usáramos el mismo nombre de usuario obtendríamos el siguiente resultado


Como vemos del Sql Injection no se escapa "casi nadie" sobre todo si somos nosotros mismos los que hacemos las cosas mal y no validamos la información introducida por el usuario.

miércoles, 25 de abril de 2012

Entity Framework Code First DbMigration (II)

En el anterior artículo sobre las migraciones automáticas en Entity Framework vimos como lanzar las migraciones desde la Package Manager Console de Visual Studio. La pregunta ahora sería, ¿podemos lanzar las migraciones desde el código de nuestra aplicación?, y la respuesta como no cabía esperar de otra manera es que si.

Para crear los ficheros de migración seguiremos usando la consola siguiendo los pasos descritos en el artículo anterior, pero para actualizar la base de datos haremos lo siguiente:
var config = new Migrations.Configuration();
var dbMigrator = new DbMigrator(config);
dbMigrator.Update();
Esto lanzará las migraciones pendientes. En caso que queramos lanzar una migración en concreto tendremos que indicárselo al método Update de la siguiente manera
var config = new Migrations.Configuration();
var dbMigrator = new DbMigrator(config);
dbMigrator.Update("201204191333459_InitialCreate");
Como vemos es bastante fácil lanzar nuestras actualizaciones desde código, pero cuidado con lo que hacemos sobre todo si la base de datos está en producción no sea que nos llevemos sorpresas desagradables.

Happy coding!

Mas info | Entity Framework Code First DbMigration (I)

lunes, 23 de abril de 2012

Entity Framework Code First DbMigration (I)

Siguiendo con mi línea de aprendizaje de Entity Framework esta vez le ha tocado el turno a una de las novedades introducidas en la versión 4.3, las migraciones automáticas o DbMigration. Creo que su nombre deja claro para que sirve, pero básicamente nos da soporte para realizar actualizaciones de nuestra base de datos. ¿Y qué podemos actualizar?, pues básicamente podremos crear, renombrar y borrar tanto tablas como columnas, así como crear índices, claves ajenas o incluso ejecutar sentencias SQL para actualizar registros. ¿Impresionado? ¿No? Pues deberías, y sino sigue leyendo para que veas lo que podrás hacer.

Bien, para poder hacer uso de las migraciones deberás tener instalado la versión 4.3 de Entity Framework como mínimo, en caso que no tengas estas versión o no estés seguro de que versión estás usando te recomiendo una lectura de mi artículo sobre Entity Framework Fluent Api, donde en la primera parte cuento como actualizar el Entity Framework a través del Package Manager Console de Visual Studio.

Lo primero que haremos será crear nuestras clases POCO, y nuestro DataContext (esto también está explicado en el artículo anteriormente mencionado).
Primero las clases POCO
public class Blog
{
 public int IdBlog { get; set; }
 public string Title { get; set; }
 public string Url { get; set; }

 // Navigation properties
 public virtual ICollection<Post> Posts { get; set; }
}

public class Post
{
 public int IdPost { get; set; }
 public string Title { get; set; }
 public string Body { get; set; }  

 // Foreign key
 public int IdBlog { get; set; }

 // Navigation properties
 public virtual Blog Blog { get; set; }
 public virtual Category Category { get; set; }
 public virtual ICollection<Tag> Tags { get; set; }
}

public class Category
{
 public int IdCategory { get; set; }
 public string Name { get; set; }

 // Navigation properties
 public virtual ICollection<Post> Posts { get; set; }
}

public class Tag
{
 public int IdTag { get; set; }
 public string Name { get; set; }

 // Navigation properties
 public virtual ICollection<Post> Posts { get; set; }
}

Y ahora nuestro DbContext
public class DataContext : DbContext
{
 public DataContext() : base("DataContext") {
            
 }

 public DataContext(string nameOrConnectionString) : base(nameOrConnectionString)
 {

 }

 public DbSet<Blog> Blog { get; set; }
 public DbSet<Post> Post { get; set; }
 public DbSet<Category> Category { get; set; }
 public DbSet<Tag> Tag { get; set; }

 protected override void OnModelCreating(DbModelBuilder modelBuilder)
 {
  modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
  
  // Primary Keys
  modelBuilder.Entity<Blog>().HasKey(c => c.IdBlog);
  modelBuilder.Entity<Post>().HasKey(c => c.IdPost);
  modelBuilder.Entity<Category>().HasKey(c => c.IdCategory);
  modelBuilder.Entity<Tag>().HasKey(c => c.IdTag);

  // Properties
  modelBuilder.Entity<Blog>().Property(p => p.Title).IsRequired();
  modelBuilder.Entity<Blog>().Property(p => p.Title).HasMaxLength(50);

  modelBuilder.Entity<Post>().Property(p => p.Title).IsRequired();
  modelBuilder.Entity<Post>().Property(p => p.Title).HasMaxLength(50);
 }
}
Llegados a este punto ya podemos empezar a usar las migraciones propiamente dichas. Lo primero que haremos será habilitar las migraciones ejecutando lo siguiente en la consola
PM> Enable-Migrations
Si todo va bien deberemos ver algo parecido a esto
Detected database created with a database initializer. Scaffolded migration '201204191333459_InitialCreate' corresponding to current database schema. To use an automatic migration instead, delete the Migrations folder and re-run Enable-Migrations specifying the -EnableAutomaticMigrations parameter.
Code First Migrations enabled for project EntityFramework.CodeFirst.DbMigration.
¿Qué mas ha pasado? Pues se habrá creado una carpeta en nuestro proyecto con dos ficheros
  • *_InitialCreate.cs: Donde tendremos el script de creación de nuestra base de datos, en base a nuestro modelo de clase POCO.
  • Configuration.cs: Donde podremos configurar la migración y sobreescribiendo el método Seed llenar de datos nuestra base de datos.
¿Y ahora? Pues simple, actualizaremos nuestra base de datos con el script que hemos creado. Para esto usaremos el siguiente comando
PM> Update-Database
Y si todo ha ido bien veremos algo así
Specify the '-Verbose' flag to view the SQL statements being applied to the target database.
Applying explicit migrations: [201204191333459_InitialCreate].
Applying explicit migration: 201204191333459_InitialCreate.
Si vamos a la base de datos veremos que se ha creado la base de datos y dentro de la tablas del sistema se ha creado __MigrationHistory. Esta tabla es usada por el Entity Framework para llevar un control de las versiones.

Ahora supongamos que queremos añadir un campo a nuestra tabla blog, por ejemplo el campo Author, que deberá ser de tipo varchar, obligatorio y tener una longitud máxima de 250 caracteres. Lo primero será añadir este campo al objeto Blog como propiedad
public string Author { get; set; }
Lo siguiente será crear un nuevo script de migración de la siguiente forma
PM> Add-Migration BlogAddAuthor
Y si todo va bien veremos lo siguiente
Scaffolding migration 'BlogAddAuthor'.
The Designer Code for this migration file includes a snapshot of your current Code First model. This snapshot is used to calculate the changes to your model when you scaffold the next migration. If you make additional changes to your model that you want to include in this migration, then you can re-scaffold it by running 'Add-Migration 201204191351431_BlogAddAuthor' again.
También veremos que se ha creado un fichero *_BlogAddAuthor.cs, con esta estructura
public partial class BlogAddAuthor : DbMigration
{
    public override void Up()
    {
        AddColumn("Blog", "Author", c => c.String());
    }
        
    public override void Down()
    {
        DropColumn("Blog", "Author");
    }
}
Primero decir, que el método Up se llama cuando actualizamos (update) la base de datos y el método Down se llama cuando retrocedemos a una versión (downgrade). Aunque podemos modificar esto para ajustarnos a nuestras necesidades
public override void Up()
    {
        AddColumn("Blog", "Author", c => c.String(nullable: false, maxLength: 250));
    }
¿Qué cosas podemos hacer en estos métodos? Pues tenemos a nuestro alcance los siguiente métodos a través de la clase DbMigration
  • AddColumn
  • AddForeignKey
  • AlterColumn
  • CreateIndex
  • CreateTable
  • DropColumn
  • DropForeignKey
  • DropIndex
  • DropPrimaryKey
  • DropTable
  • MoveTable
  • RenameColumn
  • RenameTable Sql
Si no hubiéramos añadido la propiedad a la clase, obtendríamos este fichero vacio y tendríamos que añadir las sentencias AddColumn y DropColumn nosotros mismos (no siempre todos lo campos de la base de datos estarán en nuestras clases). Para actualizar nuestro modelo nuevamente usaremos Update-Database aunque esta vez usaremos el modificador -Verbose para ver un log de lo se está haciendo
PM> Update-Database -Verbose
Using NuGet project 'EntityFramework.CodeFirst.DbMigration'.
Using StartUp project 'EntityFramework.CodeFirst.DbMigration'.
Target database is: 'DbMigration' (DataSource: .\sql2k8r2, Provider: System.Data.SqlClient, Origin: Configuration).
Applying explicit migrations: [201204191358443_BlogAddAuthor].
Applying explicit migration: 201204191358443_BlogAddAuthor.
ALTER TABLE [Blog] ADD [Author] [nvarchar](250) NOT NULL DEFAULT ''
[Inserting migration history record]


¿Podemos hacer más? Pues si, por ejemplo volver a un versión. En este caso volveremos a la versión inicial ejecutando esto
PM> Update-Database –TargetMigration:$InitialDatabase –Force
Specify the '-Verbose' flag to view the SQL statements being applied to the target database.
Reverting migrations: [201204191358443_BlogAddAuthor, 201204191333459_InitialCreate].
Reverting explicit migration: 201204191358443_BlogAddAuthor.
Reverting explicit migration: 201204191333459_InitialCreate.
Si queremos ir a nuestra versión en concreto ejecutaremos
‘Update-Database –TargetMigration:"NombreDeLaVersion" –Force’
Para obtener más ayuda sobre los distintos comandos que tenemos (Enable-Migrations: Enables Code First Migrations in a project. Add-Migration: Scaffolds a migration script for any pending model changes. Update-Database: Applies any pending migrations to the database. Get-Migrations) tan solo debemos escribir lo siguiente en la Package Manager Console
PM> get-help Update-Database
Y obtendremos una ayuda detallada del comando.

Como vemos tenemos en nuestras manos una excelente herramienta para llevar una sincronización perfecta de nuestras base de datos

Happy coding!

viernes, 20 de abril de 2012

Introducción al Sql Injection en Sql Server

Desde hace algún tiempo intento participar lo más que puedo en los foros en español de la MSDN. Es un buen punto de encuentro para resolver dudas y la verdad es que de algunas respuestas se aprende muchísimo, e intentando ayudar a la gente refrescas muchos conocimientos que podrías tener en el baúl de los recuerdos.

Una de las cosas que me más sorprende es que en el foro de ADO.NET hay muchísimas preguntas en las que viendo el código se ve como la gente sigue trabajando con DataSet o DataTable. Las causas pueden ser muchas y este post no es el lugar más adecuado para discutir si este sistema de acceso a datos es mejor o peor que otros. Aunque como digo me sorprende, sobre todo si son aplicaciones nuevas teniendo en cuenta que hay alternativas que desde mi punto son mejores e incluso más fáciles de usar como Entity Framework, NHibernate o LLBLGen.

Otras cosas que se ven muchísimo, y es el motivo por el que escribo este post, es que se siguen concatenando cadenas para formar las consultas sql que luego se ejecutan a través de SqlCommand. Esto es un error desde muchos puntos de vista. El principal problema es que quedamos expuestos a ataques de Sql Injection, sin tener en cuenta que mantener este tipo de consultas es complicado.

 Este artículo no pretende ser una explicación completa y rigurosa sobre Sql Injection ya que existe gran cantidad de información al respecto en Internet pero si pretende ser una pequeña introducción sobre este tema.

Para este artículo creamos la sigiuente table en nuestro Sql Server
CREATE TABLE dbo.[User]
 (
 IdUser int NOT NULL IDENTITY (1, 1),
 Username varchar(50) NOT NULL,
 Password varchar(200) NOT NULL,
  Name varchar(50) NOT NULL
 )  ON [PRIMARY]
GO
ALTER TABLE dbo.[User] ADD CONSTRAINT
 PK_User PRIMARY KEY CLUSTERED 
 (
 IdUser
 ) WITH( STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]

GO
CREATE UNIQUE NONCLUSTERED INDEX IX_Username ON dbo.[User]
 (
 Username
 ) WITH( STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
GO
ALTER TABLE dbo.[User] SET (LOCK_ESCALATION = TABLE)
GO
COMMIT

Y añadimos un registro con la siguiente sentencia
INSERT INTO [User] ([Username], [Password], [Name])
VALUES ('admin', 'admin', 'Administrador')
Como vemos es una tabla muy simple que podemos usar para el inicio de sesión en nuestra aplicación. También crearemos un programa muy simple que tendrá el siguiente aspecto


Para seguir con la demostración y tomándome alguna licencia sobre la arquitectura de aplicaciones usaremos el siguiente código en el botón de iniciar sesión
private void buttonLogin_Click(object sender, EventArgs e)
{
 try {
  string connectionString = ConfigurationManager.ConnectionStrings["Foo"].ConnectionString;
  using (SqlConnection connection = new SqlConnection(connectionString))
  {
   connection.Open();
   string cmdText = "SELECT * FROM [User] WHERE [Username]='" + textUsername.Text + "' AND [Password]='" + textPassword.Text + "'";
   SqlCommand command = new SqlCommand(cmdText, connection);    
   SqlDataReader dataReader = command.ExecuteReader();
   if (dataReader.HasRows)
   {
    dataReader.Read();
    string name = dataReader["Name"].ToString();
    labelStatus.Text = "Bienvenido al sistema " + name;
   }
   else
    labelStatus.Text = "Nombre de usuario o contraseña incorrectos";
  }
  }
  catch (Exception ex)
  {
   labelStatus.Text = ex.Message;
  }
}
Como vemos el código es muy simple. Obtenemos la cadena de conexión del fichero de configuración y creamos una conexión y un comando. A este comando, le asignamos la consulta que creamos y la ejecutamos. Si nos devuelve un registro, mostramos el nombre del usuario y si no hay registros mostramos el clásico error de inicio de sesión incorrecto. En caso de excepción, mostramos el mensaje.

Si nos ponemos a hacer pruebas, veremos que el sistema se comporta como esperamos. Si usamos admin/admin el sistema nos da la bienvenida


Pero si usamos otra combinación el sistema nos devuelve un error


Hasta aquí todo bien. Pero ahora vamos a ponernos un poco juguetones, y vamos a suponer que sabemos que en el sistema hay un usuario admin (¿en que sistema no hay un usuario admin, administrador, administrator o root?) pero no sabemos sabemos la contraseña (como es lógico, sino esto no tendría sentido). Si ponemos admin' AND 1=1 --' como nombre de usuario sin importar lo que pongamos como contraseña obtenemos lo siguiente


¿Sorprendido? ¿No? Pues deberías. El problema está en la concatenación de la consulta que usamos para autenticar al usuario. Al usar ese nombre de usuario la consulta que se ejecuta es la siguiente
SELECT * FROM [User] WHERE [Username]='admin' AND 1=1 --'' AND [Password]='loquesea'
Y está consulta siempre nos devolverá el usuario admin sin necesidad de la contraseña. Es verdad que hemos usado algo de información, ya que conocíamos la existencia de un usuario admin, pero nos sirve de demostración de los peligros que corremos si no hacemos nuestras consultas de manera correcta.

Supongo que a estas alturas te estarás preguntando como podemos hacer para evitar esto. Pues existen dos formas
  1. Usar parámetros.
  2. Usar un procedimiento almacenado.
Para usar parámetros tan sólo debemos modificar nuestro código anterior de la siguiente forma
try
{
 string connectionString = ConfigurationManager.ConnectionStrings["Foo"].ConnectionString;
 using (SqlConnection connection = new SqlConnection(connectionString))
 {
  connection.Open();
  string cmdText = "SELECT * FROM [User] WHERE [Username]=@Username AND [Password]=@Password";
  SqlCommand command = new SqlCommand(cmdText, connection);
  command.Parameters.Add(new SqlParameter("@Username", textUsername.Text));
  command.Parameters.Add(new SqlParameter("@Password", textPassword.Text));
  SqlDataReader dataReader = command.ExecuteReader();
  if (dataReader.HasRows)
  {
   dataReader.Read();
   string name = dataReader["Name"].ToString();
   labelStatus.Text = "Bienvenido al sistema " + name;
  }
  else
   labelStatus.Text = "Nombre de usuario o contraseña incorrectos";
 }
}
catch (Exception ex)
{
 labelStatus.Text = ex.Message;
}
Y si ejecutamos nuestra aplicación con el usuario "tramposo" vemos que el sistema se comporta de la manera correcta.


El uso de los parámetros hace que la comparación se haga de manera literal y no se interprete.
[Username] = admin' AND 1=1 --'
No se interpretan ni las comillas simples ni el doble guion que sirve para poner comentarios.
Para usar la opción de un procedimiento almacenado, tendremos que crear éste, en la base de datos de la siguiente manera
CREATE PROCEDURE SP_Login
 @Username VARCHAR(50),
 @Password VARCHAR(200)
AS
BEGIN
 SELECT * FROM [User] WHERE [Username]=@Username AND [Password]=@Password
END
GO
Y para usarlo tendremos que modificar nuestro código, quedando éste así
try
{
 string connectionString = ConfigurationManager.ConnectionStrings["Foo"].ConnectionString;
 using (SqlConnection connection = new SqlConnection(connectionString))
 {
  connection.Open();
  string cmdText = "SP_LOGIN";
  SqlCommand command = new SqlCommand(cmdText, connection);
  command.CommandType = CommandType.StoredProcedure;
  command.Parameters.Add(new SqlParameter("@Username", textUsername.Text));
  command.Parameters.Add(new SqlParameter("@Password", textPassword.Text));
  SqlDataReader dataReader = command.ExecuteReader();
  if (dataReader.HasRows)
  {
   dataReader.Read();
   string name = dataReader["Name"].ToString();
   labelStatus.Text = "Bienvenido al sistema " + name;
  }
  else
   labelStatus.Text = "Nombre de usuario o contraseña incorrectos";
 }
}
catch (Exception ex)
{
 labelStatus.Text = ex.Message;
}
Y vemos que una vez más el sistema se comporta de la manera esperada.


El alcance de este tipo de ataques no solo se reduce a poder acceder al sistema conociendo un nombre de usuario, sino a cosas mucho más peligrosas, como poder incluso a borrar una tabla, aunque para esto hacen falta que se cumplan más requisitos como conocer el nombre de la tabla en nuestra base datos (que también se puede obtener via Sql Injection). Simplemente imaginemos que usamos como nombre de usuario admin' admin AND 1=1; DROP TABLE [User] --.

Como vemos se pueden correr muchos muchísimos peligros si hacemos las cosas sin pensar, y hacer las cosas bien no cuesta tanto.

Happy coding!

martes, 17 de abril de 2012

Entity Framework Code First Fluent API (I)

Code First es un nuevo enfoque de trabajo que aparece en el Entity Framework a partir de las versión 4.1, que nos permite crear nuestro modelo mediante clases POCO (Plain Old CLR Object), a partir de las cuales se generará nuestro modelo de base de datos. Otra característica es que en este modelo no existe el fichero edmx de definición del modelo (conceptual y de datos).

La principal ventaja de usar clases POCO es que el modelo de clases es más limpio ya que las clases no tiene ninguna dependencia con el Entity Framework (clase EntityObject). Por defecto se utiliza el nombre de nuestras clases y correspondientes propiedades para generar los nombre de tablas y campos. Esto puede ser más que suficiente para muchos proyectos, sobre todo si están empezando de cero, pero, ¿que ocurre si   queremos hacer modificaciones en nuestro modelo de datos? Para eso tenemos dos alternativas
  • Data Annotations
  • Fluent Api
Como ya he comentado al principio, para poder trabajar con CodeFirst es necesario usar la versión 4.1 (o superior) del Entity Framework. Para descargar e instalar esta versión la mejor alternativa es utilizar NuGet Gallery. Para eso en nuestro Visual Studio vamos a Herramientas > Library Package Manager > Package Manager Console. Con esto se nos abrirá una consola


donde escribiremos
Install-Package EntityFramework -Version 4.3.1
Si todo ha ido bien veremos algo parecido a esto


Ya tenemos la última versión del Entity Framework instalada en nuestro proyecto, así que, ¿ahora qué? Para demostrar el uso de Fluent Api para definir nuestro modelo de datos, voy a usar la estructura que podría tener un blog (muy simplificado). Para estos tendremos
  • Un blog con su identificador y título.
  • Una lista de post asociada a nuestro blog. Cada post tendrá su identificador, titulo, un título en html y un texto.
  • Cada post pertenecerá a una categoría y podrá tener una lista de etiquetas (tags). Tanto las categorías como las etiquetas, tendrán tanto un identificador como un nombre.
Con este esquema tan simple crearemos nuestras clases de la siguiente manera.
public class Blog
{
    public int IdBlog { get; set; }
    public string Title { get; set; }

    // Navigation properties
    public virtual ICollection<Post> Posts { get; set; }
}

public class Post
{
    public int IdPost { get; set; }
    public string Title { get; set; }
    public string HtmlTitle { get; set; }
    public string Body { get; set; }

    // Foreign key
    public int IdBlog { get; set; }

    // Navigation properties
    public virtual Blog Blog { get; set; }
    public virtual Category Category { get; set; }
    public virtual ICollection<Tag> Tags { get; set; }
}

public class Category
{
    public int IdCategory { get; set; }
    public string Name { get; set; }

    // Navigation properties
    public virtual ICollection<Post< Posts { get; set; }
}

public class Tag
{
    public int IdTag { get; set; }
    public string Name { get; set; }

    // Navigation properties
    public virtual ICollection<Post> Posts { get; set; }
}


Una vez hecho esto empezaremos a definir nuestro contexto de la siguiente manera
public class DataContext : DbContext { }
Ahora definiremos un constructor donde especificaremos el nombre de la cadena de conexión y el conjunto de entidades que serán mapeadas (en nuestro caso todas)
public class DataContext : DbContext
{
    public DataContext() : base("DataContext") { }

    public DbSet<Blog> Blog { get; set; }
    public DbSet<Post> Post { get; set; }
    public DbSet<Category> Category { get; set; }
    public DbSet<Tag> Tag { get; set; }
}
Lo siguiente que debemos hacer es definir nuestro modelo de datos, por ejemplo, definiendo cuales son las claves de nuestras tablas. Para estas definiciones usaremos la sintaxis que nos brinda Fluent Api, así que, sobreescribiremos el método OnModelCreating de nuestro DataContext y escribiremos lo siguiente para definir nuestras claves primarias
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    // Primary Keys
    modelBuilder.Entity<Blog>().HasKey(c => c.IdBlog);
    modelBuilder.Entity<Post>().HasKey(c => c.IdPost);
    modelBuilder.Entity<Category>().HasKey(c => c.IdCategory);
    modelBuilder.Entity<Tag>().HasKey(c => c.IdTag);           
}
Con este código "casi" ya podríamos crear nuestro primer modelo de datos, y ver como funciona Code First con Fluent Api. Tan solo deberíamos añadir este código a nuestro programa (para este ejemplo yo lo estoy poniendo en el constructor de un formulario)
public Main()
{
    InitializeComponent();

    Database.SetInitializer<DataContext>(new DropCreateDatabaseIfModelChanges<DataContext>());

    using (DataContext context = new DataContext()) {
        Blog b = context.Blog.FirstOrDefault();
    }
}
Bien este código tiene dos partes importantes
  • En la primera parte indicaremos cual es la estrategia de incialización de nuestro modelo. Entity Framework Code First incluye 3 inicializadores (ojo con estos inicializadores cuando pasemos nuestra aplicación a producción porque podemos perder todos nuestros datos): 
    1. CreateDatabaseIfNotExist
    2. DropCreateDatabaseAlways
    3. DropCreateDatabaseIfModelChanges
  • En la segunda parte hacemos una simple consulta (obtenemos el primer blog) a nuestro contexto porque si no realizamso una consulta no se ejecutaría nuestro inicializador (si nunca accedemos a nuestros datos, para que se va a perder el tiempo en crear el modelo de datos).
Si todo va bien veremos que el siguiente modelo de datos se ha creado

Vemos que con el modelo conceptual (clases POCO) y la definición de las claves primarias Entity Framework ha creado un esquena de base de datos que se ajusta perfectamente a nuestra definición. Inlcluso a creado una tabla intermedia (TagPost) para poder gestionar la relación muchos a muchos que hay entre los Posts y los Tags.

A partir de aquí podemos modificar nuestro modelo de datos a través de Fluent Api añadiendo estas lineas al método OnModeloCreating. Por ejemplo:

Evitar que la clave primaria sea un identity en la tabla Categoria
modelBuilder.Entity<Category>()
  .Property(p => p.IdCategory) 
  .HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);
Forzar a que un campo sea obligatorio (NOT NULL) y tenga una longitud máxima de 50 caracteres
modelBuilder.Entity<Blog>().Property(p => p.Title).IsRequired();
modelBuilder.Entity<Blog>().Property(p => p.Title).HasMaxLength(50);
Evitar que una propiedad de nuestro modelo conceptual esté en en la base de datos
modelBuilder.Entity<Post>().Ignore(p => p.HtmlTitle);
Si volvemos a ejecutar nuestra aplicación y dado que nuestra estrategia de inicialización era borrar la base de datos si el modelo ha cambiado, y éste ha cambiado, veremos que que nuestros cambios se ha realizado de la forma esperada.
  • El identificador de la tabla Category no es un identity
  • Se han modificado la características del campo Title de la tabla Blog

  • El campo HtmlTitle no se mapea en la tabla Post

Como vemos, tenemos total libertad para ir refinando nuestro modelo de datos a partir de nuestro modelo conceptual.

Happy coding !

lunes, 16 de abril de 2012

Registrando los cambios de nuestros objectos en Entity Framework (II)

En mi anterior artículo sobre como registrar cambios de nuestros objetos en el Entity Framework dejé sin terminar una parte. En concreto, la parte en la que obtendríamos el IdResource en nuestro TrackFactory.

La primera alternativa que utilice fue la siguiente
private Track TrackFactory(ObjectStateEntry entry)
{
 Track track = new Track();
 track.IdTrack = Guid.NewGuid();
 track.TableName = entry.EntitySet.Name;
 track.FC = DateTime.Now;
 track.UC = string.Empty;

 if (entry.State == EntityState.Added)
 {
  // Insert (I)
  track.IdResource = entry.EntityKey.EntityKeyValues[0].Value.ToString();  
  track.NewData = this.GetXml(entry, false);
  track.Action = TrackActionsEnum.I.ToString();
 }
 else if (entry.State == EntityState.Deleted)
 {
  // Update (U)
  track.IdResource = entry.EntityKey.EntityKeyValues[0].Value.ToString();
  track.OldData = this.GetXml(entry, true);
  track.NewData = this.GetXml(entry, false);
  track.Action = TrackActionsEnum.U.ToString();
 }
 else
 {
  // Delete (D)
  track.IdResource = entry.EntityKey.EntityKeyValues[0].Value.ToString();
  track.OldData = this.GetXml(entry, true);
  track.Action = TrackActionsEnum.D.ToString();
 }

 return track;
}
Esta solución hubiera sido fantástica salvo porque no funciona a la hora de obtener el identificador en la inserción de un registro, ya que EntityKey es nulo. Tras darle varias vueltas al asunto la única posibilidad que me quedó fue recorrer por reflexión todas las propiedades del objeto y detectar aquella que tuviera el atributo EdmScalarPropertyAttribute definido y la propiedad EntityKeyProperty a true. Al final el código no queda muy largo y es el siguiente
private string GetIdResource(ObjectStateEntry entry) {
 object entity = entry.Entity;

 PropertyInfo[] properties = entity.GetType().GetProperties();
  foreach (PropertyInfo pi in properties)
  {
  System.Object[] attributes = pi.GetCustomAttributes(true);
  foreach (object attribute in attributes)
    {
   if (attribute is EdmScalarPropertyAttribute)
    if (((EdmScalarPropertyAttribute)attribute).EntityKeyProperty == true)
     return pi.GetValue(entity, null).ToString();
  }
 }

 return null;
}
Por lo que el método TrackFactory quedo así al final
private Track TrackFactory(ObjectStateEntry entry)
{
        // ...
 if (entry.State == EntityState.Added)
 {
  // Insert (I)
  track.IdResource = this.GetIdResource(entry); 
  track.NewData = this.GetXml(entry, false);
  track.Action = TrackActionsEnum.I.ToString();
 }
        // ...
}
Con esta mejora es fácil hacer el seguimiento a nuestros cambios. Por ejemplo con este código
using (DataContext context = new DataContext())
{
 // Insertamos una películas
 context.AddToPelicula(new Pelicula() { IdPelicula = 1, Titulo = "El padrino", Sinopsis = "...", Calificacion = 10 });
 context.SaveChanges();
    
 // Modificamos la película
 Pelicula p1 = context.Pelicula.Where(m => m.IdPelicula == 1).Single();
 p1.Calificacion = 9;
 context.SaveChanges();

 p1 = context.Pelicula.Where(m => m.IdPelicula == 1).Single();
 p1.Calificacion = 10;
 context.SaveChanges();

 // Borramos la película
 p1 = context.Pelicula.Where(m => m.IdPelicula == 1).Single();
 context.Pelicula.DeleteObject(p1);
 context.SaveChanges();
}
Sería fácil (sabiendo el Id que tenía la película en la base de datos, ver la secuenda de la vida del registro en la base de datos como vemos en nuestra tabla.


En la próxima entrega intentaré resolver de una forma "no muy elegante" (pero tampoco he encontrado otra) el caso de la inserciones de registros donde el Id es un identity, por lo que no conocemos el valor del Id antes de grabar. Lo que nos llevará a tener que grabar todos nuestros objectos en la base de datos para posteriormente grabar el registro de cambios.

Happy coding!

 Mas Info | Registrar cambios en nuestros objecto en el Entity Framework (I)

miércoles, 11 de abril de 2012

Registrando los cambios de nuestros objetos en Entity Framework (I)

En estos días de descanso me he propuesto meterme más en las tripas del Entity Framework. Para ello me he planteado hacer un pequeño sistema de control de cambios en el que se controle cuando se inserta o elimina un registro, o si se modifica, los valores que tenía antes y después de la modificación.

La primero entonces sería interceptar, o bien las inserciones, borrado o modificaciones de los objetos o detectar cuando se van a grabar los cambios. Tras leer la documentación que hay parece ser que la única valida es engancharnos a el evento SavingChanges que dispara la clase ObjectContext antes de iniciar el grabado de nuestros datos.

Con esto claro, me creo el edmx de una base de datos muy simple donde tan solo tengo una tabla que almacena películas, con su titulo, sinopsis y calificación. Despliego el Model.edmx y luego abro el Model.Designer.cs para ver el nombre exacto de la clase y así poder crear una clase parcial donde pondré toda la lógica de mi sistema de control de cambios.



Con esto me creo mi clase parcial DataContext y ahí me engancharé al evento SavingChanges. Ahora bien, ¿donde me engancho a este evento? Revisando el objeto DataContext veo que hay un método parcial que se llama OnContextCreated que es llamado cuando el contexto se crea y que parece un buen sitio donde engancharnos al evento, ya que sino tendríamos que sobreescribir los 3 constructores de la clase ObjectContext. El código de nuestra clase quedaría así

public partial class DataContext
{
  partial void OnContextCreated()
  {
    this.SavingChanges += new EventHandler(DataContext_SavingChanges);
  }

  private void DataContext_SavingChanges(object sender, EventArgs e)
  {
    // Gestión del evento SavingChanges
  }
}

Con esto ya tenemos la primera parte del trabajo hecha, que es interceptar las llamadas al método SaveChanges de nuestro contexto. Ahora queda la segunda parte del trabajo, detectar los cambios en nuestros objectos para registrar la información donde estimemos oportuno, en mi caso, en la base de datos.


Para esto lo primero que haremos será definir una tabla donde almacenaremos los cambios. La tabla tendría una pinta como esta


Donde:
  • IdTrack: Identificador de cada registro en la tabla.
  • TableName: Nombre de la tabla donde estamos insertando, modificando o borrando los registros.
  • Action: Acción a realizar, inserción (I), modificación (U) o borrado (D).
  • IdResource: Identificador del registro insertado, modificado o borrado en su tabla.
  • OldData y NewData: Objeto antes y después de ser insertado, modificado o borrado.
  • UC y FC: Usuario y fecha de creación del registro en la tabla de Tracks.
Una vez hecho tendremos que recorrer la lista de objectos que mantiene en nuestro DataContext y obtener aquellos que han cambiado. Para esto usaremos el ObjetctStateManager. El ObjectStateManager mantiene el estado de los objetos y la administración de identidades en las instancias de tipo de entidad y en las instancias de relación.
IEnumerable<ObjectStateEntry> pendingChanges = 
    this.ObjectStateManager.GetObjectStateEntries(
        EntityState.Added | 
        EntityState.Deleted | 
        EntityState.Modified);

En pendingChanges tenemos aquellos elementos que han cambiado y que deberemos procesar para registrar los cambios. Tan solo debemos recorrer la lista descartando aquellos elemento que sean una relación o que sean del tipo Track (no vamos a registrar los cambios de los objetos que registran los cambios).
foreach (ObjectStateEntry obj in pendingChanges)
{
    // Comprobamos si el objeto no es una relación y no es de tipo Track
    if (!(obj.IsRelationship) && (obj.Entity != null) && !(obj.Entity is Track))
    {
        Track audit = this.TrackFactory(obj);
        tracks.Add(audit);
    }
}

if (tracks.Count > 0)
{
    foreach (var audit in tracks)
        this.AddToTrack(audit); // Añadimos los Tracks
}
Para crear el objeto Track, usaremos el método TrackFactory que tendría esta pinta
private Track TrackFactory(ObjectStateEntry entry)
{
    Track track = new Track();
    track.IdTrack = Guid.NewGuid();
    track.TableName = entry.EntitySet.Name;
    track.FC = DateTime.Now;        
    track.UC = string.Empty;

    if (entry.State == EntityState.Added)
    {
        // Insert (I)
        track.NewData = this.GetXml(entry, false);
        track.Action = TrackActionsEnum.I.ToString();
    }
    else if (entry.State == EntityState.Modified)
    {
        // Update (U)
        track.OldData = this.GetXml(entry, true);
        track.NewData = this.GetXml(entry, false);
        track.Action = TrackActionsEnum.U.ToString();
    }
    else
    {
        // Delete (D)
        track.OldData = this.GetXml(entry, true);
        track.Action = TrackActionsEnum.D.ToString();
    }

    return track;
}
Como vemos en el método tan solo miramos el estado en el que se encuentra la entidad (Added, Modified o Deleted) para consecuentemente montar el objeto Track. En caso de ser un objeto nuevo, dicho objeto sólo tendrá valores nuevos, y en caso de ser un borrado, sólo tendrá valores viejos. En el caso de las modificaciones, guardamos los valores antes y después de la modificación. El método GetXml tan solo genera un xml con el objeto que le pasemos haciendo uso del XmlSerializer de .NET.

Con esto ya tenemos montado la estructura básica de nuestro sistema, así que va siendo hora de probarlo. Para probarlo primero insertamos dos registros en nuestra base de datos de la siguiente manera
using (DataContext context = new DataContext())
{
    // Insertamos dos películas
    context.AddToPelicula(new Pelicula() { IdPelicula = 1, Titulo = "El padrino", Sinopsis = "...", Calificacion = 10 });
    context.AddToPelicula(new Pelicula() { IdPelicula = 2, Titulo = "La guerra de las galaxias", Sinopsis = "...", Calificacion = 10 });
    context.SaveChanges();
}
Y si consultamos la base de datos vemos lo siguiente
Luego probaremos a modificar y a borrar para ver el resultado
// Modificamos una película
Pelicula p1 = context.Pelicula.Where(m => m.IdPelicula == 1).Single();
p1.Calificacion = 9;
context.SaveChanges();

// Borramos una película
Pelicula p2 = context.Pelicula.Where(m => m.IdPelicula == 2).Single();
context.Pelicula.DeleteObject(p2);
context.SaveChanges();
Si consultamos la base de datos veremos que también se han registrado las operaciones
Si analizamos los campos OldData y NewData en el caso de la modificación vemos que efectivamente se guarda el estado del objeto antes de modificar y el estado del mismo después de la modificación
Como vemos el sistema funciona correctamente aunque hay algunos detalles que pulir como asignar el identificador del objecto que se está registrando al objeto Track. Esto es relativamente simplemente si lo identificadores son asignados por nosotros, pero se complica es caso que sean valores asignados en la base de datos, como puede ser valores por defecto (newid() en caso de los uniqueidentifier) o identity.

Otro punto a tener en cuenta es buscar la forma de desacoplar este código del ObjectContext y que pudiera ser un módulo independiente y que funcionará con cualquier .dbml que queramos. Tengo algunas al respecto pero lo dejaré para otros artículos.

Cualquier duda, comentario o sugerencia será bien recibida.

Happy coding!

Mas info | Entity Framework