Curso de Angularjs!!!

Después de mucho tiempo sin escribir debido a que he empezado una aventurar en una start-up, voy a retomar el tema del blog y empezar a hacerlo en dos líneas:

  • .Net
    • Donde voy a exponer muchas cosas de las aprendidas de código, arquitectura, diseño, …
  • Angularjs
    • Que es la librería de javascript que utilizo en cliente y cómo lo he utilizado.

Así que antes de empezar voy a informar de un curso que vamos a realizar mi gran amigo Pedro Hurtado (@_PedroHurtado) y yo donde explicaremos qué es Angularjs, como utilizarlo y una propuesta de arquitectura de alta productividad que permite llevar a programación declarativa casi toda la lógica de cliente.

El curso será un curso online a través de GeeksHub Academy, y constará de:

  • 20h en vídeos
  • Tutorías para resolver dudas en directo
  • Resolución de retos y resolución online

Puedes visitar http://www.geekshubsacademy.com/courses/aprende-angularjs.html donde te podrás inscribir y obtener más información.

Au

MythBuster: Velocidad de acceso a datos

Después de mucho tiempo de parón voy a retomar el blog que lo tenia muy abandonado, a ver si a partir de ahora le doy más continuidad Sonrisa.

Este post surge como consecuencia de una conversación con @_PedroHurtado sobre las diferencias de rendimiento entre DataTables y DataReader (dejo para otro día el acceso con Linq + EF) y la supuesta afirmación de que el DataReader es lo más rápido para la de cargar datos.

Para hacer las pruebas he creado una BD en SQLServer con el siguiente TSQL:

   1: create database Post

   2: go

   3: create table Persona(

   4:     Id int identity(1,1) primary key,

   5:     Nombre nvarchar(max) not null,

   6:     Apellidos nvarchar(max) not null,

   7:     Nacimiento datetime,

   8:     Direccion nvarchar(max) not null,

   9:     Poblacion nvarchar(max) not null

  10: )

  11: go

  12: declare @i int

  13: set @i = 1

  14: while (@i <= 10000)

  15: begin

  16:     insert Persona (Nombre, Apellidos, Nacimiento, Direccion, Poblacion) 

  17:     values ('Nombre ' + cast(@i as nvarchar), 'Apellidos' + cast(@i as nvarchar), SYSDATETIME(), 'Dirección ' + cast(@i as nvarchar), 'Poblacion ' + cast(@i as nvarchar))

  18:  

  19:     set @i = @i + 1

  20: end

Con esto hemos creado una tabla con 10000 elementos que luego cargaremos y tomaremos tiempos. La idea es cargar los 10000 elementos para ver qué es más rápido. Lógicamente el sistema que cargue 10000 instancias y no sea un reporting recomendaría encarecidamente revisar la arquitectura y diseño ya que lo más lógico sería paginar o buscar soluciones similares que limiten la carga. Simplemente lo hacemos de este tamaño para que los tiempos sean relevantes.

La forma de acceder a los datos se ha pensado como si se realizara una consulta tipo a la lógica de negocio. La idea es simular una llamada a una server API REST y generar el JSON con la información que se enviaría al cliente. La información que se devuelve normalmente es la misma que se carga de la BD sin ni siquiera tratar por lo que realmente eso es lo que replicaremos.

DataTable

Las pruebas para el uso de DataTable se ha realizado siguiendo el siguiente código:

   1: public async Task<long> Execute(string connectionString)

   2: {

   3:     var stopwath = new Stopwatch();

   4:     stopwath.Reset();

   5:     stopwath.Start();

   6:  

   7:     var result = new DataTable();

   8:     using (var connection = new SqlConnection(connectionString))

   9:     using (var cmd = new SqlCommand("select * from persona"))

  10:     {

  11:         connection.Open();

  12:         cmd.Connection = connection;

  13:         SqlDataAdapter dataAdapter = new SqlDataAdapter(cmd);

  14:         dataAdapter.Fill(result);

  15:     }

  16:     var json = Newtonsoft.Json.JsonConvert.SerializeObject(result);

  17:  

  18:     stopwath.Stop();

  19:     return stopwath.ElapsedMilliseconds;

  20: }

Este código es muy sencillo y al terminar se tiene en una variable local el string con toda la información que se devolvería al cliente.

DataReader

Por otro lado, para el uso de DataReader que hemos implementado es el siguiente que equivalente funcionalmente al anterior y es más o menos lo que cualquier persona realizaría:

   1: public async Task<long> Execute(string connectionString)

   2: {

   3:     var stopwath = new Stopwatch();

   4:     stopwath.Reset();

   5:     stopwath.Start();

   6:  

   7:     using (var connection = new SqlConnection(connectionString))

   8:     {

   9:         connection.Open();

  10:  

  11:         using (var command = connection.CreateCommand())

  12:         {

  13:             command.CommandText = "select * from persona";

  14:  

  15:             var dataReader = await command.ExecuteReaderAsync();

  16:             var numColumns = dataReader.FieldCount;

  17:             var values = new object[numColumns];

  18:             var names = new List<string>();

  19:  

  20:             var firstTime = true;

  21:             var result = new List<Dictionary<string, object>>();

  22:             while (dataReader.Read())

  23:             {

  24:                 var record = new Dictionary<string, object>();

  25:  

  26:                 if (dataReader.GetValues(values) > 0)

  27:                 {

  28:                     for (int i = 0; i < numColumns; i++)

  29:                     {

  30:                         if (firstTime)

  31:                             names.Add(dataReader.GetName(i));

  32:  

  33:                         var value = values[i];

  34:                         record.Add(names[i], value != System.DBNull.Value ? value : null);

  35:                     }

  36:                     firstTime = false;

  37:                 }

  38:                 result.Add(record);

  39:             }

  40:             var json = Newtonsoft.Json.JsonConvert.SerializeObject(result);

  41:         }

  42:     }

  43:  

  44:     stopwath.Stop();

  45:     return stopwath.ElapsedMilliseconds;

  46: }

Este código que parece complejo es así porque se está al mismo tiempo que se carga la información definiendo la estructura de datos de la información a devolver. Para devolver la información parece lógico utilizar un ExpandoObject o un DynamicObject y meter los objetos creados en una lista. Pero como el resultado es el mismo a meter un diccionario en la lista y la información que se genera también, pues utilizaremos un diccionario que simplifica mucho la lógica y evita crear objetos al vuelo.

Toma de tiempos

Para tomar tiempos lazaremos una vez cada una de las implementaciones para calentar la conexión, BD, … y luego lanzaremos 10 veces la consulta de carga de toca la información y sacaremos la media. Los tiempos son siempre en Debug.

Los resultados han sido los siguientes:

  • DataTable
    • 1a iteración: 1366ms
    • las siguientes 10: 111ms de media
  • DataReader
    • 1a iteración:165ms
    • las siguientes 10: 120 de media
    • un 8.11% más de tiempo que el datatable

Alguien puede pensar que el algoritmo para cargar utilizando DataReader se puede optimizar sobre todo utilizando tareas que paralelicen cosas.

DataReader con Tasks

Por ello, vamos a hacer la misma lógica pero utilizando tasks que permitan hacer asíncrono el proceso.

