JASoft.org

El blog de José Manuel Alarcón Aguín. Programación web y mucho más...

MENÚ - JASoft: JM Alarcón

Catálogos de WebParts Dinámicos

WebpartsNOTA: Estas técnicas no están documentadas y se basan en mis indagaciones sobre cómo conseguir crear catálogos dinámicos de WebParts. Todo surgió a raíz de la pregunta de un alumno de mi curso de preparación del examen de certificación 70-515 de ASP.NET.

¿Cómo puedo crear un catálogo de WebParts para una página personalizada al cual le pueda añadir yo los controles que quiera dinámicamente?. Es decir, que los controles que aparecerán en el catálogo en lugar de estar determinados de antemano como en los casos habituales (en un catálogo estático de WebParts), se puedan cargar desde una base de datos o similar.

La solución

Primero, para esto hay que crear nuestra propia clase plantilla de catálogo que es la que se encargará de contener a los controles que voy a ir añadiendo. Esta clase debe implementar la interfaz ITemplate. Ello implica implementar el método InstantiateIn de esta interfaz que será llamado por la infraestructura de página cuando se deba inicializar el catálogo. Es en ese momento cuando se deben añadir los controles que deseemos a la colección de controles de la plantilla.

Dado que no tenemos forma de añadirlos desde este método tendremos que definir una colección de controles para almacenarlos temporalmente y añadirlos antes de esa instanciación. Después en la inicialización los añadiremos a la colección de controles real de la plantilla. En definitiva el código de esta clase queda así (en VB):

   1: Public Class miPlantillaDeCatalogo
   2:   Implements ITemplate
   3:  
   4:   Private _controles As New Collection()
   5:  
   6:   Public Sub InstantiateIn(ByVal container As System.Web.UI.Control) 
   7:     Implements System.Web.UI.ITemplate.InstantiateIn
   8:     If _controles.Count > 0 Then
   9:       Dim ctl As Control
  10:       For Each ctl In _controles
  11:         container.Controls.Add(ctl)
  12:       Next
  13:     End If
  14:   End Sub
  15:  
  16:   'Expongo la propiedad Controls
  17:   Public ReadOnly Property Controls() As Collection
  18:     Get
  19:       Return _controles
  20:     End Get
  21:   End Property
  22: End Class

Luego entenderemos mejor su funcionamiento.

De acuerdo. Los controles catálogo están contenidos en controles de zona. Éstos son también plantillas que se usan simplemente para mostrar los catálogos. A su vez estos controles de zona están contenidos en controles de tipo CatalogZone que son los que podemos arrastrar y soltar desde la barra de herramientas de ASP.NET.

Sabiendo esto debemos crear un control de zona personalizado que nos permita contener al catálogo antes definido. Al igual que antes tenemos que implementar la interfaz ITemplate, y como antes tampoco tenemos forma de indicar en la inicialización qué catálogo vamos a usar, por lo que tendremos que establecerlo mediante una propiedad personalizada. El código de la clase queda así:

   1: Public Class MiPlantillaDeZona
   2:   Inherits WebPart
   3:   Implements ITemplate
   4:  
   5:   Private _catalogo As miPlantillaDeCatalogo
   6:  
   7:   Public Property Catalogo() As miPlantillaDeCatalogo
   8:     Get
   9:       Return _catalogo
  10:     End Get
  11:     Set(ByVal value As miPlantillaDeCatalogo)
  12:       _catalogo = value
  13:     End Set
  14:   End Property
  15:  
  16:   Public Sub InstantiateIn(ByVal container As System.Web.UI.Control) 
  17:      Implements System.Web.UI.ITemplate.InstantiateIn
  18:     Dim dcp As New DeclarativeCatalogPart()
  19:     dcp.WebPartsTemplate = _catalogo
  20:     dcp.ID = "Mi catálogo"
  21:     dcp.Title = "Catálogo personalizado"
  22:     container.Controls.Add(dcp)
  23:   End Sub
  24: End Class

Como vemos lo que se hace es exponer una propiedad para almacenar el catálogo que queremos usar, y luego durante la inicialización de la plantilla, lo añadimos a los controles de ésta.

Ahora ya tenemos todo lo de base que necesitamos. Nos falta añadir controles a nuestro catálogo y asociarlo a un control CatalogZone que ya tendremos previamente en la página. Podemos hacerlo por ejemplo en el evento Load del formulario Web:

   1: Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs)_ 
   2:    Handles Me.Load
   3:   Dim tmpl As New miPlantillaDeCatalogo()
   4:   Dim wp1 As New MiWebpart()
   5:   wp1.ID = "Usuario actual WP"
   6:   tmpl.Controls.Add(wp1)
   7:   Dim mpz As New MiPlantillaDeZona()
   8:   mpz.Catalogo = tmpl
   9:   CatalogZone1.ZoneTemplate = mpz
  10: End Sub

Como vemos lo que se hace es instanciar un catálogo personalizado. Luego se instancia el control Webpart que queremos añadir al catálogo y se añade a la colección de controles del catálogo. Se crea nuestra plantilla de zona personalizada, asignándole el catálogo para su correcta inicialización. Finalmente se asigna esta zona como plantilla de zona para el control catálogo de la página, que es el que se encarga de gestionar la infraestructura subyacente para poder añadir los controles.

