Click here to Skip to main content
15,867,308 members
Articles / Desktop Programming / XAML

Extending Central Silverlight Business Rules to Client and Server Side Field-Level Validation

Rate me:
Please Sign up or sign in to vote.
4.45/5 (6 votes)
25 Aug 2010CPOL27 min read 23.9K   157   9   5
ViewModel provides both IDataErrorInfo and collection binding for field-level errors and exceptions on client and server.

Introduction

Download Visual Studio Project - 67.64 KB

Typically IDataErrorInfo is implemented to support binding to error messages that are generated by validation as the user inputs each field. This project provides a simple mechanism to extend this binding to include error messages from validation on the server. This provides the additional benefit that business rule violations undetectable on the client can be displayed at the field level after being detected and reported back from the server. This extends the concept of reporting validation errors back from the server in a collection of strings in Central Silverlight Business Rules Validation by Michael Washington by including a property name with each error.

Image 1 

You can see an example in the screen shot above where IDataErrorInfo is utilized to cause the display of a business rule violation reported back from the server. In addition to IDataErrorInfo implementation to provide field-level error message binding, the ViewModel also provides a collection of errors the designer can bind to as you can see at the bottom of the screen shot.

In addition, any unexpected errors, such as exceptions thrown on the server, are also reported. For instance, let's assume that the unique name and birthdate rule are only enforced through a unique key on the Artists table. In that case you would see something like the screen below.

Image 2

Also see:

The Mechanics of IDataErrorInfo

A control can bind to the error information if the control's text property is bound to a member of a class that implements IDataErrorInfo. When the user changes the input value and tabs away, text binding causes the underlying property in the ViewModel to be updated by the property setter; when the setter subsequently raises the PropertyChanged event the error binding causes System.Windows.Data.BindingExpression to call the ViewModel's IDataErroInfo implementation to determine if the value is valid, and if not, pushes the error message, also provided by the IDataErrorInfo implementation, to the TextBox. Note that IDataErrorInfo only provides the error status and message. The error binding itself takes place through Data.BindingExpression, because in the text binding expression the designer has set ValidatesOnDataErrors = True, as shown below:

XML
<TextBox x:Name="txtName" Text="{Binding CurrentArtistBuffer.Name, Mode=TwoWay, ValidatesOnDataErrors=True}"/>

So if Name is required and the user changes it to a blank field and tabs away, this is what they would see.

Image 3

Everything I've described constitutes client-side validation. This article demonstrates how to use the same IDataErrorInfo / BindingExpression pattern to display business rule violations caught on the server.

The Demo

1. Any input errors or business rule violations detected prior to clicking ‘Add’ will be displayed as you enter them.

3. Any business rule violations detected on the server will be returned and displayed field by field. Any exception caught on the server will be displayed at the bottom of the page.

This project only supports the Add operation because I just wanted to focus on the validation issue. If you like this approach, it should be pretty easy to modify and extend to your own needs by just adding new tables and code based on what's already there.

The Project

I've given you a brief idea of how IDataErrorInfo and error message binding works and what to expect from this project. Let's get creation of the project out of the way so I can describe what's going on with actual code and pictures.

Image 4 

The database

Create a database called Music and add a table called Artists:

