我的 ASP.NET 应用程序支持它自己的主题版本(不是标准的 ASP.NET 主题)。有一个文件夹包含应用程序页面使用的所有用户控件。我想提供一种方法来选择性地“覆盖”,因为没有更好的术语,这些用户控件通过主题中的控件文件夹。
挑战在于引用用户控件的是应用程序页面,而不是主题文件。我想要的行为是,当主题控件文件夹中存在同名控件时,它将被加载而不是普通控件。我想在不改变应用程序页面(即它们的注册指令)的情况下实现这一点。
实现这一目标的最佳方法是什么?似乎我应该能够为我的用户控件使用一个基类,它将检查控件的特定于主题的版本,如果存在,则停止加载自身并加载另一个控件。
下面的函数是实现我的目标的粗略尝试。虽然页面确实加载了所需的用户控件,但 ASP.NET 仍在后台完整地处理两个用户控件事件处理程序(例如 Init、PreRender、Load 等)。我想中止处理此控件以减少服务器上的负载。
protected void Page_Init(object sender, EventArgs e)
{
if (File.Exists("~/themes/mytheme/controls/mycontrol.ascx")
{
this.
UserControl ctrl = this.LoadControl("~/themes/mytheme/controls/mycontrol.ascx");
this.Controls.Clear();
this.Controls.Add(ctrl);
}
}
public void RemoveAllEventHandlers()
{
RemoveAllEventHandlers(this);
}
public static void RemoveAllEventHandlers(Control ctrl)
{
if (ctrl != null) {
Type ctrlType = ctrl.GetType();
PropertyInfo propInfo = ctrlType.GetProperty("Events", BindingFlags.Instance | BindingFlags.NonPublic);
EventHandlerList handlerList = (EventHandlerList)propInfo.GetValue(ctrl, null);
FieldInfo headInfo = handlerList.GetType.GetField("head", BindingFlags.Instance | BindingFlags.NonPublic);
Dictionary<object, Delegate[]> handlerDict = new Dictionary<object, Delegate[]>();
object head = headInfo.GetValue(handlerList);
if (head != null) {
Type entry = head.GetType();
FieldInfo handlerFI = entry.GetField("handler", BindingFlags.Instance | BindingFlags.NonPublic);
FieldInfo keyFI = entry.GetField("key", BindingFlags.Instance | BindingFlags.NonPublic);
FieldInfo nextFI = entry.GetField("next", BindingFlags.Instance | BindingFlags.NonPublic);
HelpAddEntry(handlerDict, head, handlerFI, keyFI, nextFI);
foreach (KeyValuePair<object, Delegate[]> pair in handlerDict) {
for (int x = pair.Value.Length - 1; x >= 0; x += -1) {
handlerList.RemoveHandler(pair.Key, pair.Value[x]);
}
}
}
}
}
private static void HelpAddEntry(Dictionary<object, Delegate[]> dict, object entry, FieldInfo handlerFI, FieldInfo keyFI, FieldInfo nextFI)
{
Delegate del = (Delegate)handlerFI.GetValue(entry);
object key = keyFI.GetValue(entry);
object nxt = nextFI.GetValue(entry);
Delegate[] listeners = del.GetInvocationList();
if (listeners != null && listeners.Length > 0) {
dict.Add(key, listeners);
}
if (nxt != null) {
HelpAddEntry(dict, nxt, handlerFI, keyFI, nextFI);
}
}
编辑
我能够使用上面更新的代码阻止原始控件的事件处理程序运行。但是,这种方法存在一个根本缺陷:这两个控件之间存在父/子关系,而不是替代。因此,在父控件上设置的任何属性都不会传递给子控件(尽管我确信这可以通过反射来完成)并且子控件的 HTML ID 值是不同的(即它们是预先挂起的带有父控件的 ID)。