[Eventos] Global Azure BootCamp 2015

El sábado 25 de Abril se vuelve a celebrar en Madrid el Global Azure BootCamp 2015, el mayor evento de comunidad sobre Microsoft Azure y en el que tengo el place de participar por segundo año consecutivo junto con mi compañero Roberto González.

Nuestra sesión la hemos titulado #DEAN DocumentDB + Express + AngularJS + NodeJS running on Azure en la que basándonos en el stack MEAN daremos una vuelta de tuerca y veremos como desarrollar una aplicación SPA haciendo uso de tecnologías como Express, NodeJS y DocumentDB para el backend y AngularJS para el front-end. Todo el desarrollo se hará aprovechando al máximo las funcionalidades de Visual Studio. Como punto adicional veremos los pasos necesarios para desplegar esta solución en Azure.

Os dejo la agenda:

azure

Y como no, mención especial a los patrocinadores del evento poque sin ellos este tipo de eventos no es posible, Gracias!!!

patro

Allí nos vemos!!!

[Clean Code] Evitando Magic Strings

Vuelvo con una nueva entrada aprovechando el MsCoders de esta tarde y esta vez quiero hablar sobre los Magic Strings.

Seguro que más de una vez te has pasado un buen rato navegando por el código para cambiar valores de literales y algún bug que otro ha aparecido cuando en una parte de tu código el valor de un literal no es el mismo en otra parte de tú código, verdad? Esto es lo que se denominan Magic Strings (En otra entrada veremos que son los Magic Numbers). Esta entrada te puede parecer trivial, pero seguro que si ahora mismo revisas tu código te encuentras fijo con mas de uno, verdad?

El problema de usarlos está muy claro y te lo voy a mostrar con un ejemplo:

Código sacado de StackOveflow:

mainMenu.MenuItems["View"].MenuItems["FullScreen"].Visible = true;

El problema radica en que si mañana el menu item  “View” pasa a llamarse “ViewData” tendremos que buscar en todo el código en que parte estamos haciendo uso de el y rezar de que no se nos olvide cambiarlo en ningún lado porque nos encontraremos un error en ejecución. Otra puede ser que en una parte del código lo pongamos con minúsculas y en otro con mayúsculas y así nuemerosas posibilidades que todas acaban en lo mismo, dolor.

¿Como se soluciona?

Pues de 2 maneras y las 2 son totalmente válidas:

  • Constantes
  • Recursos

La primera es muy utilizada y la segunda yo no la usaba hasta hace relativamente poco tiempo, desde que Unai me comentó un día como lo hacía el equipo de producto de Entity Framework. Ellos se han montado un plantilla T4 que lee de un fichero de recursos y generar por decirlo de alguna manera ese fichero de constantes. Seguro que piensas que para eso me ahorro la T4 y me genero yo el fichero, verdad? Pero lo bueno es que interpreta también los strings con parámetros de formato y genera métodos con argumentos. Te lo muestro:

image

Yo en este momento estoy trabajando en una aplicación que genera SQL Dinámico y necesito de ciertas consultas que no quiero tenerlas en el código como Magic Strings:

image

Una vez que añades la entrada a los recursos, ejecutas la T4:

image

Y ya tienes listo el método parametrizado:

image

Así pues lo uso para estos casos y para strings como vimos en el caso anterior me viene muy bien.

Un saludo.

[Eventos] Foro Arquitectos 2015

Este año tengo el placer de volver a participar en el Foro de Arquitectos de Microsoft  que se celebra el día 26 de Enero en las oficinas de Microsoft Ibérica. Este año vuelvo a tener el placer de compartir escenario con mi amigo Roberto González y este año vamos a hablar sobre CQRS y Event Sourcing.

A continuación os dejo la agenda, así como el link para poder registraros:

9:00 – 9:30 – Registro

9:30 – 10:45 – CQRS/ES – Cambiando tu forma de pensar

10:45 – 12:00 – Fuentes de datos e integraciones de aplicaciones

12:00 – 12:30 – Coffee break

12:30 – 13:45 – Caché en Azure a fondo

13:45 – 15:00 – Rendimiento y monitorización en aplicaciones en la nube

El enlace al registro:

https://msevents.microsoft.com/cui/EventDetail.aspx?EventID=1032609425&culture=es-ES

Os esperamos!

[LinksFriday] Listado de enlaces interesantes 16/01/2015

Pensaba publicar a partir de hoy en Twitter (Y lo he hecho) un listado de enlaces interesantes que voy recopilando durante toda la semana, pero han sido varios los que me han dicho que Twitter es un poco efímero para esto, así pues escribiré todos los viernes un post con el listado de enlaces.

Aquí va el primero:

 

ASP.NET MVC

MEF with MVC 4 or 5 – Pluggable Architecture (2014)

http://stackoverflow.com/questions/21017036/mef-with-mvc-4-or-5-pluggable-architecture-2014

Running ASP.NET 5 applications in Linux Containers with Docker

http://blogs.msdn.com/b/webdev/archive/2015/01/14/running-asp-net-5-applications-in-linux-containers-with-docker.aspx

 

Azure

Integrate a cloud service with Azure CDN

http://azure.microsoft.com/en-us/documentation/articles/cdn-cloud-service-with-cdn/#bundling

 

Development

Modern Web Development Tools in Visual Studio 2015 with Mads Kristensen

http://channel9.msdn.com/Shows/Web+Camps+TV/Modern-Web-Development-Tools-in-Visual-Studio-2015-with-Mads-Kristensen

The God Login

http://blog.codinghorror.com/the-god-login/

Automating Windows environments’ setup with Boxstarter and Chocolatey packages

http://blogs.msdn.com/b/cesardelatorre/archive/2015/01/13/automating-windows-environments-setup-with-boxstarter-and-chocolatey-packages.aspx

Why NULL is Bad?

http://www.yegor256.com/2014/05/13/why-null-is-bad.html

Using Task.ConfigureAwait(false) to prevent deadlocks in async code

http://blog.ciber.no/2014/05/19/using-task-configureawaitfalse-to-prevent-deadlocks-in-async-code/

 

Front-End

The Story of the HTML5 Shiv

http://www.paulirish.com/2011/the-history-of-the-html5-shiv/

 

Books

Top 20 Agile Books for Software Developers

http://www.dkrimmer.de/2015/01/14/top-20-agile-books-software-developers/

 

Un saludo.

[ASP.NET] Revisa la configuración de ELMAH en producción

Uno de los mayores problemas que tenemos los desarrolladores cuando usamos alguna librería o plugin de terceros es no documentarnos correctamente antes de usar dicho componente. Posteriormente, una vez “probado todo” subimos nuestra aplicación a producción y sin darnos cuenta podemos tener una brecha de seguridad bastante importante, concretamente en el caso de usar mal ELMAH, bastante “gorda”. Esto se acentua con el el uso de Nuget, que es un pedazo de herramienta ojo!, pero a veces nos hace ser vagos o puede ser que no haga todo lo que tenga que hacer y nosotros no lo verifiquemos. Añadimos un paquete desde Visual Studio y por arte de magia todo funciona, pero no nos paramos a revisar que es lo que ha hecho el paquete, ¿Qué ficheros  ha incluido?, ¿Qué configuraciones ha hecho?…

Nota: No recuerdo a partir de que versión del paquete de ELMAH ha sido corregido un pequeño fallo de seguridad de configuración (Vamos a verlo ahora) a la hora de instalar ELMAH a través del paquete Nuget elmah pero ya había mecanismos en ASP.NET para evitar este fallo de seguridad utilizando el elemento location en el web.cofig, al fin y al cabo ELMAH usa un handler axd que podemos securizar.

Demo

Lo primero será crear una proyecto web que usaremos como ejemplo:

image

Un sitio MVC como muestra la siguiente imagen:

image

Voy a publicarlo en un WebSite de Azure para comprobar que ocurre cuando intentamos acceder a ELMAH en remoto:

image

Una vez creado el sitio, añadimos el paquete de Nuget  de ELMAH:

image

image

Una vez instalado, publico la aplicación web e intentamos acceder a la consola de errores de ELMAH en la dirección:

http://elmahdemoseguridad.azurewebsites.net/elmah.axd

image

Como os comentaba antes, el fallo de seguridad debido a una falta de configuración que había antes, ha sido resuelto en las nuevas versiones de ELMAH. Vamos a ver que han modificado y que puede pasar sino tenemos cuidado.

Abrimos el web.config y vamos a la sección de configuración de ELMAH:

image

Si queremos poder acceder remotamente a la consola de ELMAH, debemos establecer el valor del atributo allowRemoteAccess a true o 1 explicitamente o por defecto aunque lo comentemos o borremos seguiremos sin poder acceder. En versiones anteriores de ELMAH creo que esto no sucedía así.

