【问题标题】:Load user control programmatically using LoadControl(Type, Object())使用 LoadControl(Type, Object()) 以编程方式加载用户控件
【发布时间】:2012-02-25 17:37:49
【问题描述】:

我正在向页面动态添加 Web 用户控件。使用仅采用指向.ascx 的虚拟路径的LoadControl 方法效果很好。但是,采用类型和参数数组的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

【问题讨论】:

    标签: .net vb.net webusercontrol


    【解决方案1】:

    根据这个帖子我发现:http://forums.asp.net/t/1375955.aspx,据说就是不要用。

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

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

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

    【讨论】:

      【解决方案2】:

      在 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
      

      【讨论】:

      • 看起来您正在对已创建的对象调用构造函数——它是如何工作的?
      • @jmoreno 由于构造函数只是带有一些额外喧嚣的静态方法,因此它可以正常工作。
      • 这是一个快速演示:ideone.com/IoqU2Z(该代码在大多数在线编辑器上都失败了,因为它需要一些安全要求,但它在完全信任的情况下运行)。
      【解决方案3】:

      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
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2013-07-25
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-02-08
        • 2010-12-13
        相关资源
        最近更新 更多