为什么子控件在其容器之前初始化?
尽管使用 WebForms 多年,我仍然发现自己时常对事件生命周期感到困惑。这与其说是一个需要解决的问题,不如说是希望更好地理解事情为何如此运作。
假设您有一个表单:
Default.aspx:
<form>
<MyControls:UserControl1 runat="server">
</form>
UserControl1:ascx:
<MyControls:UserControl2 runat="server">
OnInit
事件按以下顺序发生:
UserControl2_OnInit
UserControl1_OnInit
Default_OnInit
这不是低音-ackwards 吗? Init 代码不应该按照控件创建的顺序运行吗?父控件不应该能够在 OnInit 运行之前初始化子控件的属性吗?也就是说,虽然您可以在标记中初始化子控件的属性,但没有直接的方法可以让父控件能够动态设置可用于其 OnInit 事件的子控件的属性。
我最终所做的是这样的事情:
override void UserControl2_OnInit()
{
NamingContainer.OnInit += new EvenHandler(UserControl1_ActualInit);
}
protected void UserControl2_ActualInit(..) {
// do actual init code here, which will occur before OnLoad but after it's parent
// OnInit
}
所以这不是一个无法克服的问题。我只是不明白为什么这是一个问题。
我意识到您可能希望能够在 OnInit 代码中初始化所有子控件。很好,您应该能够首先调用 base.OnInit,而不是在您自己的初始化代码之后调用,这应该会导致所有子控件 OnInit 事件运行。但事件生命周期并不是这样工作的。 Init 事件不是递归链接的,它们似乎独立于父事件运行,并且最里面的事件总是首先运行。但如果它们只是简单地递归链接,那么生活似乎会容易得多,这样您就可以在任何给定情况下执行操作之前调用(或不调用)基本事件。我是否遗漏了一些东西,使得这种看似违反直觉的情况变得可取甚至必要?
Despite working with WebForms for years I still find myself getting confused about the event lifecycle from time to time. This isn't so much a problem that needs resolving, as hoping to get a better understanding of why things work the way they do.
Suppose you have a form:
Default.aspx:
<form>
<MyControls:UserControl1 runat="server">
</form>
UserControl1:ascx:
<MyControls:UserControl2 runat="server">
The OnInit
events occur in this order:
UserControl2_OnInit
UserControl1_OnInit
Default_OnInit
Isn't this just bass-ackwards? Shouldn't the Init code be run in the order that controls are created? Shouldn't a parent control be able to initialize properties of a child before its OnInit runs? That is, while you can initialize properties of subcontrols in markup, there's no direct way to have a parent control be able to dynamically set properties of the child control that will be available to its OnInit event.
What I've ended up doing is stuff like this:
override void UserControl2_OnInit()
{
NamingContainer.OnInit += new EvenHandler(UserControl1_ActualInit);
}
protected void UserControl2_ActualInit(..) {
// do actual init code here, which will occur before OnLoad but after it's parent
// OnInit
}
So it's not an insurmountable problem. I just don't understand why it's a problem in the first place.
I realize that perhaps you might want to be able to have all your child controls initialized in your OnInit code. So fine, you should be able to call base.OnInit first, instead of after, your own initialization code, which should cause all the child control OnInit events to get run. But the event lifecycle doesn't work that way. The Init events are not chained recursively, they seem to run independently the parent events, and the innermost one always gets run first. But seems life would be a lot easier if they were simply chained recursively so you could either call the base event (or not) before you do your thing in any given situation. Is there something I'm missing that makes this seemingly counterintuitive situation desirable or even necessary?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
本文档应该是您的生命周期问题的主要事实来源。
基本上,OnInit 在控件内部初始化完成后触发。由于页面控件是第一个初始化的控件,并且在其内部初始化期间它会初始化所有子控件(可能按照 Designer.cs 文件给出的顺序),因此页面的 OnInit 事件是最后一个调用是有意义的,因为直到它的所有子控件都被初始化并且它们的 OnInit 事件被触发之前它才完成初始化。从图形上看,它看起来像这样:
因此,在这种情况下,init 的顺序是:
Load 也以类似方式工作。一般来说,您应该将大多数事件视为控件将首先执行其内部流程(包括在所有子控件上调用相同的事件),然后再触发自定义事件处理代码。
您发现的大多数示例都专门使用 Page_Load,因为这应该是该阶段中调用的最后一个事件(并且是在加载回发数据之后)。首先调用 Page_Load 效果不太好,并且存在控件未处于自定义事件处理代码完全加载状态的风险。
This document should be the main source of truth for your lifecycle questions.
Basically, OnInit fires after a control's internal initialization in finished. Since the page control is the first control initialized, and during it's internal initialization it initializes all sub-controls (perhaps in the order that the Designer.cs file gives), then it makes sense for the Page's OnInit event to be the last one called, since it's not finished initializing until all it's sub-controls are initialized and their OnInit events fired. Graphically, it looks like this:
So order of inits in this case is:
Load also work similarly. In general you should treat most of the events as though the control will go through it's internal process first (which includes calling the same event on all sub-controls), and then fire your custom event handling code afterwards.
Most examples you find use Page_Load specifically because that should be the last event called in that phase (and it's after post back data is loaded). It wouldn't work very well for Page_Load to be called first and risk having controls not in a fully loaded state for your custom event handling code.
asp.net 父级和 ASP.NET 的心态子控件是:
父母了解其孩子的一切,但孩子对父母一无所知。
这种心态对于可重用的服务器控件是有意义的。可重用性需要自定义子控件不对使用它的页面做出任何假设。
您提供的代码片段让我猜测您的子用户控件本身并不是为了可重用;而是专门的控件,您可以使用它们来分解大型应用程序的复杂性。棘手的用户界面?
在这种情况下,我仍然会尝试以“孩子对父母一无所知”的心态进行工作。想想 http://www.google.co.uk/search?q= gof+mediator+pattern 其中父页面是您孩子之间的调解者(维基百科页面很好)。
但是你的孩子仍然需要了解一些关于家长的知识,因为他们正在做复杂的UI交互?您可以通过接口来解决这个问题。每个子项不依赖于父项,而是依赖于一个准确定义子项需要访问的接口。正如 http://en.wikipedia.org/wiki/SOLID 所说,“依赖于抽象” ,而不是在结核上。每个子控件都有一个接口:“许多客户端特定接口比一个通用接口更好”
但这一切最终看起来都过度设计,不是吗?事实证明,组件必须交互的组件化 UI 很复杂,而且组件可能会变得又大又笨重。恕我直言,这是 MS Web 表单 ajax 控件输给 jQuery 和 c 的原因之一。甚至在 MVC 出现之前。
除此之外,Web 表单 ui 很难进行单元测试;您对软件质量的信心也会下降。
我推荐:
如果可以的话,转而使用 MVC 进行重写。
如果不能,请考虑放弃执行客户端行为的服务器端控件,而使用 jQuery。
如果你做不到这一点,请简化 UI。即使这会降低它的功能。
如果您不希望这样,那么您的选择是: 支付设计良好 UI 的费用;或因设计不当而付出代价。
The mindset for asp.net parent & child controls is:
Parents know all about their children, but children know nothing about their parent.
This mindset makes sense for re-usable server controls. Re-usability needs the custom child control making no assumptions about the page it gets used on.
The snippet you give makes me guess that your child user controls are not aimed at re-usable as such; but rather are specialized controls which you use to break down the complexities of a large & tricky UI?
In this case I would still try to work with the 'children known nothing about their parent' mindset. Think http://www.google.co.uk/search?q=gof+mediator+pattern where the parent page is the mediator between your children (the wikipedia page is good).
But your children still need to know something about the parent right, because they are doing complex UI interactions? You can address this with interfaces. Each child depends not on the parent, but on an interface that defines exactly what the children need access to. As http://en.wikipedia.org/wiki/SOLID puts it, 'depend on abstractions, not on concretions'. DO one interface per child control: 'many client specific interfaces are better than one general purpose interface'
But it all ends up looking over-engineered, doesn't it? It turns out that a componentised UI where the components must interact, just is complex, and the components may turn out big n clunky. This was, imho, one of the reason for MS web forms ajax controls losing out to jQuery &c. even before MVC came along.
Add to this that web forms ui is very hard to unit test; and your confidence in your software quality dives.
I recommend:
If you can, escape to a rewrite in MVC.
If you can't, consider abandoning server-side controls which do clientside behaviour, and use jQuery instead.
If you can't do that, simplify simplify simplify the UI. Even if that makes it less functional.
If you don't want that, then your choices are: pay the expense of engineering the UI well; or pay the expense of not engineering it well.