Manage IT 2010 - Gestión de Proyectos y Arquitectura de Software

por Daniel Laco  26. abril 2010

Se viene el evento nuevamente !!!

 

Este año estaremos repitiendo el evento sobre Gestión de Proyectos y Arquitectura de Software.

VEMN SA conjuntamente con el MUG, Lagash SA y el apoyo de la Universidad Argentina de la Empresa (UADE) realizaremos el evento el día 8 de Junio a las 9.00 hs. en la sala Magna de la UADE.

Temario

8:30 - 9:30 Acreditación
9:30 – 10:00 Presentación del Evento y KeyNote
10:00 – 11:15 Value Express. Optimizando los resultados de sus inversiones realizadas.
(Susana Silberberg)
11:15 - 11:45 Break
11:45 – 13:00 Desarrollo de aplicaciones de misión crítica.
(Diego Gonzalez y Rodolfo Finochietti)
13:00 – 14:15 Almuerzo Libre
14:15 – 15:30 Estimación de Proyectos de Software. La cara oculta de las diferencias.
(Daniel Laco y Patricia Scalzone)
15:30 – 16:00 Break
16:00 – 17:15 ¿Sueñan los Gerentes de IT con programadores eléctricos?
(Luis Ávalo)
17:15 – 17:30 Cierre y Sorteos
Es un evento de interés para profesionales de TI, Gerentes de Sistemas, Líderes de Proyectos, Arquitectos, Analistas y todo aquel interesado en el mundo TI.


El evento es gratuito. Vacantes limitadas, con inscripción previa.

Para mas información en www.manageit.com.ar.


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

por fernando  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)

Tags: , , ,

.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!

Tags:

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

Cuál es la mejor forma de realizar procesos batch con Entity Framework?

por Daniel Laco  16. abril 2010

Es muy común que en las aplicaciones sea necesario realizar procesos de actualizaciones masivas. Siempre está la tentación de usar EF para hacer estos procesos, ya que es muy fácil buscar y actualizar registros. Pero en el caso de EF el costo que se paga por toda la infraestructura que tiene es muy alto.

En el caso de procesos masivos, si hay que actualizar o borrar un registro, primero hay que traerlo (esto implica que EF mantenga el tracking con el ObjectStateManager) posteriormente hacer la actualización o borrado y por último realizar la actualización con el ObjectContext.

Resumiento, para realizar procesos batch lo mejor es NO USAR Entity Framework !!

Pero que opciones tengo entonces?

Voy a mostrar 2 alternativas de las muchas que hay, simulando que hay que leer un archivo .TXT de longitud fija  y posteriormente hay que realizar una actualización de miles de registros en una tabla de la base de datos.

El escenario es que recibo un archivo con códigos de productos de la empresa y códigos de productos de proveedores externos. El proceso que hay que realizar es: leer el archivo y actualizar los códigos de de los proveedores externos en cada registro de los productos de la empresa.

Para esto usaremos como ayuda la excelente libreria FileHelpers realizada por Marcos Meli

El formato en que viene el archivo es el siguiente:

 

/// <summary>
/// Archivo de Unión de Códigos de Productos de la Empresa y Proveedores
/// </summary>
 
[FixedLengthRecord(FixedMode.AllowLessChars)]
public class UnionProductos
{
    [FieldFixedLength(7)]
    public int EmpresaCodigo;
 
    [FieldFixedLength(7)]
    public string Proveedor1Codigo;
 
    [FieldFixedLength(7)]
    public int Proveedor2Codigo;
 
}

 

El proceso de lectura con FileHelpers:

FileHelperEngine engine = new FileHelperEngine(typeof(UnionProductos));
UnionProductos[] UnionLeido = engine.ReadFile(nombreArchivo) as UnionProductos[];
 
 

Opción 1

Recorrer el array de Productos y ejecutar un Store Procedure con la actualización correspondiente:

 

