Josh Heyse

Thoughts Defragmented

Archive for the 'Dynamic Data' Category

Presentation: ASP.NET Dynamic Data @ IndyNDA

Posted by jheyse on 29th December 2008

While at PDC, Dan Rigsby, invited me down to give a presentation and IndyNDA.  I thought that this would be an excellent opportunity to talk about my favorite feature in .NET 3.6 SP1, ASP.NET Dynamic Data.  I plan to make this presentation extremely code based and create a web site based on the AdventureWorks database.  The abstract for the presentation is located below, along with details on the event.  I will be posting the power point slides and code samples here, after the presentation.

Presentation Abstract

ASP.NET Dynamic Data is a new framework released with the .NET Framework 3.5 SP1 that lets you create data-driven ASP.NET Web applications easily. It does this by automatically discovering data-model metadata at run time and deriving UI behavior from it.  A scaffolding framework provides a functional Web site for viewing and editing data. You can easily customize the scaffolding framework by changing elements or creating new ones to override the default behavior.  Come see how ASP.NET Dynamic Data can drastically reduce development time, while still giving you the flexibility necessary to create rich data driven web applications.

Meeting Time & Location

Date: January 8th, 2009
Time: 6:00 pm

Doors open at 5:30pm and the meeting starts at 6:00pm. Everyone is welcome to join us, and the admission is free. Presentations are targeted towards developers and IT professionals.

Thanks to our sponsors, we will again be having FREE PIZZA and cokes beginning at 5:30 (while supplies last).

Location:

The Gene B. Glick Junior Achievement Education Center
7435 North Keystone Ave
Indianapolis, IN 46240

Map and directions

Posted in ASP.NET, Dynamic Data, Speaking | No Comments »

Dynamic Data Filtering – Table Text Search

Posted by jheyse on 17th October 2008

I’m pretty happy with the number of people who have been downloading and trying out Dynamic Data Filtering.  On average there are about 20 downloads a day with around 50 page views.  There has been a lot of good feedback on both the ASP.NET forums and on the CodePlex project site 5over the last week or so.  So much actually that I am having trouble keeping up with answering the questions coming in. 

One question did catch my eye though.  It was a member of the ASP.NET forum asking about being able to search on all of the columns within a given table.  [View the Post]  Dynamic Data doesn’t support this functionality out of the box, but it is flexible enough to write something yourself to do it.  The main things that makes this possible is that a single FilterTemplate can return one ore more DynamicFilterParameters which produce lambda predicates.

In this case, you can accomplish this logic of searching multiple columns in a table by saying where ProductName LIKE [Value] or ProductNumber LIKE [Value] etc…  To accomplish this in Dynamic Data Filtering you would utilize the OrExpressionParameter to create a collection of LikeExpressionParameters to do the predicate for each individual column.

I spent a little time creating a simple example which performs this logic.  I also implemented a Columns property which allows you to specify using a comma separated list the columns you want searched.  If Columns is left null or empty all columns are searched.  The aspx for the user control only contains a single asp:TextBox with the Id=TextBox1.

public partial class TableTextSearch : Catalyst.Web.DynamicData.FilterTemplateUserControlBase
{
    public string Columns { get; set; }
 
    private MetaTable Table
    {
        get
        {
            IDynamicDataSource source = this.FindDataSourceControl();
            if (source != null)
                return source.GetTable();
            return null;
        }
    }
 
    public override IEnumerable<Parameter> GetWhereParameters(System.Web.DynamicData.IDynamicDataSource dataSource)
    {
        OrExpressionParameter parameter = new OrExpressionParameter();
 
        string[] cols = null;
        if (!string.IsNullOrEmpty(Columns))
        {
            cols = Columns.Split(',');
            for (int i = 0; i < cols.Length; i++)
                cols[i] = cols[i].Trim().ToUpper();
        }
 
        var columns = (from c in Table.Columns
                       where cols == null || cols.Length == 0 || cols.Contains(c.Name)
                       group c by c.TypeCode).ToDictionary(g => g.Key, g => g);
 
        foreach (var column in columns[TypeCode.String])
        {
            parameter.Parameters.Add(new LikeExpressionParameter()
                    {
                        Type = TypeCode.String,
                        Name = column.Name,
                        Value = TextBox1.Text,
                        Like = LikeExpressionParameter.LikeType.Contains
                    });
        }
        yield return parameter;
    }
 
    public override void LoadQueryStringParameters(System.Collections.Specialized.NameValueCollection parameters)
    {
        TextBox1.Text = parameters["TableTextSearch"];
    }
 
