2012-02-25 94 views
6

我正在动态地将Web用户控件添加到页面。使用LoadControl方法只需要一个指向.ascx的虚拟路径就能很好地工作。然而,LoadControl的过载需要一个类型和一个参数数组,这使我很头痛。使用LoadControl(Type,Object())以编程方式加载用户控件

Web用户控件按预期方式实例化,但Web用户控件中包含的控件为空,只要我尝试使用它们,就会收到异常。奇怪,因为它使用LoadControl的第一个版本时正在工作。

该网站的用户控制,操作简单,具有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 

回答

1

由史蒂芬·罗宾斯一点帮助从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 
+1

看起来你正在调用已经创建的对象的构造函数 - 这是如何工作的? – jmoreno 2016-05-19 01:04:07

+0

@jmoreno由于构造函数只是一些额外的喧嚣的静态方法,它只是工作。 – 2017-05-04 10:29:57

+0

下面是一个快速演示:http://ideone.com/IoqU2Z(大多数在线编辑器的代码失败,因为它需要一些安全需求,但它完全信任)。 – 2017-05-04 10:41:12

2

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

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

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

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

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 
相关问题