TypeCasted

My .Net Adventure

IDataErrorInfo and FluentValidation

with 6 comments

I Got the chance to look at the FluentValidation.Net Framework this week. Overall its pretty spiffy! The API seems to handle most validation situations pretty well. Often times these things fall apart as soon as you run into a mildly complex situation such as dependent rule processing etc.  Using this for WPF and MVVM as it turns out is pretty simple. In this post I will show you a simple example of how to integrate FluentValidation.Net into your View Models, using the IDataErrorInfo interface and the databinding support provided by WPF.

Here is what I will cover in the post.

  1. Create a View for the MainWindow
  2. Create a ViewModel for the MainWindow
  3. Create a Validator for the MainWindowViewModel
  4. Setup Dependency Injection so ViewModels and Validators are both Injected as needed
  5. Implement IDataErrorInfo so it routes calls our Validator implementation
If you do not have Unity and the FluentValidation dll’s, please learn how to use NuGet and go get it.

The MainWindow View

Very basic XAML here the intent is only to get the moving parts setup, not to win a beauty contest. Two things to notice here.

  1. I have the binding set to update its source upon PropertyChanged this is not needed but I like instant feedback when possible.
  2. In order to support IDataErrorInfo implementations you must set the dependency property ValidatesOnDataErrors to true.

<ribbon:RibbonWindow
    x:Class="MoreFluent.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:ribbon="clr-namespace:Microsoft.Windows.Controls.Ribbon;assembly=RibbonControlsLibrary"
    Title="MainWindow"
    x:Name="RibbonWindow"
    Width="640"
    Height="480">
    <Grid
        x:Name="LayoutRoot">
        <Grid.RowDefinitions>
            <RowDefinition
                Height="Auto" />
            <RowDefinition
                Height="*" />
        </Grid.RowDefinitions>
        <ribbon:Ribbon
            x:Name="Ribbon">
            <ribbon:Ribbon.ApplicationMenu>
                <ribbon:RibbonApplicationMenu
                    SmallImageSource="Images\SmallIcon.png">
                    <ribbon:RibbonApplicationMenuItem
                        Header="Hello _Ribbon"
                        x:Name="MenuItem1"
                        ImageSource="Images\LargeIcon.png" />
                </ribbon:RibbonApplicationMenu>
            </ribbon:Ribbon.ApplicationMenu>
            <ribbon:RibbonTab
                x:Name="HomeTab"
                Header="Home">
                <ribbon:RibbonGroup
                    x:Name="Group1"
                    Header="Group1">
                    <ribbon:RibbonButton
                        x:Name="Button1"
                        LargeImageSource="Images\LargeIcon.png"
                        Label="Button1" />
                    <ribbon:RibbonButton
                        x:Name="Button2"
                        SmallImageSource="Images\SmallIcon.png"
                        Label="Button2" />
                    <ribbon:RibbonButton
                        x:Name="Button3"
                        SmallImageSource="Images\SmallIcon.png"
                        Label="Button3" />
                    <ribbon:RibbonButton
                        x:Name="Button4"
                        SmallImageSource="Images\SmallIcon.png"
                        Label="Button4" />
                </ribbon:RibbonGroup>
            </ribbon:RibbonTab>
        </ribbon:Ribbon>
        <Grid
            Grid.Row="1"
            Margin="7,7,7,7">
            <StackPanel
                Orientation="Horizontal"
                Height="24"
                Width="250">
                <Label>Name</Label>
                <TextBox
                    Width="150"
                    Text="{Binding Path=Name, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}"></TextBox>
            </StackPanel>
        </Grid>
    </Grid>
</ribbon:RibbonWindow>

Not So Fancy eh?

I suppose I should give you a screen shot. Not so pretty but good enough to get the point across!

Ugly Main Window!

And The Code Behind

And of course the CodeBehind. However minimal this is there are a few important things here to point out.

  1. The Dependency Attribute. Unity uses this attribute to facility property dependency injection upon build up.
  2. The setter for the ViewModel property wraps the DataContext making MainWindowViewModel the DataContext for the view.

using Microsoft.Practices.Unity;
using Microsoft.Windows.Controls.Ribbon;

namespace Validation
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : RibbonWindow
    {
        /// <summary>
        /// Gets or sets the view model.
        /// </summary>
        /// <value>The view model.</value>
        [Dependency]
        public MainWindowViewModel ViewModel
        {
            get { return (MainWindowViewModel)DataContext; }
            set { DataContext = value; }
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="MainWindow"/> class.
        /// </summary>
        public MainWindow()
        {
            InitializeComponent();
        }
    }
}

The MainWindow ViewModel

The MainWindowViewModel exposes a single property and the implementation of the IDataErrorInfo interface. Notice again we are relying on Unity to inject the Validator. In real life the the IDataErrorInfo implementation would reside in some sort of base ViewModel class. For example purposes I will not be doing that here.

