Skip to main content

Reflection sobre .aspx, eh?

Acceder a absolutamente todo mediante reflection es algo que se es poniendo cada vez mas de moda y puede traer varios problemas. Pero no voy a hablar de como solucionar los problemas sino de como usar Reflection aun mas :) En algunos casos puede ser muy util ver que controles hay en una pagina que de mi aplicacion, por ejemplo puede servir para configurar permisos sobre controles como Toolbars o Grillas, editar informacion dinamicamente en base a WebParts de otras paginas, etc.. Cuando se crea un Toolbar en una pagina lo mas normal es cargar la informacion sobre que operaciones (botones) tiene en una base de datos manualmente, pero es aun mucho mejor si la pagina en la que se configuran esos permisos puede saber automaticamente que botones tiene cada pagina.... Lo primero que hay que hacer para obtener una pagina es crear un PageHandler, para eso lo unico necesario es la URL, por ejemplo "/Carpeta/Pagina1.aspx":
// Creo un Request Mock, realmente no se va a realizar ningun Request.
SimpleWorkerRequest request = new SimpleWorkerRequest("/Carpeta/Pagina1.aspx", string.Empty, TextWriter.Null);
HttpContext mockContext = new HttpContext(request);

// Obtengo el handler de la pagina en cuestion...
IHttpHandler handler = PageParser.GetCompiledPageInstance(virtualPath, fileName, mockContext);

// Si se quiere ya se puede leer el nombre de la clase de esa pagina :)
className = handler.GetType().BaseType.FullName;
Una vez que tenemos el HttpHandler que se encarga de procesar la pagina, lo que tenemos que hacer es procesarla. Pero primero creemos un delegado que nos va a servir para indicarle a la pagina que cree los controles que nosotros queremos dinamicamente. Durante todo este ejemplo se obtienen controles Toolbar, que es hipoteticamente un control personalizado, pero podria ser cualquier otro tipo (incluso todos los controles).
delegate Toolbar BuildToolbarDelegate();
Ahora, el codigo que recorre todos los controles declarados y compara el tipo. Es importante notar que lo que en realidad se esta recorriendo no es la clase que nosotros creamos sino una clase que ASP.NET genera dinamicamente cuando se modifica un ASPX. Esa clase posee informacion sobre todos los controles y metodos necesitados por el runtime de .NET.

private IEnumerable<Toolbar> GetPageControls(IHttpHandler handler)
{
  Type handlerType = handler.GetType();

  // Busco todos los controles definidos en la pagina
  FieldInfo[] fields = handlerType.GetFields(BindingFlags.NonPublic | BindingFlags.Public |
      BindingFlags.Instance);
  foreach (FieldInfo field in fields)
  {
      // Si el field que estoy recorriendo actualmente es un control del tipo Toolbar...
      if (typeof(Toolbar).Equals(field.FieldType))
      {
          // Llamo a un metodo generado en runtime por ASP.NET para cargar la informacion del
          // control, el metodo se llama "__BuildControl" + id_del_control
          string MethodName = string.Format("__BuildControl{0}", field.Name);
          MethodInfo BuildControl = handler.GetType().GetMethod(MethodName, BindingFlags.Instance |
                                          BindingFlags.NonPublic);

          // Forma simple y lenta de llamar a ese metodo:
          //yield return (Toolbar)BuildControl.Invoke(handler, null);

          // Forma rapida (aunque hay mejores aun):
          BuildToolbarDelegate buildDelegate = (BuildToolbarDelegate) Delegate.CreateDelegate(
                                                                          typeof (BuildToolbarDelegate),
                                                                          handler,
                                                                          BuildControl,
                                                                          true);

          Toolbar t1 = buildDelegate();
          yield return t1;
      }
  }
}

Esta tecnica simple permite hacer muchas cosas y extender la configuracion de la aplicacion usando bastante poco codigo. Incluso si se quiesiese se podria definir los tamaños de todos los controles en runtime, y para setearlos no seria tan complicado. Al menos no tanto como si no se usase esta tecnica.

De todas formas hay que tener cuidado porque cuando se procesan varias paginas puede empezar a ser lento para el usuario, pero eso se puede solucionar con Cache...

Suerte!

Comments

  1. Exelente Diego....

    una pregunta viejo....
    en la linea donde se buscan todos los controles definidos

    FieldInfo[] fields = handlerType.GetFields( BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);

    No deberia tener OR entre cada Bandera ????

    se que el codigo es algo antiguo.. pero espero sepas la respuesta....

    gracias

    ReplyDelete
  2. Ahi lo corregi, gracias. No salio cuando lo pegue en el blog.
    Los flags van separados por el OR binario (|).

    Saludos

    ReplyDelete
  3. Exelente, funciona perfecto.

    para quienes deseen una version mas generalizada.

    *Search & Replace "Toolbar" por "Control"
    *Modificar el unico 'if' dentro del foreach por "field.IsFamily" (esto evitara que en el proceso no se ejecuten fields que corresponden al request, ensamblados y demas)

    Pura Vida

    ReplyDelete

Post a Comment

Popular posts from this blog

Making Celery 4 work in Django 1.10 and Elastic Beanstalk

Finally after many many days of trying to make it work and reading thousand of pages, I got Celery working with django 1.10 in Amazon AWS Elastic Beanstalk with SQS (Simple Queue Services) – including Celery Beat!. First, the files I ended up with, then the explanation of what I understand (some of those things still remain being a mystery) STEP 0: Install using the following: pip install -U celery[sqs] pip install django-celery-beat I’m using the following versions of the apps: boto (2.45.0) botocore (1.4.63) celery (4.0.2) Django (1.10.1) django-celery-beat (1.0.1) kombu (4.0.2) pip (9.0.1) pycurl (7.43.0) FILE: /src/PROJECT_NAME/celery.py from __future__ import absolute_import , unicode_literals import os from celery import Celery # set the default Django settings module for the 'celery' program. # DONE IN __init__.py os . environ . setdefault ( "DJANGO_SETTINGS_MODULE" , "PROJECT_NAME.settings.production" ) app = Celery ( 'PR

Stripping HTML from text in SQL Server–Version 3

  I’ve used the HTML stripping function for SQL Server available in lazycoders.blogspot.com , which is the second version of the originally published in blog.sqlauthority.com . But neither one removes the comments in this case: <!-- <b>hello world</b> --> Hello which is more or less the code that MS Word generates. Well, the function with that fixed is this (changes are in bold): ALTER FUNCTION [dbo].[DeHtmlize] ( @HTMLText varchar ( MAX ) ) RETURNS varchar ( MAX ) AS BEGIN DECLARE @ Start int DECLARE @ End int DECLARE @Length int -- Replace the HTML entity &amp; with the '&' character (this needs to be done first, as -- '&' might be double encoded as '&amp;amp;') SET @ Start = CHARINDEX( '&amp;' , @HTMLText) SET @ End = @ Start + 4 SET @Length = (@ End - @ Start ) + 1 WHILE (@ Start > 0 AND @ End > 0 AND @Length > 0) BEGIN SET @HTMLText = STUFF(@HTMLText, @ Start , @Le