Vamos a actualizar el valor del atributo allowRemoteAccess para simular que es lo que pasaba antes por defecto con ELMAH y que es como actualmente muchos sitios web se encuentran en internet (Luego lo demostraremos). Además añadimos el errorLog de Xml para grabar los errores de ELMAH.

image

Volvemos a acceder a nuestro sitio web:

image

A partir de ahora, cualquier usuario podría acceder a visualizar los errores que se han porducido en el servidor, eso te pasa también si usas incorrectamente el elemento customErrors, aunque para mi lo más preocupante es el tema relacionado con el ROBO DE SESION

Robando la sesión a un usuario

Vamos a modificar la aplicación web para que se produzca un error cuando el usuario visita la página de About 

image

Arrancamos la aplicación y visitamos la página de About y se produce el error

image

Ahora vamos a la consola de ELMAH para ver el error con más detalle:

image

Como podemos observar, el campo User está vacío pues estamos accediendo anónimamente a la aplicación. Si pulsamos sobre el enlace Details… podemos ver con todo detalle la traza de la excepción así como un conjunto de variables de servidor. Nos vamos a quedar con HTTP_COOKIE:

image

Actualmente no hay ninguna cookie de autenticación, así pues vamos a registrarnos en la aplicación con un usuario:

image

Esto nos creará una cuenta nueva y nos redireccionará a la home autenticados:

image

Ahora vamos a volver a navegar a About

image

Volvemos a la consola de ELMAH

image

Ahora si tenemos un usuario en el campo User. Pinchamos sobre el detalle del error y vamos a ver que contiene HTTP_COOKIE:

image

Ahora si está presente .AspNet.ApplicationCookie que es la cookie que genera AspNetIdentity y que ha sido registrada por ELMAH cuando se produjo el error.

Ahora vamos a copiar esta cookie y vamos a usar un plugin de Chrome para manipular cookies que se llama EditThisCookie. Para ello abrimos una nueva ventana en modo incognito y verificamos que no hay nadie con login:

image

Ese icono es EditThisCookie y como bien nos muestra la imagen no hay cookies:

image

Pulsamos sobre el icono + y añadimos la cookie que copiamos de ELMAH:

image

Refrescamos el navegador y voliá!!!

image

Hemos robado la sesión al usuario elmah@demo.es sin unos cracks en seguridad informática.

¡AUN HAY MÁS! Como nos solía recordar el mítico SUPER RATÓN, al ser una página pública, Google indexa la página de ELMAH sino le decimos lo contrario o no la securizamos, así pues podemos consultar a Google sobre sitios que contengan esa Url:

image

Recomendaciones

Es una mala práctica manipular el atributo allowRemoteAccess para poder acceder remotamente ELMAH. Sería recomendable acceder al servidor a través de un escritorio remoto y acceder de manera local.

Siempre tratar de actualizar este tipo de componentes a la última versión y estar al tanto de posible fallos de seguridad que se encuentren.

Utilizar alguna herramienta para revisar la seguridad de nuestros sitios web como esta:

image

Un saludo y happy hacking.

[SignalR] Autenticación JSON Web Token (JWT) en hubs de SignalR

A lo mejor el título no te dice mucho y quizás nunca te hayas encontrado con este caso desarrollando aplicaciones que usan Signalr, así pues voy a tratar de explicar lo mejor posible mi experiencia personal en un caso real con ASP.NET Web Api y Signalr y autenticación JWT.

Si quieres saber que es JWT (JSONWeb Token) te recomiendo este artículo de Atlassian Understanding JWT

 

El escenario

La imagen que a continuación os pongo resume un poco el escenario que me encontré:

image

Un ERP desarrollado en .NET en el cual querían exponer una API REST para que sus clientes pudieran consumir sus servicios. En este caso concreto he puesto un Prestashop que pudiera mostrar el catálogo de productos y poder hacer pedidos a través de esta plataforma desarollada en Php, pero puede ser cualquier otro cliente (SmartPhones, tablets…) o plataforma de terceros (Umbraco, Alfresco…)

Para securizar la API decidimos usar JWT. Para eso expusimos un endpoint llamado /token al cual se le pasa usuario y contraseña, lo valida contra el ERP y devuelve un token JWT como el que os muestro a continuación:

{

    "access_token":"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9._N1iZEvnhyE8k9knbwGtJ5IFsPR9Xsv3VXUz8...",

    "token_type":null,

    "expires_in":0,

    "userName":"bsmith",

    "issued":"07/15/2014 07:55:47",

    "expires":"07/29/2014 07:55:47"

}

Con ese token, el cliente puede hacer llamadas a la API añadiendo en cada petición la cabecera Authorization:

image

Hasta aquí todo correcto y funcionando, pero añadimos una capa de complejidad más:

Notificaciones en tiempo real con Signalr

Imaginaros que el cliente da de alta un pedido en Prestashop y para ello hace una llamada a la API. La API a su vez llama al core del ERP para dar de alta el pedido y el ERP contesta con un OK y la API a su vez contesta a Prestashop que OK. Todo esto desata en brackground un proceso/s de negocio en el ERP, pero nuestra API ya contestó a Prestashop y por lo tanto no hay una conexión abierta que indique que está ocurriendo o que ha ocurrido.

El cliente y cuando digo cliente, no me refiero al usuario que está comprando en Prestashop, sino al propietario de Prestashop, quiere poder suscribirse a notificaciones que le permita saber que ocurre con los procesos de negocio del ERP, es decir que el ERP le notifique y no tenga que estar consultando continuamente como se encuentran los pedidos (Por poner un ejemplo) y en base a ello poder actualizar información y actuar.

Para notificar a cada cliente correctamente necesitamos desde la parte servidora saber a quién tenemos que notificar y para ello necesitamos que Signalr asocie las conexiones a cada cliente y aquí es donde entra en juego el token JWT que la API nos emitió. Con este token no sólo sabremos a que cliente tenemos que notificar sino que securizaremos nuestro hub para que nadie sin un token válido pueda acceder a información confifencial de cada cliente.

Mi solución

Lo que os voy a contar ahora, quizás nos sea la mejor manera de hacerlo, pero lo he intentado de otra forma como creando un atributo de autorización personalizado y no he lo he conseguido, por lo menos en este escenario no encajaba. Con Owin y el middleware de de JWT se puede hacer pero en mi caso no usamos este middleware para generar los tokens JWT y por eso tampo encajaba (Sobre esto escribiré otro post más adelante). Por último, intentaré traducir este artículo para recibir feedback del equipo de producto de Signalr por si lo que estoy haciendo está mal y hay mejores maneras de hacerlo.

Lo primero que me puse a investigar es como podía pasar el token JWT a Signalr y gracias a Rui Marinho me enteré que se podía pasar por QueryString usando la propiedad qs del hub:

$(document).ready(function(){

    var hub = $.connection.erpHub;

    $.connection.hub.logging = true;

    $.connection.hub.qs = "Bearer=eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1...";

    hub.client.onProccesedOrder = function (order) {

        alert(order.status);

    }

 

    $.connection.hub.start();

});

A través de esa propiedad podéis pasar cualquier parámetro por QueryString a vuestros hubs.

A continuación, necesitamos obtener el token en el hub para validarlo. Para ello sobreescribimos los métodos OnConnected() y OnDisconnected() del hub.

public override Task OnConnected()

{

    return ValidateJwt(Add);

}

 

public override Task OnDisconnected()

{

    return ValidateJwt(Remove);

}

 
La firma del método ValidateJwt recibe un Func<string, Taks> por temas de refactorización y evitar código duplicado porque los métodos de OnConnected() y OnDisconnected() solo difieren en una línea de código como ahora veremos. (Aquí agradezco la ayuda de mi compañero Andrés aunque tenemos pendiente una refactorización usando el patrón estrategia que creo quedará más claro de leer, pero lo importante era evitar código duplicado)
 
private Task ValidateJwt(Func<string, Task> execute)

{

    var bearer = Context.QueryString.Get("Bearer");

 

    if (String.IsNullOrWhiteSpace(bearer))

    {

        return Task.FromResult(new HttpResponseMessage(HttpStatusCode.Unauthorized));

    }

 

    var principal = CreateClaimsPrincipalFrom(bearer);

 

    if (principal == null || principal.Identity == null || !principal.Identity.IsAuthenticated)

    {

        return Task.FromResult(new HttpResponseMessage(HttpStatusCode.Unauthorized));

    }

 

    SetContext(principal);

 

    var userName = Context.User.Identity.Name;

 

    return execute(userName);

}

Los métodos Add y Remove sólo se encargan de añadir la empresa a un grupo para poder comunicarnos con ella (ahora en el ejemplo completo podréis verlos) pero realmente importante es el siguiente método:

private void SetContext(ClaimsPrincipal principal)

