Sunday, 16 January 2011

Setting the Initial Sort Order – Dynamic Data

This is just a quick note on a post I made on the Dynamic Data Forum to answer a question where setting the DisplayColumnAttribute should set the default sort order of the GridView on the List page but didn’t. Here’s how I solved it with a hint or two from Marcin Dobosz.

[MetadataType(typeof(Orders_Metadata ))]
[DisplayColumn("OrderID", "OrderID", true)]
public partial class Orders
{
    public class Orders_Metadata
    {       
        // ...
    }
}

Listing 1 – Example Metadata

Assuming you have a table with the DisplayColumnAttribute set you could put this on your List page: 

table = GridDataSource.GetTable();

// set default sort
if (!IsPostBack && table.SortColumn != null)
    GridView1.Sort(table.SortColumn.Name, table.SortDescending ? SortDirection.Descending : SortDirection.Ascending);

Listing 2 – Add to Page_Init (.Net 3.5)

// set default sort
if (!IsPostBack && table.SortColumn != null)
{
    var order = new OrderByExpression()
    {
        DataField = table.SortColumn.Name,
        Direction = table.SortDescending ? SortDirection.Descending : SortDirection.Ascending,
    };
    GridQueryExtender.Expressions.Add(order);
}

Listing 3 – Add to Page_Init (.Net 4)

public static T GetAttribute<T>(this MetaTable table) where T : Attribute
{
    return table.Attributes.OfType<T>().FirstOrDefault();
}

Listing 3 – you will also need this extension method see Writing Attributes and Extension Methods for Dynamic Data

Add your DisplayColumnAttribute (see Listing 1) to the table you want sorted (note: it must have the second string constant even if it’s the same name, as the second string it the one that the sort is done on, and the third value if true causes the sort to be descending). Then in Listing 2 you get the attribute using the extension method from Listing 3 and apply the relevant sort.

This is here mainly so I can find it again! HappyWizard

Thursday, 13 January 2011

Adding a Multi-Column Search to the Default List page in Dynamic Data (UPDATED)

This will extend the default filters that Dynamic Data provides by adding a free text search box the will search multiple text fields on the same page. I have done this in a few projects now as one off but after this post Custom filters & searches within DD in Visual Web Developer 2010 on the Dynamic Data Forum I decided to document it here.

Multi Column Search

Figure 1 – Multi Column Search

Here in Figure 1 you can see the multi column search box, in this example the search is on FistName, LastName, Address, City and Country, it will even allow search across entities e.g. on Orders Employee.LastName etc. which make it very flexible.

Things we will need to do

  • Attribute to tell the system which columns to search on.
  • Add mark-up to show the UI on the List page.
  • Code to wire up the UI and QueryExtender.

The Attribute

This attribute will be added to the Class/Table not it’s columns and will have only one property Columns.

[AttributeUsage(AttributeTargets.Class)]
public class MultiColumnSearchAttribute : Attribute
{
    public String[] Columns { get; private set; }

    public MultiColumnSearchAttribute(params String[] columns)
    {
        Columns = columns;
    }
}

Listing 1 – MultiColumnSearchAttribute

and we apply it as in Listing 2 The Employee table show standard multi-column search but the Order table shows how we can search across entity boundaries you can see this in Figure 2.

[MetadataTypeAttribute(typeof(Employee.EmployeeMetadata))]
[MultiColumnSearch(
    "LastName", 
    "FirstName", 
    "Address", 
    "City", 
    "Country")]
public partial class Employee
{
    internal sealed class EmployeeMetadata
    {
        //...
    }
}

[MetadataTypeAttribute(typeof(Order.OrderMetadata))]
[MultiColumnSearch(
    "Employee.LastName", 
    "Employee.FirstName", 
    "Customer.CompanyName", 
    "Customer.ContactName", 
    "Shipper.CompanyName")]
public partial class Order
{
    internal sealed class OrderMetadata
    {
        //...
    }
}

Listing 2 – sample metadata

Cross Entity Search

Figure 2 – cross entity search

Adding UI Mark-Up

I have added the multi column search UI in between the validators and the QueryableFilterRepeater see Listing 3

<asp:DynamicValidator runat="server" ID="GridViewValidator" ControlToValidate="GridView1"
    Display="None" CssClass="DDValidator" />

<fieldset id="MultiSearchFieldSet" class="DD" runat="server" visible="false">
    <legend>Full Text Search</legend>
    <asp:TextBox ID="txbMultiColumnSearch" CssClass="DDTextBox" runat="server" />
    <asp:Button ID="btnMultiColumnSearchSubmit" CssClass="DDControl" runat="server" Text="Search"
        OnClick="btnMultiColumnSearch_Click" />
    <asp:Button ID="btnMultiColumnSearchClear" CssClass="DDControl" runat="server" Text="Clear"
        OnClick="btnMultiColumnSearch_Click" />
</fieldset>
<br />

<asp:QueryableFilterRepeater runat="server" ID="FilterRepeater">

Listing 3 – Multi Column Search UI

Code to wire up the UI and QueryExtender

/// <summary>
/// Setups the multi column search.
/// </summary>
private void SetupMultiColumnSearch()
{
    // get multi column search attribute
    var multiColumnSearch = table.GetAttribute<MultiColumnSearchAttribute>();

    if (multiColumnSearch != null)
    {
        var searchExpression = new SearchExpression()
            {
                DataFields = multiColumnSearch.Columns.ToCsvString(),
                SearchType = SearchType.Contains
            };
        
        // create control parameter
        var controlParameter = new ControlParameter() { ControlID = txbMultiColumnSearch.ID };
        
        // add control parameter to search expression
        searchExpression.Parameters.Add(controlParameter);

        // set context
        searchExpression.SetContext(GridQueryExtender, Context, GridDataSource);

        // add search expression to query extender
        GridQueryExtender.Expressions.Add(searchExpression);

        // make multicolumn search field set visible
        MultiSearchFieldSet.Visible = true;
    }
}

Listing 5 – SetupMultiColumnSearch method

In Listing 5 (which is called from the Page_Init method) we get the attribute and the SearchExpression the if both are not null we add the fields to the SearchExpressions DataFileds property, then we make the whole thing visible.

Note: Much thanks to David Ebbo who sorted out the issue I had had with adding the search expression in code rather than declaratively.

protected void btnMultiColumnSearch_Click(object sender, EventArgs e)
{
    var button = (Button)sender;
    if (button.ID == btnMultiColumnSearchClear.ID)
        txbMultiColumnSearch.Text = String.Empty;
}

Listing 6 – button event

And lastly we need an event to fire when the clear button is fired.

Note: I have both buttons wired-up to this handler as I also add session history here so that when you search on something and go to another page when you return you get the same search, but I also want it cleared if I click the clear button.

Download