March 2009 - Posts

External ITemplates and Hierarchical Databinding

Ever wish you could declare a template outside of the control you were defining the template for? We always get requests to have FormView's InsertItem template fall back on the EditItemTemplate and vice versa. That would be easy if we could do what was mentioned above.

Consider:

<asp:FormView ID="myFormView" runat="server" DefaultMode="Edit" 
    EditItemTemplate="editTemplate" 
    InsertItemTemplate="editTemplate">
</asp:FormView>
 
<asp:Template runat="server" ID="editTemplate">
    Name : <asp:TextBox runat="server" Text='<%# Bind("Name") %>'></asp:TextBox>
    Age : <asp:TextBox runat="server" Text='<%# Bind("Age") %>'></asp:TextBox>
</asp:Template>

Then you could do things like hierarchical databinding pretty easily; just define the template in terms of itself. Today, properties typed as ITemplate are treated specially by the ASP.NET parser, and what is written above will not work.

How would you do this with what asp.net offers now? Well check out this sample:

<cc:SpecialRepeater runat="server" ItemTemplateID="folderTemplate" DataSource='<%# GetDirectories() %>' />        
<cc:Template runat="server" ID="folderTemplate">
    <ItemTemplate>
        <ul>
            <li>
                <%# Eval("Name") %>
                <cc:SpecialRepeater runat="server" DataSource='<%# GetDirectories((string)Eval("FullName")) %>' ItemTemplateID="folderTemplate" />
                <ul>
                    <cc:SpecialRepeater runat="server" DataSource='<%# GetFiles((string)Eval("FullName")) %>' ItemTemplateID="fileTemplate" />
                </ul>
            </li>
        </ul>
    </ItemTemplate>
</cc:Template>
 
<cc:Template runat="server" ID="fileTemplate">
    <ItemTemplate>
        <li> <%# Eval("Name") %>
        </li>
    </ItemTemplate>
</cc:Template>

We have a SpecialRepeater that understands how to hookup template properties through their ID (it's just FindControl) and a Template control that defines our file template and folder template. We define the folder template in terms of itself. Can you think of any more uses for something like this?

Get the code here.

What do you think?

Posted by davidfowl with 9 comment(s)

Invalid postback or callback argument

I'm sure many of you have seen this error message when developing your web application:

Server Error in '/' Application.
--------------------------------------------------------------------------------

Invalid postback or callback argument.  Event validation is enabled using <pages enableEventValidation="true"/> in configuration or <%@ Page EnableEventValidation="true" %> in a page.  For security purposes, this feature verifies that arguments to postback or callback events originate from the server control that originally rendered them.  If the data is valid and expected, use the ClientScriptManager.RegisterForEventValidation method in order to register the postback or callback data for validation.

I'm going to discuss this in the context of the data controls. This happens when a control that isn't registered for event validation causes a postback, but surely that can't be the case.. right?

Let's look at a small repro:

Markup:

<asp:GridView ID="GridView1" runat="server">
    <Columns>
        <asp:TemplateField>
            <ItemTemplate>
                <asp:Button runat="server" Text="Button" />
            </ItemTemplate>
        </asp:TemplateField>
    </Columns>
</asp:GridView>

Code behind:

public partial class _Default : System.Web.UI.Page {
    protected void Page_Load(object sender, EventArgs e) {
        GridView1.DataSource = Enumerable.Range(0, 5);
        GridView1.DataBind();
    }
}

Now click on the button and see the dreaded error message. Why does this happen? EventValidation was added in ASP.NET to ensure that controls causing the postback came from the same page being rendered. Take a look at __EVENTVALIDATION hidden field on the page. It is a serialized version of all of the controls registered for postbacks(read more here). You might be wondering how they got in there and why is the button inside of a GridView a special case. It's not a special case, in fact, Button registers itself with the current page.

The reason this happens is because we rebind the data control in Page_Load every time which means that we will lose all of the posted data and viewstate. As a result, the ID of the button is different and when the event is validated there will be no matching unique id and hence event validation will fail. We are acutally raising an event for a button that is no longer in the control tree.

You can work around this by wrapping that code in if (!IsPostBack). This is a good proof of why you should use DataSource controls.

Hope this helps

Posted by davidfowl with 16 comment(s)
Filed under: ,
More Posts