Search code examples
asp.netdynamic-dataasp.net-dynamic-data

ASP.NET Dynamic Data Filtering


I'd like to use ASP.NET Dynamic Data 4.0 with an EF POCO ObjectContext as a simple entity administration console for system administrators (I'm contemplating abandoning this technology...).

I've got insert/update working.

I've figured out how to add filters for extra data types (because the built in ones are useless). I can't figure out how to make the QueryableFilterRepeater expose my columns as filtering. I can't added FilterUIHints to all applicable types, so that's not an acceptable solution. I do want to leverage the built in Entity Framework Metadata Model Provider. I don't want to write my own. The problem seems to boil down to the fact that the QueryableFilterIterator is calling MetaTable.GetFilteredColumns() and it's only returning bool/int/DateTime columns (which is useless).

I checked out ASP.NET Dynamic Data Filtering (http://dynamicdatafiltering.codeplex.com/), but it doesn't seem to be maintained for 4.0.

I guess I have two questions:

  1. Is there a way to do this (get my columns as filterable)?
  2. Is Dynamic Data actually suitable to use in the real world?

Solution

  • Got frustrated and took the direct route. If there's not a better solution to this...well, then Microsoft did a really poor job at this:

    public class QueryableFilterRepeater : System.Web.DynamicData.QueryableFilterRepeater
        {
            private static readonly FieldInfo FiltersField = typeof(System.Web.DynamicData.QueryableFilterRepeater).GetField("_filters", BindingFlags.Instance | BindingFlags.NonPublic);
            private static readonly FieldInfo DataSourceField = typeof(System.Web.DynamicData.QueryableFilterRepeater).GetField("_dataSource", BindingFlags.Instance | BindingFlags.NonPublic);
            private static readonly MethodInfo FilterInitializeMethod = typeof(DynamicFilter).GetMethod("Initialize", BindingFlags.Instance | BindingFlags.NonPublic);
            private static readonly PropertyInfo FilterContextProperty = typeof(DynamicFilter).GetProperty("Context", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.DeclaredOnly);
            private static readonly MethodInfo PageInitCompleteMethod = typeof(System.Web.DynamicData.QueryableFilterRepeater).GetMethod("Page_InitComplete", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.DeclaredOnly);
            private static readonly EventInfo InitCompleteEvent = typeof(Page).GetEvent("InitComplete");
    
            private readonly List<DynamicFilter> filters;
            private int filterCount;
            private bool initialized;
    
            public IEnumerable<DynamicFilter> Filters
            {
                get { return filters; }
            }
    
            public QueryableFilterRepeater()
            {
                filters = (List<DynamicFilter>)FiltersField.GetValue(this);
            }
    
            private IQueryableDataSource DataSource
            {
                get { return DataSourceField.GetValue(this) as IQueryableDataSource; }
            }
            protected override void OnInit(EventArgs e)
            {
                base.OnInit(e);
    
                InitCompleteEvent.RemoveEventHandler(Page, Delegate.CreateDelegate(typeof(EventHandler), this, PageInitCompleteMethod));
    
                Page.InitComplete += new EventHandler(Page_InitComplete);
            }
    
            protected override void OnLoad(EventArgs e)
            {
                filters.Select((f, i) => new { f, i }).Where(x => x.i > filterCount - 1).ToList().ForEach(x => filters.Remove(x.f));
                Controls.OfType<Control>().Select((c, i) => new { c, i }).Where(x => x.i > filterCount - 1).ToList().ForEach(x => Controls.Remove(x.c));
                base.OnLoad(e);
            }
    
            private void Page_InitComplete(object sender, EventArgs e)
            {
                if (!initialized)
                {
                    Controls.Clear();
                    filters.Clear();
    
                    MetaTable metaTable = DataSource.GetMetaTable();
                    int num = 0;
                    foreach (MetaColumn column in metaTable.Columns)
                    {
                        string filterUIHint = GetFilterUIHint(column);
                        if (filterUIHint == null) continue;
    
                        var filterRepeaterItem = new FilterRepeaterItem();
                        filterRepeaterItem.DataItemIndex = num;
                        filterRepeaterItem.DisplayIndex = num;
                        FilterRepeaterItem container = filterRepeaterItem;
                        num++;
                        ItemTemplate.InstantiateIn(container);
                        Controls.Add(container);
                        var dynamicFilter = container.FindControl(DynamicFilterContainerId) as DynamicFilter;
                        if (dynamicFilter == null)
                        {
                            throw new InvalidOperationException();
                        }
                        FilterContextProperty.SetValue(dynamicFilter, new HttpContextWrapper(Context), null);
                        dynamicFilter.DataField = column.Name;
                        container.DataItem = column;
                        container.DataBind();
                        container.DataItem = null;
    
                        dynamicFilter.FilterUIHint = filterUIHint;
                        filters.Add(dynamicFilter);
                        filterCount++;
                    }
                    filters.ForEach(f => FilterInitializeMethod.Invoke(f, new[] { DataSource }));
                    initialized = true;
                }
            }
    
            private string GetFilterUIHint(MetaColumn column)
            {
                if (GetUnderlyingType(column.ColumnType) == typeof(string))
                {
                    return "String";
                }
                if (GetUnderlyingType(column.ColumnType) == typeof(bool))
                {
                    return "Boolean";
                }
                if (GetUnderlyingType(column.ColumnType).IsEnum)
                {
                    return "Enumeration";
                }
                if (GetUnderlyingType(column.ColumnType) == typeof(DateTime))
                {
                    return "DateTime";
                }
                if (column is MetaForeignKeyColumn)
                {
                    return "ForeignKey";
                }
                if (column is MetaChildrenColumn)
                {
                    return "Children";
                }
    
                return null;
            }
    
            private Type GetUnderlyingType(Type type)
            {
                return Nullable.GetUnderlyingType(type) ?? type;
            }
    
            // Nested Types
    
            #region Nested type: FilterRepeaterItem
    
            private class FilterRepeaterItem : Control, IDataItemContainer
            {
                // Properties
    
                #region IDataItemContainer Members
    
                public object DataItem { get; internal set; }
                public int DataItemIndex { get; internal set; }
                public int DisplayIndex { get; internal set; }
    
                #endregion
            }
    
            #endregion
        }