HTML Render de controles ASP.NET

 

Hace algún tiempo, en una compañía dedicada a consultaría Web en donde trabajaba surgió un requerimiento de “integrar” ASP.NET con un CMS que permite únicamente tener contenido estático HTML, CSS y JavaScript en tiempo de diseño.

El escenario era el siguiente:

  1. Un sitio en ASP.NET con varios User Controls.
  2. Los editores usan el CMS para crear nuevas páginas, y en tiempo de diseño pueden insertar los controles de usuario del proyecto de ASP.NET.
  3. En tiempo de diseño, el CMS no soporta ASP.NET.
  4. Cuando el editor envía a publicar las páginas, estas son copiadas dentro del sitio ASP.NET, donde recién el código ASP.NET podrá ser ejecutado.
  5. En tiempo de diseño del CMS, el editor debe poder ver el preview de los controles de ASP.NET.

Era una tarea bastante interesante, la cual de lo que me comentaron no se había podido realizar antes, por suerte con un poco C# se me ocurrió la siguiente idea:

Crear una página en el proyecto ASP.NET que reciba como parámetros el nombre del UserControl que se debe mostrar y los parámetros para inicializarlo, luego obtener el HTML resultante y devolverlo al cliente como una función JavaScript. De esta forma quien quiera mostrar un control de usuario en sitios no ASP.NET, solo necesita usar un poco de JavaScript (muy poco a decir verdad).

RenderControl.aspx

Esta es la página que recibe como parámetro en el QueryString el nombre del User Control y crea dinámicamente una función JavaScript con el HTML resultante. Adicionalmente el UserControl puede recibir parámetros de entrada como Strings, Booleanos, o números. Haciendo uso de Reflection, esta página se encarga de asignar dinámicamente estas propiedades antes de dibujar el control.

Creando los User Controls

Lo primero que haremos es crear unos 3 controles de usuario de ejemplo:

image AllUsers.ascx: Contiene un ASp.NET GridView con una lista de usuarios obtenida de un archivo XML.

 

 

image MyCalendar.ascx: Contiene un ASP.NET Calendar

 

 

 

 

 

image UserProfile.ascx: Muestra los datos de un usuario especificado, el usuario se asigna usando la propiedad Username, la cual lee de nu archivo XML los datos del usuario.

public string Username
        {
            set
            {
                lblUsername.Text = value;
                LoadProfile();
            }
            get
            {
                return lblUsername.Text;
            }
        }

              private void LoadProfile()
        {
            if (!String.IsNullOrEmpty(lblUsername.Text))
            {
                XDocument userXml = XDocument.Load(Server.MapPath("Data/Users.xml"));

                var userProfile = (from user in userXml.Descendants("user")
                                   where user.Attribute("id").Value == lblUsername.Text
                                   select user).Single();

                lblFirstname.Text = userProfile.Attribute("firstName").Value;
                lblLastname.Text = userProfile.Attribute("lastName").Value;
                lblRole.Text = userProfile.Attribute("role").Value;
            }
        }

image

Estos son User Controls normales, que pueden tener elementos ASP.NET cuyo HTML resultando será mostrado en otro sitio externo.

Crear la función JavaScript

Para que nuestra página RenderContro.aspx sea capaz de dibujar el control y devolver código JavaScript necesitamos sobreescribir dos métodos de la clase Page:

  1. CreateHtmlTextWriter
  2. Render

Si recordamos el ciclo de vida de una página ASP.NET: Init, Load, Render y Unload, nos damos cuenta de que justamente el método Render es el encargado de “dibujar en la página” el contenido:

StringWriter sw = new StringWriter();
HtmlTextWriter pageWriter = null;

protected override HtmlTextWriter CreateHtmlTextWriter(TextWriter tw)
        {
            pageWriter = base.CreateHtmlTextWriter(tw);
            return base.CreateHtmlTextWriter(sw);
        }

        protected override void Render(HtmlTextWriter writer)
        {
            base.Render(writer);
            string html = sw.ToString();
            html = html.Replace(Environment.NewLine, String.Empty);
            html = html.Replace("\"", "’");

            string script = "function render_" + controlName + uniqueId + "() { document.write(\"" + html + "\");}";

            pageWriter.Write(script);
        }

En el método CreateHtmlTextWriter estamos pasando un objeto StringWriter en lugar del predeterminado (TextWriter), es en este objeto donde el método Render insertará el código HTML de los controles. Todo el contenido de la página en forma de HTML, cualqueira que se este, será escrito dentro de nuestro  StringWriter, y posteriormente podremos manipularlo según nuestras necesidades.

En el método Render modificamos el HTML antes de que s dibuje en la página, de tal forma que el resultado sea una función JavaScript con el método document.write.

La función es creada dinámicamente y se forma de la siguiente forma:

  1. El prefijo “render_”
  2. El nombre del control.
  3. Un identificador único para el nombre de la función.
  4. El cuerpo de la función consiste del método document.write y el contenido es el HTML resultando de la página.

