2012-02-25 16 views
6

Estoy agregando controles de usuario web a una página dinámicamente. Usar el método LoadControl que solo toma una ruta virtual apuntando al .ascx funciona muy bien. Sin embargo, la sobrecarga de LoadControl que toma un tipo y una matriz de parámetros me está causando algunos dolores de cabeza.Cargar el control de usuario programáticamente usando LoadControl (Type, Object())

El control del usuario web se instancia como se esperaba, pero los controles contenidos en el control del usuario web son nulos y recibo una excepción tan pronto como intento trabajar con ellos. Extraño, porque funciona cuando se usa la primera versión de LoadControl.

El control de usuario Web, simple, con un control Literal:

<%@ Control Language="vb" AutoEventWireup="false" CodeBehind="MyControl.ascx.vb" Inherits="MyControl" %> 
<asp:Literal ID="myLiteral" runat="server"></asp:Literal> 

código Los controles atrás:

Public Class MyControl 
    Inherits System.Web.UI.UserControl 

    Public Property Data As MyData 

    Public Sub New() 

    End Sub 

    Public Sub New(data As MyData) 
    Me.Data = data 
    End Sub 

    Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load 
    myLiteral.Text = Data.ID ' The Literal is null, but ONLY when I use the second LoadControl() method! 
    End Sub 

End Class 

Y el código correspondiente de la .aspx de la que estoy tratando de cargar dinámicamente el control:

Private Sub Page_Init(sender As Object, e As System.EventArgs) Handles Me.Init 
    Dim x = LoadControl(GetType(MyControl), New Object() {New MyData With {.ID = 117}}) 
    Page.Controls.Add(x) 

    ' Using LoadControl("MyControl.ascx") works as expected! 
End Sub 

Respuesta

1

Con un poco de ayuda de this article por Steven Robbins, que terminó con un método de extensión muy conveniente en su lugar:

Imports System.Runtime.CompilerServices 
Imports System.Web.UI 
Imports System.Reflection 

Module LoadControls 
    <Extension()> _ 
    Public Function LoadControl(templateControl As TemplateControl, virtualPath As String, ParamArray constructorParams() As Object) As UserControl 
    Dim control = TryCast(templateControl.LoadControl(virtualPath), UserControl) 
    Dim paramTypes = constructorParams.Select(Function(p) p.GetType()).ToArray 
    Dim constructor = control.GetType().BaseType.GetConstructor(paramTypes) 

    If constructor Is Nothing Then ' Nothing if no such constructor was found. 
     Throw New ArgumentException(String.Format("No constructor for control '{0}' with {1} parameter(s) were found.", virtualPath, paramTypes.Count)) 
    Else 
     constructor.Invoke(control, constructorParams) 
    End If 

    Return control 
    End Function 

End Module 
+1

Parece que está invocando un constructor en un objeto ya creado, ¿cómo funciona eso? – jmoreno

+0

@jmoreno Como los constructores son solo métodos estáticos con un poco de alboroto adicional, simplemente funciona. –

+0

Aquí hay una demostración rápida: http://ideone.com/IoqU2Z (el código falla en la mayoría de los editores en línea porque requiere cierta demanda de seguridad, pero se ejecuta con total confianza). –

2

Por esta posición t Encontré: http://forums.asp.net/t/1375955.aspx, se dijo que simplemente no lo use.

Una página que carga un control de usuario utilizando Page.LoadControl (Type, Object []) no parece crear sus elementos secundarios agregados en el archivo ascx. El uso de Page.LoadControl (String) funciona como se esperaba.

Según tengo entendido, basado en el código detrás de las cosas, el ascx es una clase hija que hereda MyControl pero no MyControl, debes entender que ascx no es una definición de MyControl sino su extensión, así que cuando pruebes use el nombre de tipo para crear el control, está creando un control primario pero no el que desea.

Para probar esto, simplemente defina una propiedad privada en MyControl, y trate de vincular el valor en el archivo ascx, luego obtendrá un error ya que la clase secundaria no puede acceder a ningún elemento privado en su clase base.

0

Jakob, muchas gracias a la función de extensión. Ha sido muy útil. Sin embargo, no cuenta para los constructores que toman los parámetros ByRef.

Estoy seguro de que la siguiente modificación puede escribirse mucho más corta y evitar volver a escribir la lógica de GetConstructor, pero esto es lo que se me ocurrió para manejar los parámetros de ByRef en el constructor.

Intenté mantenerlo genérico, por lo que la configuración de los parámetros en ByRef se basa en un constructor coincidente en lugar de un código fijo para un índice de parámetro.

Editar: Esta función tiene un inconveniente. Los constructores de su control de usuario reciben dos llamadas. Una vez en el primer LoadControl y luego otra vez cuando se encuentra el segundo constructor y se le dan los parámetros. Tenga en cuenta que esto tiene Page_Load también ejecutándose dos veces. Encapsulé la lógica de page_load real en un sub y tiene el segundo constructor que la llama, evitando el problema.

Imports System.Runtime.CompilerServices 
Imports System.Web.UI 
Imports System.Reflection 

<Extension()> Public Function LoadControl(templateControl As TemplateControl, virtualPath As String, ParamArray constructorParams() As Object) As UserControl 
    Dim control As UserControl = TryCast(templateControl.LoadControl(virtualPath), UserControl) 
    Dim paramTypes() As Type = constructorParams.Select(Function(p) p.GetType()).ToArray 
    Dim isMatch As Boolean = True 

    ' ByRef Parameters 
    For Each cnst As ConstructorInfo In control.GetType.BaseType.GetConstructors 
     If cnst.GetParameters.Count = paramTypes.Count Then 
      Dim tempTypes(paramTypes.Count - 1) As Type 
      isMatch = True 
      Array.Copy(paramTypes, tempTypes, paramTypes.Length) 

      For i As Integer = 0 To paramTypes.Count - 1 
       If cnst.GetParameters(i).ParameterType.FullName.TrimEnd("&") = paramTypes(i).FullName Then 
        If cnst.GetParameters(i).ParameterType.IsByRef Then tempTypes(i) = paramTypes(i).MakeByRefType Else tempTypes(i) = paramTypes(i) 
       Else 
        isMatch = False 
       End If 
      Next 

      If isMatch Then 
       cnst.Invoke(control, constructorParams) 
       Exit For 
      End If 
     End If 
    Next 

    If not isMatch Then 
     Throw New ArgumentException(String.Format("No constructor for control '{0}' with {1} parameter(s) were found.", control, paramTypes.Count)) 
    End If 

    Return control 
End Function 
Cuestiones relacionadas