Primero crearemos una clase llamada Reader que se encargará de abstraer y cargar la información de forma asíncrona:

   1: public class Reader : IDisposable

   2: {

   3:     static string connectionString = @"Data Source=(LocalDb)v11.0;Initial Catalog=Post;Integrated Security=SSPI; MultipleActiveResultSets=True";

   4:     private SqlConnection _connection;

   5:  

   6:     public async Task<IEnumerable<dynamic>> Read()

   7:     {

   8:         _connection = new SqlConnection(connectionString);

   9:         _connection.Open();

  10:  

  11:         using (var command = _connection.CreateCommand())

  12:         {

  13:             command.CommandText = "select * from persona";

  14:  

  15:             return ReadData(await command.ExecuteReaderAsync());

  16:         }

  17:     }

  18:     private IEnumerable<dynamic> ReadData(SqlDataReader dataReader)

  19:     {

  20:         var numColumns = dataReader.FieldCount;

  21:         var values = new object[numColumns];

  22:         var names = new List<string>();

  23:  

  24:         var firstTime = true;

  25:         while (dataReader.ReadData())

  26:         {

  27:             var record = new Dictionary<string, object>();

  28:  

  29:             if (dataReader.GetValues(values) > 0)

  30:             {

  31:                 for (int i = 0; i < numColumns; i++)

  32:                 {

  33:                     if (firstTime)

  34:                         names.Add(dataReader.GetName(i));

  35:  

  36:                     var value = values[i];

  37:                     record.Add(names[i], value != System.DBNull.Value ? value : null);

  38:                 }

  39:             }

  40:             firstTime = false;

  41:  

  42:             yield return record;

  43:         }

  44:     }

  45:     public void Dispose()

  46:     {

  47:         if (_connection != null)

  48:         {

  49:             _connection.Close();

  50:             _connection.Dispose();

  51:             _connection = null;

  52:         }

  53:     }

  54: }

  55: public static class SqlDataReaderExtension

  56: {

  57:     public static bool ReadData(this SqlDataReader that)

  58:     {

  59:         //var task = that.ReadAsync();

  60:         //task.Wait();

  61:         //return task.Result;

  62:         return that.Read();

  63:     }

  64: }

y despues la algorítmica será la siguiente:

   1: public async Task<long> Execute(string connectionString)

   2: {

   3:     var stopwath = new Stopwatch();

   4:     stopwath.Reset();

   5:     stopwath.Start();

   6:  

   7:     using (var reader = new Reader())

   8:     {

   9:         var result = await reader.Read();

  10:         var json = Newtonsoft.Json.JsonConvert.SerializeObject(result);

  11:     }

  12:  

  13:     stopwath.Stop();

  14:     return stopwath.ElapsedMilliseconds;

  15: }

Con esto conseguimos que la carga de la información no bloquee el hilo principal y se paralelicen más cosas.

Toma de tiempos 2

Ahora vamos a comparar los 3 tipos de algoritmos para la resolución del problema.

  • DataTable
    • 1a iteración: 445ms
    • las siguientes 10: 114ms de media
  • DataReader
    • 1a iteración:168ms
    • las siguientes 10: 119ms de media
    • un 4.39% más de tiempo que el datatable
  • DataReader con Tasks 
    • 1a iteración:120ms
    • las siguientes 10: 117ms de media
    • un 2.63% más de tiempo que el datatable

Aun así no hemos sido capaces de ser más rápidos que un DataTable Triste.

Conclusiones

En definitiva, después de realizar y medir todas las pruebas e intentar optimizar cada uno de los casos podemos concluir:

  1. Los tiempos de DataReader suelen ser superiores al uso de DataTable, aunque usemos Tasks para no bloquear y paralelizar. A veces el DataTable es más lento que el DataReader pero no suele ser lo habitual.
  2. Faltaría probar qué sucede con Linq + EF para abarcar los principales modos de acceso a los datos, pero dudo que sea más rápido que DataTable o DataReader (dejo resto para un siguiente post).
  3. Yo con mi experiencia recomendaría el uso de DataReader con tasks porque la diferencia de tiempos no es significativa y el no bloquear el hilo principal poder paralelizar puede ser muy ventajoso.
  4. El uso de DataReader y cargar la información en colecciones y listas es algo que da más flexibilidad y utiliza estructuras más simples. Por otro lado el uso de DataTables, DataSets, … complica luego el uso si se va a realizar algo más sobre estos datos en la lógica de negocio.
  5. En resumen el mito está cazado, ya que EL DATAREADER NO ES MÁS RÁPIDO QUE EL DATATABLE, aunque tampoco es que sea mucho más lento.

DataReader directo a Json

Una vez hechas todas estas pruebas podemos decir que los tiempos no son nada concluyentes, pero aquí surge una pregunta: ¿Para qué recorrer el DataReader para cargar la información en objetos y luego recorrer los objetos para generar el JSON? ¿No sería más lógico cargar ya directamente en el json?

Viendo este post a medio implementar pero que puede ser ilustrativo: http://weblog.west-wind.com/posts/2009/Apr/24/JSON-Serialization-of-a-DataReader, hace pensar que se puede conseguir mayor productividad cargando la información directamente a JSON, a cambio de no tener los objetos cargados y no poder tratarlos de ninguna de las formas (simplemente cargar y devolver).

Por ello pasamos a implementar el siguiente código:

   1: public async Task<long> Execute(string connectionString)

   2: {

   3:     var stopwath = new Stopwatch();

   4:     stopwath.Reset();

   5:     stopwath.Start();

   6:  

   7:     using (var connection = new SqlConnection(connectionString))

   8:     {

   9:         connection.Open();

  10:  

  11:         using (var command = connection.CreateCommand())

  12:         {

  13:             command.CommandText = "select * from persona";

  14:  

  15:             var dataReader = await command.ExecuteReaderAsync();

  16:             var stringBuilder = new StringBuilder();

  17:             WriteValue(stringBuilder, dataReader);

  18:         }

  19:     }

  20:     stopwath.Stop();

  21:     return stopwath.ElapsedMilliseconds;                

  22: }

  23: private void WriteValue(StringBuilder stringBuilder, object val)

  24: {

  25:  

  26:     if (val is IDataReader)

  27:         WriteDataReader(stringBuilder, val as IDataReader);

  28:     //else if (val is IDictionary)

  29:     //    WriteDictionary(sb, val as IDictionary);

  30:     //else if (val is IEnumerable)

  31:     //    WriteEnumerable(sb, val as IEnumerable);

  32:     else if (val is DateTime)

  33:         stringBuilder.Append(((DateTime) val).ToString("o"));

  34:     else

  35:         stringBuilder.Append(val);

  36: }

  37:  

  38: private void WriteDataReader(StringBuilder stringBuilder, IDataReader dataReader)

  39: {

  40:     var numColumns = dataReader.FieldCount;

  41:     var names = new List<string>();

  42:  

  43:     var rowCount = 0;

  44:     stringBuilder.Append("{[");

  45:     while (dataReader.Read())

  46:     {

  47:         stringBuilder.Append("{");

  48:  

  49:         for (int i = 0; i < numColumns; i++)

  50:         {

  51:             if (rowCount == 0)

  52:                 names.Add(dataReader.GetName(i));

  53:  

  54:             stringBuilder.AppendFormat(""{0}":", names[i]);

  55:             this.WriteValue(stringBuilder, dataReader[i]);

  56:             stringBuilder.Append(",");

  57:         }

  58:  

  59:         if (numColumns > 0)

  60:             stringBuilder.Length -= 1;

  61:  

  62:         stringBuilder.Append("},");

  63:         rowCount++;

  64:     }

  65:     if (rowCount > 0)

  66:         stringBuilder.Length -= 1;

  67:     stringBuilder.Append("]}");

  68: }

Este código está a medio implementar, ya que habrá que terminar el método WriteValue con la serialización de IEnumerable, IDictionary e inclusive los números reales al igual que se ha hecho con los DateTimes para dejarlos en un formato estándar y no tener problemas de localización.

Al final los tiempos pasan a ser:

  • DataTable
    • 1a iteración: 373ms
    • las siguientes 10: 112ms de media
  • DataReader
    • 1a iteración:161ms
    • las siguientes 10: 115ms de media
    • un 2.68% más de tiempo que el datatable
  • DataReader con Tasks
    • 1a iteración:125ms
    • las siguientes 10: 113ms de media
    • un 0.89% más de tiempo que el datatable
  • DataReader directo a JSON
    • 1a iteración: 75ms
    • las siguientes 10: 67ms de media
    • un 40.18% menos de tiempo que el datatable

Conclusiones definitivas

Esto nos lleva a pensar: ¿Por qué cargamos la información a entidades u objetos para luego serializarla? Ahí queda la pregunta.

Y como el cargar directamente a JSON solo se puede hacer con DataReader y no con DataTable cambiamos nuestro resultado aunque condicionado a simplemente devolver el JSON:

Au

PD: Código de la prueba https://github.com/XaviPaper/DataTable_DataReader

SqlServer MythBuster: Una teoría de conjuntos

Hola a todos!!!

Después de bastante tiempo sin escribir por diversos líos que llevo de los que pronto empezaré a escribir, voy a retomar el tema para que no se me oxide la pluma. Imaginaros si estaba desconectado que hasta Jose Bonnin (@wasat) me lanzó un guante el otro día para que escribiese más 😉

Este corto post viene de una discusión que he tenido con Pedro (@_pedrohurtado) y Carry (@3lcarry) sobre cómo escribir las sentencias SQL, y de paso aprovecharé para explicar un poco cómo funciona SQL Server arriesgándome a que algún crack de los que tanto aprecio me tire de las orejas.

Para empezar describiré un poco el contexto y el origen de este extraño post de SQL Server. La discusión viene de una sentencia Linq que habíamos escrito más o menos así (elimino cosas sin interés y cambio los nombres para que sea autoexplicativo):

using (var modelo = new PruebasEntities())
{
    var result1 = (
        from l in modelo.Linea
        where l.Factura.cliente == "Cliente 10000"
        select l.articulo
    ).ToList();
}

Pedro comentaba que esto es (o como mínimo era) ineficiente y que no aplicaría bien el índice, ya que el filtro se hace sobre un campo Cliente de Factura y debería empezar siempre poniendo el primer FROM sobre esa tabla (la de Factura). Por ello, él proponía hacerlo de la siguiente manera aun sabiendo que era más clara y legible la primera:

using (var modelo = new PruebasEntities())
{
    var result2 = (
        from f in modelo.Factura
        from l in f.Lineas
        where f.cliente == "Cliente 10000"
        select l.articulo
    ).ToList();
}

Así que vamos a analizar un poco lo que sucede para resolver nuestras dudas.

Montaje del entorno

Lo primero que he hecho es montar todo el entorno en SQL Server y para ello en contra de la opinión de mucha gente he ejecutado un script para crear la estructura, he rellenado unos poquitos datos (más de 1MM de filas), y he levantado el EF con Data First.

create database Pruebas
go
use Pruebas
go
create table Factura (
    id int identity(1, 1) primary key,
    cliente nvarchar(max)
)
create table Linea (
    id int identity(1, 1) primary key,
    factura_id int not null references Factura (id),
    articulo nvarchar(max)
)
CREATE NONCLUSTERED INDEX FK_Linea_Factura ON Linea (factura_id)
go
set identity_insert Factura on
go
declare @i integer
set @i = 1
while (@i <= 10000) begin
    insert Factura (id, cliente) values (@i, 'Cliente ' + CAST(@i as nvarchar(max)))
    set @i = @i + 1
end
go
set identity_insert Factura off
go
set identity_insert Linea on
go
declare @i integer
set @i = 1
while (@i <= 1000000) begin
    insert Linea (id, factura_id, articulo) values (@i, (@i % 10000) + 1, 'Articulo ' + CAST(@i as nvarchar(max)))
    set @i = @i + 1
end
go
set identity_insert Linea off

 

Sentencia generada por Entity Framework

Después he decidido utilizar nuestro querido SQL Server Profiler para ver qué genera en cada caso. La idea que perseguía era ver si el generador de EF ejecutaba la misma SQL y por tanto problema resulto.

Pero no, en el primer caso ha lanzado la siguiente consulta:

SELECT [Extent1].[articulo] AS [articulo] 
FROM [dbo].[Linea] AS [Extent1] 
INNER JOIN [dbo].[Factura] AS [Extent2] ON [Extent1].[factura_id] = [Extent2].[id] 
WHERE N'Cliente 10000' = [Extent2].[cliente]

y en el segundo:

SELECT [Extent2].[articulo] AS [articulo]
FROM  [dbo].[Factura] AS [Extent1]
INNER JOIN [dbo].[Linea] AS [Extent2] ON [Extent1].[id] = [Extent2].[factura_id]
WHERE N'Cliente 10000' = [Extent1].[cliente]

Así que la primera conclusión es que la sintaxis se propaga y en el primer caso se empieza por Linea y en el segundo por Factura.

Plan de ejecución ejecutado

Lo siguiente es ver qué sucede internamente en Sql Server e intentaré ver si es el propio Sql Server el que resuelve nuestra duda, y para ello ejecutaré las siguientes sentencias y veré los planes de ejecución que se generan:

PlanEjecucion1

PlanEjecucion2

Y ahora sí… voilá.

Sucedió lo que tenia que suceder: SQL Server sigue como era de esperar la teoría de conjuntos y por tanto poco importa el orden de los FROM ni de los WHERE, ni si es mejor poner un JOIN o una “,” con un WHERE… da igual. El plan de ejecución que se genera a partir de la sentencia es el mismo.

SQL Server sigue la teoría de conjuntos y no se puede suponer nunca el orden de nada si no se explicita. Es decir, si pides que te venga la población de una tabla, lo más lógico es que esperes recibir los datos por id o fecha de creación, pero no tiene porqué ser así. Lo más usual es que sí lo sea, ya que el motor recorrerá el índice clustered que está ordenado por id, pero ¿quien dice que no recorra otro filtro? ¿o al hacer JOIN los datos venga por Foreign Key porque para hacer un JOIN ha preferido ordenar? De esto solo podemos sacar una conclusión: Si quieres un orden has de ponerlo, aunque sea por id, sino espérate a recibir los datos como a SQL Server le sea más óptimo.

De igual manera pasa con los FROM, WHERE, … SQL Server los ejecuta en el orden que cree más optimo basándose en cosas como estadísticas, filtros disponibles, …  pero no el orden en el que se escriba la sentencia.

Espero haber aclarado algunas cosillas y que me haya explicado con suficiente claridad.

Au

PD: Para los que al ver las sentencias y los planes de ejecución han visto algo extraño… enhorabuena y sí… tenéis razón: El índice que he creado podría haberlo hecho de forma que cubriese bien la sentencia y evitar la búsqueda por el índice clustered pero el objetivo era mostrar este caso y no optimizar la consulta 😉

Versionando WebApi – Después del uno, el dos

El principal problema en la implementación de un API es el versionado de este para que aplicaciones antiguas o que no tenemos nosotros el control no dejen de funcionar.

El pensar que una aplicación no va a evolucionar a lo largo del tiempo es una quimera, y dentro de poco o mucho al final acaba por evolucionar (si es que no muere antes) y por tanto lo mejor es pensar ya en cómo vas a evolucionar y versionar el API de tus aplicaciones para no pensar algo que luego no te va a valer.

El tener una buena estrategia de versionado hace mucho más fácil la evolución de la aplicación, ya que permite mejorar en fases una aplicación. Por ejemplo, se pueden evolucionar los servicios o capa de lógica de negocio y después la capa de presentación u otras aplicaciones dependientes. De esta forma será posible acceder por los dos caminos, el antiguo y el nuevo de forma simultánea mientras lo veamos necesario.

En nuestro caso vamos a analizar cómo versionar WebApi, la capa REST que nos proporciona ASP.Net MVC.

Entorno sin versionado

Para crear el entorno de ejemplo vamos a crear una aplicación de tipo ASP.NET MVC 4 con la plantilla WebApi, esto nos creará toda la estructura de la aplicación necesaria para ejecutar WebApi.

Una vez el proyecto creado añadiremos estas dos clases en la carpeta Modelo, una será la entidad que dará soporte al modelo y la otra la interface del repositorio que nos permitirá acceder a los datos:

public class personas
{
    public int Id { get; set; }
    public string Name { get; set; }
}
public interface IPersonasRepository
{
    IEnumerable<personas> All { get; }
    void Commit();
}

Una vez definido el modelo y la interface del repositorio, veremos cómo será el Controller inicial para responder a WebAPI.

namespace PapelMojado.VersionadoWebApi.Controllers
{
    public class PersonasController : ApiController
    {
        private static PersonasRepository Repository = new PersonasRepository();

        public IEnumerable<personas> Get()
        {
            return Repository.All;
        }
    }
}

La forma en que he implementado el repositorio no es importante porque es para un ejemplo, pero yo os recomendaría para esto ver cómo funciona Entity Framework, NHibernate o cualquier otro repositorio que persista la información.

El el ejemplo he implementado el repositorio como colecciones en memoria y por ello he creado una variable global en el Controller para mantenerlo vivo. De todas formas para implementaciones recomendaría inicializarlo mediante DI + IoC como Unity, Ninject o cualquier otro

Una vez esto implementado vamos a ejecutar la aplicación y realizaremos una pruebas. Al pulsar F5 vemos que en mi caso la aplicación ha arrancado en el puerto 63619 de localhost.

image

Ahora pasamos a arrancar nuestro querido Fiddler (viva el #orgullobackend) y en la pestaña Composer lanzamos la siguiente llamada “GET http://localhost:63619/api/personas

imageimage

Aquí podemos ver como ha devuelto una instancia con id=1 y nombre=Xavi, y por tanto nos aseguramos que el sistema funciona y que tanto la consulta puede visualizarse perfectamente.

Entorno versionado

Una vez que el sistema está en funcionamiento, lo más normal es que nos toque algún día evolucionar el API y cambiar la interface haciendo back-compatibility o lo que es lo mismo que no dejen de funcionar todas las aplicaciones que acceden a esta interface.

En nuestro ejemplo vamos a añadir una nueva propiedad a la entidad persona donde guardaremos el twitter, por ello, la consulta nos devolverá el id, name y twitter. Para hacerlo un poco más interesante, vamos a saltar de la V1 a la V3, dejando un hueco en V2 (simulando que la V2 no afecta a este Controller)

Para resolver esto en todos los casos hay que crear un nuevo controlador que maneje la nueva versión y otra que maneje la anterior, pero existen realmente 2 posibilidades en cuanto a la forma de realizar la llamada:

  1. Tener dos URLs, una con la versión anterior y otra con la nueva para saber diferenciar a qué versión se está llamando. Por ejemplo, se podría utilizar http://localhost:63619/api/personasV1 o http://localhost:63619/api/V1/personas para la versión anterior y http://localhost:63619/api/personasV3 o http://localhost:63619/api/V3/personas para la nueva. En el primer caso se resolvería simplemente creando un nuevo controlador para PersonasV2Controller y en el segundo además habría que modificar el MapHttpRoute del WebApiConfig.cs.
  2. Mantener la misma URL pero añadiendo el header X-Api-Version que indicaría qué versión estamos utilizando. Con esto nos acercaríamos más al estándar y haríamos más fácil la invocación.

Para el ejemplo vamos a tomar esta segunda opción y por ello tendremos los siguientes requisitos:

  • Si viene con el valor 1 deberá ejecutar igual que el ejemplo anterior, el request y el response deberán ser exactamente iguales (a excepción de que ahora aparecerá el header)
  • Si viene con el valor 2 deberá ejecutarse como si de la 1 se tratase, ya que es la versión anterior y por tanto vigente en ese momento
  • Si viene con el valor 3 deberá manejar el nuevo campo twitter
  • Si viene con el valor 4 deberá ejecutarse como si de la 3 se tratase
  • Si no aparece el header de versión deberá devolver la última versión, en nuestro caso también la 3

Para empezar vamos a evolucionar la entidad persona para guardar este nuevo campo, y dejaremos igual el repositorio ya que no hace referencia en ningún momento al nuevo campo:

public class personas
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Twitter { get; set; }
}

Por otro lado tendremos que modificar el antiguo controlador para que continúe devolviendo lo mismo. Para ello realizaremos los siguientes cambios:

  • Crearemos un instancia del siguiente controlador al que llamaremos para obtener la información
  • En el Get devolveremos un enumerador de tipo Object, ya que persona ahora tiene más campos y queremos devolver lo mismo que antes. La idea es instanciar objetos de tipo anónimo compatible con lo anterior a partir del resultado del nuevo controller
namespace PapelMojado.VersionadoWebApi.Controllers.Api.V1
{
    public class PersonasController : ApiController
    {
        private V3.PersonasController controller = new V3.PersonasController();

        public IEnumerable<object> Get()
        {
            return
                from i in controller.Get()
                select new
                {
                    Id = i.Id,
                    Name = i.Name
                };
        }
    }
}

En el caso del controlar de la V3, será por casualidad muy parecido al original con la salvedad del namespace, ya que simplemente retorna las nuevas entidades.

namespace PapelMojado.VersionadoWebApi.Controllers.Api.V3
{
    public class PersonasController : ApiController
    {
        private static PersonasRepository Repository = new PersonasRepository();

        public IEnumerable<personas> Get()
        {
            return Repository.All;
        }
    }
}

Una vez hecho todo esto, si ejecutáis veréis que no funciona y nos informa de que hay múltiples controladores cuyo nombre coincide con el nombre del controlador pasado en la URL.

GET http://localhost:63619/api/personas

imageimage

