Click here to Skip to main content
15,867,686 members
Articles / Web Development / ASP.NET

Using Dynamic Data Services in a WPF Application

Rate me:
Please Sign up or sign in to vote.
4.92/5 (9 votes)
3 Jan 2010Public Domain7 min read 38.3K   587   16   5
A short, didactic look at using the ASP.NET Dynamic Data Framework to provide a WPF application with data model metadata.

Background

I sat down to work through a book I found hidden and wanting attention in the office, Programming WPF, Second Edition, by Chris Sells and Ian Griffiths.

This is far from a book review, but let me say that although Chris Sells, I'm not buying. The book is technically good, but by now a little dated, and I found the examples very academic and impractical. I chose a project I'd been thinking of implementing on the WPF platform (in fact, I have all but abandoned WinForms, as I only code hobby projects for the desktop anyway), and set out applying principals covered by the book as I worked through it.

While I was very pleased to discover the DataGrid class (inSystem.Windows.Controls) new to .NET 4, I was somewhat disappointed with the lack of a column metadata service. For example, a column bound to a non-nullable string property will gladly accept an empty string, whether or not the binding is configured to treat empty strings as null; columns bound to boolean properties render cell values as "true" or "false"; numeric columns, despite offering 'default' validation, e.g., they won't accept non-numeric text values, or nulls if the underlying property is not nullable, also fall somewhat short of the configurability I have grown accustomed to in more mature grids and in the list views of ASP.NET Dynamic Data Framework based applications.

As I am busy with a series of in-depth articles on the inner workings of the Dynamic Data Framework and hard-core customization of the services provided by this framework, I set out to investigate using its rich, extensible, and extremely configurable metadata architecture outside of ASP.NET. This is the first of the two articles that detail my findings, where I take a superficial look at the MetaModel object that forms the foundation of Dynamic Data metadata and the use of some of the more basic metadata it provides. My next article will expand on this by eventually using all the metadata provided in the sample project I begin in this article.

Metadata uncommented

Although the heading hints at an epic quest, finding the metadata used by Dynamic Data is anything but. If we carefully sneak up on a new Dynamic Data website and catch it before we change anything, we will find the start of the trail in Global.aspx, where before any request for the site (viz. the IIS/ASP.NET application) gets served, our code registers a data context and sets up routing definitions. The data context can be either an ADO.NET Entity Data Model, i.e., an EF ObjectContext, or a LINQ to SQL DataContext.

In Global.aspx, one important line of code is camouflaged by several comment lines instructing us to uncomment it!

RegisterContext.png

Figure 1 - The commented out call to MetaModel.RegisterContext in Global.asax

Now, let's go through the motions and add an ADO.NET Entity Model to our project, and let Visual Studio generate it from that pillar of technical articles, the Northwind sample database. If we accept default naming all the way down, our context type will be NorthwindEntities, and the method call in Global.asax should now look like this, uncommented:

C#
model.RegisterContext(typeof(NorthwindEntities), 
      new ContextConfiguration { ScaffoldAllTables = true });

What does all this mean? At the top of Global.asax, we can see that the model is declared as type MetaModel. A look at MetaModel in the object browser shows one or two interesting looking properties, and one vaguely familiar method:

BrowsingMetaModel.png

Figure 2- Looking at the MetaModel class in the Object Browser.

Members worth mention here are:

  • GetTable(System.Type entityType)
  • Gets the metadata that describes the specified table, returning a MetaTable object that is, as you will see, the next clue on our quest to find the metadata!

  • RegisterContext(System.Type contextType, System.Web.DynamicData.ContextConfiguration configuration)
  • Registers a data-context instance by using the specified context configuration. ContextConfiguration is just used to pass the optional configuration parameters using the object initializer syntax, as seen above when we pass new ContextConfiguration { ScaffoldAllTables = true }.

  • VisibleTables
  • Returns a List<MetaTable>, with one MetaTable representing each table for which scaffolding is enabled.

    Scaffolding is the process of dynamically building a UI for a table at 'late runtime', i.e., only when an action on that table is requested, versus other strategies of using metadata to build UIs for tables by generating code at compile time, or during application load, or 'early runtime'.

