ServicesResourcesConferencesOur TeamWeblogsAboutContact
   
Multiple Form Configurations with ASP.NET Dynamic Data

As some of you know, I enjoy working at high-throughput web applications. One thing I however don't particularily like, is the creation of the necessary backend administration tools. In a lot of the applications I've worked on, data is fed using various XML-ish or proprietary data feeds and is usually 99.9% correct. For the remaining 0.1%, support personell needs some possbility to maintain and change data in every way imaginable. This usually means that it's necessary to create CRUD-style data maintainance applications for dozens or - more likely - hundreds of tables.

As this doesn't really sound like a lot of fun, I had high hopes that ASP.NET Dynamic Data would help our clients a bit in this regard. And, yes, it does! Now, to be fair: it's Version One and lacks a few features which would be nice. But the cool thing is that it's extremely extensible.

As those of you who've looked at dynamic data before will know, ADD by default bases its view of the world on annotated partial classes based on code generated from an Entity Framework EDMX or on Linq-To-SQL classes. This of course also means that, by default, you will get exactly one representation for each class (one set of configured columns). There is no built-in (automatic) way to simply configure different views, including different columns in different orders for each table. For most of the applications my clients work with, this is unfortunately not enough, as their users usually want to show/hide different columns based on the particular use case.

What I'd like to do instead was to define multiple views (say, in an XML file) and define key for each view which can then be used to retrieve the desired layout. (Note: the following sample is based on an Entity Framework Dynamic Data Web Site based on the Northwind database ... But I'm sure that you can follow even without this setup):

The first thing was to define the different columns I'd like to show in the different contexts. To do this, I've created a file called ColumnConfiguration.config in the Web's root directory:

<ColumnConfigurations>
  <Configuration Table="Customers" Name="Phonelist" Columns="CompanyName, ContactName, ContactTitle, Phone, Country" />
  <Configuration Table="Customers" Name="Complete" Columns="*" />
  <Configuration Table="Products" Name="Shortlist" Columns="ProductName, UnitsInStock, QuantityPerUnit" />
</ColumnConfigurations>

I've then created a custom field generator (loosely based on the one which has been published last year with the ASP.NET dynamic data futures ... from which I also took the ColumnOrder-Attribute which I include for completeness' sake).

 public class ConfigurationBasedFieldGenerator : IAutoFieldGenerator
{
  private MetaTable _table;
  private bool _multiItemMode;
  private string _configurationName;

  public static Dictionary> _configurations = new Dictionary>();
  public static DateTime _nextRefresh = DateTime.MinValue;

  private void EnsureConfigurations()
  {
      lock (_configurations)
      {
        if (_nextRefresh > newConfigs = new Dictionary>();

          using (XmlReader rdr = new XmlTextReader(HttpContext.Current.Server.MapPath("~/ColumnConfiguration.config")))
          {
            rdr.Read();
            rdr.ReadToDescendant("Configuration");

            while (rdr.Name == "Configuration")
            {
              string name = rdr.GetAttribute("Name");
              string table = rdr.GetAttribute("Table");
              string columns = rdr.GetAttribute("Columns");
              List tmp = new List(columns.Split(','));
              for (int i = 0; i ().DefaultIfEmpty(ColumnOrderAttribute.Default).First();
  }

  public ICollection GenerateFields(Control control)
  {
    EnsureConfigurations();

    bool isWildcardConfig= true;

    string key = _table + "|" + _configurationName;

    List columnsToInclude=null;

    bool hasKey = _configurations.TryGetValue(key, out columnsToInclude);
    if (hasKey)
    {
      if (columnsToInclude.Count==0 || columnsToInclude[0] != "*")
      {
        isWildcardConfig = false;
      }
    }


    if (isWildcardConfig)
    {
      // use standard config, ordered by ColumnOrderAttribute - borrowed from an older version 
      // of the ASP.NET futures
      var fields = from column in _table.Columns
                   where IncludeField(column)
                   orderby ColumnOrdering(column)
                   select new DynamicField()
                   {
                     DataField = column.Name,
                     HeaderText = column.DisplayName
                   };

      return fields.ToList();
    }
    else
    {
      var allFields = from column in _table.Columns
                   where IncludeField(column)
                   select new DynamicField()
                   {
                     DataField = column.Name,
                     HeaderText = column.DisplayName
                   };

      List fields = new List();

      foreach (string col in columnsToInclude)
      {
        DynamicField field = allFields.FirstOrDefault(p=>p.DataField == col);
        if (field != null) fields.Add(field);
      }
      return fields;
    }
  }
}

// borrowed from an older version of the asp.net futures
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, Inherited = true, AllowMultiple = false)]
public class ColumnOrderAttribute : Attribute, IComparable
{

  public static ColumnOrderAttribute Default = new ColumnOrderAttribute(0);

  public ColumnOrderAttribute(int order)
  {
    Order = order;
  }

  /// 
  /// The ordering of a column. Can be negative.
  /// 
  public int Order { get; private set; }

  public int CompareTo(object obj)
  {
    return Order - ((ColumnOrderAttribute)obj).Order;
  }
}

To enable this custom field generator, I've changed the default List.aspx (and the other view as well) to explicitly use it:

protected void Page_Init(object sender, EventArgs e)
{
  table = GridDataSource.GetTable();
  string configurationName = Request.QueryString["config"];
  DynamicDataManager1.RegisterControl(GridView1, true /*setSelectionFromUrl*/);
  GridView1.ColumnsGenerator = 
       new ConfigurationBasedFieldGenerator(table, configurationName, true);
}

Et voilà. If I browse to http://localhost/Customers/List.aspx, I get the full list (as configured in the metadata), but if I browse to http://localhost/Customers/List.aspx?config=Phonelist, I only get the fields which have been configured in ColumnConfiguration.config (CompanyName, ContactName, ContactTitle, Phone, Country .... exactly in this sequence!). I could of course also define more than one additional view just by adding the entries to the configuration file.

And to take this one step further, you could imagine to also add attributes like "RoleName" to the configuration file to automatically check the user's role membership to ensure that only users with the correct access rights can view certain combinations of columns.

posted on Sunday, March 08, 2009 7:27 PM

# Link Listing - March 8, 2009 @ Monday, March 09, 2009 6:15 AM

Podcasts 10-4 Episode 10: Making Web Deployment Easier [Via: gduthie ] Code Cast #24 – On Functional...
Christopher Steen

# Multiple Form Configurations with ASP.NET Dynamic Data - Ingo Rammer's Weblog @ Tuesday, March 10, 2009 2:30 AM

Thank you for submitting this cool story - Trackback from DotNetShoutout
DotNetShoutout

# New and Notable 303 @ Tuesday, March 10, 2009 9:26 PM

&lt;p&gt;Windows Azure/Cloud Computing Cloud Computing Links March 10, 2009 Windows Azure and Cloud Computing Posts for 3/9/2009+ Agile/Mocks/Software Design/Software Development Tools Moq 3.0 RTM!!! Scott Ambler shares the the Manifesto for Software Craftsmanship which extends the Agile Manifesto . Framework...&lt;/p&gt;
Sam Gentile's Blog (if (DeveloperTask == Communication && OS == Windows)

# re: Multiple Form Configurations with ASP.NET Dynamic Data @ Monday, May 18, 2009 5:35 PM

The posted code is not correct, can you paste or escape correctly the code?

Costantino


Powered by Community Server, by Telligent Systems