2012-02-25 9 views
6

Aggiungo dinamicamente i controlli utente Web a una pagina. L'utilizzo del metodo LoadControl che richiede solo un percorso virtuale che punta a .ascx funziona molto bene. Tuttavia, il sovraccarico di LoadControl che accetta un tipo e un array di parametri mi sta causando alcuni grattacapi.Carica il controllo utente a livello di codice utilizzando LoadControl (Type, Object())

Il controllo utente Web viene istanziato come previsto, ma i controlli contenuti nel controllo utente Web sono nulli e ottengo un'eccezione non appena provo a lavorare con essi. Strano, perché funziona quando si utilizza la prima versione di LoadControl.

Il controllo utente Web, semplice, con un controllo Literal:

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

codice I comandi dietro:

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 

E il relativo codice dalla .aspx da cui sto cercando di caricare dinamicamente il controllo:

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 

risposta

1

Con un po 'di aiuto da parte this article da Steven Robbins, ho finito con un metodo di estensione molto conveniente, invece:

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

Sembra che si stia invocando un costruttore su un oggetto già creato: come funziona? – jmoreno

+0

@jmoreno Dato che i costruttori sono solo metodi statici con qualche confusione extra, funziona. –

+0

Ecco una breve demo: http://ideone.com/IoqU2Z (il codice non funziona sulla maggior parte degli editor online perché richiede una certa richiesta di sicurezza, ma funziona con piena fiducia). –

2

Per questa posizione Ho trovato: http://forums.asp.net/t/1375955.aspx, è stato detto che non lo uso.

Una pagina che carica un controllo utente utilizzando il Page.LoadControl (Tipo, Oggetto []) non sembra creare i relativi figli aggiunti nel file ascx. L'utilizzo di Page.LoadControl (String) funziona come previsto.

La mia comprensione è che in base al codice alla base, l'ascx è una classe figlio che eredita MyControl ma non MyControl stesso, è necessario capire che ascx non è una definizione di MyControl ma è ed estensione, quindi quando provi usa il nome del tipo per creare il controllo, stai creando un controllo genitore ma non quello che vuoi.

Per dimostrarlo, è sufficiente definire una proprietà privata in MyControl e provare a associare il valore su ascx, quindi si otterrà un errore poiché la classe figlio non può accedere ad alcuna cosa privata nella sua classe base.

0

Jakob, grazie tanto per la funzione di estensione. È diventato molto utile. Tuttavia non tiene conto dei costruttori che accettano parametri ByRef.

Sono sicuro che la seguente modifica può essere scritta molto più breve ed evitare di riscrivere la logica di GetConstructor ma questo è quello che mi è venuto in mente per gestire i parametri ByRef nel costruttore.

Ho cercato di mantenerlo generico in modo che l'impostazione dei parametri su ByRef sia basata su un costruttore corrispondente anziché su un hard index codificato su un indice di parametro.

Modifica: c'è uno svantaggio di questa funzione. I costruttori del tuo controllo utente vengono chiamati due volte. Una volta dal primo LoadControl e poi di nuovo quando viene trovato il secondo costruttore e dati i parametri. Tieni presente che anche questo Page_Load viene eseguito due volte. Ho incapsulato l'effettiva logica page_load in un sub e il secondo costruttore lo chiama, evitando il 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 
Problemi correlati