Lambda-based binding for the business layer or your View Model
Lambda Bindings are built on top of the Lambda Dependencies project I published a while ago. The original Lambda Dependencies allow you to observe object graphs for changes using simple LINQ expressions. Lambda Bindings leverage this pattern by not just publishing a change event but synchronizing target properties or fields automatically.
This provides you with a generic binding framework that can be used wherever you want to synchronize objects. Let’s have a first example:
public void TestBinding(Student student)
{
string cityName = "";
var binding = LambdaBinding.BindOneWay(
() => student.School.Address.City,
() => cityName);
student.School.Address.City = "Sin City";
Assert.AreEqual("Sin City", cityName);
}
What’s happening in the snippet above is that I created a binding between a nested property of a referenced object and a local field. As soon as the binding source (the City
property of a school’s address) is changed, the local cityName
field is being updated as well.
However, the Lambda Dependencies not only cover the source properties but the whole object graph. Accordingly, exchanging the whole School
(or the Student
instance) also triggers an update. In the snippet below, the cityName
variable is being updated twice:
[Test]
public void Updating_Intermediary_Object_Should_Update_Target(Student student)
{
string cityName = "";
var binding = LambdaBinding.BindOneWay(
() => student.School.Address.City,
() => cityName);
student.School.Address.City = "Paris";
Assert.AreEqual("Paris", cityName);
School englishSchool = new School();
englishSchool.Address = new Address {City = "London"};
student.School = englishSchool;
Assert.AreEqual("London", cityName);
}
Value Conversion
You can do simple value conversion by just submitting a converter to the binding expression. This allows you to intercept the binding pipeline or bind objects of different types together. If you’re coming from WPF, this feels natural anyway, but the solution here does not require you to implement a value converter - a simple Func<TSource, TTarget>
is sufficient.
Here’s a simple sample that performs a conversion of a boolean flag to into a corresponding Visibility enum
value:
[Test]
public void Boolean_Should_Be_Converted_To_Visibility()
{
Window window = new Window { Visibility = Visibiliy.Collapsed };
MyViewModel viewModel = new MyViewModel { IsVisible = false };
LambdaBinding.BindOneWay(
() => viewModel.IsVisible,
() => window.Visibility,
b => b == true ? Visibility.Visible : Visibility.Collapsed;
viewModel.IsVisible = true;
Assert.AreEqual(Visibility.Visible, window.Visibility);
}
Two-Way-Binding
Two way binding works too, of course:
[Test]
public void Updates_Should_Work_Both_Ways()
{
var binding = LambdaBinding.BindTwoWay(
() => FirstStudent.Name,
() => SecondStudent.Name);
FirstStudent.Name = "Peter";
Assert.AreEqual("Peter", SecondStudent.Name);
SecondStudent.Name = "Parker";
Assert.AreEqual("Parker", FirstStudent.Name);
}
In case you need to perform type conversion, you need to supply two converter functions for forward / reverse conversion:
var binding = LambdaBinding.BindTwoWay(
() => ModelItem.IsEnabled,
() => MyControl.IsVisible,
b => b == true ? Visibility.Visible : Visibiliy.Collapsed
v => v == Visibility.Visible ? true : false);
Default Values
In case the object graph is being broken (e.g. because the School
was set to null
), the target node will be automatically set to its default value (null
for an object
, 0
for an int
, etc.). However, you can also specify a default value of your own:
private string schoolCity;
[Test]
public void Breaking_The_Chain_Should_Assign_Default_Value_To_Target_If_Specified()
{
var binding = LambdaBinding.BindOneWay(
() => Student.School.Address.City,
() => schoolCity,
"[No City]");
Student.School = null;
Assert.AreEqual("[No City]", schoolCity);
}
(BTW: the above snippet also shows you that you can easily bind to a field rather than a property).
Weak References
The underlying Lambda Dependencies only use weak references so you’re not at risk of creating memory leaks. However, LambdaBinding
implements IDisposable
, so the proper way to clean things up would be to dispose your binding.
Things to Consider
Remember that the underlying Lambda Dependencies rely on the INotifyPropertyChanged interface, so don’t expect source binding to fields (or properties that do not fire a PropertyChanged
event) to magically update your targets.
codeproject