string stringDeConexion = "XXXXXXXXXXXXXXXX";
using (SqlConnection conn = new SqlConnection(stringDeConexion))
{
    conn.Open();
    SqlCommand cmd = new SqlCommand("nombreStore", conn);
    cmd.Parameters.Add("@empresaCodigo", SqlDbType.Int);
    cmd.Parameters.Add("@proveedor1Codigo", SqlDbType.VarChar);
    cmd.Parameters.Add("@proveedor2Codigo", SqlDbType.Int);
 
 
    foreach (UnionProductos producto in UnionLeido)
    {
        cmd.Parameters[0].Value = producto.EmpresaCodigo;
        cmd.Parameters[1].Value = producto.Proveedor1Codigo;
        cmd.Parameters[2].Value = producto.Proveedor2Codigo;
 
        cmd.ExecuteNonQuery();
 
    }
}

 

Por supuesto el Store hace algo asi como:

UPDATE Productos 
SET Proveedor1Codigo = @proveedor1Codigo, 
    Proveedor2Codigo = @proveedor2Codigo
WHERE Productos.Codigo = @empresaCodigo

 

Opción 2

Esta es la mas performante de las 3 opciones. El único problema es que solo es posible en SQL Server 2008.

Está implementada utilizando una Tabla como Parámetro y realizando toda la actualización mediante una sentencia SQL.

Veamos como hacelo:

Se crea un tipo de dato Table en SQLServer

CREATE TYPE UnionProductosTbl AS TABLE 
(
    EmpresaCodigo int,
    Proveedor1Codigo varchar(7),
    Proveedor2Codigo int
)

 

Se crea el Procedimiento Almacenado usando ese tipo como parámetro:

CREATE PROCEDURE ActualizarCodigosProveedores
    @unionProductosTbl dbo.UnionProductosTbl READONLY
AS
BEGIN
 
    UPDATE Productos p
    SET p.Proveedor1Codigo = tbl.Proveedor1Codigo,
    p.Proveedor2Codigo = tbl.Proveedor2Codigo
    FROM Productos
    INNER JOIN @unionProductosTbl tbl ON p.Codigo = tbl.empresaCodigo
    
END

 

Y el proceso en el código:

//Lectura del Archivo
               FileHelperEngine engine = new FileHelperEngine(typeof(UnionProductos));
               //Devuelve como DataTable
               DataTable unionLeidoDT = engine.ReadFileAsDT(nombreArchivo);
 
               //El proceso de actualización
               string stringDeConexion = "XXXXXXXXXXXXXXXX";
               using (SqlConnection conn = new SqlConnection(stringDeConexion))
               {
                   conn.Open();
                   SqlCommand cmd = new SqlCommand("nombreStore", conn);
                   //Se define un parámetro de tipo Structured para que reciba un DataTable
                   SqlParameter par = cmd.Parameters.Add("@unionProductosTbl", 
                       SqlDbType.Structured);
                   par.Value = unionLeidoDT;
                   cmd.ExecuteNonQuery();
               }
               
 

Realizando el proceso con 17.000 registros los resultados fueron los siguientes (los tiempos son orientativos):

Opción Tiempo

Utilizando EF

8 minutos

Utilizando ADO.NET nativo procesando registro a registro con un Store Procedure

4 minutos

Utilizando un Store Procedure en SQL 2008 con una tabla como parámetro

4 segundos !!

 

Para finalizar, la opción de realizar el proceso batch usando una tabla como parámetro de un Store es la mejor opción si la prioridad es la performance de la solución.

Tags: ,

ADO.NET | Entity Framework | SQL Server

Versiones de VS 2010

por Daniel Laco  15. abril 2010

Siempre es motivo de preguntas el tema de que trae cada versión de Visual Studio y cuanto cuesta cada una de ellas. Aqui les dejo un link donde ver una matriz con las comparaciones de cada versión de Visual Studio 2010.

Tags:

Visual Studio

Acerca de los Autores

Este es el blog del equipo de VEMN SA 
Presentaremos temas que nos parezcan de interés sobre tecnología .NET, Procesos y Metodologías y todo aquello relacionado con el proceso de desarrollo de Software

Month List

BlogRoll

Download OPML file OPML