La castaña con gusanos

Una de las cosas que más me gusta de estas fechas es poder pasear con un cucurucho de castañas. Por tres cosas principalmente

  1. Se me calientan las manos.
  2. Se me llena la barriga.
  3. Me sale barato y en tiempos de crisis no hay nada como algo barato y bueno.

 

castañas

 

En contra tiene una pega y es que alguna puede llevar premio en forma de gusano.

Sí a estás alturas no has cerrado el post, vas a ver que es lo que para mí es una “castaña con gusanos”.

Si señores las routas de mvc y WebApi por lo menos con la versión con la cual yo estoy trabajando(4.0).

Mientras que los métodos en WebApi se llamán Get,Post,Put,Delete, todo es perfecto, ahora cuando en un mismo controlador necesitas otra cosa, ponte a llorar.

Bueno pues dicho esto ya sabéis de que va el post. Los otros días tras esta entrada en twitter.

TwitterRutas

Para los que quieran la conversación completa os paso la url

https://twitter.com/_PedroHurtado/status/410525801314938881.

Mi buen amigo @nicolocodev me recomendó que utilizase Attribute Routing in Web API 2. Aunque ya había leído sobre esto se me planteaba algún que otro problema y es que yo no tenía mi proyecto en 2.0. Lo típico te vas a Nuget, buscas y te lleva a este sitio http://attributerouting.net/ la verdad que es un proyecto de lo más interesante y se puede ejecutar en mi escenario, pero…. Que veo!!!

Un símbolo rojo en WebApi y este cartel más bien llamativo.

Performance

Con lo cual al leer algo de performance como que se me activan las neuronas y de una conversación de las muchas que mantengo por twitter nace todo lo que vais a ver.

Si hablamos de performance, porque no creamos 1000 controladores con un método “GET” que simplemente devuelva una cadena y vemos si es verdad lo que dicen.

Manos a la obra.

1. Vía Nuget o bien desde el Pakage Manager Console instalas AttributeRouting.

2. Una vez que se instala se crea una clase en la carpeta App_Start con nombre AttributeRoutingHttpConfig. Donde rápidamente puedes ver que cuando la web se activa se ejecuta el método Start.

[assembly: WebActivator.PreApplicationStartMethod(typeof(MvcApplication18.AttributeRoutingHttpConfig), "Start")] 

3. Pero como creo yo 1000 controladores sin T4 y sin copiar ni pegar. Sencillo pero misterioso. De esas cosas que dan miedo hasta que no las pruebas. Os presento a …. Don Reflection Emit.

En esa misma clase voy a crear un método llamado CreateManyControladores con el siguiente código.

   1: private static Assembly  CreateManyControladores()

   2: {

   3:    AppDomain ad = AppDomain.CurrentDomain;

   4:    AssemblyName am = new AssemblyName();

   5:    am.Name = "TestAttributeRouting";

   6:    AssemblyBuilder ab = ad.DefineDynamicAssembly(am, AssemblyBuilderAccess.RunAndSave,"c:\mvc");

   7:    ModuleBuilder mb = ab.DefineDynamicModule("testmod", "TestAttributeRouting.dll");

   8:    Type t = null;

   9:    for (int i = 0; i < 1000; i++)

  10:    {

  11:        var controllerName = string.Format("Type{0}Controller", i);

  12:        var routeUrl = string.Format("/Hello{0}", i);

  13:        var returnMessage = string.Format("This is a string from method {0}", i);

  14:  

  15:        var typeBuilder = mb.DefineType(controllerName, TypeAttributes.Public, typeof(ApiController));

  16:  

  17:        var methodBuilDer = typeBuilder.DefineMethod("Get", MethodAttributes.Public, typeof(string), null);

  18:        var constructor = typeof(GETAttribute).GetConstructor(new Type[] { typeof(string) });

  19:        methodBuilDer.SetCustomAttribute(new CustomAttributeBuilder(constructor, new object[] { routeUrl }));

  20:  

  21:        ILGenerator il = methodBuilDer.GetILGenerator();

  22:        il.Emit(OpCodes.Ldstr, returnMessage);

  23:        il.Emit(OpCodes.Ret);

  24:        t = typeBuilder.CreateType();

  25:    }

  26:    

  27:    ab.Save("TestAttributeRouting.dll");

  28:    return t.Assembly;

  29: }