{

    Context.Request.Environment["server.User"] = principal;

    Context = new HubCallerContext(

        new ServerRequest(Context.Request.Environment), Context.ConnectionId);

}

Lo que estamos haciendo básicamente es crear un contexto nuevo con los claims que han sido generadas a partir de la validación del token JWT que pasamos al Hub por QueryString y así cuando accedamos a Context.User.Identity.Name tendremos el nombre de la empresa que venía en los claims.

El código completo del hub es este:

public class ErpHub : Hub

{

    public override Task OnConnected()

    {

        return ValidateJwt(Add);

    }

 

    public override Task OnDisconnected()

    {

        return ValidateJwt(Remove);

    }

 

    private Task Add(string groupName)

    {

        Groups.Add(Context.ConnectionId, groupName);

 

        return base.OnConnected();

    }

 

    private Task Remove(string groupName)

    {

        Groups.Remove(Context.ConnectionId, groupName);

 

        return base.OnDisconnected();

    }

 

    private Task ValidateJwt(Func<string, Task> execute)

    {

        var bearer = Context.QueryString.Get("Bearer");

 

        if (String.IsNullOrWhiteSpace(bearer))

        {

            return Task.FromResult(new HttpResponseMessage(HttpStatusCode.Unauthorized));

        }

 

        var principal = CreateClaimsPrincipalFrom(bearer);

 

        if (principal == null || principal.Identity == null || !principal.Identity.IsAuthenticated)

        {

            return Task.FromResult(new HttpResponseMessage(HttpStatusCode.Unauthorized));

        }

 

        SetContext(principal);

 

        var userName = Context.User.Identity.Name;

 

        return execute(userName);

    }

 

    private ClaimsPrincipal CreateClaimsPrincipalFrom(string bearer)

    {

        var securityTokenBuilder = new SecurityTokenBuilder();

 

        var tokenValidationParameters = new TokenValidationParameters

        {

            AllowedAudience = Constants.AllowedAudience,

            SigningToken = securityTokenBuilder.CreateFromCertificate(Constants.CertificateSubjectName),

            ValidIssuer = Constants.Issuer

        };

 

        var tokenString = bearer;

        var tokenHandler = CreateTokenHandler();

        var token = CreateToken(tokenString);

        var principal = tokenHandler.ValidateToken(token, tokenValidationParameters);

        return principal;

    }

 

    protected virtual IJwtSecurityToken CreateToken(string tokenString)

    {

        return new JwtSecurityTokenAdapter(tokenString);

    }

 

    protected virtual IJwtSecurityTokenHandler CreateTokenHandler()

    {

        return new JwtSecurityTokenHandlerAdapter();

    }

 

    private void SetContext(ClaimsPrincipal principal)

    {

        Context.Request.Environment["server.User"] = principal;

        Context = new HubCallerContext(

            new ServerRequest(Context.Request.Environment), Context.ConnectionId);

    }

}

 

La demo

Llamamos al hub pasándole el token JWT por QueryString y como podemos observar accedemos a el:

image

Después validarlo y de establecer el contexto nuevo ya podemos acceder al nombre del cliente:

image

Y por último lo añadimos al grupo para poder enviarle notificaciones:

image

Ahora desde el servidor podemos mandar notificaciones a bsmith:

var userName = ClaimsPrincipal.Current.Identity.Name;

var hub = GlobalHost.ConnectionManager.GetHubContext<ErpHub>();

hub.Clients.Group(userName).onProccesedOrder(new Order(){Status = "Cancelado"});

image

Como os he comentado, esta ha sido mi manera de solucionar este caso concreto y quería compartirlo para a ser posible recibir feedback de cualquier tipo así como mejores implementaciones.

Me queda escribir un post comentando como hacer esto mismo usando si usamos el middleware de Owin JwtBearerAuthentication

Un saludo.

[ASP.NET MVC] Usa sólo el motor de vistas que necesites en tu aplicación.

Introducción

Cuando creamos un proyecto ASP.NET MVC, por defecto tenemos habilitados  los 2 motores de vistas que incluye el framework de MVC:

  1. El motor de vistas de Web Forms.
  2. El motor de vistas de Razor.

Para comprobar que esto es verdad y no te miento, basta con crear una aplicación web de ASP.NET MVC, borrar una de las vistas que vienen por defecto en la plantilla y tratar de acceder a ella:

image

