Gal Ratner
Gal Ratner is a Techie who lives and works in Los Angeles CA and Austin TX. Follow galratner on Twitter Google
Having some fun with the ListView control in ASP.NET

The ListView control is a natural progression of the GridView. It displays data by using user-defined templates instead of row fields, allowing for layout flexibility.  Paging, sorting, inserting and editing records are all built into a ListView, and, can be greatly manipulated both in the layout and functionality.
Let’s look at some cool features we can implement on our own ListView. For the sake of this article I have created a basic ListView that has an ObjectDataSource along with an in memory list of customers.  CRUD methods are implemented using LINQ to objects and all the ObjectDataSource uses methods to select, delete and update the ListView.

<asp:ObjectDataSource 
                ID="ObjectDataSource1"
                runat="server" 
                DataObjectTypeName="Customer"
                DeleteMethod="DeleteCustomer" 
                InsertMethod="AddCustomer"
                SelectMethod="GetCustomers"
                SelectCountMethod="GetCustomerCount"
                EnablePaging="true"
                StartRowIndexParameterName="startRowIndex"
                MaximumRowsParameterName="maxRecords"
                TypeName="CustomerDatabase"
                UpdateMethod="UpdateCustomer">
                <SelectParameters>
                    <asp:Parameter Name="SortExpression" DbType="String" ConvertEmptyStringToNull="true" />
                </SelectParameters>
                </asp:ObjectDataSource>

This is how our ListView looks:

Now let’s start adding some features to it.


Saving multiple rows with changes detection.


By default, we can only edit one row at a time. Editing a row will set the EditIndex property of the view to the row we are currently editing and use the EditItemTemplate to lay out the input controls. Let’s change that and allow multiple row save. First let’s add a button at the bottom of the List next to the DataPager and add an event that saves all the rows.

protected void SaveAllButton_Click(object sender, EventArgs e)
    {
        foreach (ListViewDataItem lv in this.ListView1.Items)
        {
            if (lv.ItemType == ListViewItemType.DataItem)
            {
                this.ListView1.UpdateItem(lv.DataItemIndex, true);
            }
        }
        this.ListView1.EditIndex = -1;
        SetItemsLayout();
    }

Off course we cannot simply save the entire ListView. If we have a screen with 100 records we cannot send 100 update statements to the database if we only changed two rows so let’s try and detect if a row actually changed and only update the rows that changed. We can do that by using the event onitemupdating and checking for this row’s old values against the new values:

protected void ListView1_ItemUpdating(object sender, ListViewUpdateEventArgs e)
    {
        Customer originalValues = (Customer)ViewState[e.ItemIndex.ToString()];
        if (originalValues == null)
            return;

        bool isDirty = false;
        object columnValue;
        foreach (DictionaryEntry de in e.NewValues)
        {
            columnValue = typeof(Customer).GetProperty(de.Key.ToString()).GetValue(originalValues, null);
            if (de.Value != null && de.Value.ToString() != columnValue.ToString())
            {
                isDirty = true;
                MessageLiteral.Text = "Data Saved";
            }
        }
        e.Cancel = !isDirty;
    }

Only when a row is dirty we go ahead and continue with the update operation. You notice that the old values are kept in the ViewState. We load them in OnItemDataBound:

 protected void ListView1_ItemDataBound(object sender, ListViewItemEventArgs e)
    {
        ListViewDataItem dataItem = (ListViewDataItem)e.Item;
        if (dataItem != null)
            ViewState[dataItem.DataItemIndex.ToString()] = dataItem.DataItem;
    }

Editing columns

If you notice the little pencil icon near the header columns its function is to allow users to edit all the rows of a column. It does that by replacing the labels that display the row values with text boxes.

This is pretty simple. It requires that we remember what columns are now editable and simply hide the labels in the table cells and show the text boxes and the validators:

/// <summary>
    /// Set layout on all rows
    /// </summary>
    private void SetItemsLayout()
    {
        foreach (ListViewDataItem lv in this.ListView1.Items)
        {
            if (lv.ItemType == ListViewItemType.DataItem)
                SetEditableItem(lv);
        }
    }

    /// <summary>
    /// Set layout on one row. This will set a column to be visible and editable
    /// </summary>
    /// <param name="lv"></param>
    private void SetEditableItem(ListViewDataItem lv)
    {
        if (lv.ItemType != ListViewItemType.DataItem)
            return;
        ControlCollection controls = lv.Controls;
        SetVisibleItem(controls);
    }

    /// <summary>
    /// Show and hide visible elements in one table row
    /// </summary>
    /// <param name="controls"></param>
    private void SetVisibleItem(ControlCollection controls)
    {
        LoadEditbleColumns();
        Control control;
        IEnumerator en = controls.GetEnumerator();
        while (en.MoveNext())
        {
            control = (Control)en.Current;
            if (control.ID == null)
                continue;
            if (control is HtmlTableCell)
            {
                control.Visible = true;
                // Show and hide the elements inside the cell
                SetEditableItem(control.Controls);
            }
        }
    }

    /// <summary>
    /// Show and hide editble elements on one cell
    /// </summary>
    /// <param name="controlCollection"></param>
    private void SetEditableItem(ControlCollection controls)
    {
        Control control;
        bool showVisible = true;
        IEnumerator en = controls.GetEnumerator();
        while (en.MoveNext())
        {
            showVisible = true;
            control = (Control)en.Current;
            if (control.ID == null)
                continue;

            // Check if this control is in the editable column list
            if (EditbleColumns.Where(c => control.ID.ToLower().Contains(c.ToLower())).Count() > 0)
                showVisible = true;
            else
                showVisible = false;

            if (control is TextBox || control is CheckBox || control is BaseValidator)
                control.Visible = showVisible;
            else
                control.Visible = !showVisible;
        }
    }

