许多开发人员使用一种众所周知的技术将.ascx动态加载到另一个自定义的.ascx中。这种技术被应用于后面的代码中,并且有许多开发人员创建的代码的许多变体。
您可能知道的第一件事是,当控件加载到asp.net页面中时,每次向服务器发出回发请求时都必须加载它,因为它需要每次呈现自己,直到我们决定使用新控件。
当动态加载的控件试图从视图状态读取/写入数据时,以及加载器控件试图这样做时,我们的问题就开始了。问题是viewstate数据仅在页面生命周期的几个步骤之间是活动的,我们将看到这一点。
看看下面的代码:
Protected Sub RadioButtonList1_SelectedIndexChanged(ByVal sender As Object, ByVal e As System.EventArgs)处理RadioButtonList1。SelectedIndexChanged
LoadControls ()
终止子
Private Sub LoadControls()
Dim c As控制
如果RadioButtonList1。SelectedValue= "1" Then
c = LoadControl(" control1.ascx")
其他的
c = LoadControl("control2.ascx")
如果
c.ID = RadioButtonList1。SelectedValue
PlaceHolder1.Controls.Clear ()
PlaceHolder1.Controls.Add (c)
终止子
受保护的子Page_Load(ByVal发送者作为对象,ByVal e作为System.EventArgs)处理我。负载
LoadControls ()
如果不是Page。IsPostBack然后
DataList1。数据源=值
DataList1.DataBind ()
如果
终止子
如您所见,代码尝试根据下拉列表中存储的值加载子控件。在第一次,这段代码将工作良好,之后,你会开始发现一些问题
问题1:IsPostBack属性不能在子控件中使用
如您所知,用于验证页面是第一次加载还是为回发而加载的最常用代码是IsPostBack变量。
如果不是isPostBack,那么
DoMyWork ()
如果
回顾我们的代码,我们会发现:
a)第一次进入页面:
主控件加载由单选按钮列表中的默认值指定的默认控件(即Control1)。子控件加载正常,IsPostBack变量为false(如预期)
b)单击单选按钮列表,选择另一个选项
主控件加载新控件(即控件2)。子控件加载,我们测试IsPostBack属性的代码将发现它的值等于true(该事件是通过单击单选按钮列表产生的真实回发)。
c)再次点击单选按钮列表,再次选择默认选项
主控件加载控件(Control1),然后Control1再次加载。控件将发现IsPostback变量等于true
在C中加载的控件将期望一个等于“false”的值来初始化它自己的内容,并且该值将仅在第一次加载页面时提供。
解决方案:使用viewstate标志来检查代码是否已经初始化
如果不是,viewstate("MyFlag")则为空
DoMyWork ()
视图状态(“MyFlag”)=“Ok”
如果
问题2:当控件未在页面生命周期的INIT部分实例化时,某些视图状态数据不可用
让我们回顾一下页面生命周期:
- 初始化
- LoadViewState(只发生在PostBack)
- LoadPostDackData(只发生在PostBack)
- 负载
- RaisePostBacKEvent(只发生在PostBack上)
- SaveViewState
- 渲染
从我们的示例中可以看到,直到“Load”阶段,我们才重新创建动态控件。到那时,LoadViewState和LoadPostBackData已经发生了。在加载事件发生时,我们的控件已经创建,所有的数据都没有被它们加载。
因此,我们需要在以前的状态下创建控件。查看我们的控件事件,我们会发现最接近的选项是INIT事件,我们将代码移动到INIT事件。
我们的单选按钮列表的值总是等于默认选择!!为什么?
答案很简单,看一下页面的生命周期。Init事件发生在LoadViewState和LoadPostBackData之前,并且主控件的代码还没有关于单选按钮的任何数据。
因此,我们在这里遇到了一个真正的问题,因为在第一种情况下,子控件的数据丢失了,而在第二种情况下,父控件的数据还没有准备好,我们仍然需要它工作。
解决方案:尽管大多数论坛或博客上的一般规则都说最好不要这样做,但是ASP的完美之处在于。NET和DotNetNuke就是你可以用一点想象力做你想做的一切
我们的主要问题是,我们需要在阶段3之前重新创建控件,并且在阶段1中没有视图状态数据。
我们的控件是通过使用OOP(面向对象编程)构建的,因此我们可以覆盖基类中的一些函数。例如,没有什么可以阻止我们重写LoadViewState函数:
Protected覆盖子LoadViewState(ByVal savedState作为对象)
MyBase.LoadViewState (savedState)
CreateMyControls ()
终止子
由于问题似乎只有在回发到位时才会出现,因此我们可以在viewstate中创建一个标志,以便在最近覆盖的函数中重新创建控件
Protected覆盖子LoadViewState(ByVal savedState作为对象)
MyBase.LoadViewState (savedState)
如果不是,viewstate(" MyFlag ")则为空
CreateMyControls ()
视图状态(“MyFlag”)=“Ok”
如果
终止子
在以下几行中可以找到经过改进的最终示例:
Protected Sub RadioButtonList1_SelectedIndexChanged(ByVal sender As Object, ByVal e As System.EventArgs)处理RadioButtonList1。SelectedIndexChanged
选择Case RadioButtonList1。SelectedValue
“1”
LoadUserControl(“MyControl1.ascx”)
“2”
LoadUserControl(“MyControl2.ascx”)
“3”
LoadUserControl(“MyControl3.ascx”)
其他情况下
LoadUserControl(“MyControl1.ascx”)
最终选择
终止子
Protected覆盖子LoadViewState(ByVal savedState作为对象)
MyBase.LoadViewState (savedState)
LoadControls ()
终止子
Private Sub LoadControls()
如果不是我。latestloaddcontrolname是空的
LoadUserControl(我。LatestLoadedControlName Me.LatestLoadedControlAjax)
其他的
LoadUserControl(“urlcontrolfile.ascx”)
如果
终止子
私有属性latestloaddcontrolname()作为字符串
得到
返回装运箱(视图状态(“LatestLoadedControlName”))
最终得到
设置(ByVal值作为字符串)
ViewState("LatestLoadedControlName") = value
终端设置
结束财产
公共子LoadUserControl(ByVal controlName作为字符串,可选ByVal useAjax作为布尔值= False)
如果不是(latestloaddcontrolname Is Nothing)那么
Dim previousControl As Control = PlaceHolder1.FindControl(LatestLoadedControlName.Split("。"c)(0))
如果不是(previousControl Is Nothing)那么
PlaceHolder1.Controls.Remove (previousControl)
如果
如果
Dim userControlID As String = controlName.Split("."c)(0)
Dim targetControl As Control = placeholder . findcontrol (userControlID)
如果targetControl为空,则
将userControl设置为Control = none
userControl = LoadControl(controlName)
用户控件。ID = userControlID。取代 ("/", "").替换(“~”,“”)
PlaceHolder1.Controls.Add(用户)
LatestLoadedControlName = controlName
如果
终止子