    public override System.Collections.Specialized.NameValueCollection SaveQueryStringParameters()
    {
        return new NameValueCollection() { { "TableTextSearch", TextBox1.Text } };
    }
 
    public override void Clear()
    {
        TextBox1.Text = string.Empty;
    }
}

To implement filters like this one you must use the DynamicFilterForm as opposed to the DynamicFilterRepeater.  The reason is that the TableTextSearch filter control is associated with any column/property in particular but the entire table instead.  It may make sense to allow the FilterAttribute to be annotated at the class level to address this.

<asp:DynamicFilterForm ID="DynamicFilterForm1" runat="server" DataSourceID="GridDataSource">
    <FilterTemplate>
        <div>
            Search
        </div>
        <div>
            Keyword: 
            <dd:TableTextSearch runat="server" ID="TableTextSearch" />
            <asp:LinkButton ID="LinkButton4" runat="server" CommandName="Search">Search</asp:LinkButton>&nbsp;&nbsp;
            <asp:LinkButton ID="LinkButton5" runat="server" CommandName="Clear">Clear</asp:LinkButton>
        </div>
    </FilterTemplate>
</asp:DynamicFilterForm>

To build upon this example it would be nice to be able to search the DisplayText of foreign columns and potentially non text values.  For example if the user entered a string which can be converted to a date, search all DateTime columns.

Posted in ASP.NET, Dynamic Data, LINQ | No Comments »

ASP.NET Dynamic Data Filtering – DynamicFilterRepeater

Posted by jheyse on 1st October 2008

One of the new features I released with Dynamic Data Filtering 1.10 is the DynamicFilterRepeater control.  This control was added based on requests from several people who have implemented Dynamic Data Filtering in their applications.  The idea is to use the model metadata to generate the set of filtering controls on each page as opposed to using the DynamicFilterForm.  This is a great addition because it allows developers to annotate their model and modify their PageTemplates to get full Dynamic Filtering capabilities throughout the site.

The first step in using the DynamicFilterRepeater is to replace the existing FilterRepeater on either the DynamicDataPageTemplatesList.aspx or DynamicDataPageTemplatesListDetails.aspx page. 

In this example we will focus on the List.aspx page. On the List.aspx page, the default FilterRepeater controls looks something like:

<asp:FilterRepeater ID="FilterRepeater" runat="server">
    <ItemTemplate>
        <asp:Label runat="server" Text='<%# Eval("DisplayName") %>' AssociatedControlID="DynamicFilter$DropDownList1" />
        <asp:DynamicFilter runat="server" ID="DynamicFilter" OnSelectedIndexChanged="OnFilterSelectedIndexChanged" />
    </ItemTemplate>
    <FooterTemplate><br /><br /></FooterTemplate>
</asp:FilterRepeater>

Delete it!

Next drag the DynamicFilterRepeater from the tool box on to the form. You will get markup that looks like the following:

Tip: I HIGHLY recommend you do this in the designer view.  If you do you will get prompted to create the FilterTemplates directory, if it is not already created.  References to Catalyst.Web.DynamicData and Catalyst.ComponentModel.DataAnnotations will also be added.

<asp:DynamicFilterRepeater ID="DynamicFilterRepeater1" runat="server">
    <HeaderTemplate>
        <div>
            Search</div>
    </HeaderTemplate>
    <ItemTemplate>
        <div>
            <asp:Label ID="Label1" runat="server" Text='<%# Eval("DisplayName") %>'></asp:Label><asp:DynamicFilterControl
                ID="DynamicFilter" runat="server">
            </asp:DynamicFilterControl>
        </div>
    </ItemTemplate>
    <FooterTemplate>
        <div>
            <asp:LinkButton ID="SearchButton" runat="server" CommandName="Search" Text="Search">
            </asp:LinkButton><asp:LinkButton ID="ClearButton" runat="server" CommandName="Clear"
                Text="Clear">
            </asp:LinkButton></div>
    </FooterTemplate>
</asp:DynamicFilterRepeater>

This is the default template for items within the DynamicFilterRepeater and is very similar to the FilterRepeater we just deleted.  You can customize this, but you must, MUST, MUST have one and only one DynamicFilterControl with the ID="DynamicFilter" in the ItemTemplate.  The DynamicFilterRepeater container uses this to create your filtering controls.

After we have the DynamicFilterRepeater control completed, we need to replace the LinqDataSource with a DynamicLinqDataSource. The existing LinqDataSource should look something like this:

<asp:LinqDataSource ID="GridDataSource" runat="server" EnableDelete="true" EnableUpdate="true">
    <WhereParameters>
        <asp:DynamicControlParameter ControlID="FilterRepeater" />
    </WhereParameters>
