Introduction
This article has is not aimed at making you an expert in WPF, NHibernate, or whatever else. Its goal is to show how to make things work together in a nice way.
WPF is the new UI framework for Windows applications developed using .NET 3.x. It didn’t come to replace Windows Forms; at least, not for a while. It introduces new concepts which make the programmer's life easier, but difficult in one aspect: the design of the UI. I say that because in most cases, programmers are not good designers, so to get an awesome UI, a designer is required. I also believe that in the near future, WPF will be largely used in line-of-business applications. There are some already, but we will see the “big mass” using it.
M-V-VM Pattern is a relative of the MVC or MVP pattern; its main purpose is to make a UI more suitable to tests, separating the UI logic from real business logic.
NHibernate is an ORM tool inspired by the most successful ORM tool ever known: the Java Hibernate. I don’t need to say anything more about it…
NHibernate Validator was also ported from a “Java relative”. It is a validation framework which can be integrated in NHibernate. Well, I believe, its main purpose is that, but it can also be used to validate objects not worrying about how they will be persisted.
The simple demo
This simple application is just a contact list where you can add your contacts and their phone numbers. To edit code we require VS2008 SP1. All contacts information you add is stored in a Firebird embedded database.
Note: To run the demo, first, you have to edit the aliases.conf file, placed in the debug folder. Open it using Notepad, and adjust the database path to your computer. For example, if you download the files in the C:\My Documents\SimpleDemo folder, the correct value is WPFNH-Demo=C:\My Documents\SimpleDemo\Database\ContactBookList.fdb.
The business model
The business model is a separated assembly, Domain, which contains six classes as shown in figure 1:
Figure 1 - Overview of the Domain assembly
Domain also contains the NHhibernate XML mapping files for the Contact
and Phone
classes. They are defined as project resources. Now, let’s see some implementation details.
A base class
Open the BindableObject
class. Essentially, it is a version of the BindableObject
class available in the Podder project, but I did some extras in mine. Now, it implements IEditableObject
and IDataErrorInfo
. To implement the IEditableObject
interface, I just make use of Reflection to get all the public properties that can be written, and save them in a oldState
private field. If editing is canceled, I just rollback values.
Listing 1 – IEditable implementation
public virtual void BeginEdit()
{
oldState = new HybridDictionary();
foreach (PropertyInfo property in this.GetType().GetProperties())
{
if (property.CanWrite)
{
oldState[property.Name] = property.GetValue(this, null);
}
}
}
public virtual void EndEdit()
{
oldState = null;
}
public virtual void CancelEdit()
{
foreach (PropertyInfo property in this.GetType().GetProperties())
{
if (property.CanWrite)
{
property.SetValue(this, oldState[property.Name], null);
}
}
oldState = null;
}
For IDataErrorInfo
, I just delegate its implementation to inherited classes, see Listing 2.
Listing 2 – Delegating implementation
public abstract string this[string columnName] { get;}
public abstract string Error { get; }
The real classes
Take a look at the Phone
class. It makes use of NHibernate.Validator
to validate an object state. You can see a private field _validator
of type ValidatorEngine
. This is the type responsible for validating an object. But now, you might ask: how can I say what is valid? NHValidator can check the constraints defined by you. Theses constraints can be written using custom attributes or you can use XML files. In this demo, I’m using custom attributes, see listing 3.
Listing 3 – Defining a constraint
[NotNullNotEmpty]
public virtual string PhoneNumber
{
get { return _phoneNumber; }
set
{
_phoneNumber = value;
base.RaisePropertyChanged;
}
}
That constraint validates the PhoneNumber
property against two possibilities, a null value or an empty value.
Note: The NHibernate Validator also allows you to write your own constraints using custom attributes. For other constraints and examples, see the Links section.
How can we integrate this validation to the IDataErrorInfo
interface? It’s quite simple. As we implement IDataErrorInfo
, we make calls to the NHValidator methods. See Listing 4.
Listing 4 – Validating an object
public override string this[stringcolumnName]
{
get
{
string result = null;
if (columnName == "PhoneNumber")
{
_isValid = true;
InvalidValue[] validationMessage =
validator.ValidatePropertyValue(this, columnName);>
if (validationMessage.Length >= 0)
{
result = validationMessage[0].Message;
_isValid = false;
}
base.RaisePropertyChanged("isValid");
}
return result;
}
}
The ValidatePropertyValue
method from the _validate
field returns an array of found errors. It runs all the checking against one instance, represented by this reserved word. So, if validationMessage
array has elements, the current object is invalid. Again I say, there are other available methods to validate an object, see the Links section.
Notifying the UI
One of the things I like in WPF is its powerful databinding mechanism; we can easily show our object properties in controls. See Listing 5.
Listing 5 – Binding to UI
<StackPanel Orientation="Horizontal"
HorizontalAlignment="Center" VerticalAlignment="Center">
<TextBlock Text="Phone number" Margin="2,2,2,2"/>
<TextBox Text="{Binding Path=PhoneNumber,
UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=true}"
Style="{StaticResource textStyleTextBox}" Width="100"/>
<TextBlock Text="Type" Margin="2,2,2,2"/>
<ComboBox x:Name="cmbType" ItemsSource="{Binding Source={StaticResource EnumValues}}"
ItemTemplate="{StaticResource EnumValueTemplate}"
SelectedItem="{Binding Path=PhoneType}"
Width="100"/>
</StackPanel>
In Listing 5, we see the XAML from the edit phone window. In version 3.5 of .NET, we get some new features, and one of them is the introduction of the IDataErrorInfo
interface. IDataErrorInfo
allows us to validate an object inside the object itself. That is what is happening in Listing 4. When an object implements that interface, WPF can check its state through the property Validation.HasError
and respond in an appropriate way. In Listing 6, we have a Style
that will be applied to TextBox
es.
List 6 – Show validation messages
<Style x:Key="textStyleTextBox" TargetType="TextBox">
<Setter Property="Foreground" Value="#333333"/>
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="ToolTip"
Value="{Binding RelativeSource={RelativeSource Self},
Path=(Validation.Errors)[0].ErrorContent}"/>
</Trigger>
</Style.Triggers>
</Style>
Binding, but not gluing
Yes, we used databinding, but our objects are not glued to the UI. Between them, we have the M-V-VM pattern to manage the UI and business classes. Basically, think about the windows as the View, the business classes as the Model, and the View-Model is the class that will interact between them. Essentially, ViewModel classes prepare the model to be presented in Views, so it is more than an adapter; it creates the necessary properties and conditions to do easy data visualization. And also, it is the ViewModel class that is bound, not the real business object. To understand this, take a look at ContactViewModel.cs. There, we have new properties like selectedContact
, selectedPhone
which are used by the UI.
Conclusion
This was a simple, introductory demonstration of how powerful your applications can be. I didn’t show here a lot of things, but if you follow the links below, I believe most of your questions will be answered. This is my first article here, so I thank you for your time, and I say sorry for my bad English.
Links:
History
- 29-10-2008: First release.
- 29-10-2008: Corrected MS-Word codes.