Marcin On ASP.NET

Keeping my eye on the dot

Posts
  • Marcin On ASP.NET

    Dynamic Data samples

    • 2 Comments

    In breaks between writing the Dynamic Data runtime I also write samples illustrating the various ways Dynamic Data can be extended and customized, often in response to questions on the Dynamic Data forum. In a series of upcoming posts I will share these samples with you as well as discuss how the various extensions work with the Dynamic Data runtime. This post is meant to serve as a list of the samples:

    The list will be updated with links as samples become available.

  • Marcin On ASP.NET

    Dynamic Data samples: Extending the FilterRepeater

    • 2 Comments

    6/25 Update: This sample has become part of the Dynamic Data Futures project on codeplex. You can still use this code but you should check out the Futures project as it contains a lot more. The key conecpts are the same, though some names or APIs might have changed.

    Note: This post is part of a series, see the list of other Dynamic Data samples.

    The FilterRepeater is a control in Dynamic Data that is responsible for automatically emitting a list of filter controls that can be used to filter data rows displayed for a table. The filters work by providing a list of where parameters to the data source for each supported column type. By default Dynamic Data supports foreign key and boolean columns and renders them using a DropDownList. This and upcoming posts will talk about ways in which you can add filtering for other column types and how to modify the filter UI.

    All of the code referenced here is part of the AdvancedFilterRepeaterSample solution.To run the sample open the solution in Visual Studio 2008 SP1 Beta or later and run AdvancedFilterRepeaterSite (note: it might not be the default project for the solution, so right click on Default.aspx inside the project and choose View in Browser). Navigate to the list page for the Products table where you can experiment with some new filtering options. For example, type "con" into the Categories filter. You should see an AJAX autocomplete drop-down instead of the default drop-down list.

    Introduction to FilterRepeater

    FilterRepeater is a specialized Repeater control that will automatically bind to a collection of filterable columns for the given request's table: if a request comes in for /Products/List.aspx, it will bind to the columns in the Products table. The table is chosen based on the route that the request matched, or it can be overridden by setting the TableName and ContextTypeName properties.

    To work correctly FilterRepeater expects its ItemTemplate to have correct content. Specifically, it requires a control with a known ID ("DynamicFilter" by default) that derives from FilterUserControlBase. When databinding occurs FilterRepeater initializes an instance of the filter control with information about the column to filter.

    Below is a snippet of the code that ships in the default Dynamic Data templates:

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

    FilterRepeater version 2.0 (well, more like 1.1 really)

    The DynamicDataExtensions project contains a simple extension of FilterRepeater called AdvancedFilterRepeater. It is not really that advanced (I started with that name and never bothered to change it) as it overrides only one method: GetFilteredColumns. This method is what is used as the databinding source. Below is a slightly simplified source of how it is implemented:

    public class AdvancedFilterRepeater : FilterRepeater {
    protected override IEnumerable<MetaColumn> GetFilteredColumns() {
    return Table.Columns.Where(c => IsFilterableColumn(c)).OrderBy(column => column, new FilterOrderComparer());
    }

    protected bool IsFilterableColumn(MetaColumn column) {
    if (column.IsCustomProperty) return false;

    var filterAttribute = column.Attributes.OfType<FilterAttribute>().FirstOrDefault();
    if (filterAttribute != null) return filterAttribute.Enabled;

    if (column is MetaForeignKeyColumn) return true;

    if (column.ColumnType == typeof(bool)) return true;

    return false;
    }

    private class FilterOrderComparer : IComparer<MetaColumn> {
    // implementation omitted for brevity
    }
    }

    The Table property is automatically populated with the right MetaTable instance based on the table resolution rules mentioned in the previous section. The rest is a simple LINQ extension method query and some boolean logic to choose the filterable columns. The only way this deviates from the default process is the addition and handling of FilterAttribute, which is a new attribute written for the purpose of this sample. It combines the roles of UIHintAttribute and ScaffoldColumnAttribute from Dynamic Data field templates and should be applied to columns in an analogous manner. Here's the full signature:

    public sealed class FilterAttribute : Attribute {
    public string FilterControl { get; set; }
    public int Order { get; set; }
    public bool Enabled { get; set; }
    }

    The FilterControl property lets you specify which user control to use as the filter while the Enabled property lets you omit a given column's filter in the AdvancedFilterRepeater. The Order property let's you specify an ordering weight.

    Specifying custom filter controls

    The reason why the implementation of AdvancedFilterRepeater is so simple is because a lot of work is done in two other components:

    • FilterFactory is the equivalent of FieldTemplateFactory for filters. It uses the information in a column's FilterAttribute to determine which filter user control to instantiate for the column. It takes the value of the attribute's FilterControl property and looks for a user control located at ~\DynamicData\Filters\{FilterControl}.ascx. If a filter attribute is not specified ~\DynamicData\Filters\Default.ascx is used, which is identical to the FilterUserControl.ascx that is part of the default Dynamic Data template.
    • DelegatingFilter is a control that derives from FilterUserControlBase (which means that it is itself a filter) and delegates the filtering behavior to another filter control. It uses the FilterFactory class to determine which control to use.

    In order to take advantage of AdvancedFilterRepeater and DelegatingFilter you need to replace the existing FilterRepeater in your List.aspx page template with the following code:

    <asp:AdvancedFilterRepeater id="AdvancedFilterRepeater" runat="server">
    <
    HeaderTemplate>
    <
    table>
    </
    HeaderTemplate>
    <
    ItemTemplate>
    <
    tr>
    <
    td valign="top"><%# Eval("DisplayName") %>:</td>
    <
    td><asp:DelegatingFilter runat="server" ID="DynamicFilter"
    OnSelectionChanged="OnFilterSelectionChanged" />
    </
    tr>
    </
    ItemTemplate>
    <
    FooterTemplate>
    </
    table>
    </
    FooterTemplate>
    </
    asp:AdvancedFilterRepeater>

    This template is pretty similar to the default one seen earlier in this post except it wraps each filtered column in a table row for cleaner formatting (yes, using tables for layout is wrong but this post is not about layouts). And of course DynamicFilter has been replaced with DelegatingFilter.

    To take advantage of the custom filter controls included in the sample such as autocomplete filter or cascading filter you need to annotate your data model with FilterAttribute. Here's a snippet of how it is done in the sample:

    [MetadataType(typeof(Product_MD))]
    public partial class Product { }

    public class Product_MD {
    // Display the Category and Supplier filters using the Autocomplete.ascx filter control
    [Filter(FilterControl = "Autocomplete")]
    public object Category { get; set; }
    [Filter(FilterControl = "Autocomplete")]
    public object Supplier { get; set; }

    // Display the Discontinued filter using the BooleanRadio.ascx filter control
    // Make sure the Discontinued filter is displayed first
    [Filter(FilterControl = "BooleanRadio", Order = 1)]
    public object Discontinued { get; set; }

    // Display the UnitsInStock filter using Integer.ascx filter control
    [Filter(FilterControl = "Integer")]
    public object UnitsInStock { get; set; }
    }

    When you run the application and view the list page for the Products table the appropriate filters get inserted automatically. Future posts will discuss how the more advanced filters are implemented.

  • Marcin On ASP.NET

    Dynamic Data Futures 6/25 update posted

    • 2 Comments

    Update: the Dynamic Data Futures was updated on 7/2.

    We have just posted an updated version of Dynamic Data Futures (known for the last 10 days as Dynamic Data Extensions) on codeplex. The Dynamic Data Features project is a combination of a class library and sample website that showcases some of the future work that will be coming to Dynamic Data. It also contains some workarounds for existing Dynamic Data bugs and issues.

    Here's a rough list of what is new from last week:

    • Moved/renamed a number of library files for better logical organization. Classes are now organized what they generally do instead of how they apply to a particular sample.

    • Updated DbImage.ascx to better handle compound and GUID primary key tables. Also, page templates will disable AJAX partial rendering when editing an image column to allow the FileUpload control to work properly.

    • Added support for complex where parameters. This can be seen in the pages under ComplexWhereParameters in the sample app that already have a Where clause for LinqDataSource but also use the filters to contribute extra components.

    • Added automatic support for enumerated type columns. UIHint is no longer needed to reference Enumeration.ascx.

    • Moved a number of extension/utility methods to the DynamicDataFutures class. This includes localization helpers, metadata helpers, and default value helpers.

    • Consolidated IAutoFieldGenerator implementations into a single AdvancedFieldGenerator class. It now includes functionality enabling column ordering, programatically excluding a list of columns, and a workaround for issues with attribute localization.

    • Refactored common LINQ Expression generation code into the LinqExpressionHelper class.

  • Marcin On ASP.NET

    Dynamic Data December preview context caching bug fix

    • 2 Comments

    Users of our recently released December preview of the new Dynamic Data feature in ASP.NET have noticed a pretty serious bug relating to foreign key column drop-downs. While this could be a breaking issue for many trying to test or adopt the new feature, I am glad to report that there is a workaround available.

    This post talks about what the bug is and what steps are needed to resolve it. I am also attaching a zip file that you can simply unzip in the root of your Dynamic Data project (warning: this could overwrite customizations you have made to the project files. Read on to see what files are affected).

    You can download the updated versions of the affected controls here (3 KB).

    Bug symptoms

    The bug affects the drop-down lists for foreign key relation columns as well foreign key filters. The problem occurs when you insert a new item into the parent table, then go to the child table and try to add a new entry, edit an existing one, or filter the table using the entry you added in the parent column. The drop-down list does not contain the newly added item and only restarting the entire app makes it appear (an analogous thing will happen if you remove an entry from the parent table).

    This bug occurs because the Dynamic Data framework is trying to cache an instance of the DataContext object that represent the data model. The DataContext instance is something that should be instantiated, used to retrieve/submit information to the database, and then discard it. Dynamic Data holds on to it a bit longer than it should.

    The solution

    You can download the file mentioned at the beginning of this post to get the updated versions of the affected files. Simply unzip into the root of your Dynamic Data project. The included files are in C# but they should work even if your project is in VB.

    The solution to the problem basically requires providing a new instance of the DataContext object to the controls responsible for rendering the drop-downs. This means you need to modify ForeignKey_Edit.ascx and FilterUserControl.ascx (both living in App_Shared\DynamicDataFields\ directory in the Dynamic Data project templates).

    • ForeignKey_Edit.ascx

      In the Page_Load method, replace the following line of code

      DropDownList1.DataSource = parentTable.Query;
      

      with this

      DropDownList1.DataSource = parentTable.DataContextProperty.GetValue(Activator.CreateInstance(DynamicDatabase.TheDatabase.CreateDataContext().GetType()), null);
    • FilterUserControl.ascx

      In the Page_Init method, replace the following line of code

      DropDownList1.DataSource = DataSource;

      with this

      var foreignKeyColumn = DynamicDatabase.TheDatabase.GetMetaTable(this.TableName).FindColumn(this.DataField) as DynamicMetaForeignKeyMember;
      if (foreignKeyColumn != null) {
          // only kick in if we are dealing with a foreign key column
          DropDownList1.DataSource = foreignKeyColumn.ParentMetaTable.DataContextProperty.GetValue(Activator.CreateInstance(DynamicDatabase.TheDatabase.CreateDataContext().GetType()), null);
      }
      else {
          DropDownList1.DataSource = DataSource;
      }

    The new code is a bit ugly but it should work. Upcoming versions of Dynamic Data will not have this problem.

  • Marcin On ASP.NET

    Dynamic Data Futures 7/2 update posted

    • 2 Comments

    We have just posted an updated version of Dynamic Data Futures on codeplex. The Dynamic Data Features project is a combination of a class library and sample website that showcases some of the future work that will be coming to Dynamic Data. It also contains some workarounds for existing Dynamic Data bugs and issues.

    Here's a rough list of what is new from last week:

  • Added DynamicHyperLink control. It generates links to Dynamic Data tables for all actions and works both in data-binding and declarative scenarios. 
  • Added Url and EmailAddress field templates. 
  • Modified ForeignKey_Edit field template to display a [Not Set] entry for required columns in insert mode. Added a RequiredFieldValidator to ensure that an entry is selected if the column is required.
  • Modified routes in Global.asax to generate pretty URLs using the new PrettyDynamicDataRoute.
  • Changed FilterAttribute.Order property to match the behavior of ColumnOrderAttribute.Order property. Now each filter has a default Order of 0; negative numbers can be used to push filters to the front, while positive numbers can be used to push filters to the back.
Page 3 of 5 (25 items) 12345