</asp:LinqDataSource>

Don’t delete it.  Switch to the designer view and select your DynamicFilterRepeater and then the Actions pop-up arrow.  This will allow you to select a DataSource.  Select your target datasource, in this case GridDataSource, from the drop down list.  You will see an action called UpgradeData source appear in the Action popup, hit it.  This will automatically change your LinqDataSource to a DynamicLinqDataSource, pretty cool, huh?

Your data source should now look like this (notice the removal of the DynamicControlParameter tag!):

<asp:DynamicLinqDataSource ID="GridDataSource" runat="server" 
    EnableDelete="True" EnableUpdate="True">
</asp:DynamicLinqDataSource>

If you dragged the DynamicFilterRepeater onto the form in DesignView, it will automatically have added a reference to Catalyst.ComponentModel.DataAnnotations, if not you must add a reference.  It should be in the list under the .NET tab, otherwise browse to %PROGRAMFILES%Dynamic Data Filteringbin. 

The final step is to modify the metamodel.  Since the samples provided with the installer use the AdventureWorksLT2008 database I am going to annotate the Product class.  This is an example of the Product class and annotations.

using System;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using Catalyst.ComponentModel.DataAnnotations;
 
namespace AdventureWorks.BusinessObjects
{
    [System.ComponentModel.DataAnnotations.MetadataType(typeof(ProductMetadata))]
    public partial class Product
    {
        /// <summary>
        /// This is the Metadata for the parent Product class.  It is a private interface to prevent 
        /// consumers from attempting to instantiate it or even see it.
        /// </summary>
        private interface ProductMetadata
        {
            [Filter(FilterMode = FilterControlMode.Contains)]
            string Name { get; set; }
 
            [Filter(FilterMode = FilterControlMode.Contains)]
            [DisplayName("Product Number")]
            string ProductNumber { get; set; }
 
            [Filter(FilterMode = FilterControlMode.Contains)]
            string Color { get; set; }
 
            [Filter(FilterMode = FilterControlMode.Range)]
            [DisplayName("List Price")]
            decimal ListPrice { get; set; }
 
            [Filter(FilterMode = FilterControlMode.Equals)]
            [DisplayName("Product Model")]
            ProductModel ProductModel { get; set; }
 
            [Filter(FilterMode=FilterControlMode.Equals)]
            [DisplayName("Product Category")]
            ProductCategory ProductCategory { get; set; }
 
            [ScaffoldColumn(false)]
            Guid rowguid { get; set; }
 
            [ScaffoldColumn(false)]
            DateTime ModifiedDate { get; set; }
        }
    }
}

One thing to note in this example is that I have made the Metadata class a private nested interface of the Product class.  I have done this to prevent visibility from outside classes and also to prevent any code within the Product class from attempting to instantiate a instance of that type.  While giving a presentation last week, Scott Seely asked if this was possible, we tried it on the stop and it worked like a charm. 

Now run the solution and you should have FilterControls defined for List.aspx page template.

Posted in ASP.NET, Dynamic Data | 3 Comments »

Dynamic Data Filtering 1.10 on CodePlex

Posted by jheyse on 30th September 2008

After almost a month of sporadic work on Dynamic Data Filtering I am happy to announce that I am releasing the next version.  This version includes several new features and a bug fix. It is downloadable from CodePlex

New Features

Dynamic Filter Repeater

Based on suggestions I have added a Dynamic Filter Repeater.  This control uses the FilterAttributes annontated in your MetaModel to automatically populate the list of filters for a given page.  This is most useful if you modify your PageTemplates/List.aspx & PageTemplates/ListDetails.aspx pages.  Filtering is OPT-IN!

Visual Studio Integration

Dynamic Data Filtering now has a design time experience.  I have added design time rendering to the DynamicFilterForm and new DynamicFilterRepeater.  The first time you drag either control on to the form the designer will automatically create the DynamicData/Filters folder and populate the folder with the default templates provided.  You will also see a designer action, "Upgrade DataSource", which will convert a LinqDataSource to a DynamicLinqDataSource.

I have also added item templates to the Add New Item… window which will add a filter to the DynamicData/Filters folder for you.  Currently I have templates for the standard equals operator and range.  I believe these are the two most common filters used, let me know if you’d like the others and I will add them in the next release.

Thank you David and Joe from the ASP.NET team for their help with the designer support.
Thank you Steve for the icon work.

Installer