A closer look at the MetaTable class and its enticing Columns collection (of positively sparkling MetaColumn objects) shows us we are pretty much sorted for metadata!

MetaTableProps.png

Figure 3 - Most MetaTable properties

MetaColumnProps.png

Figure 4 - Some MetaColumn properties

Testing the waters

This all looks good, but does it work outside of ASP.NET? There is no reason it shouldn't; even if you're a bit uneasy about having a System.Web reference in a desktop application. Let's run a quick and dirty check and get something like this going down in the constructor of a WPF Window:

C#
/// <summary>
/// Interaction logic for Window1.xaml
/// </summary>
public partial class Window1 : Window
{
    public Window1()
    {
        InitializeComponent();
        MetaModel model = new MetaModel();
        model.RegisterContext(typeof(NorthwindEntities), 
           new ContextConfiguration {ScaffoldAllTables = true});

        foreach (var table in model.Tables)
        {
            Debug.WriteLine(table.Name);
        }
    }
}

Output:

Categories
CustomerDemographics
Customers
Employees
Order_Details
Orders
Products
Region
Shippers
Suppliers
sysdiagrams
Territories
Listing 1 - A quick and dirty proof of concept

Diving in

The two validation services we are aiming to enhance in this article are provided by the DataGridTextColumn and its ancestor, the DataGridColumn. Other services we will be customizing in the next article can be found between these two classes and the one between them in the inheritance chain, the DataGridBoundColumn.

I set out to address the DataGrid normally, allowing null or empty values on non-nullable columns. Seeing as all this lovely metadata was throwing itself at me, I reasoned that preventing any editing at all where it shouldn't happen is 'a stitch in time', saving unnecessary further checks if values aren't even supposed to change.

Some browsing of the inheritance chain from DataGridTextColumn, which nearly all columns are by default, and DataGridColumn, the granddaddy of all DataGrid column types, shows that we can override DataGridTextColumn.GenerateEditingElement to guarantee a read-only editing element for all read-only columns, and we can override DataGridColumn.CommitCellEdit to complain when a required field is left empty.

Overriding both methods at the bottom of the chain makes most sense here, as we are only dealing with text columns for now.

C#
/// <summary>
/// An extension of the WPF DataGridTextColumn that recognises
/// column metadata provided by the Dynamic Data framework.
/// </summary>

public class DynamicDataGridTextColumn: DataGridTextColumn
{
    public MetaColumn MetaColumn { get; set; }

    // Override tests IsRequired rule for
    // all text columns (including numeric columns).
    protected override bool CommitCellEdit(FrameworkElement editingElement)
    {
        // Play it safe. Derived classes may not use
        // a TextBox as the editing element,
        // but then they must override this method.
        var etb = editingElement as TextBox;
        if (etb == null)
        {
            return base.CommitCellEdit(editingElement);
        }

        // Check for a required field.
        if ((MetaColumn.IsRequired) && (string.IsNullOrEmpty(etb.Text)))
        {
            // Not quite sure how the base does it, but close enough.
            etb.BorderBrush = Brushes.Red;
            etb.BorderThickness = new Thickness(1);
            return false;
        }
        return base.CommitCellEdit(editingElement);
    }

    // Override to enusre IsReadOnly rule for all
    // text columns (including numeric columns).
    protected override FrameworkElement 
              GenerateEditingElement(DataGridCell cell, object dataItem)
    {
        // This cast is 'guaranteed' by the documentation
        // for the base method, which says it returns a TextBox.
        var etb = base.GenerateEditingElement(cell, dataItem) as TextBox;    
  
        // Keeping it simple at this early stage.
        etb.IsReadOnly = MetaColumn.IsReadOnly;
        return etb;
    }
}
Listing 2 - Extending the DataGridTextColumn

