Click here to Skip to main content
11,503,113 members (60,962 online)
Click here to Skip to main content

Safe WinForms Databinding

, 5 Aug 2009 CPOL 36.3K 777 36
Rate this:
Please Sign up or sign in to vote.
This article describes a very simple way to make WinForms databindings without the need to refer the datasource property names with magic strings.

SafeDataBinding

Introduction

To make a long story short, instead of writing:

textBoxCustomerName.DataBindings.Add("Text", bindingSource, "CustomerName");

my suggestion is to write something like:

dataSource.CreateBinding(textBoxCustomerName, ctl => ctl.Text, data => data.Name);

This way, your code will still run without any problems when you refactor your entities (let’s say, if you rename the customer’s Name property to CompanyName).

Why you should consider safe data-binding?

Microsoft has devoted a lot of time and effort to create a Rapid Application Development Environment. You can quickly create your WinForms application with Visual Studio by just doing some drag and drop and setting some data-source properties. You hit F5 and the whole thing works like it is supposed to work. You write some event handling code, check the behavior once again, and you can deploy your simple application to your customer. Very quick.

The problems start when you have to revisit your code to add functionality, to customize behavior for a second client, or to do some refactoring. The quick and dirty way to create a WinForms application by drag and drop is hard to maintain over time, and will require a lot of manual re-testing of your application.

For example, let’s say you have a Customer entity with Name, City, and InternalCode properties, and a couple of Windows Forms databound on this entity. After your initial deployment, you get a requirement to add a new ContactName property, and you decide to rename your old Name property to CompanyName. A simple thing like renaming one property will create chaos in all your forms which are databound on that Customer entity. Depending on the way data-binding is done, you will get run-time exceptions, or the data-binding will silently fail, showing controls and grid columns without any data.

This means you need extensive testing of your forms every time you touch your entities. You have to pay for human testing, or you have to invest a lot of time (= money) writing unit tests for your Windows Forms.

These high re-testing costs paid after every small change will create pressure on programmers. Programmers will have second thoughts when they have to change existing code – and this will mean less flexibility in adapting to customer specific needs, or degrading code quality (after all, in the previous simple example, you can decide to leave the Name property as it was and just add the ContactName, but this “no refactoring” approach will dramatically affect the code over time).

How should you do it?

One simple rule is “avoid using magic strings – no matter if you are writing the code or if the code is generated by the designers”.

I’m sure you had heard this one before (you probably had to define a lot of constants when you wrote your C programs?). But this time, defining constants would be of little use. Instead, we can use the ExpressionTrees in C# 3.0 to create a function which can return the name of any property. You can write:

var propertyName = FlexReflector.GetPropertyName<Customer>(item => item.Name)

instead of referring the Customer Name property with the magic string “Name”.

I know, it looks scary first, but next time you feel the need to rename that property to CompanyName, you will use the Rename options from Refactoring context menu (or Ctrl + “.”) and every piece of code in your solution will be updated to your latest change. No more manual testing for each form, no more fear of change, you get instant update of your binding code, or compile time errors that can easily be fixed. Bottom line, no more run-time errors means less testing needed.

The way you write code to get your property names can be improved to be less intimidating, too. For example, you can define an extension method on an object, and you will write:

string propertyName = customer.GetPropertyName( item => item.Name);

or you can add some handy methods and overloads on different objects often used for databinding (like datasources or datagrids), and you can write:

_dataSource.CreateBinding(textBoxCustomerCity, ctl => ctl.Text, data => data.City);

How does it work?

The core function here is FlexReflector.GetPropertyName which takes a lambda expression and returns the property name invoked in the lambda expression.

For those of you new to lambda expressions, the customer => customer.City expression can be interpreted like “given a customer object, take (and return) its City property”. Usually, lambda expressions are used instead of delegates or predicates, and are executed (evaluated) like any other delegate. For example, you can filter a customers array like this:

var fromMyTown = someCustomers.Where(item => item.City == "Bucharest");

In this case, the expression will be evaluated against each and every customer object in a foreach like loop, keeping just the customers on which the evaluation of that expression returns true.

