0

我的 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)。

4

2 回答 2

0

这里缺少一些东西。试试这个:

protected void Page_Init(object sender, EventArgs e)
{
    string path = "~/themes/mytheme/controls/mycontrol.ascx";
    if (File.Exists(Server.MapPath(path)))
    {
        UserControl ctrl = (UserControl)this.LoadControl("~/themes/mytheme/controls/mycontrol.ascx");
        this.Controls.Clear();
        this.Controls.Add(ctrl);
    }
    else 
    {

        //Load Other controls

    }

}
于 2013-10-21T03:15:16.163 回答
0

我找到了一种更优雅的方法来解决这个问题,方法是在页面指令中“重写”控件的 Src 属性。这样做的美妙之处在于它甚至在 ASP.NET 解析页面之前就完成了。为此,您创建一个继承自 PageParserFilter 的类。然后在 Web.config 文件的节点中,您可以将 pageParserFilterType 属性设置为新创建的类,这样现在站点的所有页面都将通过新类运行。

于 2013-10-28T19:12:58.757 回答