NOTA: Es siempre importante remover saltos de línea y comillas que del contenido puedan generar errores en la función JavaScript.

Insertar el control dinámicamente

La última parte de nuestro proyecto es insertar el control dinámicamente en la página para que se cree la función JavaScript. Insertar un control dinámicamente es muy sencillo en ASp.NET:

Control ctrl = LoadControl(@"~\UserControls\" + controlName + ".ascx");
canvas.Controls.Add(ctrl);

La parte interesante está en asginar las propiedades de forma dinámica a cada control. Por ejemplo asignar el valor de la propiedad Username de control UserProfile. Esto lo logré usando Reflection, gracias a esto puedo asignar el valor de cualqueir propiedad cuyo tipo de dato soporte el método Parse:

private void SetProperties(Control ctrl)
        {
            Type controlType = ctrl.GetType();
            List<PropertyInfo> properties =  controlType.GetProperties().ToList<PropertyInfo>();

            foreach (var qs in Request.QueryString)
            {
                if (!qs.ToString().Equals(qsControl, StringComparison.InvariantCultureIgnoreCase)
                    && !qs.ToString().Equals(qsUniqueId, StringComparison.InvariantCultureIgnoreCase))
                {
                    PropertyInfo currentProperty = properties.Find(delegate(PropertyInfo pi)
                    {
                        return pi.Name == qs.ToString();
                    });

                    if (currentProperty != null)
                    {
                        if (currentProperty.PropertyType.Name == "String")
                        {
                            currentProperty.SetValue(ctrl, Request.QueryString[qs.ToString()], null);
                        }
                        if (currentProperty.PropertyType.GetMember("Parse").Length > 0)
                        {
                            Object parsedValue = currentProperty.PropertyType.InvokeMember("Parse", BindingFlags.InvokeMethod, null, ctrl, new object[] { Request.QueryString[qs.ToString()] });
                            currentProperty.SetValue(ctrl, parsedValue, null);
                        }
                    }
                }
            }
        }

Este método recibe como parámetro el Control y lee del QueryString las propiedades que se le deben asignar mediante Reflection. De especial interés es el siguiente bloque de código:

if (currentProperty.PropertyType.GetMember("Parse").Length > 0)
                        {
                            Object parsedValue = currentProperty.PropertyType.InvokeMember("Parse", BindingFlags.InvokeMethod, null, ctrl, new object[] { Request.QueryString[qs.ToString()] });
                            currentProperty.SetValue(ctrl, parsedValue, null);
                        }

Esta sección de código permite transformar el valor de la propiedad que siempre viene como String al tipo de dato que espera la propiedad, por ejemplo Bool o Int. La transformación se la hace invocando dinámicamente el método Parse del tipo de datos de la propiedad.

Finalmente ya podemos incrustar controles ASP.NET en paginas desarrolladas en otras tecnologías, o páginas simples HTML, así:

<head>
    <title></title>
    <script src="RenderControl.aspx?control=UserProfile&Username=cintriago&UpperCase=true"></script>
    <script src="RenderControl.aspx?control=UserProfile&uniqueId=2&Username=jane"></script>
    <script src="RenderControl.aspx?control=UserProfile&uniqueId=3&Username=john"></script>
    <script src="RenderControl.aspx?control=AllUsers"></script>
    <script src="RenderControl.aspx?control=MyCalendar"></script>
</head>
<body>
<script language="javascript" type="text/javascript">        render_AllUsers();</script>
    <script language="javascript" type="text/javascript">        render_UserProfile();</script>
    <script language="javascript" type="text/javascript">        render_UserProfile2();</script>
    <script language="javascript" type="text/javascript">        render_MyCalendar();</script>
    <script language="javascript" type="text/javascript">        render_UserProfile3();</script>
</body>
</html>

Y en el navegador tendremos el siguiente resultado:

image Espero que este artículo les haya pareceido de utilidad en interés. Cualquier duda, comentario o sugerencia es bienvenida. Hasta la próxima!!.

Bajar el proyecto y código fuente

3 comentarios to “HTML Render de controles ASP.NET”

  1. Luis Crespo Says:

    Me parece mas que Exelente tu articulo:
    1) Esta en español!!
    2) Esta muy claro
    3) YA PROBE EL CODIGO y si funciona!!
    Esto es lo que lo hace para mi VALIOSO, es importante la teoria, pero si no lo veo en la PRACTICA, siempre lo pongo en duda. Si eres maestro o alumno te sirve, pero si eres desarrollador (que es mi caso) , lo practico es lo valioso.
    Un 10, desde mi punto de vista… Gracias por la contribucion!!

  2. muy intetensante tu contribucion. a esto le llamo yo aportacion valiosa

  3. saludos

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s

A %d blogueros les gusta esto: