如何最好地拦截ASP.NET用户控件的子树中所有元素的解析

Joh*_*n K 7 asp.net user-controls webforms

我想拦截我自己的用户控件的整个子树中的每个控件的解析.

目前我已经在我的用户控件中重写了受保护的方法Control.AddParsedSubObject.它仅限于在声明性语法中截取对直接子控件的解析,因为每个控件都有自己的AddParsedSubObject方法来进一步解析自己的子控件.

从用户控件我无法进入孩子的孩子来拦截那些解析呼叫.

在我的用户控件的以下声明性示例中,我可以从User Control的AddParsedSubObject覆盖内部访问tv对象.

<asp:TreeView runat="server" id="tv" />
Run Code Online (Sandbox Code Playgroud)

但是,我无法访问以下示例中的tv对象(或第一个Panel的其他子对象),因为该解析由Panel或其子代进行处理.

<asp:Panel runat="server">
    <asp:TreeView runat="server" id="tv" />
    <asp:Panel runat="server">
        <asp:TextBox runat="server" />
    </asp:Panel>
</asp:Panel>
Run Code Online (Sandbox Code Playgroud)

我在用户控件中的代码隐藏看起来像这样

    // User control interception of its parsed children 
    protected override void AddParsedSubObject(object obj) {

        // Do some custom work with the control object. 
        if (obj is Control && ((Control)obj).ID == "tv") {
            TreeView tv = (TreeView)obj;
            DoSomethingWithParsedObject(tv);
        }

        // Let ASP.NET continue and put the control in the page hierarchy
        base.AddParsedSubObject(obj);
    }
Run Code Online (Sandbox Code Playgroud)

寻找有关如何拦截用户控件的整个子树中的每个控件的解析的想法.例如,我想在每个解析步骤中写出自定义信息.

Ser*_*nov 4

如果您使用.Net 4.5,有一个很好的方法来实现它。Asp.Net 使用 ControlBuilder 从 aspx 布局构建临时 cs 文件。在.Net 4.5之前,您只能通过反射并切换ControlBuilder类中的一些内部静态变量来拦截它。

但在 .Net 4.5 中,他们添加了新类ControlBuilderInterceptor。然后你可以写这样的代码:

namespace TestInterceptApp
{
    public class BuilderInterceptor : ControlBuilderInterceptor
    {
        public override void OnProcessGeneratedCode(ControlBuilder controlBuilder, CodeCompileUnit codeCompileUnit, CodeTypeDeclaration baseType,
                                                    CodeTypeDeclaration derivedType, CodeMemberMethod buildMethod,
                                                    CodeMemberMethod dataBindingMethod, IDictionary additionalState)
        {
            base.OnProcessGeneratedCode(controlBuilder, codeCompileUnit, baseType, derivedType, buildMethod, dataBindingMethod, additionalState);

            buildMethod.Statements.Insert(
                      buildMethod.Statements.Count - 1, 
                      new CodeSnippetStatement("TestInterceptApp.ControlInterceptor.Intercept(@__ctrl);"));
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

然后你需要修改web.config中的编译部分以将此类注册到Asp.Net:

  <system.web>
       <compilation debug="true" targetFramework="4.5" controlBuilderInterceptorType="TestInterceptApp.BuilderInterceptor"/>
  </system.web>
Run Code Online (Sandbox Code Playgroud)

然后你可以像这样编写这个拦截器类:

namespace TestInterceptApp
{
    public static class ControlInterceptor
    {
        public static void Intercept(TreeView control)
        {
            // Do some custom work with the control object. 
            if (control.ID == "tv")
            {
                control.Nodes.Add(new TreeNode("Test"));
            }
        }

        public static void Intercept(object obj)
        {
           // just ignore all others controls
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

基本上当ControlBuilder创建新的cs文件时,它会插入这一行

    "TestInterceptApp.ControlInterceptor.Intercept(@__ctrl);"
Run Code Online (Sandbox Code Playgroud)

到每个控制生成块的末尾。您可以使用对象参数在 ControlInterceptor 类中定义一个方法,也可以使用特定参数进行一些所需的重载。并加上一个带有对象参数的基本方法。当CLR执行它时,如果控件是TreeView,那么它会将其发送到正确的方法。对于所有其他控件,如 Literal、Button、HtmlHead、Page 等,CLR 将使用带有对象签名的方法。

我的示例将仅向当前应用程序中 ID=“tv”的每个 TreeView 添加新的 TestNode。ControlBuilder生成的代码是这样的:

    [System.Diagnostics.DebuggerNonUserCodeAttribute()]
    private global::System.Web.UI.WebControls.TreeView @__BuildControltv() {
        global::System.Web.UI.WebControls.TreeView @__ctrl;

        #line 32 "c:\users\someuser\documents\visual studio 11\Projects\TestInterceptApp\TestInterceptApp\Default.aspx"
        @__ctrl = new global::System.Web.UI.WebControls.TreeView();

        #line default
        #line hidden
        this.tv = @__ctrl;
        @__ctrl.TemplateControl = this;
        @__ctrl.ApplyStyleSheetSkin(this);

        #line 32 "c:\users\someuser\documents\visual studio 11\Projects\TestInterceptApp\TestInterceptApp\Default.aspx"
        @__ctrl.ID = "tv";

        #line default
        #line hidden

        #line 32 "c:\users\someuser\documents\visual studio 11\Projects\TestInterceptApp\TestInterceptApp\Default.aspx"
        this.@__BuildControl__control3(@__ctrl.Nodes);

        #line default
        #line hidden
        // here is that line that we added via ControlBuilderInterceptor
        TestInterceptApp.ControlInterceptor.Intercept(@__ctrl);
        this.@__PageInspector_SetTraceData(new object[] {
                    @__ctrl,
                    null,
                    1838,
                    368,
                    false});
        return @__ctrl;
    }
Run Code Online (Sandbox Code Playgroud)