Es más, por defecto siempre usa como primera opción el motor de vistas de Web Forms y sino encuentra ninguna vista en alguna de las localizaciones que están establecidas por defecto (basandose en conveciones), lo intentará con el motor de vistas de Razor, y sino encuentra ninguna, recibiremos ese bonito error.

Que os recomiendo yo, que sólo uses el motor de vistas que necesites, porque ese tiempo extra de estar localizando las vistas te lo puedes ahorrar y mejorar un poquito el rendimiento de tu aplicación ASP.NET MVC.

¿Como le digo a ASP.NET MVC qué motor de vistas quiero usar?

En ASP.NET MVC esto es muy sencillo de configurar. Vamos a decirle a ASP.NET MVC que use Razor como motor de vistas. Para ello vamos al Global.asax y añadimos estas 3 líneas al principio del método Application_Start:

var razorViewEngine = new RazorViewEngine();

ViewEngines.Engines.Clear();

ViewEngines.Engines.Add(razorViewEngine);

Al final quedaría así:

public class MvcApplication : HttpApplication

{

    protected void Application_Start()

    {

        var razorViewEngine = new RazorViewEngine();

        ViewEngines.Engines.Clear();

        ViewEngines.Engines.Add(razorViewEngine);

 

        AreaRegistration.RegisterAllAreas();

        FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);

        RouteConfig.RegisterRoutes(RouteTable.Routes);

        BundleConfig.RegisterBundles(BundleTable.Bundles);

    }

}

Volvemos a ejecutar y comprobamos que ahora solo usa el motor de vistas de Razor:

image

Con esas 3 líneas ya estamos ahorrandonos ese tiempo extra.

Buen fin de semana a todos!!!