Now, we can surreptitiously inject our DynamicDataGridTextColumn objects in among collections of lesser DataGridTextColumn objects and expect them to be treated equally, but how can we go about this? Let's encapsulate all the logic required to provide a DataGrid with a collection of columns into a new class, the DynamicDataGridBuilder. Using Generics, we can keep things safe and tidy, without any runtime type instantiation or other messy things, required. To use the builder class, you just declare it for a specific entity type (table) and the data context type it needs to handle.

This class only needs one public method at this stage, BuildColumns, which takes a DataGrid and adds one column for each column in the entity (table) that the class was declared for. I thought this would be an appropriate time to squeeze in a little freebie, being the use of a proper DataGridCheckBoxColumn for boolean underlying properties. Note: we aren't doing anything to this, nor are we using a derived type, as the checkbox column already has all we need. Right now. In the next article, I will expand this repertoire to include dropdown columns for foreign keys, up-down editors, numeric editors etc., as specialisations of existing column types. Here is the final code:

C#
public class DynamicDataGridTextColumn: DataGridTextColumn
{
    public MetaColumn MetaColumn { get; set; }

    // Override tests IsRequired rule for
    // all text columns (including numeric columns).
    protected override bool CommitCellEdit(FrameworkElement editingElement)
    {
        // Play it safe. Derived classes may not use a TextBox
        // as the editing element, but then they must override this method.
        var etb = editingElement as TextBox;
        if (etb == null)
        {
            return base.CommitCellEdit(editingElement);
        }

        // Check for a required field.
        if ((MetaColumn.IsRequired) && (string.IsNullOrEmpty(etb.Text)))
        {
            // Not quite sure how the base does it, but close enough.
            etb.BorderBrush = Brushes.Red;
            etb.BorderThickness = new Thickness(1);
            return false;
        }
        return base.CommitCellEdit(editingElement);
    }

    // Override to enusre IsReadOnly rule
    // for all  text columns (including numeric columns).
    protected override FrameworkElement GenerateEditingElement(
                       DataGridCell cell, object dataItem)
    {
        // This cast is 'guaranteed' by the documentation
        // for the base method, which says it returns a TextBox.
        var etb = base.GenerateEditingElement(cell, dataItem) as TextBox;    
  
        // Keeping it simple at this early stage.
        etb.IsReadOnly = MetaColumn.IsReadOnly;
        return etb;
    }
}
Listing 3 - Wrapping it all up in a builder class

That's all for now folks

There you have it! Two very small and simple classes that introduce a very under-utilsied framework into WPF, and quite nicely improves on the column editing capabilities of the WPF DataGrid. In my next article, I will take us through the exercise of recognising foreign key fields and building appropriate combobox editors for columns bound to these fields, as well as building custom editor controls for grid columns.

License

This article, along with any associated source code and files, is licensed under A Public Domain dedication


Written By
Founder Erisia Web Development
South Africa South Africa
I am a software developer in Johannesburg, South Africa. I specialise in C# and ASP.NET MVC, with SQL Server, with special fondness for MVC and jQuery. I have been in this business for about eighteen years, and am currently trying to master Angular 4 and .NET Core, and somehow find a way to strengthen my creative faculties.
- Follow me on Twitter at @bradykelly

Comments and Discussions

 
QuestionIs the next article finished already? Pin
rafa huys7-May-10 10:14
rafa huys7-May-10 10:14 
AnswerRe: Is the next article finished already? Pin
Brady Kelly7-May-10 11:44
Brady Kelly7-May-10 11:44 
GeneralRe: Is the next article finished already? Pin
rafa huys10-May-10 11:16
rafa huys10-May-10 11:16 
GeneralGenerateEditingElement in DataGridTextColumn Pin
klauswiesel22-Mar-10 23:36
klauswiesel22-Mar-10 23:36 
GeneralMy vote of 1 Pin
arvinder_aneja29-Jan-10 23:45
arvinder_aneja29-Jan-10 23:45 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.