No te asustes que te lo explico.

    1. Crear un AssemblyBuilder y le dices que es para ejecutar y guardar e indicas una ruta donde guardar la dll, en mi caso c:mvc.
    2. Creamos un modulo que es donde vamos a compilar todos nuestros tipos.
    3. Creamos 1000 controladores con nombre Type000Controller, 001,002,3 etc,etc con el Atributo “GET”  y  con la ruta “Hello0,1,2,3”
    4. Devolvemos una cadena que nos indica que método se está ejecutando.
    5. Por último guardamos la dll “TestAttributeRouting.dll” y devolvemos el Assembly.

¿Que no te lo crees?

Pues mira si existe el archivo y con ILSpy descompílalo y vas a obtener esto.

ilspy

Hay Dios, “dichoso del que cree sin ver” Sonrisa.

Y ahora vamos con la confirmación de una mala “performance”.

Para ello en el método RegisterRoutes de la clase AttributeRoutingHttpConfig vamos a agregar el siguiente código y a tomar tiempos.

   1: public static void RegisterRoutes(HttpRouteCollection routes) 

   2: {    

   3:     // See http://github.com/mccalltd/AttributeRouting/wiki for more options.

   4:     // To debug routes locally using the built in ASP.NET development server, go to /routes.axd

   5:  

   6:    

   7:     routes.MapHttpAttributeRoutes();

   8:     

   9:     //Create assembly

  10:     var stopwatch = Stopwatch.StartNew();            

  11:     var assembly = CreateManyControladores();

  12:     stopwatch.Stop();

  13:     var miliseconds = stopwatch.ElapsedMilliseconds;

  14:  

  15:     //Compile routes

  16:     stopwatch.Start();

  17:     routes.MapHttpAttributeRoutes(c => c.AddRoutesFromAssembly(assembly));

  18:     stopwatch.Stop();

  19:     miliseconds = stopwatch.ElapsedMilliseconds;

  20:     

  21:     

  22: }

Creación de la dll dinámica.

Assembly

 

Compilación de rutas.

CompileRutes

Efectivamente hemos confirmado que existe en esta dll un serio problema de rendimiento. No os voy a explicar más de esto puesto que tenemos que pasar a otra cosa, pero yo me he bajado el código fuente de AttributeRouting y si que tiene alguna que otra sentencia rara, yo creo que desconocimiento de Reflectión como el siguiente de los protagonista.

Debería decir lo que es, pero lo dejo para otro capítulo o mejor para que investigues tú Sonrisa.

Si ya tenemos todo esto. Es el momento de ejecutar desde nuestro explorador cada una de las rutas y ver lo que pasa.

Response

Pín,Pín,Pum!!! algo raro está pasado. Venga ánimo que vamos a ver lo que es.

Dentro de todo lo que pasa en WebApi hay una serie de Servicios que se encargan de algunas cosas y concretamente el problema lo tenemos en el servicio DefaultHttpControllerTypeResolver.

En este servicio hay bajo mi punto de vista un problema y es que una dll dynamica no devuelve los tipos con esta instrucción.

assembly.GetExportedTypes();

Y claro el señor que escribió esta clase por lo visto no conocía,no recuerda o tuvo un lapsus para saber como obtener los tipos públicos de un ensamblado dinámico.

assembly.GetTypes();  

Es por eso por lo que escribió el siguiente código.

Código malo

O lo mismo estaba en la especificación y entonces el no es el culpable sino otro.

No permitir definir 1000 controladores al vuelo y tampoco ejecutar dll’s dinámicas de serie cosa que se hace una y otra vez con las vistas. Pero bueno… Vamos con la solución.