Dynamic Data is now distributable via an MSI installer.  This installer will install to %Program Files%Dynamic Data Filtering by default.  Included in this folder are the binaries, samples and the default templates.  The installer also automatically adds the VS integrations mentioned above.  This was my first experience with WIX and I must say I am impressed and have found my new installer development technology.

Thank you James who helped convert the C# examples and templates to VB.

Bug Fixes

It was identified that multiple queries were being performed against the database on pages that had a DynamicFilterForm on them.  It was found that this was caused by the BaseDataSource.PerformSelect() method.  This method has been overridden to prevent this behavior in both the DynamicFilterForm and DynamicFilterRepeater.

Posted in ASP.NET, Dynamic Data, LINQ | No Comments »

Presentation: ASP.NET Dynamic Data @ LCNUG on 9/25

Posted by jheyse on 16th September 2008

I’m giving my first presentation at the Lake County .NET Users Group (LCNUG) on Thursday, September 25th.  The topic is on ASP.NET Dynamic Data, a new feature released with the .NET Framework 3.5 SP 1.  I plan to make this presentation extremely code based and create a simple, but functional, automobile classified web site. The abstract for the presentation is located below, along with details on the event.  I will be posting the power point slides and code samples here, after the presentation.

Presentation Abstract

ASP.NET Dynamic Data is a new framework released with the .NET Framework 3.5 SP1 that lets you create data-driven ASP.NET Web applications easily. It does this by automatically discovering data-model metadata at run time and deriving UI behavior from it.  A scaffolding framework provides a functional Web site for viewing and editing data. You can easily customize the scaffolding framework by changing elements or creating new ones to override the default behavior.  Come see how ASP.NET Dynamic Data can drastically reduce development time, while still giving you the flexibility necessary to create rich data driven web applications.

Meeting Time & Location

Subs and will be provided at 6:30. Talk starts at 7:00.

College of Lake County
Technology Building Room T326-328
19351 West Washington Street
Grayslake, Illinois, 60030

Click on here for directions.

Map image

Posted in ASP.NET, Dynamic Data, Speaking | No Comments »

Dynamic Data Filtering on CodePlex

Posted by jheyse on 6th August 2008

Last month while I was up in Redmond I meet with the members of the ASP.NET Dynamic Data team and we talked about how to resolve several issues.  The main focus was to remove the need for Reflection to access non-public framework members.  Based on these changes I am now comfortable with the project and I am publishing the code to the community through CodePlex. 

The URL to the CodePlex project is http://www.codeplex.com/DynamicDataFiltering.  I am looking for contributors if anyone is interested in helping out.

Posted in .NET, ASP.NET, Dynamic Data | No Comments »

A Richer DynamicDataFilter – Part 5 ColumnContains

Posted by jheyse on 23rd May 2008

I’m not really sure what to call this post, so it’s just the name of the control I wrote.  A reader left me the following message on a previous post during this series on the extended DynamicDataFilter:

Hi

I was wondering if you could guide me on how to do the following:

On every List page, I need a dropdownlist populated with  all the column names from the Table. Adjacent to that should be a textbox where the user can enter some text. And then a ‘Search’ button.

When clicked on this button, the following should happen:

Perform search on the Table where dropdownlist.selectedcolumnname CONTAINS textbox.text.

Repopulate Gridview with the search results.

I’d appreciate any help or atleast an idea on how to go about this. I’m new to Dynamic Data

Abby, unfortunately the functionality you are looking for isn’t currently supported in DynamicData, the main issue is that the LinqDataSource does not understand CONTAINS the where parameters you supply are all equals.  But, the DynamicData Filtering I have written does support this. (My code isn’t production quality, and should only be used in production at your own risk.)  Here is a quick screen shot:

ColumnContains

To allow for this I created a ColumnContains control which has a DrowDownList of the columns available in the DataSource’s Table and a TextBox which accepts your CONTAINS value.

<table>
    <tr>
        <td>
            <asp:DropDownList ID="ddlColumn" runat="server">
            </asp:DropDownList>
        </td>
        <td>
            <asp:TextBox ID="tbText" runat="server"></asp:TextBox>
        </td>
    </tr>
</table>

The code behind for the control is responsible for binding the list of string based columns to the DropDownList and creates the LikeExpressionParameter which is responsible for the LIKE query.

public partial class ColumnContains : Catalyst.Web.DynamicData.FilterTemplateUserControlBase
{
    protected void Page_Init(object sender, EventArgs e)
    {
        ddlColumn.DataTextField = "Name";
        ddlColumn.DataSource = Table.Columns.Where(c => c.TypeCode == TypeCode.String);
        ddlColumn.DataBind();
    }