But it’s possible for a programmer to inspect a lambda expression without executing it (this is used in things like LINQ to SQL to translate expressions from C# to TSQL instead of executing them in .NET). To be able to inspect such a lambda expression, you have to define a variable, or write a method which takes a special argument. The FlexReflector.GetPropertyName method looks like that:

public static class FlexReflector 
{ 
  public static string GetPropertyName<TItem, 
         TResult>(Expression<Func<TItem, TResult>> expression) 
  { 
    if (expression.Body.NodeType == ExpressionType.MemberAccess) 
    { 
        return (expression.Body as MemberExpression).Member.Name; 
    } 
  } 
}

FlexReflector.GetPropertyName<Customer>( item => item.City)

The compiler will not convert the lambda expression to a delegate (to execute it), but will generate a so called “expression tree” describing the code in the lambda expression.

This way, you can find information in the expression argument describing the operators and the names of the literals involved in the lambda expression. What you decide to do with this information depends only on your imagination – you can do a lot - from very simple things like getting some property name, to complex things like translating the whole expression to TSQL (see LINQ to SQL).

The sample code

The sample code illustrates a couple of very simple scenarios in which expression trees can be used to do a safer WinForms databinding.

The FlexBindingSource<TDataSource> encapsulates a standard System.Windows.Forms.BindingSource and provides typed access in situations where you need simple databinding (property – to – property) between textboxes and datasources. You can access the CurrentItem property to get or set entity objects to the FlexBindingSource in a type safe manner, and you will use the CreateBinding method to create databindings without any magic strings, like this:

_dataSource = new FlexBindingSource<Customer>();
_dataSource.CreateBinding(textBoxCustomerName, ctl => ctl.Text, data => data.Name); 
_dataSource.CreateBinding(textBoxCustomerCity, ctl => ctl.Text, data => data.City);

The method can enforce the type compatibility between the control’s property and the datasource property. For example, you can decide to bind DateTime datasource properties only to DateTime properties from specialized controls like DatePicker or custom DateTimeBox controls, and reject binding to the Text properties from TextBox controls (accept DateTime to DateTime bindings, reject string to DateTime bindings). Sometimes, without this kind of type check, it is possible to create some strange databindings, or you can have unwanted results after a property type change – that’s why I prefer using some custom controls like IntBox with an int value property, and I enforce the type compatibility between the datasource property and the control property. On the other hand, writing all this typed custom controls is not feasible in some situations, so it’s a matter of choosing a CreateBinding signature that does not enforce type compatibility.

public void CreateBinding<TControl>(TControl controlInstance, 
  Expression<Func<TControl, object>> controlPropertyAccessor, 
  Expression<Func<TDataSource, object>> datasourceMemberAccesor)

public void CreateBinding<TControl, TBindingType>(TControl controlInstance, 
  Expression<Func<TControl, TBindingType>> controlPropertyAccessor, 
  Expression<Func<TDataSource, TBindingType>> datasourceMemberAccesor)

Based on this TypedDataGrid, you can define a customer grid like this:

private class CustomerGrid : TypedDataGrid<customer />
{ 
    public CustomerGrid() 
    { 
        this.AddTextBoxColumn(item => item.Name, "Name", 300); 
        this.AddTextBoxColumn(item => item.City, "City", 150); 
    } 
}

To demonstrate the difference between standard and “safe” databinding, I encourage you to change the City or Name properties from the Customer class to CompanyCity or CompanyName (using (or not) Refactoring -> Rename option from the context menu).

After the name change, run the application and check the Standard and the Safe sample forms – for simple binding and for datagrid binding. You will notice that the standard binding forms will throw an exception or will no longer show data in the changed columns, but the “safe binding” forms are still working after the code change.

Is the same thing possible for earlier versions of the .NET Framework or in ASP.NET?

As a matter of fact, I initially implemented the “safe binding” idea in .NET 2.0 using the C# 2.0 compiler, without Expression Trees. It is possible to write a FlexReflector.GetPropertyName implementation using the RealProxy object from Remoting and a lot of CallContext conventions. You need to add some constraints as well (for example, you must derive your entities from MarshalByRef to be able to use RealProxy). It’s not so fun, but it can be done using .NET 2.0 or even 1.0.

On the other hand, I didn’t test safe databinding with ASP.NET - and I don’t recommend it for performance reasons. Expression trees have some small performance cost, so you will have to do some performance testing and decide when this technique is appropriate for performance-critical or more scalable systems.

The sample shows how to add columns by calling the AddTextBoxColumn method with a lambda expression defining the desired databound property, the text to be shown in the column header, and the desired width of the column. You can use the returning DataGridViewColumn to set any other style properties (like background color or font). The other example in the code sample defines a TypedDataGrid<TItem>. This is a base class that should be specialized in every form where you want to use it. Because this function takes an Expression<Func<Titem,TResult>> argument, when it is called with a version with type compatibility, it throws a NotSupportedException("This overload accepts only member access lambda expressions");.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Cosmin Oprea (aka somalezu)
Software Developer (Senior) Flex Solutions
Romania Romania
No Biography provided

Comments and Discussions

 
QuestionHave you considered using Extension methods? Pin
Roger Willcocks18-Dec-14 9:56
memberRoger Willcocks18-Dec-14 9:56 
QuestionMy vote of 5 + improovement and extensions Pin
Paganel9028-Aug-14 11:18
memberPaganel9028-Aug-14 11:18 
AnswerRe: My vote of 5 + improovement and extensions Pin
Cosmin Oprea (aka somalezu)1-Sep-14 20:56
memberCosmin Oprea (aka somalezu)1-Sep-14 20:56 
QuestionVery nice article but with some corrections to set Pin
Mystcreater25-May-12 4:29
memberMystcreater25-May-12 4:29 
GeneralMy vote of 4 Pin
Mystcreater22-May-12 15:51
memberMystcreater22-May-12 15:51 
GeneralSafe Binding using Lambdas Pin
Rolf Kristensen24-Feb-11 9:59
memberRolf Kristensen24-Feb-11 9:59 
GeneralMy vote of 5 Pin
edge942120-Nov-10 16:14
memberedge942120-Nov-10 16:14 
GeneralContributing an improvement Pin
edge942120-Nov-10 16:12
memberedge942120-Nov-10 16:12 
GeneralRe: Contributing an improvement Pin
Member 284026723-Nov-10 8:21
memberMember 284026723-Nov-10 8:21 
GeneralRe: Contributing an improvement Pin
edge942124-Nov-10 8:23
memberedge942124-Nov-10 8:23 
GeneralError on non string fields Pin
daryl_macam17-Apr-10 2:26
memberdaryl_macam17-Apr-10 2:26 
GeneralRe: Error on non string fields Pin
Cosmin Oprea (aka somalezu)18-Apr-10 19:08
memberCosmin Oprea (aka somalezu)18-Apr-10 19:08 
GeneralRe: Error on non string fields Pin
EmanuelHaisiuc6-Jul-10 8:15
memberEmanuelHaisiuc6-Jul-10 8:15 
GeneralRe: Error on non string fields Pin
hofingerandi13-Sep-10 23:02
memberhofingerandi13-Sep-10 23:02 
GeneralHello Cosmin - GOOD WORK! Pin
Mecu Sorin27-Aug-09 11:31
memberMecu Sorin27-Aug-09 11:31 
GeneralVery nice Pin
ignatandrei17-Aug-09 6:45
memberignatandrei17-Aug-09 6:45 
GeneralRe: Very nice Pin
Cosmin Oprea (aka somalezu)17-Aug-09 8:16
memberCosmin Oprea (aka somalezu)17-Aug-09 8:16 
Generalnice but .. Pin
gpgemini6-Aug-09 19:51
membergpgemini6-Aug-09 19:51 
GeneralRe: nice but .. [modified] Pin
Cosmin Oprea (aka somalezu)6-Aug-09 20:48
memberCosmin Oprea (aka somalezu)6-Aug-09 20:48 
GeneralRe: nice but .. Pin
gpgemini7-Aug-09 0:44
membergpgemini7-Aug-09 0:44 
GeneralRe: nice but .. Pin
Cosmin Oprea (aka somalezu)7-Aug-09 1:26
memberCosmin Oprea (aka somalezu)7-Aug-09 1:26 
GeneralRe: nice but .. Pin
Cosmin Oprea (aka somalezu)9-Aug-09 21:23
memberCosmin Oprea (aka somalezu)9-Aug-09 21:23 
GeneralRe: nice but .. Pin
gpgemini10-Aug-09 10:03
membergpgemini10-Aug-09 10:03 

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

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

| Advertise | Privacy | Terms of Use | Mobile
Web03 | 2.8.150520.1 | Last Updated 5 Aug 2009
Article Copyright 2009 by Cosmin Oprea (aka somalezu)
Everything else Copyright © CodeProject, 1999-2015
Layout: fixed | fluid