SQL
USE [Music]
GO
/****** Object:  Table [dbo].[Artists]    Script Date: 07/18/2010 12:00:17 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[Artists](
	[ID] [int] IDENTITY(1,1) NOT NULL,
	[Name] [nvarchar](255) NOT NULL,
	[DateOfBirth] [datetime] NULL,
	[BirthCity] [nvarchar](255) NULL,
	[BirthStateOrProvince] [nvarchar](255) NULL,
	[BirthCountry] [nvarchar](50) NULL,
 CONSTRAINT [PK_Artists] PRIMARY KEY CLUSTERED 
(
	[ID] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
CREATE UNIQUE NONCLUSTERED INDEX [IX_Artists_Name_DateOfBirth] ON [dbo].[Artists] 
(
	[Name] ASC,
	[DateOfBirth] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
GO

Note: The download contains a file, ReadMe_DatabaseSetup.txt, that contains the script for the table.

Add a Linq to SQL class to the MusicCatalog.Web site called Catalog.dbml.

Image 5

Resulting in:

Image 6

Select the Server Explorer and create a connection to the Music database.

Image 7

Drag the Artists table to the Object Relational Designer surface.

Image 8

Save and close the dbml file.

The Web Service

We'll start at the lowest level, which is our web service that actually updates the database. It contains a single Web Method, namely AddArtist. But the principle for all Web Methods will be the same; in addition to whatever the Web Method would ordinarily return it will also return a collection of PropertyError objects. There will be at least one PropertyError entry for each validation error found on the server; more if more than one property is involved. PropertyName will be null if the error is not related to a specific property.

C#
namespace MusicCatalog.Web
{
    public class PropertyError
    {
        public string PropertyName { get; set; }
        public string ErrorMessage { get; set; }

        public PropertyError()
        {
            PropertyName = string.Empty;
            ErrorMessage = string.Empty;
        }


        public PropertyError(string name, string message)
        {
            PropertyName = name;
            ErrorMessage = message;
        }
    }
}

Keep in mind, we create the PropertyError class on the hosting website where the Web Service runs. When we create the service reference in the Silverlight project later, the PropertyError class is included in the generated code, so a PropertyError created by the Web Service can be consumed directly in the Silverlight ViewModel.

So instead of just returning a record id, our AddArtist web method will return an ArtistID object:

C#
namespace MusicCatalog.Web
{
    public class ArtistID
    {
        public int ID { get; set; }
        public List<PropertyError>Errors = new List<PropertyError>();
    }
}

And here's the AddArtist Web Method:

C#
namespace MusicCatalog.Web
{
    [WebService(Namespace = "http://hscoders.org/")]
    [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
    [System.ComponentModel.ToolboxItem(false)]
    public class CatalogService : System.Web.Services.WebService
    {
		...
        [WebMethod]
        public ArtistID AddArtist(Artist artist)
        {   
            const string ERR_NAME_AND_DOB_MUST_BE_UNIQUE = "Name and Date of Birth combination must be unique.";
            const string ERR_NAME_IS_REQUIRED = "Name is required";
            const string NAME_PROPERTY = "Name";
            const string DATE_OF_BIRTH_PROPERTY = "DateOfBirth";

            ArtistID returnID = new ArtistID();

            try
            {
                if (artist.Name == null || artist.Name.Trim().Length < 1)
                    returnID.Errors.Add(new PropertyError(NAME_PROPERTY, ERR_NAME_IS_REQUIRED));
                else
                {
                    using (CatalogDataContext db = new CatalogDataContext())
                    {
                        var selectArtist = from artists in db.Artists
                                           where artists.Name == artist.Name
                                           select artists;
                        if (artist.DateOfBirth.HasValue)
                            selectArtist = from artists in selectArtist
                                           where artists.DateOfBirth.HasValue &&
                                                artists.DateOfBirth.Value.Date == artist.DateOfBirth.Value.Date
                                           select artists;
                        else
                            selectArtist = from artists in selectArtist
                                           where artists.DateOfBirth.HasValue == false
                                           select artists;

                        if (selectArtist.Count() > 1)
                        {
                            db.Artists.InsertOnSubmit(artist);
                            db.SubmitChanges();
                            returnID.ID = artist.ID;
                        }
                        else
                        {
                            returnID.Errors.Add(new PropertyError(NAME_PROPERTY, ERR_NAME_AND_DOB_MUST_BE_UNIQUE));
                            returnID.Errors.Add(new PropertyError(DATE_OF_BIRTH_PROPERTY, ERR_NAME_AND_DOB_MUST_BE_UNIQUE));
                        }

                    }
                }
            }
            catch (Exception ex)
            {
                returnID.Errors.Add(new PropertyError(null, ex.Message));
            }
            return returnID;
        }
	}
}	        

Creating The Service Reference

We're done for now on the hosting site. Now we need to create the Service Reference that will bridge the gap between the Web Service and the Silverlight application. Right-click on the MusicCatalog project and select Add Service Reference... from the menu that appears.

Image 9 

(1) Click the Discover Button (2) Enter CatalogService as the Namespace and  (3) Click the OK button

Image 10

This will generate a new namespace, CatalogServiceReference, containing classes that the Silverlight application uses to talk to the WebService. One class in particular provides the actual communication mechanism, CatalogServiceSoapClient. To add an artist, we make a call into the CatalogServiceSoapClient class from our model class, MusicCatalogModel:

The Model - MusicCatalogModel

C#
using Microsoft.VisualBasic;
using System;
using MusicCatalog.CatalogServiceReference;
using System.ServiceModel;

namespace MusicCatalog
{
    public class MusicCatalogModel
    {
        #region AddArtist
        public static void AddArtist(Artist theArtist, EventHandler<AddArtistCompletedEventArgs> completionHandler)
        {
            CatalogServiceSoapClient client = new CatalogServiceSoapClient();
            client.Endpoint.Address = new EndpointAddress(GetBaseAddress());
            client.AddArtistCompleted += completionHandler;
            client.AddArtistAsync(theArtist);
        }
        #endregion

        #region GetBaseAddress
        // GetBaseAddress courtesy of Michael Washington
        private static Uri GetBaseAddress()
        {
            // Get the web address of the .xap that launched this application     
            string strBaseWebAddress = App.Current.Host.Source.AbsoluteUri;
            int PositionOfClientBin =
                App.Current.Host.Source.AbsoluteUri.ToLower().IndexOf(@"/clientbin");
            // Strip off everything after the ClientBin directory         
            strBaseWebAddress = Strings.Left(strBaseWebAddress, PositionOfClientBin);
            Uri UriWebService = new Uri(String.Format(@"{0}/CatalogService.asmx", strBaseWebAddress));
            return UriWebService;
        }

        #endregion
    }
}

You can see in the code above that we reference MusicCatalog.CatalogServiceReference. This gives us access to the CatalogServiceSoapClient class. In the AddArtist method, we create an instance of the that type and make a call to CatalogServiceSoapClient.AddArtistAsync(). Since the call is asynchronous, the results won't be returned here when the AddArtist Web Method on the server completes. We have to specify where we want the results to be returned, so before we make the call we add an EventHandler to the CatalogServiceSoapClient.AddArtistCompleted event. By accepting this EventHandler as a parameter from the caller the Model is completely de-coupled from the ViewModel. This is an important point. If the model added its own method as the EventHandler then that method would need some reference back to the requestor. But if the EventHandler is just some function passed in as an argument, the model has no knowledge of the caller.

The ViewModel - MainPageViewModel

So as you can see, our AddArtist() method in the Model is a passive transmitter of requests to the Web Service. It's the ViewModel that knows what to do with it, so getting the results here by passing the EventHandler as an argument not only decouples the two, but it's more convenient.

As you know, MusicCatalogModel.AddArtist accepts an EventHandler as its second argument. If you look at MainPageViewModel.AddArtist in the code below, you'll see the EventHandler  makes up most of the code in the body of the AddArtist Method. If it's easier to think of it as a callback, then go ahead - everything after (sender, eventArgs) => makes up the callback function that we want to receive the ultimate results of the AddArtist call. If you're not familiar with this kind of syntax in passing an EventHandler, just picture everything after => as being the body of a method that you've coded elsewhere and given a name, and replace the entire second argument to MusicCatalogModel.AddArtist() with the name of that method, and in my opinion, make a point of learning this syntax in passing methods, meaning lambdas and delegates.

C#
using System;
using System.Windows.Input;
using System.ComponentModel;
using System.Collections.ObjectModel;
using MusicCatalog.CatalogServiceReference;
using System.Collections.Generic;

namespace MusicCatalog
{
    public class MainPageViewModel : INotifyPropertyChanged
    {
		...
        public void AddArtist(ArtistBuffer theArtistBuffer)
        {
            ClearStatus();
            MusicCatalogModel.AddArtist(theArtistBuffer.RawArtist, (sender, eventArgs) =>
            {
                // We're going to divide any errors into two lists. We'll make them public when we're done
                List<PropertyError> namedList = new List<PropertyError>();
                List<PropertyError> anonyList = new List<PropertyError>();

                // We're catching all exceptions in the WebMethod, so this should only be non-null if the service timed out
                if (eventArgs.Error == null)
                {
                    // Result is typed to ArtistID through type inference. The call to AddArtist accepts an EventHandler that takes
                    // an argument of type AddArtistCompletedEventArgs, which has a Result property of type ArtistID, which
                    // is originally defined on the website that hosts the web service and gets mapped here as a class in the 
                    // service reference namespace, CatalogServiceReference
                    ArtistID returnedArtist = eventArgs.Result;

                    // Check the Errors collection
                    if (returnedArtist.Errors == null || returnedArtist.Errors.Count < 1)
                    {
                        CurrentArtistID = returnedArtist.ID;
                        CurrentStatusMessage = "New Artist ID: " + CurrentArtistID.ToString();
                    }
                    else
                    {
                        // We have errors, split them up. In practice only one list will end up with entries
                        foreach (PropertyError propError in returnedArtist.Errors)
                        {
                            if (propError.PropertyName == null)
                                anonyList.Add(propError);
                            else
                                namedList.Add(propError);
                        }
                        bool bBadInput = false;
                        if (anonyList.Count > 0)
                            bBadInput = true;

                        if (namedList.Count > 0)
                        {
                            bBadInput = true;
                            foreach (PropertyError namedError in namedList)
                            {
                                // This will trigger IDataErrorInfo binding to controls that have ValidateOnDataErrors = True
                                theArtistBuffer.ValidateRule(namedError.PropertyName, namedError.ErrorMessage, () => (false));
                            }
                        }
                        // Draw attention to errors
                        if (bBadInput)
                            CurrentStatusMessage = "Correct input errors";
                    }
                }
                else
                {
                    // Overall web service exception
                    CurrentException = eventArgs.Error;
                }
                // Make the lists public
                ServerErrors = new ObservableCollection<PropertyError>(anonyList);
                PropertyErrors = new ObservableCollection<PropertyError>(namedList);
            });
        }
	}
}   

When we get the results in the event handler we update properties and collections that the View (MainPage) can bind to. If the call was successful, we update the CurrentArtistID and CurrentStatusMessage. But if not, we place the error information in collections the designer can bind to:

The eventArgs parameter is of type AddArtistCompletedEventArgs. This type is generated when we create the service reference namespace, CatalogServiceReference. We are interested in two properties of eventArgs, Error, and Result. The Error property is of type Exception. We should always find Error to be null unless the call times out. If any unhandled exception were to be thrown from the web method, the Error property would be populated with an Exception, but the message would always be Error occurred on the Server: Not Found, except in the case of the call timing out. Since we’re doing our own exception handling and the server is on our desktop we can only get an error here if we are in debug and let the call time out.

If Error is null, then we should find that Result is populated. Result is of type ArtistID in the CatalogServiceReference namespace. Since the Result property of AddArtistCompletedEventArgs is of type ArtistID, that's what we see in the event handler. The compiler know that by inference from the MusicCatalogModel.AddArtist() signature.

C#
public static void AddArtist(Artist theArtist, EventHandler<AddArtistCompletedEventArgs> completionHandler)

The second  parameter is an EventHandler that accepts a parameter of type AddArtistCompletedEventArgs.  The AddArtistCompletedEventArgs.Result property is of type ArtistID. So back in the event handler we see eventArgs.Result as a specific type, ArtistID

So, assume we find that the ArtistID.Errors collection has some entries. They can be of two types, Business Rule violations or an unexpected exception thrown on the server. In the latter case, the PropertyName property of the PropertyError object will be null.  I parse the PropertyError collection into two different lists and set the CurrentStatusMessage. Then, by calling ArtistBuffer.ValidateRule, I use each PropertyError that has a PropertyName to trigger Data.BindingExpression to fetch any error from IDataErrorInfo and push the error to the appropriate textbox, it the designer has bound to it, of course.

C#
foreach (PropertyError namedError in namedList)
{
    // This will trigger error message binding to controls that have ValidateOnDataErrors = True
    theArtistBuffer.ValidateRule(namedError.PropertyName, namedError.ErrorMessage, () => (false));
}

Implementing IDataErrorInfo

In our example project, the properties in ArtistBuffer are bound to from the Artist TextBoxes in the View (MainPage.xaml). By implementing IDataErrorInfo in ArtistBuffer, we support the ability of Data.BindingExpression to make calls against the IDataErrorInfo  implementation to determine the validity of and retrieve the error message relevant to each ArtistBuffer property. So any TextBox that binds to an ArtistBuffer property can set ValidatesOnDataErrors = True to bind to error messages generated by validation of that property. INotifyPropertyChanged implementation is also required to  cause the Data.BindingExpression validation to fire when the field loses focus.

C#
namespace MusicCatalog
{
    public class ArtistBuffer : INotifyPropertyChanged, IDataErrorInfo
    {
Here's the code required to implement IDataErrorInfo:
C#
public string Error { get { return null; } }
// The 'this' property indexer takes a property name and returns either an error message to be used in IDataErrorInfo binding or
// null if the property value is valid. This information is stored by ValidationHandler when we called ValidationHandler.ValidateRule
// in the setter for the property
public string this[string FieldName]
{
    get {return _validation.BrokenRuleExists(FieldName) ? _validation[FieldName] : null; }
}
We can ignore the Error property, it's intent is for errors that don't apply to a specific property. The IDataErrorInfo.this indexer is what we're interested in. When binding is triggered, the this indexer is queried with the name of the field being bound to. If the property is valid, it returns null, if invalid it returns an error message. As you can see above, instead of checking the current value of the field, we make a call to _validation.BrokenRuleExists[FieldName]. The _validation object is an instance of the ValidationHandler class created by John Papa.

ValidationHandler

If we look more closely at ArtistBuffer, we see it creates a ValidationHandler:

C#
namespace MusicCatalog
{
    public class ArtistBuffer : INotifyPropertyChanged, IDataErrorInfo
    {
        private ValidationHandler _validation = null;

        public ArtistBuffer(Artist artist)
        {
            _validation = new ValidationHandler();
            RawArtist = artist;
        }
        ...
     }
}

To put it into action, all we have to do is:

C#
namespace MusicCatalog
{
    public class ArtistBuffer : INotifyPropertyChanged, IDataErrorInfo
    {
		...
        public string Name 
        {
            get { return _rawArtist.Name; }
            set 
            {
               _validation.ValidateRule("Name", "Name is required", () => (value != null && value.Trim().Length > 0));
               _rawArtist.Name =  value != null ? value.Trim() : null;
               NotifyPropertyChanged("Name");
            }
        }
        ...
    }
}

In the setter, we call ValidationHandller.ValidateRule() with three arguments. 

  • The name of the field we want to validate.
  • The error message to return if it’s invalid.
  • A function that returns a Boolean, true if the field is valid.
Here’s the actual ValidateRule method in ValidationHandler:
C#
// From http://johnpapa.net/silverlight/enabling-validation-in-silverlight-4-with-idataerrorinfo/

namespace MusicCatalog
{
    public class ValidationHandler
    {
    ...
        public bool ValidateRule(string property, string message, Func<bool> ruleCheck)
        {
            if (!ruleCheck())
            {
                this.BrokenRules.Add(property, message);
                return false;
            }
            else
            {
                RemoveBrokenRule(property);
                return true;
            }
        }
    ...
    }
}    

If the function we passed returns false, ValidationHandler.ValidateRule() adds an entry to its BrokenRules dictionary, using the property and message parameters as the key and value respectively; otherwise it removes any existing entry keyed on that name. Now when the IDataErrorInfo.this indexer is called and checks ValidationHandler.BrokenRuleExists(), the result will depend on whether the entry was added or deleted by the ValidateRule() call we just made in the setter.

The important thing to take from this is that it all takes place in the scope of the setter, so to emphasize that, let’s review the sequence of events:

  • In the setter, we call ValidationHandler.ValidateRule()
  • ValidateRule adds an entry keyed on the name of the property being set to its BrokenRules dictionary if validation fails, otherwise removes it.
  • Back in the setter, when ValidateRule() returns, we set the value and call NotifyPropertyChanged.
  • The call to NotifyPropertyChanged triggers the binding to ValidatesOnDataErrors = True
  • This causes Data.BindExpression.DataErrorValidationRule to access the IDataErrorInfo.this indexer.
  • The indexer returns either an error message or null, depending on the value returned by ValidationHandler.BrokenRuleExists().

It may help to look at some of the code involved. I set a breakpoint on ValidationHandler.ValidateRule(). Let's take a look at what's going on when that breakpoint gets hit:

Image 11 

If you take a look at the call stack, you can see that the DateOfBirth property is being set as a result of the TextBox losing focus in the View.

Now I set a breakpoint on MusicBuffer.this resulting in:

Image 12 

The top line in the call stack is a little confusing until you look closely. It's not the DateOfBirth getter, it's the IDataErrorInfo.this getter - just look at the property name above the hightlighted source code.

You can see that binding code is being invoked going back the other way. When the setter was called, it was because the TextBox lost focus and it was time for the binding to update the MusicBuffer.DateOfBirth property. Now the call to NotifyPropertyChanged has triggered Data.BindingExpression.GetDataErrorInfoMessage to call our implementation of IDataErrorInfo.this  to get the error message, if any. Then, although we can't see it, control presumably bubbles back up to BindingExpression.DataBindingExpression.UpdateValue which continues on with pushing the error to the TextBox which changes its border and shows the error message if the user hovers the mouse over the little triangle or the field gains focus.. In this case 2/31/1944 is invalid, so when we called ValidateRule it added the BrokenRule to its dictionary, then when we call ValidationHandler.BrokenRuleExists on "DateOfBirth" we get back the message we originally passed to ValidateRule and we end up with:

Image 13

Now here's something you should be aware of. Let's say I'm in the debugger and I change the DateOfBirth input value to another invalid date, such as 2/31/1945 and tab away. When the setter calls ValidationHandler.ValidateRule this time, here's the result:

Image 14

There's already a dictionary entry for DateOfBirth so when I try to add a duplicate key it's throwing an exception. But the exception is thrown in the scope of a binding operation so it gets swallowed up somewhere and there is no indication there was a problem. If I just continue on by hitting F11, the next thing I see is:

Image 15

At some point after the dialog box told me I had an unhandled exception it got handled. From this point I can continue on and no error will be shown on the screen. As we go forward, keep in mind that when I continued on from the exception the next thing I saw is Data.BindingExpression fetching the property validation error from IDataErrorInfo as if the exception had never been thrown. Now here's what happens when I cause the same exception by calling the setter directly:

Image 16 

You can see there's no binding going on.  Next I get the same exception dialog as above (not shown). You'll recall that when I continued from there by hitting F11 the next thing I saw was IDataErrorInfo being called  by Data.BindingExpression to get the error state of the property. This time when I hit F11 I see:

Image 17

 Followed by:

Image 18

If I continue on I see:

Image 19 

So the message to take away from this is that if any of your code is hooked in the context of a binding operation anything that fails within that code will fail silently. Probably I should not be surprised by this. It's that same reason that practically speaking, you can't use an actual Artist.DateOfBirth property for the DateOfBirth TextBox to bind to. Any invalid date entered in the textbox will not be bound to the Artist.DateOfBirth property, meaning the conversion silently failed at some point  If your table accepts a null date and you enter an invalid one, validation will miss it. There's not even a way to test a DateTime? type for validity. It's either null or contatins a valid DateTime. So the null date silently slips through instead of the one you thought you entered.

All very philosophical, but we should fix the problem that brought this all up. We just replace the dictionary Add with an assignment, which is ok if you just want to overwrite the old value. It's conceivable you'd want to combine messages but not necessary here.

C#
namespace MusicCatalog
{
	// From http://johnpapa.net/silverlight/enabling-validation-in-silverlight-4-with-idataerrorinfo/
	public class ValidationHandler
	{
		public bool ValidateRule(string property, string message, Func <bool> ruleCheck)
		{
		    if (!ruleCheck())
		    {
		        // Add throws an exception if there is already an entry with the same key
		        // this.BrokenRules.Add(property, message);
		        // Assignment adds the key if necessary and doesn't throw an exception if it already exists.
		        // The previous value is overwrittern
		        this.BrokenRules[property] = message;
		        return false;
		    }
		    else
		    {
		        RemoveBrokenRule(property);
		        return true;
		    }
		}		    
	}
}

What About The Server Errors?

So now I've discussed ArtistBuffer and how it implements IDataErrorInfo and uses ValidationHandler; all of which explains the client-side validation, but, what about the calls to ArtistBuffer.ValidateRule?

C#
namespace MusicCatalog
{
    public class ArtistBuffer : INotifyPropertyChanged, IDataErrorInfo
    {
	//...
        public void ValidateRule(string property, string message, Func<bool> ruleCheck)
        {
            // Cause the property BrokenRule and message to be added to ValidationHandler (or removed)
            _validation.ValidateRule(property, message, ruleCheck);
            // Cause the binding to take place so the message shows next to controls that set ValidatesOnDataErrors=true;
            NotifyPropertyChanged(property);
            // We don't want this error to persist as it will interfere with normal client-side validation.
            _validation.RemoveBrokenRule(property);
        }
        //...
     }
}

We call ValidationHandler.ValidateRule() to force the BrokenRule to be added, then we call NotifyPropertyChanged to invoke the binding which brings up the error message. Immediately thereafter we call RemoveBrokenRule, because we have no way of determining when the condition has been cleared, so we want to trigger it one time, then clear the decks for client-side validation to take over. I’ve found that leaving the BrokenRule in place interferes with that.

Now I’m not sure how I feel about this next piece of code. Let’s say that the unique key violation has been triggered so there are errors on both the Name and DateOfBirth fields. I’d like for a change to either field to clear the error condition on both, or more precisely to trigger the binding on both that will clear the error messages. I can do that by calling NotifyPropertyChanged on both properties if either changes, as shown below in the Name setter. I do the same in the DateOfBirth setter. You may notice that the setter here differs from the example above. I've indulged my dislike of repetitive calls to value.Trim() at the expense of slightly obscuring what's going on with _validation.ValidateRule().

C#
namespace MusicCatalog
{
    public class ArtistBuffer : INotifyPropertyChanged, IDataErrorInfo
    {
	//...
        public string Name 
        {
            get { return _rawArtist.Name; }
            set 
            {
                if (value != null)
                    value = value.Trim();
                if (_rawArtist.Name == value)
                    return;
                // Substitute null for empty string
                bool bNullValue = string.IsNullOrWhiteSpace(value);
               _validation.ValidateRule("Name", "Name is required", () => (bNullValue == false));
               _rawArtist.Name = bNullValue ? null : value.Trim();
                // If an error exists on these two fields because of a unique key violation, changing one of them will clear both because
                // we call NotifyPropertyChanged on both. 
               NotifyPropertyChanged("Name");
               NotifyPropertyChanged("DateOfBirth");
            }
        }
        //...
    }
}

How Do I Control the Button's Enabled/Disabled State?

Support for validation message binding is about all you get from IDataErrorInfo. You're also going to need some way for the designer to control the Add button's enabled state based on whether all the data in CurrentArtistBuffer is valid, which brings us to ICommand.

It's ICommand that allows control events in the View to bind to actions in the View Model. When the user clicks the Add button in the View, MainPage, I want the AddArtist action to take place in MainPageViewModel, so I provide a public propery named AddArtistCommand. This property is a reference to an ICommand implementation. One of the ICommand methods is CanExecute(). When a button is bound to an ICommand, the button's enabled state is automatically bound to the ICommand.CanExecute() method. Also, when the button is clicked, the ButtonBase.ExecuteCommand calls ICommand.CanExecute() one last time, and if true is returned, calls ICommand.Execute() to actually carry out the operation.

Image 20

Image 21

It's important to note that enabling the 'Add' button through binding to CanExecute is completely separate from the IDataErrorInfo implementation. There may be a way to integrate the two by having CanExecute check ValidationHandler for BrokenRules, but you're probably going to want the code that enables or disables the button to do so without without triggering the error message binding - when the form first comes up for instance, so I just bite the bullet and do full on validation in CanExecute()

Or more precisely, in CanAddArtist(). Let's back up and look at the AddArtistCommand property in the MainPageViewModel. I define the AddArtistCommand property which I declare as type ICommand. I also provide two methods, AddArtist, and CanAddArtist. Now if you'll look in the constructor you'll see that I'm setting the AddArtistCommand property to an instance of DelegateCommand and passing the two methods to the constructor. The constructor sets internal references, canExecute and executeAction, to point to those two methods so they can be called to implement the two ICommand methods, CanExecute() and Execute(). You could write each implementation yourself, but using DelegateCommand is much easier. That's DelegateCommand, brought to you by the inimitable John Papa.

Here's the AddArtistCommand in MainPageViewModel.

C#
namespace MusicCatalog
{
    public class MainPageViewModel : INotifyPropertyChanged
    {
        public MainPageViewModel()
        {
           AddArtistCommand = new DelegateCommand(AddArtist, CanAddArtist);
		   InitializeCurrentArtistBuffer();           
        }    
       ...
        public ICommand AddArtistCommand { get; set; }
        
        public void AddArtist(object param)
        {
            AddArtist (param as ArtistBuffer);
        }

        private bool CanAddArtist(object param)
        {
            ArtistBuffer theArtist = param as ArtistBuffer;
            if (theArtist == null)
                return false;

            if (string.IsNullOrWhiteSpace(theArtist.Name))
                return false;

            if (theArtist.Name.IndexOf("<Required>") == 0)
                return false;

            if (string.IsNullOrWhiteSpace(theArtist.DateOfBirth) == false)
            {
                DateTime dateOfBirth = new DateTime();
                if (DateTime.TryParse(theArtist.DateOfBirth, out dateOfBirth) == false)
                    return false;
            }
            return true;
        }
        ...
	}
}       

As you'll see shortly, I initialize the Name field to "<Required>" as a hokey way to cause error binding to kick in if the user tries to change it to empty. If the text property starts out empty then tabbing away without entering anything will not trigger the binding because the value doesn't change. That's why CanAddArtist returns false if the NameProperty is "<Required>".

So far we have the enabled state of the button bound to the ICommand.CanExecute method. But this is a method, not a property, so how does the binding know when to call it? It turns out the button is also automatically subscribed to the ICommand.CanExecuteChanged event, which you can see being triggered here:

Image 22

In the code above, you can see CanExecute being called from within MainPageViewModel when one of the properties in CurrentArtistBuffer changes. The call to the canExecute reference executes CanAddArtist which validates the buffer contents and returns true, which in this case is a change from the current value of canExecuteCache, so the CanExecuteChanged event is fired. In the code below you can see ButtonBase.CanExecuteChanged catching the event and making its own call to CanExecute.

Image 23 

Here's the code in MainPageViewModel that makes that happen:

C#
namespace MusicCatalog
{
    public class MainPageViewModel : INotifyPropertyChanged
    {
	    ...
        void CurrentArtistBuffer_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
        // This call will cause the buffer to be validated, resulting in a call
        // to CanExecuteChanged if validity changes from what it is currently
            AddArtistCommand.CanExecute(CurrentArtistBuffer);
            RaiseCurrentArtistBufferPropertyChanged();
        }
        #endregion

        private void RaiseCurrentArtistBufferPropertyChanged()
        {
            NotifyPropertyChanged("CurrentArtistBuffer");
        }


        #region CurrentArtistBuffer
        // CurrentArtistBuffer is the instance of the ArtistBuffer class used for binding from MainPage.xaml. 
        private ArtistBuffer _currentArtistBuffer = null;
        public ArtistBuffer CurrentArtistBuffer
        {
            get { return _currentArtistBuffer; }
            private set
            {
                if (_currentArtistBuffer != value)
                {
                    _currentArtistBuffer = value;
                    RaiseCurrentArtistBufferPropertyChanged();
                }
            }
        }
        #endregion

        #region InitializeCurrentArtistBuffer
        private void InitializeCurrentArtistBuffer()
        {
            if (CurrentArtistBuffer != null)
                CurrentArtistBuffer.PropertyChanged -= CurrentArtistBuffer_PropertyChanged;
            CurrentArtistBuffer = new ArtistBuffer(new Artist());
            CurrentArtistBuffer.Name = "<Required>";
            CurrentArtistBuffer.PropertyChanged += new PropertyChangedEventHandler(CurrentArtistBuffer_PropertyChanged);
        }
        #endregion
}       

Actually, I have a problem with this code. In CurrentArtistBuffer_PropertyChanged, MainPageViewModel has to know to pass CurrentArtistBuffer to CanExecute(). And as I pointed out before, the button invocation of CanExecute will also pass CurrentArtistBuffer to CanExecute(). That's not good, two calls from different places have to pass the same buffer for this to work correctly. Let's look at how I would prefer to do it:

The Add button should continue to pass in CurrentArtistBuffer as the parameter and CanExecute should work exactly as it does now. What I would get rid of is the internal call to CanExecute() that exists entirely to trigger the CanExecuteChanged event if the validity changes. Instead, I would cause the binding to be triggered whenever CurrentArtistBuffer changed. There's even a ButtonBase.OnCommandPropertyChanged method just for that purpose. You can see it below called when the property is brand-new.

Image 24

You might think that changing CurrentArtistBuffer to a dependency property would help, but no. When the ICommand is first bound, the CommandParameter is wrapped in a dependency property, which you can see in the call stack above. Long story short, changing a CurrentArtistBuffer property is not the same as changing CurrentArtistBuffer. I can make the NotifyPropertyChanged("CurrentArtistBuffer") call, but unless the object has really changed, the DependencyObject wrapped around it isn't fooled. I proved this to myself in two ways. If I create a new ArtistBuffer object from CurrentArtistBuffer and then use it to replace CurrentArtistBuffer, the binding is triggered. Or even simpler, if I save CurrentArtistBuffer, then set it to null, then set it back to the original value, the binding is triggered, twice actually.

Image 25

I don't see either approach as practical.

I know I could create some boolean that would be set by validation and then passed to CanExecute instead of CurrentArtistBuffer, but that just moves the problem around. The validation call would still have to be made from within MainPageViewModel to cause the boolean to change, and anyway, when push comes to shove, I prefer that methods operate on parameters, not global variables. Here's the pattern we end up with:

  1. Binding causes ButtonBase to call CanExecute with CurrentArtistBuffer as the parameter when the page is initially loaded. In our case, the Name property is set to "<Required>", which CanExecute recognizes as invalid by convention, returns false and the Add button is disabled.
  2. The user changes a value in one of the TextBoxes and tabs away. Binding causes the underlying property setter in ArtistBuffer to update the property and if the value changed, fire the PropertyChanged event
  3. MainPageViewModel subscribed to ArtistBuffer.PropertyChanged when it initialized CurrentArtistBuffer; now it catches the event. Potentially the validity of CurrentArtistBuffer has changed, so it calls AddArtistCommand.CanExecute(CurrentArtistBuffer);
  4. CanExecute sees its parameter as just an object and calls the internal reference to MainPageViewModel.CanAddArtist(param).
  5. CanAddArtist casts param back to ArtistBuffer then validates the properties and returns true or false to CanExecute. If the value returned is different from the previous value then the CanExecuteChange event is fired.
  6. The Add button  was automatically subscribed to CanExecuteChange when it was bound to CanAddArtistCommand, so ButtonBase catches the event and in response makes its own call to CanExecute to see what the value is, passing CurrentArtistBuffer as the parameter.
  7. CanExecute again calls AddArtist which again validates the properties of CurrentArtistBuffer and the result is returned.
  8. ButtonBase sets the enabled state of the button accordingly.

The benefit of this approach is that CurrentArtistBuffer is validated one last time before the actual operation is carried out. Passing some boolean to CanExecute that represents the result of the most recent validation relies on the properties being unchanged since then, and  MainPageViewModel.AddArtist(), called by Execute(), would need a hard-coded reference to CurrentArtistBuffer. So, in order to allow CanExecute and Execute  to receive CurrentArtistBuffer as a parameter, I'm willing to live with the need for MainPageViewModel.CurrentArtistBuffer_PropertyChanged to also pass it as a parameter to CanExecute in order to trigger the CanExecuteChanged event. Not ideal, more the lesser of two evils.

The View - MainPage

To create the View, start by right-clicking on MainPage.xaml and opening in Expression Blend

Image 26 

Click on LayoutRoot in Objects and Timeline

Image 27

Under Properties/Common Properties click the 'New' DataContext button

Image 28

In the Select Object Dialog that appears, set it to MainPageViewModel.

Image 29

Create the User Interface. Let me point out in passing that strictly speaking I don't think any of the controls rerquire a name, but good luck keeping track of what's going on. If you're open to suggestions on when to name UIElements I'd say whenever it adds to clarity. Sometimes you can just move things around. I moved all the TextBoxes in the Objects and Timeline window so each falls after the TextBlock that provides its caption.

Image 30 

Now bind the individual Textbox Text properties to CurrentArtistBuffer properties in the MainPageViewModel. I think drag and drop is the easiest way to do this...

Image 31 

Make all the Textboxes visible in Objects and Timeline, click on the Data tab, then just drag each property from DataContext to the Textbox (or ListBox) you want it to bind to. I'll point out again that the textboxes can appear in any order in the Objects and Timeline window. I moved them for clarity - ok, I lie - I moved them to make the arrows look better - it turned out that it added to clarity.

Each Textbox Text property is now bound to a property in MusicBuffer. Because MusicBuffer implements IDataErrorInfo, each Text property can also bind to validation error messages so the message appears next to the textbox whenever the binding occurs and the value is invalid. There's one more step to actually bind to the error message through Data.BindingExpression, however, and that's to set ValidatesOnDataErrors = true in the xaml that binds the Text property.

XML
<TextBox x:Name="txtName" ...Text="{Binding CurrentArtistBuffer.Name, Mode=TwoWay, ValidatesOnDataErrors=True}"/>

Fortunately you can add this setting in design mode. Click on the textbox under Objects and Timeline, select the Properties tab, then advanced options next to the txtName Text Property 

Image 32

Select Custom Expression:

Image 33

You can now edit the expression that sets the Text Property of the txtName TextBox.

Image 34

Blend will catch errors if you get it wrong.

Image 35

Ultimately you want it to look like this:

Image 36

Assuming Name is required, the user would see something like this if they changed the name to blank and then tabbed away (and hovered the mouse over the little red triangle).

Image 37

What If I Don’t Want to Use IDataErrorInfo?

The display aspect of all our validation has been tailored to IDataErrorInfo. But just because we support it doesn’t mean the designer uses it. We need a more generic way to l make the PropertyError collection available to the designer, which we accomplish by doing just that. To make it simpler for the designer, I break it down into two collections, ServerErrors and PropertyErrors. So preparatory to doing that, I split the collection into two lists, anonyList and namedList, depending on whether PropertyName is null or not. Then I use those two lists to determine how to do some error formatting.

Finally I make them available as the PropertyErrors and ServerErrors collections. I’ll demonstrate the use of PropertyErrors in a listbox. I’m not going to show you all the code for the listbox; it’s there in the download if you want to look at it. I want to focus on the mechanics of the binding.

First of all, let’s give the designer the ability to determine if the list has any entries:

C#
namespace MusicCatalog
{
    public class MainPageViewModel : INotifyPropertyChanged
    {
	//...

        #region PropertyErrors
            private ObservableCollection<PropertyError> _PropertyErrors;
        public ObservableCollection<PropertyError> PropertyErrors
        {
            get { return _PropertyErrors; }
            set
            {
                if (_PropertyErrors != value)
                {
                    _PropertyErrors = value;
                    NotifyPropertyChanged("PropertyErrors");
                    NotifyPropertyChanged("HasPropertyErrors");
                }
            }
        }

        public bool HasPropertyErrors
        {
            get { return PropertyErrors != null ? PropertyErrors.Count > 0 : false; }
        }
        #endregion
	}
}        

I’ve added the HasPropertyErrors property.  It returns true if the PropertyErrors is not null  and not empty. Since HasPropertyErrors is directly dependent on PropertyErrors the PropertyErrors setter calls NotifyPropertyChanged(“HasPropertyErrors”), and HasPropertyErrors does not have a setter.

Putting my designer hat back on, I decide I want to have a Property Errors list box and I want to make it visible only if there are actually some Property Errors to display, don’t see any way to do that; so going back to programmer wear I whip up a value converter. (Actually, after watching some Ian Griffiths videos I've caved on the boolean/Visibility issue and would now just provide a Visibility property).

C#
namespace MusicCatalog
{
    public class BoolToVisibilityConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            return (bool)value ? Visibility.Visible : Visibility.Collapsed;
        }

        public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            return ((Visibility)value) == Visibility.Visible ? true : false;
        }
    }
}

Back in designer chic and thinking programmer guy should dress better, I setup the binding:

Image 38

(1) Select the ListBox whose visibility is to be bound

(2) Select the Advanced Options icon and then data binding from the dialog that will appear but is not shown.

(3) In the Create Data Binding dialog which is shown, select the DataContext tab

(4) Enter HasPropertyErrors in the Converter parameter text box.

(5) Select the BoolToVisibilityConverter from the Value converter dropdown. If it doesn’t appear, click on the browse (…) button next to the textbox to find it.

And click ok.

ListBox ItemTemplate

Because it's not really germane I'm just going to show you how to edit the ItemTemplate I already created in the downloaded project, you should be able to figure it out from there.

Image 39

You can see below how to bind the PropertyName to the first TextBlock, do the equivalent to bind the ErrorMessage property to the second TextBlock.

Image 40 

Hooking Up The Add Button

Now let's bind the View's Add button to the ViewModel's AddArtistCommand by dragging from the Data ContextPanel::

Image 41 

Make sure you see the message box shown above - here's what you Don't Want:

Image 42

Easy to avoid if you're paying attention but easy to miss if you're not.

Now drag CurrentArtistBuffer to the Add button:

Image 43

A Data Binding dialog will appear. Expand the dropdown, select CommandParameter and click OK:

Image 44

The Add button is now not only bound to the command, but ButtonBase is subscribed to the ICommand.CanExecuteChanged event and knows to respond to it by calling ICommand.CanExecute, passing CurrentArtistBuffer as the parameter.

And that should cover it. The main disadvantage to this approach is that you must duplicate business rules on the client and the server; the main advantage is that it's simple, which makes it easy to implement by just copying the code in this project and changing it to match your data. I may extend this to a full CRUD application but as far as the validation is concerned I don't think that will add much to what you already see here.

Points of Interest

  • Client and Server-Side Validation and Exception Handling
  • Error message binding through Data.BindingExpression and IDataErrorInfo
  • Error Persistence through ValidationHandler
  • The perils of Exceptions thrown within Binding operations.
  • ICommand Wrapper Class DelegateCommand
  • ICommand.CanExecute implementation details

Other Acknowdegements

DelegateCommand class from John Papa

License

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



Comments and Discussions

 
GeneralMy vote of 1 Pin
AndrusM30-Aug-10 23:41
AndrusM30-Aug-10 23:41 
GeneralINotifyDataErrorInfo exists, article describes bad programming practice and should re-written to use INotifyDataErrorInfo Pin
AndrusM30-Aug-10 23:39
AndrusM30-Aug-10 23:39 
GeneralRe: INotifyDataErrorInfo exists, article describes bad programming practice and should re-written to use INotifyDataErrorInfo [modified] Pin
Richard Waddell31-Aug-10 9:58
Richard Waddell31-Aug-10 9:58 
GeneralMy vote of 5 Pin
defwebserver26-Aug-10 5:44
defwebserver26-Aug-10 5:44 
GeneralMy vote of 5 Pin
Arun Jacob25-Aug-10 19:23
Arun Jacob25-Aug-10 19:23 

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.