Por cierto, es necesario ponerle un identificador al Webpart o romperá la aplicación.

Controles de servidor

El código anterior funciona bien si lo que tenemos en el catálogo son controles que heredan de Webpart y por lo tanto son Webarts "puras". Pero ¿qué pasa si quiero añadir a mi catálogo personalizado controles de servidor normales (que no son webParts) o incluso controles de usuario (que tampoco son Webparts)?.

La infraestructura de Webparts de ASP.NET usa la clase GenericWebpart para envolver este tipo de controles y poder utilizarlos, pero nosotros no lo tenemos tan fácil para usar esto directamente, así que para conseguirlo he tenido que hacer también mucha prueba y error. El resultado es el siguiente:

   1: Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) _
   2:     Handles Me.Load
   3:   Dim tmpl As New miPlantillaDeCatalogo()
   4:   Dim wp1 As GenericWebPart
   5:   Dim uc1 As UserControl
   6:   uc1 = LoadControl("~/usuarioActual.ascx")
   7:   uc1.ID = "uc1"
   8:   uc1.Attributes.Add("Title", "Usuario actual")
   9:   wp1 = WebPartManager1.CreateWebPart(uc1)
  10:   wp1.ID = "Usuario actual UC"
  11:  
  12:   tmpl.Controls.Add(wp1)
  13:   Dim mpz As New MiPlantillaDeZona()
  14:   mpz.Catalogo = tmpl
  15:   CatalogZone1.ZoneTemplate = mpz
  16: End Sub

En este caso la cosa se complica un poco.

Lo primero es instanciar el control que queremos usar. En mi caso he usado un control de usuario que se limita a mostrar el nombre del usuario actual. Para ello usamos el método LoadControl de la página. Ahora nos toca envolver ese control en un Webpart genérico. Pero para ello no podemos usar el constructor de esta clase, que es lo que hace ASP.NET por debajo, ya que éste es interno y protegido. Así que tenemos que usar el método CreateWebPart del WebPartManager que tengamos en la página. Para ello antes, hay que asignarle un identificador al control u obtendremos un error. Además le añadiremos un atributo "Title" con la descripción que queremos que aparezca en el catálogo (esta última me costó mucho Reflector para llegar a ella), o de otro modo el usuario no sabrá distinguir unos controles de otros en el catálogo.

Una vez creado el Webpart genérico es necesario asignarle un identificador, y ya podemos añadirlo a nuestro catálogo personalizado y proceder como antes.

¡Espero que te sea útil!

José Manuel Alarcón José Manuel Alarcón
Fundador de campusMVP.es, el proyecto de referencia en formación on-line para programadores en lengua española. Autor de varios libros y cientos de artículos. Galardonado como MVP de Microsoft desde 2004. Gallego de Vigo, amante de la ciencia y la tecnología, la música y la lectura. Ayudando a la gente en Internet desde 1996.
Descarga GRATIS mi último libro (no técnico): "Tres Monos, Diez Minutos".
Banner

Comentarios (4) -

Colombia Cesar Verano

Hola, gracias por el articulo,

Tengo una pregunta que me quito varias horas, ya solucione el inconveniente pero sigo sin entender que lo causa, quisiera saber si me pudieras dar una pequeña ayuda:

Yo cree un sitio nuevo desde Visual Studio .net 2010, no tengo instalado SQLServerExpress tengo una versión de pago de SQL, siempre que intentaba hacer lo que dice tu articulo me salia el error "SQLExpress database file auto-creation error:" y le hice de todo lo que decian los articulos, permisos, habilitar puertos y demas; hasta que me di cuenta que es porque al parecer no tengo instalado SQLServerExpress, lo que hice fue crear una BD en SQL, correo allí el aspnet_regsql y luego cambiar la cadena de conexión del web.config a esta base, pero tambien me toco remover la cadena LocalSqlServer y a la que cree colocarle este nombre.

Todo esto me parece muy extraño, porque razón siempre hay que indicar cual es la cadena de conexión para LocalSqlServer?

Muchas gracias y feliz día .........

Responder

Spain José Manuel Alarcón

Hola,

La cadena de conexión LocalSqlServer está definida en el web.config global, por eso siempre la tienes por defecto. Lo que puedes hacer aparte de lo que has hecho (que está bien) es simplemente indicarle a los proveedores de Membership y demás (incluyendo el de Webparts) cuál es el nombre de tu cadena de conexión, y así puedes usar cualquiera que quieras crear. De todos modos lo que has hecho tú es quizá lo ma´s cómodo.

Saludos,

Responder

Colombia Cesar Verano

Hola Jose Manuel,

Los WebParts son solo para usuarios autenticados, no se puede para usuarios anónimos?

Gracias ........

Responder

Spain José Manuel Alarcón

Puedes usarlo con usuarios anónimos, pero si quieres que queden guardados los cambios en la bbdd necesitas estar autenticado.

Responder

Agregar comentario