Para que el sistema de creación del controlador sepa cual utilizar es necesario cambiar el selector de controladores por uno que tenga en cuenta la versión. Para ello se ha creado la clase HeaderVersionControllerSelector y algunas clases auxiliares

    internal class NamespaceLocator : Dictionary<string, ControllerLocator>
    {
        public NamespaceLocator() : base(StringComparer.CurrentCultureIgnoreCase) { }

        public bool TryGetValue(string nameSpace, string controller, int version, out HttpControllerDescriptor controllerDescriptor)
        {
            controllerDescriptor = null;

            ControllerLocator locator;
            if (!TryGetValue(nameSpace, out locator))
                return false;

            return locator.TryGetValue(controller, version, out controllerDescriptor);
        }
        public void Add(string nameSpace, string controller, int version, HttpControllerDescriptor descriptor)
        {
            var locator = this.ElementOrDefault(nameSpace);
            if (locator == null)
            {
                locator = new ControllerLocator(this);
                Add(nameSpace, locator);
            }

            locator.Add(controller, version, descriptor);
        }
    }
    internal class ControllerLocator : Dictionary<string, VersionLocator>
    {
        public NamespaceLocator Parent { get; private set; }

        public ControllerLocator(NamespaceLocator parent)
            : base(StringComparer.CurrentCultureIgnoreCase)
        {
            Parent = parent;
        }
        public bool TryGetValue(string controller, int version, out HttpControllerDescriptor controllerDescriptor)
        {
            controllerDescriptor = null;

            VersionLocator locator;
            if (!TryGetValue(controller, out locator))
                return false;

            return locator.TryGetValue(version, out controllerDescriptor);
        }
        public void Add(string controller, int version, HttpControllerDescriptor descriptor)
        {
            var locator = this.ElementOrDefault(controller);
            if (locator == null)
            {
                locator = new VersionLocator(this);
                Add(controller, locator);
            }

            locator.Add(version, descriptor);
        }
    }
    internal class VersionLocator : Dictionary<int, HttpControllerDescriptor>
    {
        public ControllerLocator Parent { get; private set; }

        public VersionLocator(ControllerLocator parent)
        {
            Parent = parent;
        }
        public new bool TryGetValue(int version, out HttpControllerDescriptor controllerDescriptor)
        {
            controllerDescriptor =
                (
                    from v1 in this
                    where v1.Key ==
                                (
                                    from v2 in this.Keys
                                    where v2 <= version
                                    select v2
                                ).Max()
                    select v1.Value
                ).FirstOrDefault();

            return controllerDescriptor != null;
        }
    }
    public class HeaderVersionControllerSelector : IHttpControllerSelector
    {
        private readonly HttpConfiguration _config;
        private readonly IHttpControllerSelector _previousSelector;
        private readonly NamespaceLocator _namespaceLocator = new NamespaceLocator();
        private const string ApiNamespace = "PapelMojado.VersionadoWebApi.Controllers";
        private const int MaxVersion = 9999;

        public HeaderVersionControllerSelector(IHttpControllerSelector previousSelector, HttpConfiguration config)
        {
            _config = config;
            _previousSelector = previousSelector;

            var types =
                from t in Assembly.GetExecutingAssembly().GetTypes()
                where
                    typeof(ApiController).IsAssignableFrom(t) &&
                    t.Namespace != null && t.Namespace.StartsWith(ApiNamespace, StringComparison.CurrentCultureIgnoreCase)
                select new
                {
                    SubNamespace = t.Namespace.Substring(ApiNamespace.Length),
                    Type = t
                };

            foreach (var type in types)
            {
                var subNamespaces = type.SubNamespace.Split('.') as IEnumerable<string>;
                if (subNamespaces.ElementAt(0).IsNullOrEmpty())
                    subNamespaces = subNamespaces.Skip(1);

                var lastNamespace = subNamespaces.LastOrDefault();
                if (string.Compare(lastNamespace.SubstringWithoutError(0, 1), "v", true) == 0)
                    lastNamespace = lastNamespace.Substring(1);

                int version;
                var initialNamespace = lastNamespace;
                if (!int.TryParse(lastNamespace, out version))
                    version = MaxVersion;
                else
                    initialNamespace = subNamespaces.Reverse().Skip(1).Reverse().JoinString(".");

                _namespaceLocator.Add(initialNamespace, type.Type.Name, version, new HttpControllerDescriptor(_config, type.Type.Name, type.Type));
            }
        }
        public IDictionary<string, HttpControllerDescriptor> GetControllerMapping()
        {
            var result = (
                                from n in _namespaceLocator
                                from c in n.Value
                                from v in c.Value
                                select new
                                {
                                    Key =
                                        n.Key + "." +
                                        (v.Key < MaxVersion ? "V" + v.Key + "." : "") +
                                        c.Key,
                                    Value = v.Value
                                }
                            ).ToDictionary(x => x.Key, x => x.Value);

            return result;
        }
        public HttpControllerDescriptor SelectController(HttpRequestMessage request)
        {
            var routeData = _config.Routes.GetRouteDataExtended(request);

            var nameSpace = GetPath(request).Replace('/', '.');
            var controllerName = routeData["controller"] as string + "controller";
            var version = routeData.ContainsKey("X-Api-Version") ? Convert.ToInt32(routeData["X-Api-Version"]) : MaxVersion;

            HttpControllerDescriptor controllerDescriptor;
            if (_namespaceLocator.TryGetValue(nameSpace, controllerName, version, out controllerDescriptor))
                return controllerDescriptor;

            return null;
        }
        private string GetPath(HttpRequestMessage request)
        {
            return string.Join("/",
                from r in request.GetRouteData().Route.RouteTemplate.Split('/')
                where !r.StartsWith("{")
                select r
            );
        }
    }

Aquí se pueden ver las 3 clases auxiliares (NamespaceLocator, ControllerLocator y VersionLocator) que forman la estructura que guardarán de forma jerárquica (Namespace/Controller/Version) el HttpControllerDescription del controlador ya versionado.

Para hacer el mapping entre la URL y el Controlador

La URL http://localhost:63619/api/personas se desglosará en:

  • Namespace: api (penúltimo directorio de la URL)
  • Controlador: personas (ultimo directorio de la URL) + Controller (sufijo que siempre se pone)
  • Versión: V (prefijo de la versión) + lo que ponga en el X-Api-Version

