我正在动态地向网页添加网络用户控件。使用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
答案 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