    private MetaTable Table
    {
        get
        {
            IDynamicDataSource source = this.FindDataSourceControl();
            if(source != null)
                return source.GetTable();
            return null;
        }
    }

    public override IEnumerable<Parameter> GetWhereParameters(IDynamicDataSource dataSource)
    {
        yield return new LikeExpressionParameter()
            {
                Name = ddlColumn.SelectedValue,
                Like = LikeExpressionParameter.LikeType.Contains,
                Value = tbText.Text
            };
    }

    public MetaColumn Column
    {
        get
        {
            return Table.GetColumn(ddlColumn.SelectedValue);
        }
        set
        {
            ddlColumn.SelectedIndex = -1;
            if (value != null)
            {
                ListItem li = ddlColumn.Items.FindByValue(value.Name);
                if (li != null)
                    li.Selected = true;
            }
        }
    }

    public string Value
    {
        get
        {
            return tbText.Text;
        }
        set
        {
            tbText.Text = value;
        }
    }

    public override void LoadQueryStringParameters(NameValueCollection parameters)
    {
        string columnName = parameters[string.Format("{0}_column", this.ID)];
        Column = Table.GetColumn(columnName);
        Value = parameters[string.Format("{0}_value", this.ID)];
    }

    public override NameValueCollection SaveQueryStringParameters()
    {
        NameValueCollection collection = new NameValueCollection();
        collection.Add(string.Format("{0}_column", this.ID), Column.Name);
        collection.Add(string.Format("{0}_value", this.ID), Value);
        return collection;
    }

    public override void Clear()
    {
        Column = null;
        Value = string.Empty;
    }
}

Posted in ASP.NET, C#, Dynamic Data, LINQ | 1 Comment »

A Richer DynamicFilterRepeater – Updated for April Release

Posted by jheyse on 11th April 2008

This week the Dynamic Data team has released a new drop of the runtime and templates.  It is substantially different and they have made many improvements to the design.  These changes, as expected, broke a significant portion of the Dynamic Filter controls I have been working on.  Last night I dug into the new bits and was able to breath life back into the dynamic filtering. 

Once I had finished doing symbolic renames on many of the properties and types renamed from the Decmeber CTP, I ran into several issues with the reflection based code I have written to inject the advanced querying capabilities into the existing controls.  I am going to start documenting what I have had to ‘hack’ to make the dynamic querying work.  Hopefully I can work with team members within Microsoft to promote some of the methods and types to the public. 

Here are some screen shots of the new implementation:

DD_4

DD_5

Download Solution: DynamicData.zip

Posted in .NET, ASP.NET, C#, Dynamic Data, LINQ | 1 Comment »

A Richer DynamicFilterRepeater – Part 4 Custom DynamicFilterControls & Expressions

Posted by jheyse on 11th April 2008

In post 2 in this series I contrived a scenario where composite parameters were used to select products from the AdventureWorks database by weight.  The weight of products is stored in either lbs or grams which make it necessary for the following SQL logic:

select
    *
from
    Production.Product
where
    (WeightUnitMeasureCode = 'LB' and Weight >= 2 and Weight <= 3) or
    (WeightUnitMeasureCode = 'G' and Weight >= 907 and Weight <= 1306)

Previously, this was accomplished by compounding a serious of And, Or, and Range expressions together.  We can condense this down to a single custom range parameter and corresponding FilterUserControl.

The first step is to create a user control which extends RangeFilterUserControlBase.  We will name it Weight and place it in the ~/App_Shared/DynamicDataFilters/ folder.  The control will allow for values in both Grams and Pounds to support globalization, so we need to add a UnitType property.  The ascx for the control defines tbMin, tbMax, and a DropDownList to allow the user to select either Pounds or Grams.  The control extends RangeFilterUserControlBase and overrides the default implementation of GetWhereParameters and returns a WeightExpressionParameter which we will define next.

public partial class Weight : Catalyst.Web.DynamicData.RangeFilterUserControlBase
{
    public enum WeightUnit
    {
        Pounds,
        Grams
    }

    public WeightUnit UnitType
    {
        get
        {
            return (WeightUnit)Enum.Parse(typeof(WeightUnit), DropDownList1.SelectedValue);
        }
        set
        {
            DropDownList1.Items.FindByValue(value.ToString()).Selected = true;
        }
    }

    public override string MinValue
    {
        get
        {
            return tbMin.Text;
        }
        set
        {
            tbMin.Text = value;
        }
    }

    public override string MaxValue
    {
        get
        {
            return tbMax.Text;
        }
        set
        {
            tbMax.Text = value;
        }
    }