Al mismo tiempo que la clase namespace PapelMojado.VersionadoWebApi.Controllers.Api.V1 { public class PersonasController : ApiController … se desglosará en:

  • Namespace: Api (penúltimo subespacio de nombres)
  • Controlador: Personas (nombre de la clase)
  • Versión: V1 (último subespacio de nombres)

Con todo esto, en el constructor de HeaderVersionControllerSelector se crea la estructura analizando por reflexión las clases del assembly, y en el método SelectController se parsea la url de la petición y se busca el controlador correspondiente recorriendo el árbol anteriormente creado.

Aquí se tiene en cuenta el caso de que no exista una versión de un controlador, con lo que se devolvería el de la versión anterior más próximo. Si no llegase la versión en la URL se devolvería el último (máxima versión del árbol).

Para simplificar un poco estas clase, se han creado algunos extensores que simplifiquen el código y que lo hagan un poco más legible:

    public static class DictionaryExtension
    {
        public static V ElementOrDefault<K, V>(this Dictionary<K, V> THIS, K key)
        {
            if (THIS.ContainsKey(key))
                return THIS[key];

            return default(V);
        }
    }
    public static class HttpRequestMessageExtension
    {
        public static IDictionary<string, object> GetRouteDataExtended(this IHttpRoute THIS, string virtualPathRoot, HttpRequestMessage request)
        {
            var routeData = THIS.GetRouteData(virtualPathRoot, request);
            if (routeData == null)
                return new Dictionary<string, object>();
            var result = routeData.Values;

            foreach (var h in request.Headers)
                result.Add(h.Key, h.Value.FirstOrDefault());

            return result;
        }
    }
    public static class HttpRouteCollectionExtension
    {
        public static IDictionary<string, object> GetRouteDataExtended(this HttpRouteCollection THIS, HttpRequestMessage request)
        {
            var routes = (
                from r in THIS
                where r is IHttpRoute
                select r
            );

            var result = new Dictionary<string, object>();
            foreach (var route in routes)
                foreach (var routeData in route.GetRouteDataExtended("", request))
                    result.Add(routeData.Key, routeData.Value);
            return result;
        }
    }
    public static class IEnumerableExtension
    {
        public static string JoinString(this IEnumerable<string> THIS, string separator)
        {
            return string.Join(separator, THIS);
        }
    }
    public static class StringExtension
    {
        public static bool IsNullOrEmpty(this string THIS)
        {
            return string.IsNullOrEmpty(THIS);
        }
        public static string SubstringWithoutError(this string THIS, int startIndex, int length)
        {
            startIndex = Math.Min(startIndex, THIS.Length);
            length = Math.Min(length, THIS.Length - startIndex);

            return THIS.Substring(startIndex, length);
        }
    }

Y para finalizar debemos añadir 2 líneas al método Application_Start del Application para sustituir el selector por defecto por este que tiene en cuenta las versiones.

    public class WebApiApplication : System.Web.HttpApplication
    {
        protected void Application_Start()
        {
            AreaRegistration.RegisterAllAreas();

            WebApiConfig.Register(GlobalConfiguration.Configuration);
            FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
            RouteConfig.RegisterRoutes(RouteTable.Routes);
            BundleConfig.RegisterBundles(BundleTable.Bundles);

            // Version manager
            var previousSelector = GlobalConfiguration.Configuration.Services.GetService(typeof(IHttpControllerSelector)) as IHttpControllerSelector;
            GlobalConfiguration.Configuration.Services.Replace(typeof(IHttpControllerSelector), new HeaderVersionControllerSelector(previousSelector, GlobalConfiguration.Configuration));
        }
    }

Pruebas finales

 

Para comprobar todo esto ejecutaremos

Versión 1 (versión anterior)

imageimage

Versión 2 (versión anterior)

Al no encontrarse al V2 se devuelve V1

imageimage

Versión 3 (versión nueva)

imageimage

Versión 4 (versión nueva)

Al no encontrarse al V4 se devuelve V3

imageimage

Versión por defecto (versión nueva)

Al no enviarse versión se envía la última que es la V3

imageimage

En el caso de otros métodos de tipo post/put/delete/… es exactamente igual, ya que lo importante aquí es que en tiempo de ejecución y en función del header X-Api-Version se decide que Controller es el que ejecuta la petición.

Para ver los ficheros modificados y la estructura de directorios que se ha quedado

image

Conclusiones

Con esta estrategia de versionado hemos conseguido:

  • Podemos invocar a cualquier versión del Controller que hayamos implementado
  • No es necesario modificar las URLs de los servicios WebApi según la versión, solo marcando el header correspondiente se indica qué versión queremos invocar.
  • Sólo tenemos que versionar los Controllers que necesitemos, no todos. Esto tiene como ventaja que si la V2 sólo afecta a un Controller el resto se pueden quedar iguales, ya que cuando se invoque con la V2 si no existe se devolverá justo la que estaba en ese momento en vigor.
  • Y como consecuencia se puede evolucionar una aplicación mucho más fácilmente ya que puedes modificar los servicios sin que ningunos de los componentes dependientes se vea afectado.
  • Al implementar el antiguo controlador en función del nuevo y no echando mano del repositorio, nos aseguramos que en posteriores versiones continuará funcionando, ya que los resultados se irán migrando en cascada y no dependerán del repositorio, que obligaría a modificar todos los Controllers cada vez que este varíe.

Espero que sea de utilidad

Au

La unión hace la fuerza – Como unir un WebRole y WorkerRole para ahorrar

Después de bastante tiempo trabajando en Azure, y como he estado optimizando el coste de Azure, voy a escribir algunos posts sobre cómo ahorrar.

Un escenario típico es el tener un front-end en web-role y unos procesos que ejecuten tareas en worker-roles (p.ej. una aplicación CQRS). Además, para poder disponer de alta disponibilidad se ha de tener al menos 2 instancias de cada, lo que nos lleva a tener 4 o más instancias!!! => $$$

Por ello, una opción es ejecutar tareas en el mismo web-role y así ahorrar in$tancia$.

Implementación de la llamada a la tarea

Para empezar mostraremos cómo empezar… Aunque es una opción no muy documentada, es posible ejecutar tareas en un web-role como si de un worker-role se tratase. Para implementar esto, simplemente hay que crear en el proyecto/aplicación web una clase que herede de RoleEntryPoint como haríamos en un worker role:

    public class WebRole : RoleEntryPoint
    {
        public override void Run()
        {
            while (true)
            {
                ...
            }
        }

        public override bool OnStart()
        {
            ...

            return base.OnStart();
        }
    }

Hasta aquí todo fácil y funciona perfectamente. En la misma instancia se ejecuta la aplicación web y el código que hemos implementado en la clase especializada de RoleEntryPoint. Esto es muy fácil, pero ahora es cuando vienen los problemas si las tareas que ejecutaba el worker-role hacia cosas un poco complejas.

Si queremos aprovecharnos de las bondades de Full IIS, ¿qué sucede si desde la especialización de RoleEntryPoint queremos acceder al fichero de configuración web.config y obtener la cadena de conexión a la BD con ADO.Net, Entity Framework, …? #FAIL

Web.config / WaIISHost.exe.config

Si pretendemos usar el Full IIS que implementa Azure, desde el código RoleEntryPoint no se puede acceder al fichero web.config para obtener cualquier tipo de dato como las cadenas de conexión o cualquier setting. Esto sucede porque cuando se aloja la aplicación web en Full IIS esta se ejecuta en el proceso w3wp.exe de IIS como siempre, mientras que el RoleEntryPoint se ejecuta en un otro proceso diferente llamado WaIISHost.exe.

Para poder leer las opciones de configuración desde el proceso WaIISHost.exe, estas deben estar definidas en otro fichero (WaIISHost.exe.config) que es el que será accedido por la aplicación como si fuera un web/app.config. Para poder ser hospedado en el destino, este fichero tendrá que tener la propiedad del fichero “Copy to Output Directory” = “Copy Always”.

ACTUALIZACIÓN: A partir de la versión 1.8 de Azure
el nombre del fichero config que se tendrá en cuenta en la ejecución del proceso
WaIISHost.exe es “<nombre aplicación web>.dll.config” y no el
WaIISHost.exe.config que se describe en este post 😉

<?xml version="1.0"?>
<configuration>
    <connectionStrings>
        <add ... />
    </connectionStrings>
</configuration>

Una vez ya tenemos esto implementado podemos darle a F5 y bieeeeen. Ya funciona, pero vamos a dar un paso más. ¿qué sucede si tenemos los ficheros Web.Debug.config, Web.Release.config, … para adaptar el fichero de configuración a los diferentes entornos? Esto mismo no puede ser implementado para este config #FAIL

Transformaciones MSBuild

Al fichero WaIISHost.exe.config no se le pueden aplicar transformaciones tan fácilmente como al web.config, pero existe una forma de hacer algo similar automáticamente añadiendo tareas MSBuild a mano en el fichero de proyecto.

Primero crearemos un fichero llamando WaIISHost.exe.transformation.config donde guardaremos las transformaciones necesarias para convertir el web.config al WaIISHost.exe.config (en este ejemplo he eliminado los 4 elementos que no necesitaba, pero se puede modificar, añadir o eliminar cualquier cosa como indica http://msdn.microsoft.com/es-es/library/dd465326.aspx).

<?xml version="1.0" encoding="utf-8"?>
<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
    <system.web xdt:Transform="Remove"/>
    <system.serviceModel xdt:Transform="Remove"/>
    <system.webServer xdt:Transform="Remove"/>
    <appSettings xdt:Transform="Remove"/>
</configuration>

Después tenemos que cerrar el Visual Studio y abrir el proyecto web *.csproj con el notepad (#OrgulloBackend de @davidsb) y añadimos el siguiente código al xml:

<Project ...>
  ...
  <Target Name="BeforeBuild">
    <Message Importance="high" Text="--- Transformando $(MSBuildProjectDirectory)WaIISHost.exe.config ---" />
    <TransformXml Source="$(MSBuildProjectDirectory)Web.config" Transform="$(MSBuildProjectDirectory)Web.$(Configuration).config" Destination="$(MSBuildProjectDirectory)WaIISHost.exe.temp" />
    <TransformXml Source="$(MSBuildProjectDirectory)WaIISHost.exe.temp" Transform="$(MSBuildProjectDirectory)WaIISHost.exe.transformation.config" Destination="$(MSBuildProjectDirectory)WaIISHost.exe.config" />
    <Delete Files="$(MSBuildProjectDirectory)WaIISHost.exe.temp" />
    <Message Importance="high" Text="--- Transformado $(MSBuildBinPath)WaIISHost.exe.config ---" />
  </Target>
  ...
</Project>

En él pretendemos además de mostrar mensajes para ir informando:

  • Añadir el UsingTask para referenciar Microsoft.Web.Publishing.Tasks.dll, y así poder ejecutar las tareas TransformXml.
  • Ejecutar 3 pasos para:
    • Transformar el fichero Web.config, según el Build Configuration “Debug, Release, …” asociado y deja el resultado en el temporal WaIISHost.exe.temp. Para ver más tipos de tareas puedes ver http://msdn.microsoft.com/en-us/library/7z253716.aspx.
    • Transformar el fichero temporal anterior con las transformaciones guardadas en WaIISHost.exe.transformation.config para convertir el web.config en WaIISHost.exe.config.
    • Eliminar el fichero temporal.

Y con esto, cada vez que compilemos el proyecto se reemplazará el fichero WaIISHost.exe.config con el Web.config modificado por las dos transformaciones: la que corresponde según el Build Configuration, y la explicita para limpiar y/o ampliar el fichero destino.

Jerarquizando un poco

Para que no aparezcan demasiados ficheros en el proyecto, yo recomiendo volver a editar el fichero *.csproj del proyecto y editar el tag xml que tiene el WaIISHost.exe.transformation.config para que quede como sigue:

<Project ...>
  ..
  <ItemGroup>
    ...
    <Content Include="WaIISHost.exe.transformation.config">
      <DependentUpon>WaIISHost.exe.config</DependentUpon>
    </Content>
    ...
  </ItemGroup>
  ...
</Project>

De esta forma aparecerá en el Solution Explorer como un nodo anidado dentro de WaIISHost.exe.config.

Screenshot

Espero que este post sea de utilidad.

Au

En toda navegación hay un patrón (MVVM)

Como he visto que existen dudas sobre como implementar la navegación en el patrón MVVM (Model – View – ViewModel) separando en varios proyectos el View y el ViewModel. Para ello voy a exponer una forma de hacerlo que creo que es la que mejor se adapta a este escenario.

El problema de la navegación radica en que si separamos el View y el ViewModel en diferentes proyectos el ViewModel no tiene una referencia al View (ni la necesita, ya que sino estaría acoplada y no podríamos reutilizar este componente) y por tanto ¿cómo llamo al entorno para que abra la ventana o página?

Para ello nos definimos estos objetivos:

  1. Vamos ha implementar la navegación entre dos ventanas de forma que al pulsar sobre un botón se abra la otra.
  2. El View y el ViewModel deben estar en dlls separadas
  3. Los Code-Behind de los XAML deben estar vacíos
  4. La aplicación debe poder tener un proyecto View y múltiples ViewModels (para ti Pedro 🙂

Ejemplo

Para el ejemplo he usado WPF, pero viendo y entendiendo el patrón es lo mismo para W8, WP7, WP8, SL y demás…

Por ello planteo el siguiente modelo:

clip_image002

Navigation

Empezando por el objeto que va a realizar la navegación, este será definido como un Command. En MVVM todas las acciones y navegaciones que se definen en el modelo deben ser comandos que después serán enlazados a los botones, links, … para lanzar su ejecución.

Como este comando tiene que tener código que abre una ventana o página su implementación debe estar en la capa View. De esta forma podemos hacer lo que queramos al tener acceso a los View y ViewModels de toda la aplicación.

    public class IrAPostByBlogNavigation : DelegateCommand
    {
        public IrAPostByBlogNavigation()
            : base((parameter) =>
            {
                var view = new PostByBlogView();
                var viewModel = view.DataContext as PostByBlogViewModel;
                viewModel.BlogId = parameter as int?;

                view.Show();
            })
        {
        }
    }

Para simplificar el código, me he permitido el lujo de refactorizar parte de esta clase en una clase más genérica llamada DelegateNavigation. Esta clase la vamos a definir en el ViewModel, pero en un proyecto genérico con clases comunes y helpers. De esta forma podremos tener varios proyectos con ViewModels y un proyecto con código común y compartido por todos.

    public class DelegateCommand : ICommand
    {
        private Action<object> Action;
        private Func<object, bool> CanExecuteFunc;

        public event EventHandler CanExecuteChanged;

        public DelegateCommand(Action<object> action)
        {
            this.Action = action;
            this.CanExecuteFunc = null;
        }
        public DelegateCommand(Action<object> action, Func<object, bool> canExecuteFunc)
        {
            this.Action = action;
            this.CanExecuteFunc = canExecuteFunc;
        }
        public bool CanExecute(object parameter)
        {
            return (this.Action != null) && ((this.CanExecuteFunc == null) || this.CanExecuteFunc(parameter));
        }
        public void Execute(object parameter)
        {
            if (this.Action != null)
                this.Action(parameter);
        }
    }

Esta clase implementa los métodos de la interface haciendo que sea más fácil implementar los comandos. Para especializarla sólo se le deben pasar por constructor 2 delegados con el código a ejecutar y el código para saber si se puede ejecutar (opcional).

Aquí uno se puede preguntar ¿hay que escribir una clase por cada navegación? Bueno, sí y no. Concretamente aquí hemos definido una clase porqué la foreign key está en post y por tanto no sabemos navegar desde el blog seleccionado. Si el caso fuera al contrario y estuviésemos implementado la navegación desde post a blog, entonces podríamos implementar un Navigation de carga por Id, y por tanto sería compartido por esta navegación y cualquier otra donde se tuviese cargado el id.

Otra buena pregunta que alguien se puede plantear es: Como solo puedo pasarle un valor al Command, ¿solo puedo abrir un nuevo escenario con un valor? No tiene porqué, en el caso descrito, yo le he pasado el id del blog como parámetro del command, pero se puede pasar todo el ViewModel origen y en el Navigation inicializar lo que uno quiera del destino con toda la información disponible en el ViewModel.

ViewModel

Una vez visto que el Command concreto está en el View y puede instanciar vistas, llamar al show, … sólo queda decláralo en el ViewModel concreto de la ventana o página para que este pueda ser enlazado (Bind) desde la vista. Como hemos comentado al principio, hemos decidido poner el Navigation en el View, por lo que no es accesible desde donde queremos y por ello tendremos que declarar una variable de tipo ICommand en el ViewModel que luego en tiempo de ejecución contendrá el Navigation correcto:

    public class BlogViewModel : ViewModelBase
    {
        private ObservableCollection<Blog> _Blogs = new ObservableCollection<Blog>();
        public ObservableCollection<Blog> Blogs { get { return _Blogs; }}

        private Blog _BlogSeleccionado;
        public Blog BlogSeleccionado
        {
            get {return _BlogSeleccionado; }
            set
            {
                if (_BlogSeleccionado != value)
                {
                    _BlogSeleccionado = value;
                    OnPropertyChanged("BlogSeleccionado");
                }
            }
        }

        public ICommand IrAPost { get; set; }

        public override void Initialize()
        {
            base.Initialize();
            this.InicializarBlogs();
        }

        public void InicializarBlogs()
        {
            var id = this.BlogSeleccionado == null ? (int?)null : this.BlogSeleccionado.id;

            this.Blogs.Clear();
            using (var application = this.Container.Resolve<IBlogApplication>())
            {
                foreach (var item in application.ObtenerBlogs())
                    this.Blogs.Add(item);
            }

            if (id != null)
                this.BlogSeleccionado = (
                    from e in this.Blogs
                    where e.id == id
                    select e
                ).FirstOrDefault();
        }
    }

View

Y… ¿Cómo vamos a crear este comando de navegación? Yo he decidido hacerlo desde un Locator inyectando a pelo, pero se puede inyectar con DI como NInject, Unity, …

    public class ViewModelLocator
    {
        public Lazy<BlogViewModel> blogViewModel = new Lazy<BlogViewModel>(() => new BlogViewModel() { IrAPost = new IrAPostByBlogNavigation() });
        public BlogViewModel BlogViewModel { get { return this.blogViewModel.Value; }}

        public Lazy<PostByBlogViewModel> postViewModel = new Lazy<PostByBlogViewModel>(() => new PostByBlogViewModel());
        public PostByBlogViewModel PostViewModel { get { return this.postViewModel.Value; } }

        public ViewModelLocator()
        {
        }
    }

Y después sólo hay que hacer Bind desde el XAML como se hace siempre:

<Window
    x:Class="GUSENet.Ejemplo.View.BlogView"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:view="clr-namespace:GUSENet.Ejemplo.View"
    xmlns:viewModel="clr-namespace:GUSENet.Ejemplo.ViewModel;assembly=GUSENet.Ejemplo.ViewModel"
    xmlns:interactivity="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
    DataContext="{Binding BlogViewModel, Source={StaticResource ViewModelLocator}}"
  Title="Blogs"
>
    <interactivity:Interaction.Triggers>
        <interactivity:EventTrigger EventName="Loaded">
            <interactivity:InvokeCommandAction Command="{Binding ViewLoaded}"/>
        </interactivity:EventTrigger>
    </interactivity:Interaction.Triggers>
    <StackPanel>
        <StackPanel Orientation="Horizontal">
            <Label Content="Blogs" Height="28" HorizontalAlignment="Left" VerticalAlignment="Top"/>
            <Button Content="->" Command="{Binding IrAPost}" CommandParameter="{Binding BlogSeleccionado.id}"/>
        </StackPanel>
        <ListBox ItemsSource="{Binding Blogs}" SelectedItem="{Binding BlogSeleccionado}"/>
    </StackPanel>
</Window>

Y voilà!!! F5 y a funcionar…

Volviendo un poco a la pregunta de antes de si hay que implementar un Navigation por cada navegación, se podría simplificar poniendo el código que navega en el locator y eliminando los Navigations, pero me parece más claro crear una clase de forma explícita:

        public Lazy<BlogViewModel> blogViewModel = new Lazy<BlogViewModel>(() => new BlogViewModel()
        {
            IrAPost = new DelegateCommand(
                (parameter) =>
                {
                    var view = new PostByBlogView();
                    var viewModel = view.DataContext as PostByBlogViewModel;
                    viewModel.BlogId = parameter as int?;

                    view.Show();
                })
        });

Conclusiones

Revisando uno poco los objetivos

  1. El ejemplo es sencillo y creo que queda claro cómo se pulsa el botón y se abre la otra ventana utilizando un comando para la navegación
  2. Debido a que el comando está declarado en el View, no existe ningún inconveniente en que ambos proyectos estén separados. Es más deben estarlo en aplicacione multiplataforma, ya que así conseguiremos compartir el proyecto ViewModel entre diferentes Views (por ejemplo: uno para W8, otro WP8, otro para WP7, otro para SL, otro para WPF, …)
  3. Los code-behind de los XAML solo contendrán los constructores típicos que es lo que queríamos.
  4. Si sacamos todo el código común a otro proyecto compartido, no existe ningún problema en tener 5000 (o más 😉 proyectos con ViewModels, lo único es que el View sí deberá tener una referencia a todos los ViewModels para poder instanciarlos y manejarlos en los Navigation

Espero que sea de utilidad mi primer post. Espero no tardar tanto en escribir el segundo,

Au

MEJORA

Durante una conversión con @_PedroHurtado por la página de Facebook del GUSENet me pregunta: ¿Pero tengo que implementar un Navigation por cada navegación y por cada plataforma (WPF, SL, W8, WP7, WP8)? Bueno, con la implementación que yo he hecho sí, pero… ¿qué sucede si refactorizamos del Navigation la parte que depende de la plataforma? Pues aquí teneis:

    public class Navigation<V, VM>
        where V : Window, new()
        where VM : ViewModelBase
    {
        public V View { get; set; }
        public VM ViewModel { get; set; }

        public Navigation()
        {
            this.View = new V();
            this.ViewModel = this.View.DataContext as VM;
        }
        public void Open()
        {
            this.View.Show();
        }
    }

    public class IrAPostByBlogNavigation : DelegateCommand
    {
        public IrAPostByBlogNavigation()
            : base((parameter) =>
            {
                var navigation = new Navigation<PostByBlogView, PostByBlogViewModel>();
                navigation.ViewModel.BlogId = parameter as int?;

                navigation.Open();
            })
        {
        }
    }

3, 2, 1, … Contacto!!!

Hola a todos!!!

Voy a escribir mi primer post para presentarme y para agradecer en esta nueva aventura (meterme en el mundo de los blogs) a toda esa gente que me ha animado y empujado a empezar en este mundo.

Mi nombre es Xavier Jorge Cerdá (@XaviPaper) y soy natural de Vilamarxant, un pequeño y bonito pueblo de Valencia al que os animo a todos a visitar. 😉 Si decidís pasaros por aquí avisadme y nos tomaremos una cerveza bien fría…

Desde que era una simple Beta allá por el año 2000 y poco, me estoy dedicando a trabajar en el mundo .Net y mi inquietud para ser cada vez mejor en mi trabajo, me ha llevado a ir participando en diversos grupos de usuario como GUSENet o VLCDev, yendo a multitud de conferencias como TechEds Europa, Summits de SolidQ o grupos de usuario de otras ciudades. En estos momentos me he centrado en la organización y colaboración en el grupo GUSENet donde por ejemplo hemos organizado el último Megathon de Windows 8 en Murcia.

La idea del blog es ir publicando cosas que voy descubriendo sobre C#/.Net o arquitectura, así como cosas útiles que creo que estaría bien tenerlas escritas y que son muy recurrentes en los foros MSDN.

No me quería despedir sin agradecer a todas esas personas que me han ayudado a estar aquí y que me han animado a dejar de ser un miembro pasivo de la comunidad y empezar a dar todo lo que otras personas como ellas me han ido dando durante todo este tiempo.

Primero agradecer a Rodrigo Corral (@r_corral) por ayudarme a tener este blog en geeks. En siguiente lugar, dar las también gracias a toda esa gente que tanto me aporta y a los que sigo con total admiración como Lluís Franco (@LluisFranco), Edurad Tomás (@eiximenis), Luís Ruíz Pavón (@LuisRuizPavon), Jorge Serrano (@J0rgeSerran0), Sergio Marrero (@SMarreroF), Javier Torrecilla (@JTorrecilla), Nicolás Herrera (@nicolocodev) y otros muchos que no puedo nombrar para no ocupar más de 100 paginas de Word.

También a todos esos amigos con los que comparto risas y experiencias, y que también han hecho, aunque algunos no lo sepan, que esté aquí: Jose Luís Latorre (@JosLat), Salva Ramos (@Salvador_Ramos), Eladio Rincón (@ERincon), Miguel Egea (@MiguelEgea), Enrique Catalá (@EnriqueCatala), Miguel Ángel Sotomayor (@MASFWorld), …

Pero sobre todo a Pedro Hurtado (@_PedroHurtado) que es el que más me ha animado a abrir este blog a través de múltiples discusiones y viajes a Madrid.

Para finalizar, muchísimas gracias a mis compañeros y amigos de la empresa Ambiental Intelligence & Interaction donde trabajo y ejerzo como CTO. En especial a Emilio Iborra y Pepe Iborra que admiro muchísimo por su incansable constancia y afán de superación, y a mi familia, a los que les robo un preciado tiempo para hacer todo esto posible.

Muchas gracias a todos y espero que el esfuerzo de crear y mantener este blog valga la pena para toda la comunidad.

Nos vemos en el siguiente capítulo!!!