加载与当前加载的用户控件不同的用户控件

时间:2013-10-21 00:37:13

标签: asp.net user-controls

我的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);
    }
}

修改

我能够使用上面更新的代码阻止原始控件的事件处理程序运行。然而,这种方法存在一个根本缺陷:这两个控件之间存在父/子关系,而不是替代品。因此,在父控件上设置的任何属性都不会传递给子控件(虽然我确定这可以通过Reflection完成)并且子控件的HTML ID值是不同的(即它们是预先设置的使用父控件的ID。)

2 个答案:

答案 0 :(得分: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

    }

}

答案 1 :(得分:0)

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