    public override TypeCode Type
    {
        get { return TypeCode.Decimal; }
    }

    public override IEnumerable<Parameter> GetWhereParameters(IDynamicDataSource dataSource)
    {
        yield return new WeightExpressionParameter()
        {
            Name = this.DataField,
            MinValue = this.MinValue,
            MaxValue = this.MaxValue,
            UnitType = this.UnitType,
            Type = this.Type
        };
    }
}

The WeightExpressionParameter extends the RangeExpressionParameter and overrides the GetLambaExpression to return a LambdaExpression which will do the Grams to Pounds conversion for us.  The WeightExpressionParameter also includes a property for the UnitType which is stored in ViewState to ensure post backs and Ajax enabled controls work correctly.

public class WeightExpressionParameter : RangeExpressionParameter
{
    private const decimal ConversionFactor = 453.59237m;

    public WeightUnit UnitType
    {
        get
        {
            object obj2 = this.ViewState["UnitType"];
            if (obj2 == null)
            {
                return WeightUnit.Pounds;
            }
            return (WeightUnit)obj2;
        }
        set
        {
            if (this.UnitType != value)
            {
                this.ViewState["UnitType"] = value;
                this.OnParameterChanged();
            }
        }
    }

    public override LambdaExpression GetLambdaExpression(Type itType)
    {
        decimal? min = (decimal?)MinParameterValue;
        decimal? max = (decimal?)MaxParameterValue;

        if (UnitType == WeightUnit.Grams)
        {
            min /= ConversionFactor;
            max /= ConversionFactor;
        }

        if(itType == typeof(BusinessObjects.Product))
        {
            Expression<Func<BusinessObjects.Product, bool>> x = p =>
                (min == null || (p.WeightUnitMeasureCode == "lb" ? p.Weight : p.Weight / ConversionFactor) >= min) &&
                (max == null || (p.WeightUnitMeasureCode == "lb" ? p.Weight : p.Weight / ConversionFactor) <= max);
            return x;
        }
        else
            throw new ArgumentException("Expected itType to be of type BusinessObjects.Product", "itType");
    }
}

The LINQ-to-SQL interpreter is amazingly smart and is able to understand fairly complex statements. This SQL that is passed to the database server is exactly what we are looking for, it is even able to short circuit the query if either min or max is null!

SELECT TOP (10) [t0].[ProductID], [t0].[Name], [t0].[ProductNumber], [t0].[MakeFlag], [t0].[FinishedGoodsFlag], [t0].[Color], [t0].[SafetyStockLevel], [t0].[ReorderPoint], [t0].[StandardCost], [t0].[ListPrice], [t0].[Size], [t0].[SizeUnitMeasureCode], [t0].[WeightUnitMeasureCode], [t0].[Weight], [t0].[DaysToManufacture], [t0].[ProductLine], [t0].[Class], [t0].[Style], [t0].[ProductSubcategoryID], [t0].[ProductModelID], [t0].[SellStartDate], [t0].[SellEndDate], [t0].[DiscontinuedDate], [t0].[rowguid], [t0].[ModifiedDate], [t2].[test], [t2].[ProductSubcategoryID] AS [ProductSubcategoryID2], [t2].[ProductCategoryID], [t2].[Name] AS [Name2], [t2].[rowguid] AS [rowguid2], [t2].[ModifiedDate] AS [ModifiedDate2], [t4].[test] AS [test2], [t4].[ProductModelID] AS [ProductModelID2], [t4].[Name] AS [Name3], [t4].[CatalogDescription], [t4].[Instructions], [t4].[rowguid] AS [rowguid3], [t4].[ModifiedDate] AS [ModifiedDate3]
FROM [Production].[Product] AS [t0]
LEFT OUTER JOIN (
    SELECT 1 AS [test], [t1].[ProductSubcategoryID], [t1].[ProductCategoryID], [t1].[Name], [t1].[rowguid], [t1].[ModifiedDate]
    FROM [Production].[ProductSubcategory] AS [t1]
    ) AS [t2] ON [t2].[ProductSubcategoryID] = [t0].[ProductSubcategoryID]
LEFT OUTER JOIN (
    SELECT 1 AS [test], [t3].[ProductModelID], [t3].[Name], [t3].[CatalogDescription], [t3].[Instructions], [t3].[rowguid], [t3].[ModifiedDate]
    FROM [Production].[ProductModel] AS [t3]
    ) AS [t4] ON [t4].[ProductModelID] = [t0].[ProductModelID]
WHERE ((
    (CASE
        WHEN [t0].[WeightUnitMeasureCode] = @p0 THEN CONVERT(Decimal(29,5),[t0].[Weight])
        ELSE [t0].[Weight] / @p1
    END)) >= @p2) AND ((
    (CASE
        WHEN [t0].[WeightUnitMeasureCode] = @p3 THEN CONVERT(Decimal(29,5),[t0].[Weight])
        ELSE [t0].[Weight] / @p4
    END)) <= @p5)
-- @p0: Input NVarChar (Size = 2; Prec = 0; Scale = 0) [lb]
-- @p1: Input Decimal (Size = 0; Prec = 29; Scale = 5) [453.59237]
-- @p2: Input Decimal (Size = 0; Prec = 34; Scale = 5) [2]
-- @p3: Input NVarChar (Size = 2; Prec = 0; Scale = 0) [lb]
-- @p4: Input Decimal (Size = 0; Prec = 29; Scale = 5) [453.59237]
-- @p5: Input Decimal (Size = 0; Prec = 34; Scale = 5) [3]
-- Context: SqlProvider(Sql2005) Model: AttributedMetaModel Build: 3.5.21022.8

Here is a screen shot of the control in action:

WeightFilterControl

Weight Filter Control Example: Weight.zip

Posted in .NET, ASP.NET, C#, Dynamic Data, LINQ | No Comments »

A Richer DynamicFilterRepeater – Part 3 DynamicFilterControl

Posted by jheyse on 10th April 2008

In part 1 of this series I discussed extending the LinqDataSource to provided advanced querying capabilities using the System.Linq.Dynamic namespace.  Part 2 demonstrated creating advanced WhereParameters which allowed for for complex binary logic.  In this posting I am going to go through our first ASP.NET Web Control which brings this powerful functionality to the user.

My original intention of this blog series was to create a more powerful DynamicFilterControl to replace the one currently in the Dynamic Data portion of the ASP.NET 3.5  Extensions.  During a recent project where we use the Dynamic Data framework, it became apparent that the repeater limited our layout ability and a DynamicFilterForm was better suited.  I choose the name DynamicFilterForm to follow the naming convention of the ASP.NET databound controls.

So, let’s start off with a demo of the the DynamicFilterForm.  In this demo I am building on the Products  example used in previous postings.  For the List.aspx page we’d like to be able to filter on the fields displayed in the DataGrid.

DynamicFilterForm_1[5]

The search allows for all of the fields, except weight, to be search on.  The Name, Product Number, Class and Color allow for partial string matches, ListPrice allows for a range, Subcategory uses a single selection drop down which is populated through the ForiegnKey, and Product Model allows for multiple selection.

The DynamicFilterForm allows for 3 commands: Search, Clear, and Browse.  (More on these later).

Let’s take a look at the ASP.NET code that runs the form:

<cc1:DynamicFilterForm DataSourceID="GridDataSource" runat="Server" ID="DynamicFilterForm1">
    <FilterTemplate>
        <div>
            Search
        </div>
        <table>
            <tr>
                <td>
                    Name:
                </td>
                <td>
                    <cc1:DynamicFilterControl ID="DynamicFilterControl3" runat="server" DataField="Name"
                        FilterMode="Contains" />
                </td>
                <td>
                    Product Number:
                </td>
                <td>
                    <cc1:DynamicFilterControl ID="DynamicFilterControl5" runat="server" DataField="ProductNumber"
                        FilterMode="Contains" />
                </td>
                <td>
                    Class:
                </td>
                <td>
                    <cc1:DynamicFilterControl ID="DynamicFilterControl6" runat="server" DataField="Class"
                        FilterMode="Contains" />
                </td>
                <td rowspan="2" valign="top">
                    Product Model:
                </td>
                <td rowspan="2" valign="top">
                    <cc1:DynamicFilterControl ID="DynamicFilterControl7" runat="server" DataField="ProductModel" FilterMode="MultiSelect" />
                </td>
                <td rowspan="2" valign="top">
                    <asp:LinkButton runat="server" CommandName="Search">Search</asp:LinkButton><br />
                    <asp:LinkButton runat="server" CommandName="Clear">Clear</asp:LinkButton><br />
                    <asp:LinkButton runat="server" CommandName="Browse">Browse</asp:LinkButton>
                </td>
            </tr>
            <tr>
                <td>
                    List Price:
                </td>
                <td>
                    <cc1:DynamicFilterControl ID="DynamicFilterControl1" runat="server" DataField="ListPrice"
                        FilterMode="Range" />
                </td>
                <td>
                    Color:
                </td>
                <td>
                    <cc1:DynamicFilterControl ID="DynamicFilterControl4" runat="server" DataField="Color"
                        FilterMode="Contains" />
                </td>
                <td>
                    Subcategory:
                </td>
                <td>
                    <cc1:DynamicFilterControl ID="DynamicFilterControl2" runat="server" DataField="ProductSubcategory" />
                </td>
            </tr>
        </table>
    </FilterTemplate>