[C#] Sobreescribir ToString en nuestras clases para mejorar la información en modo depuración

Seguro que como yo muchas veces te has preguntado cual puede ser una razón para sobreescribir ToString en alguna clases, verdad?

Yo hace poco empecé a hacerlo para mejorar la información que recibo en modo depuración en Visual Studio de las listas, colecciones, enumerados

Ejemplo

Supongamos que partimos de la siguiente clase:

public class Product

{

    public string Code { get; set; }

    public string Name { get; set; }

    public string Description { get; set; }

 

    public Product(string code, string name, string description)

    {

        Code = code;

        Name = name;

        Description = description;

    }

}

Ahora vamos a crear una lista de productos:

class Program

{

    static void Main(string[] args)

    {

        var products = new List<Product>

        {

            new Product("XBOX1", "XBox One", "New generation of games and entertainment."),

            new Product("PS4", "Play Station 4", "New generation of games and entertainment."),

            new Product("WIIU", "Wii U", "New generation of games and entertainment.")

        };

    }

}

Y vamos a depurar este pequeño programa:

image

De un simple vistazo no podemos ver que contiene la lista de productos, solo que son del tipo PocNet.Product y que hay 3 y para ver que hay en cada uno deberíamos desplegar otro nivel:

image

El caso es que el intellisense de Visual Studio para mostrarnos esa información llama al método ToString de los objectos y por defecto nos muestra el tipo del objeto. Para modificar este comportamiento vamos a sobreescribir ToString y a mostrar el código del producto:

public class Product

{

    public string Code { get; set; }

    public string Name { get; set; }

    public string Description { get; set; }

 

    public Product(string code, string name, string description)

    {

        Code = code;

        Name = name;

        Description = description;

    }

 

    public override string ToString()

    {

        return String.Format("Code - {0}", Code);

    }

}

Depuramos la aplicación, y ya en el primer nivel podemos ver al menos el código del producto que me aporta más información que el tipo.

image

Y como plus, si quieres mostrar por ejemplo todos los elementos de la clase producto vamos a utilizar JSON.NET para que lo serialice a JSON:

public class Product

{

    public string Code { get; set; }

    public string Name { get; set; }

    public string Description { get; set; }

 

    public Product(string code, string name, string description)

    {

        Code = code;

        Name = name;

        Description = description;

    }

 

    public override string ToString()

    {

        return JsonConvert.SerializeObject(this);

    }

}

Depuramos, y ya tenemos una vista de todas las propiedades y sus valores en el primer nivel:

image

Un saludo y buen fin de semana a todos.

[NoSQL] Replace vs Modified arrays en MongoDb

Introducción

MongoDb es una base de datos documental (Orientadas a documentos) y forma parte de la familia de las bases de datos NoSQL. MondoDb almacena la información en documentos en vez de guardar los datos en tablas relacionales como lo hacen las base de datos relacionales. Estos documentos se almacenan en un formato propio de MongoDb que es BSON (JSON binario) lo que hace que el sistema de almacenamiento sea muy dinámico y no esté ligado a un esquema como en las bases de datos relacionales (se conoce como schema free).

Para trabajar con MongoDb existen una gran variedad de drivers. Yo voy usar el driver oficial de C# que puedes añadirlo a trus proyectos desde Nuget.

Bueno, vista una pequeñisima introducción teórica a que es MongoDb. Recomendaría para seguir el artículo, leer un poco acerca de arrays en MongoDb y documentos embebidos. Pues dicho esto, vamos a ver que diferencia hay entre las operaciones Replace y Modified y cual usar en cada caso.

Caso práctico

Debemos usar Replace siempre que nuestra prioridad no sea el rendimiento y la concurrencia y si modelar un dominio de aplicación y que este no esté acoplado a MongoDb (Ya veremos más adelante porque).

Disclaimer: Quizás no sea el mejor ejemplo del mundo y el más acertado pero es sufiente para ver la diferencia entre ambos comandos.

Por ejemplo, imaginad que estamos diseñando un blog para una pequeña empresa. En este caso contamos con que el número de comentarios que se harán sobre un post será muy pequeño (Tal vez 5 o 6 comentarios) puesto que esta empresa no es muy conocida y la temática será sobre la vida en dicha empresa y solo los clientes comentarán los artículos. En este caso hemos decidido modelar un dominio de aplicación para nuestro blog y los vamos a  usar en un controlador para añadir un comentario:

[HttpPost]

public ActionResult AddComment(string message, string id)

{

    var post = GetPost(id);

    var comment = new Comment(message);

    

    post.AddComment(comment);

    _blogContext.Posts.Save(post);

 

    return RedirectToAction("Details", new {id});

}

El método Save si no existe  el documento lo inserta y si existe lo reemplaza (http://docs.mongodb.org/manual/tutorial/modify-documents/). Como dijimos anteriormente el rendimiento no es nuestra prioridad y y sí diseñar un modelo no anémico. Pero, ¿Que pasa si usamos esta aproximación en un blog cuyas visitas e interacción de los usuarios se cuentan por miles? Pues que vamos a encontrarnos con un problema de rendimiento y/o concurrencia y vamos a ir viendo por qué.

Lo primero que vamos a hacer es ver que pasa cuando ejecutamos un Replace sobre un documento en el que hemos añadido un nuevo elemento a un array de documentos.

Voy a usar un cliente visual para administrar MongoDb, en mi caso MongoVue:

image

Como podmeos observar, tenemos una colección donde almacenamos los posts y dentro de cada post tenemos un array para almacenar los comentarios (Hay que vigilar cuantos comentarios vamos a insertar y su tamaño ya que el tamaño máximo de un documento en MongoDb es de 16Mb)

Vamos a activar el profiler de MongoDb sobre la base de datos de blog para ver que pasa cuando añadimos un nuevo comentario:

image

image

o desde la consola de MongoDb con el comando db.setProfilingLevel(2)

Cuando activamos el profiler se crea una colección llamada system.profile

image

Añadimos un nuevo comment y vamos a buscar la operación de update:

image

o desde la consola de MongoDb con el comando db.system.profile.find({ «op» : «update» }).limit(50)

Y vamos a estudiar el resultado:

image

Como podemos observar el campo updateobj contiene el objeto post que había antes pero ahora la colección de comentarios tiene 2, el anterior que había y el nuevo que hemos añadido, es decir que cada vez que añadamos un nuevo comentario hará un Replace de todo el documento añadiendo el nuevo comentario al array de documentos.

¿Pero esto impacta al rendimiento?

Pues si, a medida que el número de comentarios crezca, cada operación de Replace ira creciendo exponencialmente. Como decía anteriormente, esto no está mal, es un comportamiento estandard de este tipo de operación, pero si buscas rendimiento esta no es tu mejor opción y vamos a ver porque.

Vamos a ejecutar una prueba para añadir 1000, 2500 y 5000 comentarios a un post y vamos a medir tiempos, ok?

image

Como podéis observar Replace es bastante más lento que Modify. Un crece de manera exponencial mientras que la otra crece de menera más o menos lineal.

¿Entonces que hace Modify con respecto a Replace?

Lo primero es mostrar la diferencia en el código:

[HttpPost]

public ActionResult AddComment(string message, string id)

{

    var comment = new Comment { Message = message };

    var update = Update<Post>.Push(p => p.Comments, comment);

    _blogContext.Posts.Update(Query.EQ("_id", ObjectId.Parse(id)), update);

 

    return RedirectToAction("Details", new {id});

}

Como podemos observar, en este caso hacemos uso de la clase Update, para hacer un push a la colección de comments. Si este código lo metemos dentro de la entidad Post estaremos acoplando nuestro modelo a MongoDb, de ahí que Replace encaja mejor en ese tipo de escenarios, pero como decíamos, si buscamos rendimiento y/o concurrencia, Modify es nuestra solución.

Si ejecutamos el profiler y añadimos un comentario con Modify:

image

¿Veís la diferencia? Ahora solo se añade un comentario al array de documentos pero no se reemplaza todo el post como ocurría con Replace, por lo tanto es una operación menos costosa.

Conclusión

Si lo que te importa es el rendimiento y la concurrencia, olvidate de modelar tu dominio y usa directamente Modify para operaciones sobre arrays, para todo lo demás Replace.

Un saludo.

[ASP.NET vNext]Primer día del TechEd 2014 North America

Como ya sabréis, y sino pues os acabáis de enterar, ayer comenzó en Houston, Texas el TechEd 2014 que viene cargadito de novedades. Yo me voy a centrar en el anuncio de ASP.NET vNext que a mí personalmente me apasiona bastante.

ASP.NET vNext es una actualización de ASP.NET que ha sido optimizada tanto para servidor como para Cloud (Ahora tenemos 2 sabores .NET vNext y .NET vNext Cloud optimized), es decir, más ligera, que nos permitirá crear aplicaciones web más rápidamente y por supuesto con una mejora de rendimento considerable.

Sobre las cosas más importante que podemos destacar de ASP.NET vNext son:

  • Optimizado para servidor y cloud.
  • Un único modelo de programación para web sites (ASP.NET MVC) y servicios (WebAPI).
  • No se necesita compilación, lo que agiliza mucho el desarrollo (Modificamos ficheros, guardamos y refrescamos el browser)
  • Inyección de dependencias de serie, se acabó tener que estar usando contenedores de terceros.
  • El runtime se despliega juento con nuestra aplicación vía Nuget.
  • Nuget por todos lados (Hasta nuestra librerías y runtime son paquetes de Nuget)
  • Y por último y no menos importante, Open Source.

ASP.NET vNext incluye nuevas versiones tanto de sitios web (MVC 6, Web Pages) como para servicios (WebApi 3, SignalR 3, Entity Framework 7)

Ahora la gestión de paquetes es más sencilla e intuitiva. Para ello han creado un nuevo fichero de proyecto llamado project.json siguiendo la idea del packages.json de Node.js, eso sí con intellisense:

image_12

Otra cosa muy importante, ASP.NET vNext y Roslyn (El nuevo compilador de .NET) se pueden ejecutar en Mono (En Mac y Linux) o lo que es lo mismo, son multiplataforma, porque aunque Mono no es un proyecto de Microsoft están colaborando con el equipo. Esto si son buenas noticias para todos los desarrolladores y para que el ecosistema .NET, que desde mi punto de vista es el mejor, llegue a todos los desarrolladores y sistemas.

Esta imagen ha sido sacada del blog de Scott Hanselman y en ella podemos ver como usando un editor que no es Visual Studio, en esta caso Sublime podemos editar nuestro sito web y ejecutarlo en un Mac:

aspnetvnextonmac_3

Es importante remarcar que la versión de vNext para Mono (Mac & Linux) es la de Cloud opmitized.

Carcaterística

.NET vNext

.NET vNext (Cloud Optimized)

Listo para el Cloud

*

*

Diseño modular

*

*

Inyección de dependencias

*

*

Depuración y trazas

*

*

Desarrollo más agil /rápido (browser refresh)

*

*

Open Source

*

*

Runtime y framework se despliegan con la aplicación

 

*

Arranque más rápido, menor consumo de memoria y mayor rendimeinto.

 

*

Uso de un conjunto de librerías menor.

 

*

Disponible para Mono, en Mac y Linux

 

*

Hoy seguiremos atentos a las nuevas novedades para seguir contandolas.

Por último, el Update 2 de Visual Studio 2013 ya está disponible para descarga:

Información de la Release

Descarga para MSDN Subscribers

Descarga para versiones de prueba

Un saludo!