Crea una clase con el siguiente código.

   1: using System;

   2: using System.Collections.Generic;

   3: using System.Linq;

   4: using System.Reflection;

   5: using System.Web;

   6: using System.Web.Http.Dispatcher;

   7:  

   8: namespace MvcApplication18.Handler

   9: {

  10:     public class MyDefaultTypeResolver : DefaultHttpControllerTypeResolver

  11:     {

  12:         public MyDefaultTypeResolver()

  13:             :base()

  14:         {

  15:         }

  16:         public override ICollection<Type> GetControllerTypes(IAssembliesResolver assembliesResolver)

  17:         {

  18:             List<Type> result = new List<Type>();

  19:             var assemblies = AppDomain.CurrentDomain.GetAssemblies().ToList();

  20:             

  21:             foreach (var assembly in assemblies)

  22:             {

  23:                 

  24:                 Type[] exportedTypes = null;

  25:                 if (assembly == null)

  26:                 {

  27:                     // can't call GetExportedTypes on a dynamic assembly

  28:                     continue;

  29:                 }

  30:  

  31:                 try

  32:                 {

  33:                     if (assembly.IsDynamic)

  34:                     {

  35:                         exportedTypes = assembly.GetTypes();

  36:                     }

  37:                     else

  38:                     {

  39:                         exportedTypes = assembly.GetExportedTypes();

  40:                     }

  41:                 }

  42:                 catch (ReflectionTypeLoadException ex)

  43:                 {

  44:                     exportedTypes = ex.Types;

  45:                 }

  46:                 catch

  47:                 {

  48:                     // We deliberately ignore all exceptions when building the cache. If 

  49:                     // a controller type is not found then we will respond later with a 404.

  50:                     // However, until then we don't know whether an exception at all will

  51:                     // have an impact on finding a controller.

  52:                     continue;

  53:                 }

  54:                

  55:                 if (exportedTypes != null)

  56:                 {

  57:                     result.AddRange(exportedTypes.Where(x => IsControllerTypePredicate(x)));

  58:                 }

  59:             }

  60:  

  61:             return result;

  62:            

  63:         }

  64:  

  65:            

  66:     }

  67: }

Y en el archivo Global.asax en el método Application_Start incluir esta línea de código.

GlobalConfiguration.Configuration.Services.Replace(typeof(IHttpControllerTypeResolver),

                new MyDefaultTypeResolver());

Como podéis ver en el código, yo si permito ensamblados dinámicos y luego cargo los tipos correctos, queréis que  todos vuestros controladores sean privados, pues otra cosa para investigar.

Assembly.GetTypes()

Ves que sencilloSonrisa.

Ahora puedo ejecutar desde el explorador estás dos rutas, pero no le des que fallaSonrisa.

http://localhost:50150/Hello999

http://localhost:50150/api/type999

Claro esto pone en favor de “AttributeRouting” otro problema y es que nuestra api ahora responde a dos rutas con lo que plantéate problemas de auditoría y me dices por donde te están llamando.

Que es? un todo o nada. Me obliga a decorar todos mis métodos con un atributo.

Hemos vuelto a

mvc restfull webservices o no hemos salido aún

Conclusiones.

1. Si esto pasa en WebApi20 es para llorar, no lo he probado.

2. Ten claro que hasta que no llega la llamada a tu controlador todo es Reflection que puede ser optimo si lo haces bien o poco optimo si lo haces mal y en este caso esta mal hecho.

3. Que no inventes lo ya inventado que todo el mercado funciona con una convención de rutas y que se ha demostrado que funciona.

4. Que no os hago una demo en #nodejs y en #go porque se está haciendo pesado.

Pero repito menos papas, guardianes ,que  funcione bien y se testee para escenarios de este tipo que con dos controladores todo funciona y tener 1000 controladores es fácil (200 entidades x 5 Métodos).

Hasta la próxima, que dada las fechas será para 2014.

FELIZ NAVIDAD Y PROSPERO AÑO NUEVO!!!

P.D. Me despido desde el blog porque desde el twitter aún os quedan aprox. unos 15 de días de guerra.

Un comentario sobre “La castaña con gusanos”

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *