Josh Heyse

Thoughts Defragmented

Archive for the 'Dynamic Data' Category

Updated ASP.NET Dynamic Data Drop

Posted by jheyse on 10th April 2008

Scott Guthrie just posted about an updated version of the ASP.NET Dynamic Data framework.  I am going to look into this and investigate if there were any breaking changes to the Dynamic Filter control set.  I’m guessing there will be several since I am being a bad kid and using reflection to call non-public methods. LOL

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

A Richer DynamicFilterRepeater: Part 2 – Advanced Parameters

Posted by admin on 5th April 2008

After extending the LinqDataSource in Part 1, I was curious to see what other types of parameters I could make.  My first idea was to create composite parameters for and/or operations.  The existing implementation of the LinqDataSource only allows you to append filter criteria to the list, which creates an AndAlso functionality.

Continuing with the AdventureWorks Products example, I devised a scenario where composite parameters would be useful. The Products in the AdventureWorks database allow you to store the weight of the item in either lbs or grams. This requires the use of a composite parameter to select all products which fall in between 2 – 3 lbs or the equivalent 907 – 1360 grams.  Since I have been playing with WPF and Silverlight I have taken a liking to the declarative syntax. The following query looked like this in my head:

<cc1:DynamicLinqDataSource ID="GridDataSource" runat="server">
    <WhereParameters>
        <cc1:OrExpressionParameter>
            <Parameters>
                <cc1:AndExpressionParameter>
                    <Parameters>
                        <cc1:EqualsExpressionParameter Name="WeightUnitMeasureCode" Value="LB" />
                        <cc1:RangeExpressionParameter Name="Weight" MinValue="2" MaxValue="3" Type="Int32" />
                    </Parameters>
                </cc1:AndExpressionParameter>
                <cc1:AndExpressionParameter>
                    <Parameters>
                        <cc1:EqualsExpressionParameter Name="WeightUnitMeasureCode" Value="G" />
                        <cc1:RangeExpressionParameter Name="Weight" MinValue="907" MaxValue="1360" Type="Int32" />
                    </Parameters>
                </cc1:AndExpressionParameter>
            </Parameters>
        </cc1:OrExpressionParameter>
    </WhereParameters>
</cc1:DynamicLinqDataSource>

Once I had the use case for the feature completed it was time to refactor my code to support the new functionality.  As I am writing this I am kicking myself for not creating some corresponding UnitTests for this little project.  If I have some time I will write up some tests before I release the code base to the public.

Anyway… To support the change in functionality I had to make several changes:

  • Modify IDyanmicExpressionParameter
  • Extend ParameterCollection
  • Create CompositeExpressionParameterBase

First, I had to modify the IDynamicExpressionParameter interface to support an additional method called GetLambdaExpression.  The reason for this is that we can’t just append to the query anymore, the composite parameters must compile their own binary tree of Lambda Expressions to work correctly.

public interface IDynamicExpressionParameter
{
    IQueryable AppendQuery(IQueryable query);

    LambdaExpression GetLambdaExpression(Type itType);
}

Next, I needed to extend the ParameterCollection to accept parameters beyond the built in ones with the .NET Framework.  Under my previous implementation parameters were not being tracked in ViewState and this was causing an issue with postbacks.  The DynamicParameterCollection accepts additional Parameter types, but still uses a hard coded list.  A better implementation would be to create a static list of knownTypes that appends new knownTypes as they are found.

public class DynamicParameterCollection : ParameterCollection
{
    private readonly Type[] _knownTypes = new Type[] { typeof(AndExpressionParameter),
                                                        typeof(OrExpressionParameter),
                                                        typeof(InExpressionParameter),
                                                        typeof(LikeExpressionParameter),
                                                        typeof(RangeExpressionParameter),
                                                        typeof(EqualsExpressionParameter) };

    protected override object CreateKnownType(int index)
    {
        int baseTypesLength = base.GetKnownTypes().Length;
        if(index < baseTypesLength)
            return base.CreateKnownType(index);

        return Activator.CreateInstance(_knownTypes[index - baseTypesLength]);
    }

    protected override Type[] GetKnownTypes()
    {
        List<Type> knownTypes = new List<Type>(base.GetKnownTypes());
        knownTypes.AddRange(_knownTypes);
        return knownTypes.ToArray();
    }
}

Lastly, I implemented CompositeExpressionParameterBase which supports both And and Or binary tree operations.  There were two key pieces to this class.

The first was wiring up the IStateManager interface to work properly with the parent DynamicParamterCollection.  This proved fairly difficult to debug before I remembered that I could now step into the .NET Framework using Visual Studio 2008. This was my first experience doing so, and I think it is a HUGE feature. The VS team rocks!

The next step was to implement binary trees as Lambda Expressions.  This forced me to get a little deeper into Lamdba Expressions and the ExpressionTreeVisualizer example paid off big time.  You can find the Debug Visualizer in the %PROGRAM FILES%Microsoft Visual Studio 9.0Samples1033CSharpSamples.zip directory.

LinqExpressionVisualizer

The code for creating a binary tree expression looks likes this:

protected LambdaExpression GetLambdaExpression(Type itType, ExpressionType binaryType)
{
    List<LambdaExpression> lambdas = new List<LambdaExpression>();
    foreach (Parameter p in Parameters)
    {
        LambdaExpression lambda = p.GetLambdaExpression(itType);
        if (lambda != null)
            lambdas.Add(lambda);
    }

    if (lambdas.Count == 0)
        return null;

    if (lambdas.Count == 1)
        return lambdas[0];

    LambdaExpression left = lambdas[0];
    LambdaExpression right = lambdas[1];

    ParameterExpression param = Expression.Parameter(left.Parameters[0].Type, "");
    BinaryExpression root = Expression.MakeBinary(binaryType, left.Body, right.Body);
    for (int i = 2; i < lambdas.Count; i++)
    {
        right = lambdas[i];
        root = Expression.MakeBinary(binaryType, root, right.Body);
    }

    left = Expression.Lambda(ReplaceParameter(root, param), param);
    return left;
}

Some other ideas for advanced parameters include a NotNullOrEmpty parameter and a Not parameter which inverses the result of the composite expression.  Up next I will talk about a writing web controls to expose this advance functionality to users.

Download Solution DynamicData.zip

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

A Richer DynamicFilterRepeater: Part 1 – Extending the LinqDataSouce

Posted by admin on 27th February 2008

Download Solution – DynamicData.zip

I’ve recently started digging into the ASP.NET Dynamic Data Framework and evaluating its usage on a new RAD web site project. The project is a short duration web site with fairly standard data entry, searching, and detail views. The Dynamic Data Framework fit the requirements well except that the dynamic searching controls did not have the required features needed for the project. I started investigating how hard it would be add the following search features:

  • Searching ranges (ListPrice > 10 AND ListPrice < 500)
  • Searching in a list of possible values (Class in (‘L’, ‘M’))
  • Partial text searching (Color LIKE ‘B%’)

The DynamicFilterRepeater is the control provided with the Dynamic Data Framework which automatically gives the ListTemplate and ListDetailsTemplate the ability to filter on any foreign key found within the type of object listed. The DynamicFilterRepeater is a UI control which dynamically generates a list of DropDown controls for the user to select from. It doesn’t actually do any of the filtering, that responsibility is handled by the LinqDataSource.

The LinqDataSource is a new control in ASP.NET 3.5 which allows the developer to specify in markup the DataContext, table, and query parameters of a LinqQuery. Other databound controls, such as GridView, DetailsView, and FormView can then reference the datasource. I was surprised to find that the LinqDataSource only supported where parameters where the searching field and value must be equal. In addition you can only AND logic between parameters in the where clause, there is no ability to do OR. There is an option to type in a parameterized clause into the Where property and then specify the names of the parameters, but this is a dynamic data project and that doesn’t sound very dynamic, does it?

No worries, Reflector was to the rescue. I popped open reflector and dug into the System.Web.Extensions 3.6 3.6 dll. First thing I noticed was their extensive use of yield statement’s which does not make for easily read code. Secondly, I saw that the ASP.NET development team did an excellent job of separating the LinqDataSource UI control, from the LinqDataSourceView which actually creates the query. After digging around a while, I found it was possible to replace the LinqDataSourceView using reflection. :)

The current architecture of the LinqDataSource and the Parameters puts all of the querying logic into the LinqDataSourceView.  This makes it extremely hard to extend and add additional capabilities.  I suggest a new design which passes the query creation responsibility on to the individual parameters.  This can be accomplished through the following interface:

public interface IDynamicExpressionParameter
{
    IQueryable AppendDynamicQuery(IQueryable query);
}

Two of the three DynamicExpressionParameters can use the System.Linq.Dynamic.DynamicQuery class.  This class isn’t in the .NET Framework but you can find it in the examples installed with Visual Studio 2008, C:Program FilesMicrosoft Visual Studio 9.0Samples1033CSharpSamples.zipLinqSamplesDynamicQuery. There are several excellent examples in here including a ExpressionTreeVisualizer.

This is an example of the RangeExpressionValidator.

public class RangeExpressionParameter : DynamicExpressionParameterBase
    {
        [DefaultValue("")]
        public string MinValue { get; set; }
 
        [DefaultValue("")]
        public string MaxValue { get; set; }
 
        public override IQueryable AppendDynamicQuery(IQueryable query)
        {
            if (!string.IsNullOrEmpty(MinValue) && !string.IsNullOrEmpty(MaxValue))
                return query.Where(string.Format("{0} >= @0 AND {0} <= @1", this.Name), new object[] { MinParameterValue, MaxParameterValue });
            else if (!string.IsNullOrEmpty(MinValue))
                return query.Where(string.Format("{0} >= @0", this.Name), new object[] { MinParameterValue });
            else if (!string.IsNullOrEmpty(MaxValue))
                return query.Where(string.Format("{0} <= @0", this.Name), new object[] { MaxParameterValue });
            else
                return query;
        }
 
        private object MinParameterValue
        {
            get
            {
                return GetValue(MinValue, null, Type, this.ConvertEmptyStringToNull, false);
            }
        }
 
        private object MaxParameterValue
        {
            get
            {
                return GetValue(MaxValue, null, Type, this.ConvertEmptyStringToNull, false);
            }
        }
    }

Here is some example markup from the aspx pages of both a standard Web site and a Dynamic Data Extensions Web Site.

<cc1:DynamicLinqDataSource ID="DynamicLinqDataSource1"  ContextTypeName="BusinessObjects.AdventureWorksDataContext" 
    TableName="Products" runat="server" EnableDelete="True" EnableInsert="True" EnableUpdate="True">
    <WhereParameters>
        <cc1:RangeExpressionParameter Name="ListPrice" MinValue="10" MaxValue="500" Type="Decimal" />
        <cc1:InExpressionParameter Name="Class" ValueType="String" ValueList="L,M" />
        <cc1:LikeExpressionParameter Name="Color" Like="StartsWith" Value="B" />
    </WhereParameters>
</cc1:DynamicLinqDataSource>

These screen shots show the above expressions in action.  The DynamicLinqDataSource works exactly like the standard LinqDataSource with the addition of the Expression Parameters.

WebPage[1] DynamicData

I am attaching the source code for the solution to this point.  My next steps are to extend the ExpressionParameter to create a CompositeExtension parameter which accepts AND / OR operations between parameters which will allow for even more complex search criteria. 

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