使用LoadControl以编程方式加载用户控件(Type,Object())

时间:2012-02-25 17:37:50

标签: .net vb.net webusercontrol

我正在动态地向网页添加网络用户控件。使用LoadControl方法只需要指向.ascx的虚拟路径就可以了。但是,带有类型和参数数组的LoadControl的重载让我有些头疼。

Web用户控件按预期实例化,但Web用户控件中包含的控件为null,一旦我尝试使用它们,我就会收到异常。很奇怪,因为它在使用第一版LoadControl时有效。

Web用户控件,简单,带有Literal控件:

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

后面的控件代码:

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

我试图动态加载控件的.aspx中的相关代码:

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

3 个答案:

答案 0 :(得分:3)

根据这篇文章,我发现:http://forums.asp.net/t/1375955.aspx,据说只是不使用它。

使用Page.LoadControl(Type,Object [])加载用户控件的页面似乎不会在ascx文件中创建其子项。使用Page.LoadControl(String)按预期工作。

我的理解是基于后面的代码,ascx是继承MyControl而不是MyControl本身的子类,你需要了解ascx不是MyControl的定义,而是它的扩展,所以当你尝试使用时键入名称以创建控件,您正在创建父控件但不是您想要的控件。

为了证明这一点,只需在MyControl中定义一个私有属性,并尝试绑定ascx上的值,然后你会得到一个错误,因为子类不能访问它的基类中的任何私有东西。

答案 1 :(得分:1)

在Steven Robbins的this article的帮助下,我得到了一种非常方便的扩展方法:

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

答案 2 :(得分:0)

雅各布,非常感谢你的扩展功能。它非常方便。但是它没有考虑带参数ByRef的构造函数。

我确信以下修改可以写得更短,并且避免重写GetConstructor逻辑,但这是我在构造函数中处理ByRef参数时想到的。

我试图保持它的通用性,以便将参数设置为ByRef是基于匹配的构造函数而不是硬编码到参数索引。

编辑:这个功能有一个缺点。用户控件的构造函数被调用两次。一旦通过第一个LoadControl,然后再次找到第二个构造函数并给出参数。请注意,这也有Page_Load也运行两次。我将实际的page_load逻辑封装在一个sub中,让第二个构造函数调用它,避免了这个问题。

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