This also requires us to add a runat=”server” along with an ID attributes to all of our table cells.

Sorting beyond the current results in a paged ListView

A ListView uses its CommandName="Sort” to set the SortExpression in order to sort all the currently visible rows on the screen. But, what happens when we have a database table with many records and we need to sort the data beyond the current page? Well in this article I won’t teach how to do it with a stored procedure, simply because I don’t use a database for this example, however, the principles I am about to show you now apply beyond LINQ to object into LINQ to SQL, Entities and pure SQL statements. I have added a LinkButton to the header of each column with the CommandName="Sort”

<asp:LinkButton runat="server" ID="SortLinkButton1" Text="CustomerID" CommandName="Sort" CommandArgument="CustomerID"/>

And used the sort event in order to concatenate a string that will hold the sort expression

protected void ListView1_Sorting(object sender, ListViewSortEventArgs e)
    {
        if (String.IsNullOrEmpty(e.SortExpression)) { return; }
        string direction = "";
        if (ViewState["SortDirection"] != null)
            direction = ViewState["SortDirection"].ToString();

        if (direction == "ASC")
            direction = "DESC";
        else
            direction = "ASC";

        ViewState["SortDirection"] = direction;

        string[] sortColumns = e.SortExpression.Split(',');
        string sortExpression = sortColumns[0] + " " + direction;
        for (int i = 1; i < sortColumns.Length; i++)
            sortExpression += ", " + sortColumns[i] + " " + direction;
        ObjectDataSource1.SelectParameters["SortExpression"].DefaultValue = sortExpression;
        ObjectDataSource1.Select();
        e.Cancel = true;
    }

The ListView’s default event is canceled each time. We don’t need it. We use our own and if you noticed the parameter SortExpression in the SelectParameters collection of the ObjectDataSource at the top of the page, you also notice that we use it now in order to load the expression we concatenated. This sort expression will be sent to the select method and be processed in our Data Layer:

 public static List<Customer> GetCustomers(int startRowIndex, int maxRecords, string SortExpression)
    {
        if (string.IsNullOrWhiteSpace(SortExpression))
            return Customers.Skip(startRowIndex).Take(maxRecords).ToList();
        else
            return Customers.AsQueryable().OrderBy(SortExpression).Skip(startRowIndex).Take(maxRecords).ToList();
    }

In order to use a string onside a LINQ statement I utilized the library System.Linq.Dynamic that is avaible as a part of the LINQ Visual Studio code samples. The result is a true data sort that goes behond the current screen.

Input validation with enterprise library’s validation block


Enterprise library 5.0’s validation block has some cool ASP.NET, WCF and WPF integration. I chose to use object attributes on my data objects along with a PropertyProxyValidator. The principle is simple. ValidationAttribute is a part of System.ComponentModel.DataAnnotations and was created for Dynamic Data. If you coded some MVC you are probably familiar with it. ET simply extended it for more functionality. Let’s see how this comes together:

Our data object looks like:

[Serializable()]
public class Customer
{
    [RangeValidator(0, RangeBoundaryType.Exclusive, 999, RangeBoundaryType.Inclusive)] 
    public int CustomerID { getset; }
    public string FirstName { getset; }
    public string LastName { getset; }
    [RegexValidator(@"\w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*", MessageTemplate="Email not valid")]
    public string Email { getset; }
}

And validating the email address on the UI looks like:

<asp:TextBox ID="EmailTextBox" runat="server" Text='<%# Bind("Email") %>' />
                                <EntLibValidators:PropertyProxyValidator
                                    ID="EmailValidator" 
                                    ValidationGroup="Insert"
                                    runat="server"
                                    ControlToValidate="EmailTextBox"
                                    PropertyName="Email"
                                    SourceTypeName="Customer"
                                    OnValueConvert="EmailValidator_ValueConvert">
                                    </EntLibValidators:PropertyProxyValidator>

With the a format validation

 protected void EmailValidator_ValueConvert(object sender, ValueConvertEventArgs e)
    {
        e.ConvertedValue = e.ValueToConvert as string;
    }

This creates the following error messages to appear when we have an invalid input:

Conclusion


As you can see the ListView is a good step forward and is certainly a good base to build on when you are trying to display any tabular data. There are many more features available to explore and I hope to expand on them in a later blog post. I have also attached the complete code example as a Visual Studio website and it is available for download at the bottom of this post.
 

Shout it


Posted 25 Jul 2010 4:11 AM by Gal Ratner
Filed under: , ,

Powered by Community Server (Non-Commercial Edition), by Telligent Systems