</cc1:DynamicFilterForm>

DynamicFiltersFolder

The DynamicFilterForm mimics the functionality of the other DynamicData controls.  The DynamicFilterForm is a container object which has a FilterTemplate template that defines the layout.  Inside the FilterTemplate you define DynamicFilterControls.  These controls are responsible for loading the correct user control from the ~/App_Shared/DynamicDataFilters/ folder.  Here you can see I have created filter controls for each of the field types declared in the DynamicDataFields folder which are included in the base solution.

The DynamicFilterControl has a property FilterMode which allows for the following different enumeration values: Equals, Contains, Range, Multiselect.  This mode is appended to the field type like the DynamicControls.  So if you want to allow for a range of integers, the control name would be Integer_Range.ascx.  To facilitate quicker development and code reuse each of the custom DynamicExpressionParameters (EqualsExpressionParameter, InExpressionParameter, etc…) has a corresponding FilterTemplateUserControlBase(EqualsFilterUserControlBase, InFilterControlUserBase, etc…).

Here are the abstract methods for the FilterTemplateUserControlBase which need to be implemented.

public abstract class FilterTemplateUserControlBase : FieldTemplateUserControlBase, IWhereParametersProvider
{
    public abstract IEnumerable<Parameter> GetWhereParameters(IDynamicDataSource dataSource);
    public abstract void LoadQueryStringParameters(NameValueCollection parameters);
    public abstract NameValueCollection SaveQueryStringParameters();
    public abstract void Clear();

}

GetWhereParameters is the meat of this class, it is what is responsible for instantiating and returning the ExpressionParameter which is added to the DynamicLinqDataSource control’s where parameters.  Don’t you love the separation of concerns? I DO! The Clear method is called by the parent DynamicFilterForm when the Clear command is issued, this clears all values and researches to bring back the unfiltered result set.  LoadQueryStringParameters and SaveQueryStringParameters allow for the filter criteria to be stored in a URL.

<rant> One of my biggest annoyances on the web is developers who store state in session or form variables around searching.  Web users expect to be able to copy the URL of the page they are on and send it to their friend so that they can see the same thing. I run into this every time my friends and I are trying to plan a trip someplace and we are using Orbitz, Travelocity, etc… </rant>

Here is an example of the RangeFilterUserControlBase and the Integer_Range.ascx.cs control which extends it.

public abstract class RangeFilterUserControlBase : FilterTemplateUserControlBase
{
    public abstract string MinValue { get; set; }
    public abstract string MaxValue { get; set; }
    public abstract TypeCode Type { get; }

    public override IEnumerable<Parameter> GetWhereParameters(IDynamicDataSource dataSource)
    {
        yield return new RangeExpressionParameter() { Name = DataField, MinValue = MinValue, MaxValue = MaxValue, Type = Type };
    }

    public override void LoadQueryStringParameters(NameValueCollection parameters)
    {
        MinValue = parameters[string.Format("{0}_Min", DataField)];
        MaxValue = parameters[string.Format("{0}_Max", DataField)];
    }

    public override NameValueCollection SaveQueryStringParameters()
    {
        NameValueCollection parameters = new NameValueCollection();
        parameters.Add(string.Format("{0}_Min", DataField), MinValue);
        parameters.Add(string.Format("{0}_Max", DataField), MaxValue);
        return parameters;
    }

    public override void Clear()
    {
        MinValue = string.Empty;
        MaxValue = string.Empty;
    }
}

The Integer_Range.ascx control has two TextBoxes named tbMin and tbMax.

public partial class Integer_Range : Catalyst.Web.DynamicData.RangeFilterUserControlBase
{
    public override string MinValue
    {
        get
        {
            return tbMin.Text;
        }
        set
        {
            tbMin.Text = value;
        }
    }

    public override string MaxValue
    {
        get
        {
            return tbMax.Text;
        }
        set
        {
            tbMax.Text = value;
        }
    }

    public override TypeCode Type
    {
        get { return this.MetaMember.TypeCode; }
    }
}

As always, below is the source to the solution so far.  In my next post I will demonstrate creating your own FilterUserControl by re-implementing filtering on the Weight column.

Download Solution: DynamicData.zip

Posted in .NET, ASP.NET, C#, Dynamic Data, LINQ | No Comments »