Notice the IDataErrorInfo indexer, this forwards the call to the Validator asking it to run any rules associated to the supplied property name. If any rules are broken the error messages are returned in a single string with line breaks between each error message. Additionally the Error property asks the Validator to Validate all of the rules on a given object and return the enumerated results, again including line breaks.

using System;
using System.ComponentModel;
using System.Linq;
using FluentValidation;
using Microsoft.Practices.Unity;

namespace MoreFluent
{
    public class MainWindowViewModel : IDataErrorInfo
    {
        #region Dependencies

        /// <summary>
        /// Gets or sets the validator.
        /// </summary>
        /// <value>The validator.</value>
        [Dependency]
        public AbstractValidator<MainWindowViewModel> Validator { get; set; }

        #endregion

        #region Properties

        /// <summary>
        /// Gets or sets the name.
        /// </summary>
        /// <value>The name.</value>
        public string Name { get; set; }

        #endregion

        #region IDataErrorInfo Members

        /// <summary>
        /// Gets an error message indicating what is wrong with this object.
        /// </summary>
        /// <value></value>
        /// <returns>An error message indicating what is wrong with this object. The default is an empty string ("").</returns>
        string IDataErrorInfo.Error
        {
            get
            {
                return Validator != null
                    ? string.Join(Environment.NewLine, Validator.Validate(this).Errors.Select(x => x.ErrorMessage).ToArray())
                    : string.Empty;
            }
        }

        /// <summary>
        /// Gets the <see cref="System.String"/> with the specified property name.
        /// </summary>
        /// <value></value>
        string IDataErrorInfo.this[string propertyName]
        {
            get
            {
                if (Validator != null)
                {
                    var results = Validator.Validate(this, propertyName);
                    if (results != null
                        && results.Errors.Count() > 0)
                    {
                        var errors = string.Join(Environment.NewLine, results.Errors.Select(x => x.ErrorMessage).ToArray());
                        return errors;
                    }
                }
                return string.Empty;
            }
        }

        #endregion
    }
}

The Validator

Of course this would be incomplete without the Validator so here it is! Just one simple rule.

namespace FluentValidation
{
    /// <summary>
    /// Validator for the MainWindowViewModel
    /// </summary>
    public class MainWindowViewModelValidator : AbstractValidator<MainWindowViewModel>
    {
        #region Constructor

        /// <summary>
        /// Initializes a new instance of the <see cref="MainWindowViewModelValidator"/> class.
        /// </summary>
        public MainWindowViewModelValidator()
        {
            RuleFor(x => x.Name).NotEmpty().WithMessage("Name is Required");
        }

        #endregion
    }
}

The Unity Setup

Setting up Unity is simple. Two things need to be done.

  1. Remove the StartupUri from your App.xaml
  2. Override OnStartup in your App.xaml.cs

The App.xaml

<Application x:Class="MoreFluent.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <Application.Resources>
		<!-- Resources scoped at the Application level should be defined here. -->

    </Application.Resources>
</Application>

The App.xaml.cs

using System.Windows;
using FluentValidation;
using Microsoft.Practices.Unity;

namespace MoreFluent
{
    /// <summary>
    /// Interaction logic for App.xaml
    /// </summary>
    public partial class App : Application
    {
        protected override void OnStartup(StartupEventArgs e)
        {
            base.OnStartup(e);
            IUnityContainer container = new UnityContainer();
            container.RegisterType<AbstractValidator<MainWindowViewModel>, MainWindowViewModelValidator>();
            var mainWindow = container.Resolve<MainWindow>();
            mainWindow.Show();
        }
    }
}

Enjoy!

Advertisement

Written by brettedotnet

October 14, 2011 at 8:59 am

6 Responses

Subscribe to comments with RSS.

  1. Good post, I think the ViewModel code has been put where the CodeBehind code should be?

    chillfrie

    November 11, 2011 at 2:22 am

    • I am not sure I follow what you mean? What code do think should be in a code behind?

      brettedotnet

      November 11, 2011 at 3:16 am

      • Hi, the code snippet under
        “And The Code Behind” heading

        Is the same code snippet that sites under the “The MainWindow ViewModel” heading

        Both are – public class MainWindowViewModel : IDataErrorInfo

        good post though, helped me out!

        chillfrie

        November 11, 2011 at 3:35 am

      • Wow, how the heck did I miss that :-| Thanks for pointing it out! I have corrected this.

        brettedotnet

        November 11, 2011 at 4:05 am

      • no stress, awesome thanks

        chillfire

        November 11, 2011 at 4:06 am

  2. Great thing :)

    Szymon

    January 27, 2012 at 1:16 pm


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

Join 36 other followers