8

我正在动态地将 Web 用户控件添加到页面。使用LoadControl仅采用指向.ascx作品的虚拟路径的方法非常好。但是,它的重载LoadControl需要一个类型和一个参数数组,这让我有些头疼。

Web 用户控件按预期实例化,但 Web 用户控件中包含的控件为空,并且在尝试使用它们时立即出现异常。奇怪,因为它在使用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
4

3 回答 3

3

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

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

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

为了证明这一点,只需在 MyControl 中定义一个私有属性,并尝试绑定 ascx 上的值,然后您将收到错误,因为子类无法访问其基类中的任何私有内容。

于 2012-02-25T18:48:58.883 回答
2

在 Steven Robbins 的这篇文章的帮助下,我最终得到了一个非常方便的扩展方法:

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
于 2012-02-26T08:29:15.503 回答
0

Jakob,非常感谢您提供的扩展功能。它非常方便。但是,它不考虑带参数 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
于 2013-05-06T16:37:54.120 回答