Contents tagged with DataControls

  • 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

    Read more...

  • A new way to DataBind()

    The Problem 

    I've been thinking alot recently about the problems with data binding (and they're alot). There are some patterns that play well with ASP.NET (and are repeated everywhere) and some that don't quite fit the model. One of those patterns that don't mesh well is, setting the DataSource property of any of the DataControls. Before ASP.NET 2.0 and DataSource controls we'd have to set the DataSource property and manually call DataBind.

    What's wrong with this you may ask? When are you supposed to DataBind? Page_Load ? Page_Init? Page_PreRender? I'm sure anyone that has had to manually data bind has had the problem of figuring out which code goes in and out of the IsPostBack block.

    Whats the solution?

    I'm looking at an alternative approach to this problem. Event handlers are nice because we all feel confident that the author of the event knows when that event should be raised. No need to figure out the postback logic because the control author already thought about that. All you have to worry about is handling the event.

    I propose a new hypothetical event OnDataRetrieveing, that would be on all DataControls in conjunction with a new flag AutoBind to enable it.

    Let's take a look at a sample implementation of a derived GridView control that has this new behavior. First the markup:

    <custom:AutoGridView 
        OnDataRetrieving="OnDataRetrieving"
        OnPageIndexChanging="OnGridViewPageIndexChanging"
        OnRowEditing="OnGridViewRowEditing"
        OnRowCancelingEdit="OnGridViewRowCancelingEdit"
        AllowPaging="true" 
        runat="server" 
        ID="GridView1" 
        AutoGenerateEditButton="true"
        AutoBind="true">
    </custom:AutoGridView>

    Looks pretty similar to the regular GridView besides the AutoBind="true" flag and OnDataRetrieving event.

    And the code behind

    protected void OnGridViewRowCancelingEdit(object sender, GridViewCancelEditEventArgs e) {
        GridView1.EditIndex = -1;
    }
     
    protected void OnGridViewPageIndexChanging(object sender, GridViewPageEventArgs e) {
        GridView1.PageIndex = e.NewPageIndex;
    }
     
    protected void OnGridViewRowEditing(object sender, GridViewEditEventArgs e) {
        GridView1.EditIndex = e.NewEditIndex;
    }
     
    protected void OnDataRetrieving(object sender, DataBindingEventArgs e) {
        NorthwindDataContext db = new NorthwindDataContext();
        e.DataSource = db.Products;
    }

    We write code as we normally would if we'd been binding manually, but instead, we assign our data to the DataSource property in the DataBindingEventArgs (Which gets called at the "right" time).

    So what is the right time to Databind and why does this control do it better than you? I'm not sure :) but let's look at the source of this control.

    public override object DataSource {
        get {
            return base.DataSource;
        }
        set {
            if (!_settingDataSource && AutoBind) {
                throw new InvalidOperationException("When AutoBind is enabled, setting the DataSource property explicitly is not allowed.");
            }
            base.DataSource = value;
        }
    }
     
    protected override void PerformSelect() {
        if (AutoBind) {                
            DataBindingEventArgs args = new DataBindingEventArgs();
            OnDataRetrieving(args); 
            _settingDataSource = true;
            DataSource = args.DataSource;
            _settingDataSource = false;
        }
        base.PerformSelect();
    }
     
    protected override void EnsureDataBound() {
        if (RequiresDataBinding && (AutoBind || IsBoundUsingDataSourceID)) {
            DataBind();
        }
    }

    The most interesting method is EnsureDataBound. This method is called from PreRender and the original condition for making the control DataBind is:

    if (this.RequiresDataBinding && ((this.DataSourceID.Length > 0) || this._requiresBindToNull)) {
        this.DataBind();
        this._requiresBindToNull = false;
    }

    In the case of manually binding this would never be called unless _requiresBindToNull is true. Our AutoGridView control changes this logic by detecting the AutoBind flag and continues to data bind as usual in PreRender.

    If you interested in when the event gets called you can put some break points in the control's code. What do you think about this alternative?

    Here is a link to the source.

    Read more...

  • AutoFieldGenerators

    In 3.5 SP1 we added new properties to GridView and DetailsView which allows the page developer to change the way AutoGenerateColumns creates its columns. This feature is well know in Dynamic Data, but it is not tied to this technology. Dynamic Data takes advantage of this by looking at the meta data that users set on properties to generate columns.

    You too can roll your own IAutoFieldGenerator. Lets look at the interface:

    public interface IAutoFieldGenerator {

        ICollection GenerateFields(Control control);

    }

    The interface itself is pretty weird but it gets the job done. GenerateFields takes the control that we're generating the fields for, and expects to get some ICollection of stuff back. If we had the chance to redo this interface we'd probaby rewrite it to be like this:

    public interface IAutoFieldGenerator {

        IEnumerable<DataControlField> GenerateFields(Control control);

    }

    Now it's clear what we expect to get back, but thats besides the point. Lets implement our own.

    public class ColumnGenerator : IAutoFieldGenerator {

    private IEnumerable<string> _columns;

    public ColumnGenerator(IEnumerable<string> columns) {

        _columns = columns ?? Enumerable.Empty<string>();

    }

     

    public ICollection GenerateFields(Control control) {

        return (from column in _columns

                select new BoundField {

                    SortExpression = column,

                    HeaderText = column,

                    DataField = column

                }).ToArray();

        }

    }


    We're going to pass a set of column names to our generator that just creates bound fields with the column's name. To make use of our new generator we can just set it like this:

    GridView

    gridView1.ColumnsGenerator = new ColumnGenerator(Columns);

    DetailsView

    detailsView1.RowsGenerator = new ColumnGenerator(Columns);

    You can do alot of cool things with these generators. Some things I can think of off the top of my head:

    • Hide/Show columns dynamically based on metadata (like what we do with dynamic data)
    • Hide/Show columns based on permissions
    • Create a configurable UI that allows users to hide or show columns based on their preferences.

    I've written a sample that allows you to hide or show columns based on a selectable UI. You can download it here:

    AutoFieldGenerator.zip

    Here is a screen shot of it running.

    Read more...

  • How <%# Bind %> Works

    In my last post I spoke about 2-way databinding and how it can be used to extract values from control properties. How does this all work? Lets take a look at a page with 2-way databinding:

    <asp:LinqDataSource ID="productsSource"

        runat="server"

        ContextTypeName="FowlerSamples.NorthwindDataContext"

        EnableDelete="True"

        EnableInsert="True"

        EnableUpdate="True" TableName="Products">

    </asp:LinqDataSource>       

    <asp:GridView ID="products"

        DataKeyNames="ProductID,CategoryID"

        AutoGenerateColumns="False"

        runat="server" DataSourceID="productsSource">

        <Columns>

            <asp:CommandField ShowEditButton="True" />               

            <asp:BoundField DataField="ProductName" />

            <asp:TemplateField>

                <EditItemTemplate>

                    <asp:LinqDataSource

                        ID="categoriesSource"

                        runat="server"

                        ContextTypeName="FowlerSamples.NorthwindDataContext"

                        TableName="Categories" AutoGenerateWhereClause="true">

                    </asp:LinqDataSource>

                    <asp:DropDownList

                        runat="server"

                        ID="categories"

                        DataSourceID="categoriesSource"

                        DataTextField="CategoryName"

                        DataValueField="CategoryID"

                        SelectedValue='<%# Bind("CategoryID") %>'>                           

                    </asp:DropDownList>                       

                </EditItemTemplate>                   

            </asp:TemplateField>

        </Columns>

    </asp:GridView>

     In the above example, the GridView has a template field with an EditItemTemplate that has a DropDownList that is 2-way databound. We're going to introduce a small error in the page in order to see what the generated code looks like:

    <EditItemTemplate>

         <asp:LinqDataSource

             ID="categoriesSource"

             runat="server"

             ContextTypeName="FowlerSamples.NorthwindDataContext"

             TableName="Categories" AutoGenerateWhereClause="true">

         </asp:LinqDataSource>

         <asp:DropDownList

            runat="server"

            ID="categories"

            DataSourceID="categoriesSource"

            DataTextField="CategoryName"

            DataValueField="CategoryID"

            SelectedValue='<%# Bind("CategoryID") %>'>                           

        </asp:DropDownList>

        <%# Eval(3) %>

    </EditItemTemplate>


    When we try to run this page we'll get a compile error and the famous ASP.NET YSOD(Yellow Screen of Death):



    Click on Show Complete Compilation Source, if your curious about how ASP.NET converts your the markup to code.

    When examining the source, we see a rather interesting method:

    [System.Diagnostics.DebuggerNonUserCodeAttribute()]

    public System.Collections.Specialized.IOrderedDictionary @__ExtractValues__control8(System.Web.UI.Control @__container) {

        System.Collections.Specialized.OrderedDictionary @__table;

        System.Web.UI.WebControls.DropDownList categories;

     

        categories = ((System.Web.UI.WebControls.DropDownList)(@__container.FindControl("categories")));

     

        @__table = new System.Collections.Specialized.OrderedDictionary();

     

        if ((categories != null)) {

            @__table["CategoryID"] = categories.SelectedValue;

        }

     

        return @__table;

    }


    As you can see in the above method, an OrderedDictionary is created and the SelectedValue property of the DropDownList is pushed into the "CategoryID" field. But how does this get all the way to the data control? Each control has a BuildControl method associated with it, if we examine the BuildControl method for the TemplateField it becomes a bit more clear how things get hooked up.

     

    [System.Diagnostics.DebuggerNonUserCodeAttribute()]

    private global::System.Web.UI.WebControls.TemplateField @__BuildControl__control7() {

        global::System.Web.UI.WebControls.TemplateField @__ctrl;

     

        @__ctrl = new global::System.Web.UI.WebControls.TemplateField();

     

        @__ctrl.EditItemTemplate = new System.Web.UI.CompiledBindableTemplateBuilder(new System.Web.UI.BuildTemplateMethod(this.@__BuildControl__control8), new System.Web.UI.ExtractTemplateValuesMethod(this.@__ExtractValues__control8));

     

        return @__ctrl;

    }

    The EditItemTemplate property is of type ITemplate. CompiledBindableTemplate implements both ITemplate and IBindableTempalte.

    public interface IBindableTemplate : ITemplate {           

        IOrderedDictionary ExtractValues(Control container);

    }

    It's slowly coming together. So lets put together what we're learnt so far:

    1. Code Gen creates ExtractValues method that returns the dictionary of values for each ITemplate that has a Bind expression.
    2. The BuildControl method for the ITemplate's container (TemplateField in this case) assigns a new CompiledBindableTemplate to an ITemplate (EditItemTemplate in this case)
    3. CompiledBindableTemplate implements IBindableTemplate, which has a method, ExtractValues which returns the dictionary given a container.

    It almost all makes sense now. Each data control uses this mechanism to extract values from template fields with 2-way databinding expressions.
    What can you do with your new found knowledge?

    <asp:FormView

        runat="server"

        ID="formView"

        DefaultMode="Edit">

        <EditItemTemplate>

            <asp:TextBox ID="textBox" runat="server" Text='<%# Bind("Name") %>'></asp:TextBox>

            <asp:TextBox ID="textBox1" runat="server" Text='<%# Bind("Age") %>'></asp:TextBox>

            <asp:Button runat="server" ID="updateButton" CommandName="Update" Text="Update" />

        </EditItemTemplate>

    </asp:FormView>


    And the code behind:

     

    class Person {

        public string Name { get; set; }

        public int Age { get; set; }

    }

     

    protected void Page_Load() {

        formView.ItemUpdating += formView_ItemUpdating;

        if (!IsPostBack) {

            formView.DataSource = new Person[] { new Person { Name = "David", Age = 22 } };

            formView.DataBind();

        }

    }

     

    protected void formView_ItemUpdating(object sender, FormViewUpdateEventArgs e) {

        IBindableTemplate template = formView.EditItemTemplate as IBindableTemplate;

        if (template != null) {

            IOrderedDictionary values = template.ExtractValues(formView);

            Response.Write(values["Name"]);

            Response.Write(values["Age"]);

        }

    }


    How cool is that? :)

    Read more...

  • Getting your data out of the data controls

    After you've created a layout with a data control of your choice and eventually bound them to some datasource, you almost always want to get that data back out of them(and why wouldn't you, its your data). One thing you'll learn about developing asp.net webpages is that its like petting a porcupine (yikes). If you go with the flow, you probably won't get stuck, but the moment you try to go against the grain you end up with a hand full of thorns.

    Most of the data controls have events ending in ing and ed e.g. RowUpdating, RowUpdated etc. In most of the event handlers of the ing events it is easy to get the values from the event args.

    private void OnGridViewRowUpdating(object sender, GridViewUpdateEventArgs e) {

        IDictionary keys = e.Keys;

        IDictionary newValues = e.NewValues;

        IDictionary oldValues = e.OldValues;

    }

    Forgive me for the variation in my code formatting I'm trying to find the right one.

    If we look at this event we can see the GridView nicely packages for us the new values, old values and the keys for the updating row. Unfortunately these dictionaries are only filled out if you are bound to a datasource control :(. So that means when you bind to some raw collection and hook up to the DataSource property then call databind, if you try to handle the delete or update events these dictionaries are going to be empty. Right now you must be asking yourself how can you get those dictionaries filled out just as if you were bound to a Datasource control? The good news is you CAN do it(this is what this blog post is all about right?).

    Going Hunting in the Control Tree



    This is probably one of the worst things you can do. From the time you write code that depends on the immediate layout of your page then your asking for trouble. I often see people on the forums writing code like this:

    ((TextBox)GridView1.Rows[e.RowIndex].Cells[2].Controls[1]).Text

    Seeing stuff like that makes my spine tingle (and not in a good way). DO NOT write code like this!

    FindControl
    FindControl is a very powerful method on Control that allows you to search for a nested control, NOT synonymous to DOM function getElementById. I Often see people abusing find control and not understand that it is NOT recursive by default and complain when code like this

    ((TextBox)GridView1.FindControl("TextBox1")).Text

    Throws a null reference exception. My advice is use Use FindControl as a last resort.

    2-way DataBinding
    2-way Databinding is a cool feature in ASP.NET 2.0 which allows the user to write some special syntax to bind against property values they would like to extract from a control.
    There is alot of magic going on behind the scenes (which I will blog about in a separate post), but you do not need to know how it works to use it, e.g

    <asp:GridView runat="server" ID="GridView1">

        <Columns>

            <asp:TemplateField>

                <EditItemTemplate>

                    <asp:TextBox ID="TextBox1" runat="server" Text='<%# Bind("ProductName") %>'></asp:TextBox>

                </EditItemTemplate>

            </asp:TemplateField>

        </Columns>

    </asp:GridView>

    When you write the <%# Bind("ProductName") #> expression then the value of the Text property is pushed into a dictionary which the data control can retrieve later. So how does GridView/FormView/DetailsView/ListView get these values? Each data control has a method which is responsible for populating a dictionary of name value pairs from field name to value. Here is a mapping of data control to method used to extract values:

    GridView => protected ExtractRowValues
    FormView => protected ExtractRowValues
    DetailsView = > protected ExtractRowValues
    ListView => public ExtractItemValues

    As we can see with the exception of ListView these oh so useful methods are protected, that means we can't call them from our code if we are using these built in controls. What can we do to surface these methods:

    • Derive new controls that expose ExtractRowValues through a public method
    • Use private reflection to call the protected method (yikes)
    • Stick all of your interesting fields in DataKeyNames, then use the DataKeys[rowIndex].Values[fieldName] (and watch your ViewState grow :( )
    • Do Nothing :)

    To rectify some of this I've written a little helper for the GridView (and the other controls if you demand) that basically duplicates the functionality of ExtractRowValues method.

    public static IDictionary GetValues(GridViewRow row) {           

        IOrderedDictionary values = new OrderedDictionary();

        foreach (DataControlFieldCell cell in row.Cells) {

            if (cell.Visible) {

                // Extract values from the cell

                cell.ContainingField.ExtractValuesFromCell(values, cell, row.RowState, true);

            }

        }

     

        return values;

    }

    The method itself is pretty simple. It iterates over the control collection of the GridViewRow and calls ExtractValuesFromCell on each cell which puts values into the dictionary.

    Uses
    Most data controls support writing custom commands and handling some Command event(RowCommand for GridView). This method would come in handy if you needed to get the values out of the GridView for some custom command you wanted to execute.

    If you can, use 2 way databinding, if your control doesn't have a useful property to bind against and you need to do some more logic to get the right value then use FindControl.

    Edit:
    I've updated the code to loop over the Cells collection instead of the Controls collection (which is alot cleaner). Also there is no real compelling reason to return a strongly typed IDictionary<string, object>, so I just return the IDictionary instead.

    DetailsView:

    Reader daveh551 needed some help adapting the code to DetailsView so here it is:

    public static IDictionary GetValues(DetailsView detailsView) {
        IOrderedDictionary values = new OrderedDictionary();
        foreach (DetailsViewRow row in detailsView.Rows) {
            // Only look at Data Rows
            if (row.RowType != DataControlRowType.DataRow) {
                continue;
            }
            // Assume the first cell is a header cell
            DataControlFieldCell dataCell = (DataControlFieldCell)row.Cells[0];                
            // If we are showing the header for this row then the data is in the adjacent cell
            if (dataCell.ContainingField.ShowHeader) {
                dataCell = (DataControlFieldCell)row.Cells[1];
            }
     
            dataCell.ContainingField.ExtractValuesFromCell(values, dataCell, row.RowState, true);
        }
        return values;
    }

    FormView:

    public static IDictionary GetValues(FormView formView) {
        IOrderedDictionary fieldValues = new OrderedDictionary();
        ExtractValuesFromBindableControls(fieldValues, formView);
        IBindableTemplate itemTemplate = null;
     
        if (formView.CurrentMode == FormViewMode.ReadOnly && formView.ItemTemplate != null) {
            itemTemplate = formView.ItemTemplate as IBindableTemplate;
        }
        else if (formView.CurrentMode == FormViewMode.Edit && formView.EditItemTemplate != null) {
            itemTemplate = formView.EditItemTemplate as IBindableTemplate;
        }
        else if (formView.CurrentMode == FormViewMode.Insert && formView.InsertItemTemplate != null) {
            itemTemplate = formView.InsertItemTemplate as IBindableTemplate;
        }
        if (itemTemplate != null) {
            foreach (DictionaryEntry entry in itemTemplate.ExtractValues(formView)) {
                fieldValues[entry.Key] = entry.Value;
            }
        }
        return fieldValues;
    }
     
    private static void ExtractValuesFromBindableControls(IOrderedDictionary values, Control container) {
        IBindableControl control = container as IBindableControl;
        if (control != null) {
            control.ExtractValues(values);
        }
        foreach (Control childControl in container.Controls) {
            ExtractValuesFromBindableControls(values, childControl);
        }
    } 

    Let me know if you find any bugs.
    Hope this helps

    UPDATE: I've had some requests to add more data controls and to convert the code to VB etc. So I decided to instead put the code in an assembly and make available for download here.

    Read more...