如何在 ASP.NET 中跨用户控件调用事件






4.29/5 (11投票s)
如何使用自注册对象和 Context.Items 在用户控件之间调用事件。
问题
我曾多次遇到以下问题:在一个用户控件中引发的事件必须在不同层中的另一个用户控件中接收。
处理页面上控件的事件很容易,但是一旦我们将公共代码重构到用户控件中,就会变得更加困难。并且随着每个添加的用户控件层,困难会增加。一种情况是将事件从用户控件发送到页面,另一种情况是将事件从用户控件内部的控件发送到不同用户控件内部的控件。
更复杂的是,我们无法在 C# 中分配给事件。因此,一种幼稚的解决方案是添加事件处理程序以引发新事件。为此,我们需要为事件必须传递的每个用户控件复制事件声明加上事件处理程序代码。这闻起来像中间人反模式。
我也不太喜欢冒泡事件。 我发现它们太难发现,而且太隐式。 代码中没有任何东西可以揭示它们的存在或用法。 我更喜欢更明确的解决方案。
一种可能的解决方案
我希望我的代码避免所有中间人,具有清晰可见的用法,并且易于使用。 这表明我们应该以通常的方式声明我们的事件,所有侦听器都应该以通常的方式注册自己,转发事件的代码不应超过一行,并且转发应该发生一次。
因此,我们需要一些东西来公开事件和调用它们的方法
- 事件和通知方法成对出现。
- 通知程序和侦听器使用相同的对象。
- 该对象将在
Context.Items
中跟踪事件处理程序委托。
在提供的代码中,我有一个带有两个按钮的用户控件来调用事件,另一个用户控件来接收它们。 我使用了一个常规的 Button
和一个 ImageButton
来演示具有不同参数类型的事件处理程序。
发送方控件使用以下代码来调用事件
private ButtonEvents events;
private void Page_Load (object sender, EventArgs e)
{
events = new ButtonEvents(Context.Items);
}
private void uxButton_Click (object sender, EventArgs e)
{
events.ClickButton(sender, e);
}
private void uxImage_Click (object sender, ImageClickEventArgs e)
{
events.ClickImageButton(sender, e);
}
接收方控件使用此代码向这些事件注册
private void Page_Load (object sender, EventArgs e)
{
ButtonEvents events = new ButtonEvents(Context.Items);
events.ImageButtonClicked +=
new ImageClickEventHandler(events_ImageButtonClicked);
events.ButtonClicked += new EventHandler(events_ButtonClicked);
}
ButtonEvents
类需要比平时更多的工作才能实现,但不是很多
internal class ButtonEvents
{
private CachedEvent buttonEvent;
private CachedEvent imageButtonEvent;
public ButtonEvents (IDictionary items)
{
buttonEvent = new CachedEvent(items, "Button");
imageButtonEvent = new CachedEvent(items, "ImageButton");
}
public event ImageClickEventHandler ImageButtonClicked
{
add { imageButtonEvent.Add(value);}
remove { imageButtonEvent.Remove(value);}
}
public event EventHandler ButtonClicked
{
add { buttonEvent.Add(value);}
remove { buttonEvent.Remove(value);}
}
public void ClickButton (object sender, EventArgs e)
{
buttonEvent.Invoke(sender, e);
}
public void ClickImageButton (object sender, ImageClickEventArgs e)
{
imageButtonEvent.Invoke(sender, e);
}
}
ButtonEvents
类使用 CachedEvent
帮助器对象。 这是实现真正解决方案的对象。 CachedEvents
在其构造函数中采用字典和键,因此它知道将事件处理程序存储在哪里。 事件处理程序是委托,并按原样存储
public CachedEvent (IDictionary items, object key)
{
this.items = items;
this.key = key;
}
private Delegate Listeners
{
set { items [key] = value; }
get { return (Delegate) items [key]; }
}
有两个辅助方法来注册和取消注册事件侦听器
public void Add (MulticastDelegate newListener)
{
Listeners = Delegate.Combine(Listeners, newListener);
}
public void Remove (MulticastDelegate formerListener)
{
Listeners = Delegate.Remove(Listeners, formerListener);
}
最后,是调用事件的方法
public void Invoke (object sender, EventArgs arguments)
{
Delegate listeners = Listeners;
if (listeners != null)
listeners.DynamicInvoke(new object[] {sender, arguments});
}
关注点
- 中间人、代码异味和其他反模式.
- 反模式目录.
- 框架是如何做到的? Lutz Roeder 的 .NET Reflector。
历史
- 文章提交 - 2006 年 9 月 12 日。