Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

WPF Business Application Series Part 3 of n - Business Object Declarative Programming: How to Implement Object Validation and Logging

0.00/5 (No votes)
31 Mar 2008 1  
WPF VB.NET Business Object Declarative Programming: How to implement object validation and logging. This article shows how to decorate business entity objects for codeless entity validation and entity log message creation.

The video links require Microsoft Silverlight 1.0. If you do not have it, you will be prompted to install it when you click on one of the links. After the short installation is completed, close the browser window that you did the install in and re-click the video you want to watch. You can also download it here. Windows XP or Vista required.

Video demo of Declarative Programming UI Features

Table of Contents

Introduction

This is Part 3 in a series of articles on writing WPF Business Applications in VB.NET using Visual Studio 2008. This series tries to take a complex form or application and deliver articles on small pieces so that each piece can be individually understood.

If you have not yet read Parts 1 and 2, please take a few minutes and read them. This series builds on previous articles and does not repeat information.

This article will focus on "how to use" the business entity base and validation classes rather than "how these classes work". The next article will cover how the business entity base class and validation code actually works.

This article is not very sexy, but there is a lot of meat and functionality here, and is the reason I chose to write two articles instead of a single long one. Form and data object data validation is essential to any data entry or transactional processing application. The material and controls presented here will help the developer to accomplish these tasks in a very straightforward manner.

I know that this "how to" article is long. In the end, you will see that the entire article compresses down to using a few attributes on business entity objects and adding a few properties to the TextBox binding statements. It is that simple. So let's get our foundation so that we can take advantage of the features and code presented in this article.

I will be providing the full source code in C# in the future. Once the entire application has been completed (this should be before all articles are written), I'll translate and post the C# version. Some great C# developers from WPF Disciples have volunteered to help me with this important task. For now, any .NET language can use the "Core" assembly by referencing it.

Previous Articles
This Article
  • Part 3 Business Object Declarative Programming: How To Implement Object Validation and Logging
Future Articles
  • Part 4 Business Object Declarative Programming: How the Business Entity Base Class Works
  • Part 5 Business Object Declarative Programming: Dynamic Form Control
  • Part 6 Exception Handling and Logging
  • Part 7 Loading Menus and ToolBar From Object Source and Application Security
  • Part 8 Business Application MDI
  • Part 9 Displaying SQL Server Reporting Services Reports in WPF
  • Part 10 Complete Layered WPF Business Application
  • Part 11 Complete Layered ASP.NET Business Application

New to WPF?

There are many fine WPF authors here on CodeProject with great insight and code. If you are new to WPF, can I suggest that you bookmark this article and take the time to read the foundational tutorials that CodeProject MVPs Josh Smith and Sacha Barber have authored? In addition to their tutorials, each have many WPF articles here on CodeProject and their blogs with sample code.

I should also mention that the SDK Team has put a lot of work into providing good, simple examples in the WPF SDK that are easy to learn from. When you install Visual Studio 2008, this is also installed. Take advantage of this free code. There is also a new Framework 3.5 SDK that was recently released. There is an issue with the install package, and when you install this new 3.5 SDK, XAML intellisense will quit working. Please read my blog post for the Registry entry fix.

If you have not read the MSDN Data Binding Overview since the .NET 3.5 framework was released, please read it here. This documentation gives the reader a comprehensive understanding of the WPF data binding pipeline and the new features in .NET 3.5. The section on data validation is outstanding. Don't miss the Debugging Mechanism section near the bottom of the article that covers the PresentationTraceSources.TraceLevel attached property. When used, this property turns on a flow of great data binding debug information in the Visual Studio Output window. The data binding topic is essential for all WPF developers to understand.

Series Goal

The primary goal of the WPF Business Application Series is to assemble disparate pieces, tools, and technologies together, and deliver a maintainable, multi-tiered application that is documented, and most of the code can be or is actually generated.

Background

Sorry, it has taken me an extra 2-3 weeks to publish Part 3 of this series. I was at MIX08 for a week, and the background material for this article took me several weeks of research and code testing. The writing of the validation code took only two days, and was very straightforward. I got hung up in code generation, and have explained the issues at the bottom of this article.

What is object validation? When should objects be validated? Where should objects be validated? Should object validation code be in the UI? How do I perform object validation when I have several UI technologies like WPF, ASP.NET, Silverlight, and mobile devices being used to expose application data by consuming the same business objects? I will attempt to address these and other questions in this article.

The use of ASP.NET to consume these business objects will be a subject of the last article in the series, where I'll write an ASP.NET AJAX web site and use the same business objects this WPF application uses. Out of the box, ASP.NET controls do not support the IDataErrorInfo Interface like WPF and WinForms does. I'll show you how to do this in that article. In the next week or so, I'll be publishing an article entitled, "iPhone + ASP.NET + Silverlight = Cool Application" here on CodeProject that demonstrates consuming business objects that implement the IDataErrorInfo Interface and use this declarative business object validation model. Currently, Silverlight controls do not support the IDataErrorInfo Interface, but I'll show you a way to do this in that article.

These next short paragraphs are written to get you thinking about object validation and application maintainability. For many of these questions, there is no perfect answer, and you will find different opinions about each of them when you research them.

Object Validation

Object validation is the enforcement of business rules. An example of a business rule could be: the Email field is stored as lower case, is required, can't be longer than 50 characters, and must be a properly formatted Internet email address. Business rules are typically established and refined during the application requirements and specification phases. I store my requirements and specifications in a database so that I can use this valuable metadata for database and Stored Procedure generation, code generation, class library help file documentation, and printed documentation.

When to Validate

The question of when an object should be validated can draw widely different answers among architects and developers, and is closely tied to another question, "should objects allow invalid property value changes" or "should an object be allowed to be in an invalid state?" For example, if the user attempts to update email with the value "first name@.com", what should the business object do? Some would throw an exception because the email text string is not a properly formatted email, some would not accept the invalid entry and would report a broken rule, others would allow the property to be set, but mark the object as invalid and report the validation rule violation.

Another question is what to do with the invalid text in the UI textbox? Should the invalid email be removed from the textbox, or should it remain as is and then visually indicate that there is a problem so that the entry can be edited and corrected?

Try this one on for size; the user has a TextBox that is bound to an Integer property. The user enters a letter in the TextBox, now what should happen? Should the letter be allowed in the TextBox in the first place, should the invalid entry be removed, or should the invalid entry remain and then display a visual indicator that a data entry error occurred? Consider if your application UI has a WPF, WinForm, ASP.NET, Silverlight, and Mobile interfaces. Do you want all these UI interfaces to work the same? Meaning, if you are going to have a numeric textbox in WPF, are you going to write similar controls for the other UIs? Will your numeric textbox handle keyboard entry, cut and paste entries, drag and drop entries, stylus entries? This is a lot to think about.

In the above string in a textbox bound to an integer property example, the WPF data binding pipeline will actually throw an exception when trying to convert a letter to an integer. The business object's property value will remain unchanged. So now, we have a form that is invalid, and potentially a business object that is invalid. So you can easily see that we must ensure that both the form controls and the bound business objects are both valid before allowing an object to be persisted. We must also provide a unified way for both the form and business object errors to be displayed to the user. I will cover solutions for all these scenarios in this article.

In my applications, I allow the user to enter anything into a textbox and then validate and format the entry and display any input errors, including exceptions thrown by the data binding pipeline. Objects are validated before persisting or transferring the object. No invalid object is ever persisted or passed to another tier.

Remember that objects are not always bound to a UI. These objects must still be validated prior to being persisted or passed to another tier. The validation system presented in this article handles these cases with no additional code on the developer's part, except for actually checking if the object is valid.

Where to Validate

The question of where objects should be validated can also draw a wide spectrum of replies. Some place the validation rules within the business object, others encapsulate the validation logic in a separate class, and a business entity is validated against that class. I know of one project that uses Workflow to validate the objects as part of their processing pipeline.

I place the validation rules inside the business object. I can query the object directly for its validation state. This makes code generation super easy, allowing me to generate the object in its entirety in one file. To see what business rules make this object valid, I only need to look in one location. Also, if that object is transferred between tiers, the validation logic goes with it.

UI Validation

Another question that needs addressing is, "should validation logic be placed in the UI?" Let's use the above email field as our example. I have two applications that will be binding to the business object that has an email property, a WPF application and an ASP.NET AJAX web site. The email has a maximum length of 50 characters. Do we limit the length of the entered text by setting the MaxLength property on the TextBoxes? If you do, you have just placed a form of validation logic in your UI. And if we limit the length of the input, why not set up the RequiredFieldValidator and RegularExpressionValidator too? Relax; Karl is not a purist by any stretch of imagination, I'm just trying to get you to think.

Do I use ASP.NET validation controls? Answer; on every web page. It is not that much work to adjust a TextBox property when an application requirement changes. Before I get flamed, consider that I designed, developed, and maintain three separate applications deployed in too many different customer sites. Each application has 50 or less pages. Very easy for one developer to maintain since they very rarely change. Now, consider you have a team of developers and some were not involved in the original design or are new .NET developers. Now you need a change management process with good application documentation and good developer supervision. So you can see how your development environment comes into play in some of your decisions.

For both WPF and ASP.NET, I also cheat. I have a UI generation program I wrote for our ASP.NET applications. For our WPF applications, we have an application that allows us to drag and drop a column from a database to a form; the correct UI control and all properties are pre-populated for us. Remember that application design metadata that I have stored in a database, well, I use it to construct the XAML for the control. A simple drag and drop to Blend or Visual Studio, and presto, a control is on the form with all the required properties set. So for me, to add some business rules like max length, RegEx validations and required field validators to the UI is a snap. Also, I don't have developers entering tooltips since these are in the metadata also.

Let's take a step back and think about our long term, "Series Goal". Our goal is to generate as much code as possible and deliver maintainable applications. Let me ask you a question. If you have a large system and a business requirement comes down that the email fields are increasing by 10 characters in length, how much work would that be for you? Can you very easily identify all places in your application that now need attention?

This is where an argument for the business rules for the email field only being in one location gets a strong vote. However, even if this was the case, there are several other locations this same change would need to be made, such as the data layer where the email database parameter is created, and in every Stored Procedure where the email is a parameter along with the database column. These locations are code generated so that no human person would have to change a line of code unless there were some non-generated Stored Procedures or non-generated classes that were written to maintain the Email property separately from the whole business object.

So we can see that change management can drive your decision on how much validation to place in your UI. How often an application changes also comes into play. Additionally, each application has its own performance and scalability requirements. If you are designing a high volume web site, performance considerations will drive the application design. We can conclude that there is no "one size fits all" application design. Again, I'm just trying to get readers to think about application design and application maintainability.

It is easy to want to follow a purist design, but in real world applications, there are many factors that must be taken into consideration when designing maintainable applications. My suggestion; study and read system design and architecture. Know your application requirements for security, scalability, performance, durability, functionality, documentation, code generation, your staff's skill sets, maintainability, and change management requirements, and then make decisions that you, your team, and customers agree with, that will serve you the best over the long haul.

ToolBar Buttons and Data Layer

To keep this example as simple as possible so that the reader can focus on learning data validation, I have not yet implemented RoutedCommands for the ToolBar buttons and have not yet implemented a true data layer. These will both be implemented and made part of the code in a future article. For now, we are just covering one item at a time, attempting to take small steps.

BusinessEntityBase

Business object declarative programming empowers developers by adding a lot of functionality to their business entity objects with or without almost no code on their part. Classes that derive from BusinessEntityBase inherit all of its powerful features, as you will soon see.

I must give great credit to Rockford Lhotka's CSLA and the Microsoft Enterprise Library Validation Application Block. I studied both of these frameworks, and stepped through code for hours, learning from them and increasing my knowledge of .NET programming in general. Both of these solutions provide entity validation as part of their full featured frameworks. If you are familiar with either of these two solutions, you will find very similar patterns and code here. For example, methods for shared and instance rule declaration, management, invocation, and broken rule reporting. I learned and extracted from both of these frameworks, then added additional code that I needed. The reason I went to the trouble of writing this library was that I wanted to be able to maintain and fully understand all aspects of my Core library. In doing so, I also simplified and flatted class hierarchies where possible so that I could have the simplest possible code to maintain.

BusinessEntityBase Features

The BusinessEntityBase class provides the following features to deriving classes:

  • Shared and instance rule management, invocation, and broken rule reporting. This includes multiple ruleset capabilities.
  • Error reporting at the property and object level. Errors can be extracted as either a string or generic list.
  • dirty tracking.
  • Very simplified method for setters to call that encapsulates the validation, event raising, and property text case logic.
  • Audit message creation based on decorated properties. Message can be a string or collection of property name value pairs.
  • Message creation for the entire class. Message can be a string or collection of property name value pairs.
  • Change notification for all entity properties.
  • Changing notification for all entity properties.
  • Intelligent change notification for the IDataErrorInfo.Error property. This permits the Error property to be data bound and always be reported correctly in the UI.
  • Rich set of overridable methods that provide the deriving classes flexibility in programming business logic if required.

BusinessEntityBase Implemented Interfaces

  • System.ComponentModel.INotifyPropertyChanged - used to notify binding clients that a property value has changed. WPF and WinForms use this Interface.
  • System.ComponentModel.INotifyPropertyChanging - used to notify binding clients that a property value is changing. LINQ uses this Interface for change tracking.
  • System.ComponentModel.IDataErrorInfo - provides custom error information for each property and the object as a whole.
  • Core.BusinessEntity.IBusinessEntityAudit - provides for simple creation of audit or class; property, property value messages.

BusinessEntityBase Events

  • System.ComponentModel.PropertyChanged - raised when a property value changes.
  • System.ComponentModel.PropertyChanging - raised when a property value is changing.

BusinessEntityBase Public Properties

  • ActiveRuleSet - used to have a specific rule set checked in addition to all rules that are not assigned a specific rule set. For example, if you set this property to Insert, when the rules are checked, all general rules will be checked and the Insert rules will also be checked.
  • IDataErrorInfo.Error - string containing all entity validation errors, separated by a carriage return line feed. Has property change notification.
  • HasBeenValidated - indicates if the entity has been validated since the last change. Checking this property before calling CheckAllRules can save processing the rules on a valid object. Has property change notification.
  • IsDirty - indicates if the object has been changed since it was created or loaded. Has property change notification.
  • IDataErrorInfo.Item - string containing all property validation errors separated by a carriage return line feed.
  • ValidationErrors - a dictionary object of all broken entity validation rules.

BusinessEntityBase Public Methods

  • AddInstanceRule - used by sub-classes and classes that consume the sub-class business entity and need to add an instance rule to the list of rules to be enforced.
  • BeginLoading - called when the business object is being loaded from a database. This saves time and processing, by passing property setter logic during loading. After the business object has been loaded, EndLoading must be called.
  • CheckAllRules - validates the entity against all shared and instance rules.
  • CheckRulesForProperty - validates the property against all shared and instance rules for the property.
  • EndLoading - after a business object has been loaded and the BeginLoading method is called, developers must call this method. This method marks the entity IsDirty = False, HasBeenValidated = False, and raises these property changed events.
  • GetAllBrokenRules - a List of ValidationError objects for the entity.
  • GetBrokenRulesForProperty - a List of ValidationError objects for the property.
  • IsValid - runs the CheckAllRules method and returns a boolean indicating if the object is valid (does it pass all Shared and Instance rules).
  • ObjectPersisted - this method should be called by the business layer after a valid business object has been persisted to a database, Web Service, etc. Calling this method marks this business object as not dirty.
  • ToAuditDictionary - used to generate a Dictionary(Of String, String) for each property decorated with AuditAttribute. The Dictionary is property name, property value. This method also has an overload that allows a target IDictionary object to be passed in. This feature is useful when generating a name value pair dictionary to store in an Exception object's Data property.
  • ToAuditString - returns a string of each property decorated with the AuditAttribute. The string displays the property name, property friendly name, and property value. This function is typically used when a developer needs to make an audit log entry. It provides a very simple method to generate these messages.
  • ToClassIDictionary - used to generate a Dictionary(Of String, String) for each property in the entity. The Dictionary is property name, property value. This method also has an overload that allows a target IDictionary object to be passed in. This feature is useful when generating a name value pair dictionary to store in an Exception object's Data property.
  • ToClassString - returns a string with each property and value in the class. The string displays the property name, property friendly name, and property value.

The above ToAuditDictionary, ToAuditString, ToClassIDictionary, and ToClassString methods are all sensitive to the presence of the Caption attribute. If the Caption attribute is applied to the property, these methods will use that value for the friendly name. If not, these methods will use the Core.StringFormatting.CamelCaseString.GetWords method to parse the property name into a friendly name.

BusinessEntityBase Protected Methods

  • AddInstanceBusinessValidationRules - override this method in your business class to be notified when you need to set up instance business rules.
  • AddSharedBusinessValidationRules - override this method in your business class to be notified when you need to set up shared business rules.
  • AddSharedCharacterCasingFormattingRules - override this method in your business class to be notified when you need to set up shared character casing rules.
  • AfterPropertyChanged - derived classes can override this method to execute logic after the property is set.
  • BeforePropertyChanged - derived classes can override this method to execute logic before the property is set.
  • OnPropertyChanged - raises the PropertyChanged event, and invokes the AfterPropertyChanged method.
  • OnPropertyChanging - raises the PropertyChanging event, and invokes the BeforePropertyChanged method.
  • SetPropertyValue(Of T) - called by business entity sub-classes in their property setters to set the non-string value of the property and perform validation.
  • SetPropertyValue - called by business entity sub-classes in their property setters to set the string value of the property, and perform validation and string case correction.

Declarative Object Validation

If you have not read the MSDN Data Binding Overview since the .NET 3.5 framework was released, please read it here. This documentation gives the reader a comprehensive understanding of the WPF data binding pipeline and the new features in .NET 3.5. The section on data validation is outstanding. Don't miss the Debugging Mechanism section near the bottom of the article that covers the PresentationTraceSources.TraceLevel attached property. When used, this property turns on a flow of great data binding debug information in the Visual Studio Output window. The data binding topic is essential for all WPF developers to understand. (This paragraph is repeated from the introduction because it's very important.)

Public Class Customer _
       Inherits BusinessEntityBase

''' <summary>
''' Gets or sets the email of the customer
''' </summary>
''' <returns>Email of the customer</returns>
''' <remarks>This property demonstrates not allowing a Null String, 
''' requiring an entry between 1 - 50 characters and 
''' validating against a regular expression.</remarks>
<Audit(3)> _
<CharacterCasingFormatting(CharacterCasing.LowerCase)> _
<StringLengthValidator(1, 50)> _
<RegularExpressionValidator(RegularExpressionPatternType.Email, True)> _
Public Property Email() As String
  Get
      Return _strEmail
  End Get
  Set(ByVal Value As String)
      MyBase.SetPropertyValue("Email", _strEmail, Value)
  End Set
End Property

The declarative object validation system allows for rules to be applied to objects in three different ways.

The first way is to decorate the properties with Validator attributes. By decorating the property, the developer is declaring the rules, hence the term Declarative Programming that I used in the title. Some may call it Attribute Programming. A friend of mine who is a very smart programmer and author told me this is called Aspect-Oriented Programming. Regardless of the programming name, the two above Validator attributes, StringLengthValidator and RegularExpressionValidator, declaratively apply two shared business rules to the Email property. Looking at the above two attributes, a developer can easily see that the email length must be between 1 and 50, and must pass a Regular Expression validation for Internet email. One of the reasons I wrote this code to use attributes was because it made reading and generating the code super simple. If a developer wants to know about the Email property, just look at it, it's self explanatory.

The second way is to add shared validation rules in code. For those that do not want to or can't decorate their properties with Validator attributes, the BusinessEntityBase class provides the overridable method AddSharedBusinessValidationRules, which is called the first time the business object is instantiated. Some would ask, when would I not be able to add attributes to my properties? Good question, and one that caused me two weeks delay in this article. If you are generating your business entity objects and the tool you are using can't add these attributes as part of the generation process, you'll need to add your business validation rules in code. I have provided a discussion of code generation issues at the bottom of this article.

Protected Overrides Sub AddSharedBusinessValidationRules( _
          ByVal mgrValidation As Core.Validation.ValidationRulesManager)
    
  'demonstrates how to add a SHARED rule in code rather that using an attribute
  
  '<CompareValueValidator(ComparisionType.GreaterThanEqual, 2000, False)>
  
  'the following code does then same thing as the above attribute  
  
  mgrValidation.AddRule(AddressOf ComparisionValidationRules.CompareValueRule, _
    New CompareValueRuleEventArgs("YearJoined", _
        ComparisionType.GreaterThanEqual, 2000, False))
End Sub

In the above AddSharedBusinessValidationRules method, the developer would add the necessary code to add the required business rules for each property in the class. This code can also be easily generated from metadata and placed in a partial class if code generation tools don't allow the developer to modify their property declarations without the risk of losing their changes. This is the main reason I wrote my own code generator so that I could decorate my class properties the way I needed.

The third way to add a validation rule is to add one or more instance rules in code. The AddInstanceBusinessValidationRules overridable method is called each time a business object entity is instantiated. Deriving classes can override this method and add instance validation rules. The method provides a means of adding validation rules based on a value determined at run-time rather than a static value assigned to a property in an attribute.

Protected Overrides Sub AddInstanceBusinessValidationRules()
    'demonstrates how to add an instance rule based on a value 
    'determined at run-time. In this case the current year.

    MyBase.AddInstanceRule(AddressOf ComparisionValidationRules.CompareValueRule, _
      New CompareValueRuleEventArgs("YearJoined", _
      ComparisionType.LessThanEqual, Now.Year, False))
End Sub

Let's take a close look at the rule being added. The rule targets the YearJoined property. For this example, our application specifications call for the YearJoined property to be less than or equal to the current year. Attributes require that constant expressions be assigned to property values, so in order to get the validation rule to test against the current year, we add the validation rule in code and pass the Now.Year value when the rule gets created. This results in a validation rule that meets the business requirements.

In the above two examples, the first parameter is the delegate for the validation rule. At your option, you can easily add more validation rules to handle your business requirements.

Additional Declarative Object Validation Examples

<CompareValueValidator(ComparisionType.GreaterThan, 0.0#, True)> _
Public Property Deposit() As Double
    Get
        Return _dblDeposit
    End Get
    Set(ByVal Value As Double)
        MyBase.SetPropertyValue("Deposit", _dblDeposit, Value)
    End Set
End Property

The above example shows how to add a rule that validates the value is greater than zero. Note the use of the "#" to tell the compiler that a Double is being passed as the value to test against. The True parameter tells the rules engine that a value must be provided.

<Audit(4)> _
<CharacterCasingFormatting(CharacterCasing.OutlookPhoneProperName)> _
<Caption("Work Phone Number")> _
<StringLengthValidator(50, PropertyFriendlyName:="Work Phone Number")> _
Public Property WorkPhone() As String
    Get
        Return _strWorkPhone
    End Get
    Set(ByVal Value As String)
        MyBase.SetPropertyValue("WorkPhone", _strWorkPhone, Value)
    End Set
End Property

The above example shows how to add a rule that limits the length of a String to 50 characters but does not require an entry. Note the use of the PropertyFriendlyName property in the StringLengthValidator constructor. This provides a means to have friendly names for property validation error messages that are displayed to the user. Without the PropertyFriendlyName being assigned, the validation messages would use "Work Phone" in the message. By adding the PropertyFriendlyName value, the validation messages would now use "Work Phone Number". This is just an example of the capabilities of this validation code. In a real world application, this feature provides developers an easy way to have user friendly messages when the developer has no control over the property names or the property names are old, poorly named, or cryptic.

Validation Rules

  • Core.StringValidationRules.RegularExpressionRule - uses a Regular Expression to validate a String.
  • Core.StringValidationRules.StringLengthRule - validates a String, checking for Nulls, DBNulls, minimum and maximum lengths.
  • Core.ComparisionValidationRules.ComparePropertyRule - validates a property by comparing it against another property in the entity object.
  • Core.ComparisionValidationRules.CompareValueRule - validates a property by comparing it against a value.
  • Core.ComparisionValidationRules.InRangeRule - validates a property ensuring that its value is within the rule's lower and upper limits.
  • Core.ComparisionValidationRules.NotNullRule - validates a property ensuring that it is not Null or DBNull.

Validation Review

From the above code and examples, it's very easy to see that business entity validation is very straightforward and simple to implement. By deriving from the BusinessEntityBase class and simply decorating the entity properties, the developer can have entity validation with almost no programming effort. Even if the developer did not have the business rules stored in a database (which I strongly recommend for all projects), they could be applied with ease. Having the business entities generated really makes development painless and very accurate.

BusinessEntityBase.SetPropertyValue Method

This method does the heavy lifting for property value assignment. When I wrote this, I wanted the business entity classes to be as simple and clean as possible. One line of code meets this requirement: MyBase.SetPropertyValue("WorkPhone", _strWorkPhone, Value).

Public Property WorkPhone() As String
    Get
        Return _strWorkPhone
    End Get
    Set(ByVal Value As String)
        MyBase.SetPropertyValue("WorkPhone", _strWorkPhone, Value)
    End Set
End Property

The method encapsulates, and is responsible for:

  • Intelligently setting the entity sub-class private member value. The entity private member that stores the property value is passed by reference to this method. In addition to actually setting this value, it also permits this value to be modified by this method and reflected back to the calling sub-class. This modification of the value is used for String properties where the CharacterCaseFormatting attribute has been applied to the property and the formatting is applied within this method.
  • Raising the required PropertyChanging and PropertyChanged events.
  • Validating the property value against all rules for that property.
  • Modifying the entity state to mark it as dirty and that it has not yet been validated (IsDirty = True, HasBeenValidated = False). Raises the required PropertyChanged events for these properties.
  • If the property is a String and a CharacterCaseFormatting rule has been applied, reformat the value according to the rule.
  • By-passing the above logic if the entity is being loaded.

Character Case Formatting

In the above image, the first and last names have been formatted using ProperCase, the email was formatted using LowerCase, and the work phone was formatted using OutlookPhoneProperName. Let's see how this character case formatting works.

To have a property's String value case changed, apply the CharacterCaseFormatting attribute to the property. Character case formatting can also be applied in the deriving sub-class in the AddSharedCharacterCasingFormattingRules method. Applying the case formatting in code is used when the developer does not have access to the property to decorate it with attributes, or if the rules need to be dynamically applied based on business requirements.

Here is an example of the CharacterCasingFormatting attribute applied to an entity property:

<CharacterCasingFormatting(CharacterCasing.OutlookPhoneProperName)> _
Public Property WorkPhone() As String
    Get
        Return _strWorkPhone
    End Get
    Set(ByVal Value As String)
        MyBase.SetPropertyValue("WorkPhone", _strWorkPhone, Value)
    End Set
End Property

The following are the options for the character case formatting of Strings:

  • LowerCase - changes the String to lower case.
  • None - no changes made.
  • OutlookPhoneNoProperName - formats the String like a Microsoft Outlook phone number, but does not change the case of the text after the phone number.
  • OutlookPhoneProperName - formats the String like a Microsoft Outlook phone number and changes the case of the text after the phone number to proper case.
  • OutlookPhoneUpper - formats the String like a Microsoft Outlook phone number and changes the case of the text after the phone number to upper case.
  • ProperName - changes the case of the String to proper case.
  • UpperCase - changes the case of the String to upper case.

The proper case function is a custom function I have been using for many years in all my UIs. I demonstrated this function in the video at the top of the article. The name of the function is Core.StringFormatting.FormatText.ApplyCharacterCasing. As part of this function's processing, if the rule calls for proper casing, it checks all proper case rules in the shared Core.StringFormatting.CharacterCasingChecks.GetChecks method, in addition to some very general rules I have put into the function itself.

Have a look at the Core.StringFormatting.CharacterCasingChecks class. You will see how the rules are added once, and can come from code, an external file, or database. Let's take a look at a few rules in the CharacterCasingChecks.GetChecks method.

Public Shared Function GetChecks() As CharacterCasingChecks

    If _instance Is Nothing Then
        _instance = New CharacterCasingChecks
        'TODO Developers load this from a data base, config file, web service, etc.
        '
        'These are values that are specific to your company or line of business
        'remove the ones that don't apply and add your own.
        'ensure that the lengths of the LookFor and the ReplaceWith strings are the same
        _instance.Add(New CharacterCasingCheck("Po Box", "PO Box"))
        _instance.Add(New CharacterCasingCheck("Vpn ", "VPN "))
        _instance.Add(New CharacterCasingCheck("Xp ", "XP "))
        _instance.Add(New CharacterCasingCheck(" Or ", " or "))
        _instance.Add(New CharacterCasingCheck(" Nw ", " NW "))
        _instance.Add(New CharacterCasingCheck(" Ne ", " NE "))
        _instance.Add(New CharacterCasingCheck(" Sw ", " SW "))
        _instance.Add(New CharacterCasingCheck(" Se ", " SE "))
        _instance.Add(New CharacterCasingCheck(" Llc. ", " LLC. "))
        _instance.Add(New CharacterCasingCheck(" Llc ", " LLC "))
        
    End If
    
    Return _instance
End Function

You can easily allow your customers to have their own case correction dictionaries by loading these values from a disk file or database. Since the function only loads the collection once, there is no overhead in using this technique.

Business Form Binding to Business Entity Objects

Now that you have been introduced to entity validation, let's get into business form binding to business entity objects. When I use the term "form", I'm referring to a WPF UserControl that contains UI controls that make up the data entry or data maintenance form.

Before we can jump into writing our form, there are a number of WPF converters I need to cover first. These converters will be used by the form TextBoxes.

ForceReReadConverter

I posted the following INotifyPropertyChanged event fires, but UI not updated if business class changes property value after UI update on the MSDN WPF forum a while back. The basic problem is that if entity classes alter the value of the property that was set in the UI during a data binding operation, the altered value will not be re-read and displayed in the UI, even though the PropertyChanged event was fired. You can see that the character case feature of the BusinessEntityBase class would be worthless unless the UI re-reads the entity changed property value. Thankfully, the Microsoft WPF Team provided a technique for getting the data binding pipeline to re-read the property value from the entity after it is assigned during the data binding operation.

This technique involves placing a converter in the TextBox binding statement. The presence of the converter causes the data binding pipeline to re-read the property value before calling the converter. If the business entity object has modified the property value in any way, that new value will now be reflected in the UI.

Core.WPF.ForceReReadConverter is a dummy converter that returns the same value it was passed in. For any form TextBoxes that data bind to a property with character case rules, or any TextBox that data binds to a property that could potentially alter the value based on a business rule implemented in the entity object, you will need to add the ForeReReadConverter to that TextBox's binding statement.

This is where having the ability to generate business entities as well as form control code comes in real handy as developers don't have to remember to add this converter. The UI code generator automatically adds the required code. If you forget to add the converter, it will be obvious the very first time you enter data in the TextBox and the case does not change as expected.

Example showing how to force the FirstName property to be re-read after the property value is set on the entity:

<TextBox Text="{Binding Path=FirstName, Mode=TwoWay, 
  Converter={StaticResource forceReReadConverter}}" />

FormattingConverter

Core.WPF.FormattingConverter will format an object based on that object type's standard .NET format string or a custom format string. This is the converter that is used for formatting dates and numbers. I have an extensive blog post, formatting data binding dates and numbers, that you can read on this converter.

The FormattingConverter.ConvertBack code has been updated in this application from Part 2. If the ConvertBack operation fails, this function will return the value passed in, unmodified.

It is vital that your value converters not throw or be allowed to bubble exceptions because the data binding pipeline does not catch, swallow, or handle these exceptions. Instead, your users will get those messy exception messages because there is no method to catch and handle exceptions in the XAML markup. Ensure that if your converter fails that you gracefully handle this conversion or convert back operation.

Below is the new ConvertBack code:

Public Function ConvertBack(ByVal value As Object, _
  ByVal targetType As System.Type, ByVal parameter As Object, _
  ByVal culture As System.Globalization.CultureInfo) _
  As Object Implements System.Windows.Data.IValueConverter.ConvertBack

    Dim objTypeConverter As System.ComponentModel.TypeConverter = _
    System.ComponentModel.TypeDescriptor.GetConverter(targetType)

    Dim objReturnValue As Object = Nothing

    If objTypeConverter.CanConvertFrom(value.[GetType]()) Then

        Try
            objReturnValue = objTypeConverter.ConvertFrom(value)

        Catch ex As Exception
            'HACK - developers you have two options here.
            '1. Return nothing which in effect wipes out the 
            '    offending text in the binding control
            '
            'objReturnValue = Nothing
            '
            '
            '2. Return the value. 
            '    Then allow the data binding to fail further down the 
            '    chain, ie. when it attempts to bind to the entity object.
            '    This failure will be handled by the data binding pipeline.
            '
            objReturnValue = value
        End Try

    End If

    Return objReturnValue

End Function

Example formatting the Deposit property like 7,500.00:

<TextBox Text="{Binding Path=Deposit, 
  Converter={StaticResource formattingConverter}, 
  ConverterParameter=\{0:N\}}"  />

NullableIntegerFormatConverter

In order to data bind to an entity property that is a generic Nullable(Of Type), a WPF value converter is required. The Core library has four of these converters, NullableDateTimeFormatConverter, NullableDecimalFormatConverter, NullableDoubleFormatConverter, and NullableIntegerFormatConverter. If you need additional converters for other types, these are very easy to write.

These converters support both the IValueConverter.Convert and IValueConverter.ConvertBack functions, and correctly handle the setting of Null values in the entity object if the TextBox.Text property is an empty String.

Additionally, they serve as a formatting converter for these types.

Below is an example of the Core.WPF.NullableIntegerFormatConverter that will correctly set the entity property YearJoined to Null if the TextBox.Text property is an empty String, and also formats the number as a whole number without a thousand separator.

<TextBox Text="{Binding Path=YearJoined, 
  Converter={StaticResource nullableIntegerFormatConverter}, 
  ConverterParameter=\{0:D\}}" /> 

Reporting of Business Entity Validation Errors and Business Form Exceptions

The above image shows the unified reporting of a broken entity validation rule and a business form exception.

We have covered a lot of ground so far, and it's almost time to reap the benefits of our labors. Business entity validation errors are reported though the IDataErrorInfo.Error property. Remember, this String property represents each of the currently broken validation rules in the entity as a whole.

If we have business entity validation errors, then what are business form exceptions? I came up with this term that describes exceptions that are thrown either by validation rule checking code as a result of a broken rule or by validation code that had an unexpected exception thrown. The validation system presented here does not throw exceptions when property validation rules are violated; rather, property broken rules are reported through the IDataErrorInfo.Item property. However, if the validation code or property setter code throws an unexpected exception, it will be caught by the data binding pipeline and reported as a business form exception.

Another type of business form exception is an exception that is thrown by the WPF data binding pipeline when attempting to set a property value but the value is not of the correct type. Take the YearJoined property for example. This property is of type Integer. If the user enters "abc" in the TextBox and presses Tab, the WPF data binding pipeline will attempt to set the YearJoined property to "abc". Since this String can't be converted into an Integer, an exception will be thrown, caught by the data binding system and reported as a business form exception.

From the above paragraph, my theology on data entry forms comes to light. I prefer to allow users to enter data in TextBoxes, validate and report entry errors, and leave any invalid entry in the TextBox for the user to see and correct. Don't forget that TextBoxes in WPF can get input from the keyboard, copy and paste, drag and drop, or the stylus. I have seen code examples on the Internet for disabling paste operations in WPF TextBoxes, but don't feel that you should limit your users' ability to get data into their form TextBoxes.

I know the concept of entity validation errors and business form exceptions is a little difficult to understand. However, it is essential for proper and complete WPF data binding error reporting, so I'll cover it again from a different angle.

It is possible for the bound business entity object to be valid and the business form have an invalid entry in a TextBox. Let me provide an example. Let's say that the user entered all the form fields correctly. There are no validation errors on any of the form controls and the FormNotification control does not report any errors. Now the user goes to the YearJoined textbox and enters "abc" and presses Tab. The TextBox will now have a validation exception in its Validation.Errors collection. This makes the business form invalid. However, the bound entity object is still valid since the WPF data binding pipeline aborted the attempt to set the property and threw an exception. I have demonstrated this condition in the video at the top of this article.

I wanted to come up with a solution that required no additional coding on the developer's part to recognize and report this condition to the user. Additionally, as a developer, you do not want to allow Save, Update, or Insert commands to be executed if the form is invalid even though the business object is valid.

Let me give you the high points of the solution and the Core.WPF.UserControlBase class. The UserControlBase.ValidationExecptionErrors property, as previously stated, is a String that represents all Validation.Errrors exception messages for all the controls on the form. The UserControlBase class implements the System.ComponentModel.INotifyPropertyChanged interface and raises the PropertyChanged event for this property when an exception is thrown or validation exception errors are cleared from the internal collection. The UserControlBase.ClearValidationExceptionErrors method should be called by the entity sub-classes when a new record is loaded to create a new internal Validation.Errors cache in the UserControlBase class.

The UserControlBase class handles the System.Windows.Controls.Validation.ErrorEvent for all controls in the form. The RoutedEvent handler is established in the constructor of the UserControlBase class, as follows:

Public Sub New()
  'this adds a form level handler and will listen for each control that has 
  '  the NotifyOnValidationError=True and a ValidationError occurs.
  
  Me.AddHandler(System.Windows.Controls.Validation.ErrorEvent, _
    New RoutedEventHandler(AddressOf ExceptionValidationErrorHandler), True)

End Sub

The UserControlBase.ExceptionValidationErrorHandler method records the entity class name, property name, and a reference to the control that has the exception. All validation exception errors, including broken rules reported by the IDataErrorInfo.Item property, will raise this ErrorEvent. However, the ExceptionValidationErrorHandler method tests for ExceptionValidationRule, and only records exceptions and not the broken rules. Notice the above code comment requirement for the TextBox binding statement to have the property NotifyOnValidationError = True. This property instructs the data binding pipeline to raise the ErrorEvent when an exception is thrown during a data binding operation. I will cover all the required binding statement properties below.

You can see that the reporting and recording of data binding business form exceptions is very straightforward. The ExceptionValidationErrorHandler method handles both the adding and removing of exceptions from the internal collection.

Notice that I only record the entity class name, property name, and a reference to the control, and not the actual exception. When UserControlBase.ValidationExceptionErrors is checked, each form control that currently has an exception associated with it is checked and all exceptions are listed individually.

The UserControlBase.ValidationExceptionErrors property is checked quite often during form processing since this property is data bound to the FormNotification control and participates in the multi-value data binding along with the entity's IDataErrorInfo.Error property. Consequentially, this function must run super fast so that it will not slow down the tabbing between UI controls. I have seen developer solutions for reporting the current form exceptions by walking the logical tree and checking each TextBox for the presence of Validation.Errrors. The FormNotification custom control provides the developer the means to display, in real-time, all form exceptions and entity broken rules. By tracking the form exceptions and maintaining a collection of exceptions, the reporting of form exceptions is super fast and efficient and does not require walking the logical tree to discover exceptions associated with form controls.

FormNotification Control

The FormNotification control was the subject of Part 2 of this series. I have added FormNotificationErrorMessageConverter to the Core library to enable unified reporting of entity validation errors and business form exceptions. Since Part 2, I have modified the FormNotification control to sort the error messages it displays. This was required since we are now binding to two or more sources for the error information.

FormNotificationErrorMessageConverter

Core.WPF.FormNotificationErrorMessageConverter is a multi-value converter that is used to concatenate the validation error messages from the business entity and the business form exceptions into a single string. In reality, this converter actually concatenates two or more strings in a binding statement.

Below is an example of the Core.WPF.FormNotification.ErrorMessage property being data bound to two different objects that both report errors. This provides codeless unified error reporting from the business entity and the form. The error path is from the IDataErrorInfo.Error property of the form DataContext. The UserControlBase.ValidationExceptionErrors property is on the base class that all business forms derive from. This property is a String that represents all the exceptions in each of the form control's Validation.Errrors collection.

<Core_WPF:FormNotification WatermarkMessage="New Record" 
    Height="28" Panel.ZIndex="99" AutoCollapseTimeout="2">
    <Core_WPF:FormNotification.ErrorMessage>
        <MultiBinding Converter="{StaticResource formNotificationErrorMessageConverter}">
            <Binding Path="Error" />
            <Binding Path="ValidationExceptionErrors" ElementName="ucPartThree" />
        </MultiBinding>
    </Core_WPF:FormNotification.ErrorMessage>
</Core_WPF:FormNotification>

Form TextBox Binding Statement

In order to activate the behavior and functionality discussed in this article, a number of data binding properties must be set in each of the form TextBoxes. Let's have a look at the XAML for a form TextBox and get an understanding of the required properties.

<TextBox Text="{Binding Path=FirstName, 
  Mode=TwoWay, 
  UpdateSourceTrigger=LostFocus, 
  ValidatesOnDataErrors=True, 
  ValidatesOnExceptions=True, 
  NotifyOnValidationError=True, 
  Converter={StaticResource forceReReadConverter}}" />

The above Mode and UpdateSourceTrigger properties are both set to the default values for a TextBox, and are really not required to be set in the binding XAML. I have done this for the purpose of clarity since other WPF controls do not share these same default values. It is up to each software shop supervisor to establish coding standards and enforce them. Being consistent throughout all your code is very important from a maintenance standpoint.

ValidatesOnDataErrors

This property is new to .NET 3.5. From the MSDN documentation: Setting this property provides an alternative to using the DataErrorValidationRule element explicitly. DataErrorValidationRule is a built-in validation rule that checks for errors that are raised by the IDataErrorInfo implementation of the source object. If an error is raised, the binding engine creates a ValidationError with the error and adds it to the Validation.Errors collection of the bound element. The lack of an error clears this validation feedback, unless another rule raises a validation issue.

When a validation rule is violated and this property is set to True, the validation rule violation will be available for reporting to the user with the TextBox Validation.ErrrorTemplate. This is covered below. Note, the entity IDataErrorInfo.Error property will report any validation error regardless of this property's value since entity broken rule errors are discovered when the property setter is called which has nothing to do with this property. This property is only used to place entity broken rules in the TextBox's Validation.Errors collection.

ValidatesOnExceptions

This property is new to .NET 3.5. From the MSDN documentation: Setting this property provides an alternative to using the ExceptionValidationRule element explicitly. ExceptionValidationRule is a built-in validation rule that checks for exceptions that are thrown during the update of the source property. If an exception is thrown, the binding engine creates a ValidationError with the exception and adds it to the Validation.Errors collection of the bound element. The lack of an error clears this validation feedback, unless another rule raises a validation issue.

When an exception is thrown during the update of the source and this property is set to True, the validation rule violation will be available for reporting to the user with the TextBox Validation.ErrrorTemplate. This is covered below.

NotifyOnValidationError

From the MSDN documentation: If the binding has ValidationRules associated with it, the binding engine checks each rule each time it transfers the target property value to the source property. If a rule invalidates a value, the binding engine creates a ValidationError object and adds it to the Validation.Errors collection of the bound object. When the Validation.Errors property is not empty, the Validation.HasError attached property of the object is set to True. If the NotifyOnValidationError property of the Binding is set to True, then the binding engine raises the Validation.Error attached event on the object.

This is the property that allows the UserControlBase class to monitor binding exceptions by handling the Validation.Errror attached event. This was covered above.

When set to True, this property instructs the WPF data binding pipeline to raise the Validation.Error event for all data binding rule violations and exceptions. This is why the UserControlBase.ExceptionValidationErrorHandler tests for the type of validation exception, and only adds or removes binding exceptions and not binding validation rules to the internal collection of UI controls with exceptions.

Form TextBox Validation.ErrrorTemplate

The below code is from the OfficeBlueResourceDictionary.xaml file.

<ControlTemplate x:Key="validationTemplate">
    <DockPanel>
        <DockPanel.Resources>
            <Core_WPF:ValdiationErrorGetErrorMessageConverter 
              x:Key="valdiationErrorGetErrorMessageConverter" />
        </DockPanel.Resources>
        
        <TextBlock Margin="5,0,5,0" Foreground="Red" FontSize="16" 
          VerticalAlignment="Center" Text="*" 
          ToolTip="{Binding ElementName=errorAdorner, 
            Path=AdornedElement.(Validation.Errors), 
            Converter={StaticResource valdiationErrorGetErrorMessageConverter}}" />
        
        <AdornedElementPlaceholder x:Name="errorAdorner" />
        
    </DockPanel>
</ControlTemplate>

<Style TargetType="{x:Type TextBox}">
    <Setter Property="Validation.ErrorTemplate" 
      Value="{DynamicResource validationTemplate}" />
</Style>

Let me cover the Core.WPF.ValdiationErrorGetErrorMessageConverter and then walk through this template.

ValdiationErrorGetErrorMessageConverter

The Validation.Errors attached property returns a collection of all active ValidationError objects on the bound element, in this case the TextBox. The ValidationError object can be a broken entity validation rule or an exception that occurred in the WPF data binding pipeline.

In most example code I've seen, including the MSDN documentation, the ToolTip used to report the broken rule or exception to the user uses the ErrorContent property similar to this: Path=(Validation.Errors)[0].ErrorContent. The problem with using this code for the ToolTip text source is that some exceptions that are reported in the WPF data binding pipeline are wrapped in other exceptions. Displaying a TargetInvocationException message to a user or yourself is worthless, and should not be done.

This is where the ValdiationErrorGetErrorMessageConverter comes into play. This converter will look for an inner exception and returns that message instead of the outer exception message. If the inner exception is Nothing, then the ErrorContent property is returned. Additionally, this converter is passed in the entire Validation.Errors collection, and will report all active data binding errors and not just the first one. Having more than one broken rule is not the norm, but is possible, so let's code for this condition.

In the above template, the Validation.Errors collection is passed to this converter using this code: Path=AdornedElement.(Validation.Errors).

Validation.ErrorTemplate

Each developer or designer will have their own take on ErrorTemplates. The look and usage is normally determined during the application specification phase. For this application series, I have decided to display a red * next to UI controls that have broken validation rules or data binding exceptions. One reason for doing this is, when a new and empty form is displayed, several controls have the red * next to them; these are the controls that are bound to the required entry properties. Users generally associate the red * with a required input field. By doing this, the red * serves two purposes. One by initially reporting required entry fields to the user that have not yet been filled in, and error reporting for data entry errors and exceptions.

Another reason for displaying a red * in the template TextBlock is so that the error can be displayed in a ToolTip. A lot of example code and the MSDN documentation use a trigger on the target TextBox to set the TextBox ToolTip to the Validation.Errors[0].ErrorConent property. However, if you use this technique and the TextBox has an assigned ToolTip, the error message will not be displayed in the TextBox ToolTip. This may be a bug, I'm not sure. Triggers are supposed to replace values when active, but for some reason, the ToolTip's content does not get changed if it has content, but only gets set when the ToolTip content is empty. You can test it out and verify this behavior in your own code. I set ToolTips for all my form controls to assist users, so I had to place the ErrorContext in another control's ToolTip.

PartThree.xaml and PartThree.xaml.vb

Take a look at the PartThree.xaml file. As you can see, this is very simple and light XAML markup. I have already covered most of the markup in this file, so let's move on to the PartThree.xaml.vb code file.

Private Sub NewRecord()
    Me.frmNotification.NotificationMessage = String.Empty
    Me.frmNotification.ErrorHeaderText = "Required Items"
    MyBase.ClearValidationExceptionErrors()
    _objCustomer = New Customer()
    Me.DataContext = _objCustomer
    'this is optional.
    'this will cause any edit errors to display
    _objCustomer.CheckAllRules()

End Sub

Private Sub btnSave_Click(ByVal sender As Object, _
  ByVal e As System.Windows.RoutedEventArgs) Handles btnSave.Click
    '
    UpdateFocusedField()

    If Not MyBase.HasValidationExceptionErrors AndAlso _
        _objCustomer.IsValid Then
        
        'simulate saving the record in a database.
        Me.frmNotification.NotificationMessage = _
           "Customer record saved"
    Else
        Me.frmNotification.ErrorHeaderText = "Save Errors"
    End If

End Sub

The NewRecord method initializes the FormNotification control, clears out any previous ValidationExceptionErrors in the base class, creates a new Customer object and assigns it to the form's DataContext, then runs the CheckAllRules method against the new Customer object to force all invalid properties to report their status.

Nothing in this method is new or noteworthy except for the second line, Me.frmNotification.ErrorHeaderText = "Required Items". Josh Smith and I were speaking on the phone about the Part 2 article, and he suggested that the initial message displayed by the FormNotification control not say "Entry Errors", like it did in Part 2. So I have set the ErrorHeaderText to display "Required Items" when a new record is displayed. This makes the interface cleaner, more accurate, and user friendly. Josh, thank you for the suggestion, it enhances the UI.

The btnSave_Click event handler is very similar to the same handler from Part 2, with some minor changes. UpdateFocusedField was covered in Part 2.

New to this handler is the testing for business form exceptions by checking the HasValidationExceptionErrors property. If any form exceptions are present or the business entity is not valid, the save routine will not be called.

When I implement RoutedCommands in the ToolBar buttons, it will be very easy to program the CanExecute method for the Save button. The HasValidationExceptionErrors property can be tested, and returns an accurate result for the CanExecute method. The difference between the ValidationExceptionErrors property and the HasValidationExceptionErrors property, besides that one returns a String and the other a Boolean, is when the ValidationExceptionErrors is read, it actually reads through the collection and creates a new String. The HasValidationExceptionErrors just checks the current internal validation errors collection for the presence of items in the collection, without any processing required.

Notice what happens when the Save button is clicked and the form has exceptions or the business entity has broken rules. The FormNotification control ErrorHeaderText property is changed from "Required Items" to "Save Errors" with the following code: Me.frmNotification.ErrorHeaderText = "Save Errors". This enhances the user's experience as I have stated above.

If you sit back and think about it, this form has very little code for all the functionality it provides. All the time you have spent reading and studying this material will pay huge dividends in your application development. With the material presented here, you can quickly decorate your entity classes as required, set up your binding statements as demonstrated, and you have all this functionality with very little effort on your part.

Declarative Logging

The above image shows the result of the call to the ToAuditString method.

I added the logging methods and attributes to this library to make it very easy to generate predefined audit messages for entities and to generate property name and property value pair collections when logging exceptions.

<Audit(1)> _
Public Property FirstName() As String
    Get
        Return _strFirstName
    End Get
    Set(ByVal Value As String)
        MyBase.SetPropertyValue("FirstName", _strFirstName, Value)
    End Set
End Property

The key to the audit messages is that they are predefined. A developer does not have to take time coding audit messages, and when changes to the entity are made, no code has to be touched to add or remove properties from the generated audit messages. Remember that our business entities are generated from the specification metadata. As changes are made to the application over time, the new entities are regenerated and the Audit attributes are applied automatically.

The constructor of the Audit attribute takes an optional Integer parameter that specifies the sort order for the property in String audit messages or in the property name, property value Dictionary.

The logging generation methods are all located in the BusinessEntityBase class, and have been previously discussed.

Private Sub btnSave_Click(ByVal sender As Object, _
  ByVal e As System.Windows.RoutedEventArgs) Handles btnSave.Click
    '
    UpdateFocusedField()

    If Not MyBase.HasValidationExceptionErrors AndAlso _
       _objCustomer.IsValid Then
       
        'simulate saving the record in a database.
        Me.frmNotification.NotificationMessage = "Customer record saved"
        '
        '
        MessageBox.Show(_objCustomer.ToAuditString(String.Empty, vbCrLf), _
          "Customer Class Audit Message", MessageBoxButton.OK, _
          MessageBoxImage.Information)
           
        MessageBox.Show(_objCustomer.ToClassString(vbCrLf, True), _
          "Customer Class Message", MessageBoxButton.OK, _
          MessageBoxImage.Information)
    Else
        Me.frmNotification.ErrorHeaderText = "Save Errors"
    End If

End Sub

Run the program. Fill in the form and press the Save button. The first message box displays a ToAuditString. Check out the string that is returned. You can easily reformat this string to meet your requirements. I use these audit strings when saving log messages in the database or event log. Since all four audit methods are members of the Core.BusinessEntity.IBusinessEntityAudit Interface, the entity object can be passed to other library objects that consume IBusinessEntityAudit objects, and the log processing can be placed in other classes.

The second message box displays a ToClasssString.

I use the ToAuditIDictionary method when logging an exception, and place the results of this method in the Exception.Data property. Since our company has customers all over the United States, comprehensive exception reporting is vital to excellent customer support and troubleshooting, should the need arise. Getting the business object values at the time of the exception can be helpful when trying to troubleshoot remotely or troubleshoot a nightly process that threw an exception. These logging methods provide the necessary functionality for logging entity member values.

Code Generation Requirements

I stated at the beginning of the article that I was delayed in getting this article out because I was looking at code generation tools. Here is Karl's short list of requirements for a code generation tool:

  • Apply metadata to entity property and classes from external metadata source in the form of Summary and Remarks XML comments.
  • Generate the Parameters, Exceptions, and Returns XML comment fields.
  • Decorate classes, properties, and methods with required attributes for LINQ, WCF, Astoria, and user driven requirements like this validation system.
  • Apply namespace to entities.
  • Apply base class to entities.
  • When generating Select and SelectAll data access and business entities, ensure that if both return the complete entity to use the same object. Currently, SQLMetal generates different entities for each operation.
  • (...)

Someone would ask, what does code generation have to do with business entity object validation? It has nothing to do with validation, but it has everything to do with delivering a stable, maintainable, and documented application to your customer. This series is about writing a small, medium, or large business application. For these types of applications, code generation is an imperative.

Starting with metadata, the generator should be able to crank out documented database objects, documented business entities, documented business layer objects for processing the business entities, and documented data layer code. We must position ourselves to generate this code. Take a small application with 75 tables, for example. Now, start counting the number of CRUD Stored Procedures, data layers classes and access methods, business entities, and textboxes in the UI, and you'll quickly agree that if we can generate most of this code, we are way ahead of the game. When I say generate the code, I'm also implying regenerating the code when the application requirements change, which they always do.

Code generation is a complete and complex area of programming in itself. It is part of the development cycle, and needs to be planned for up front. There are many great code generation tools and even a few books on the market.

I got tangled up in code generation when I started using the Visual Studio 2008 and SQLMetal ORM tools. I very quickly found out that my approach to codeless validation will not work with these tools because I'm using attributes on entity members as a way of declaring the validation rules for that property. Additionally, I have other attributes that I need on the entities. One of the reasons I added the ability to add shared rules in code was to work around the limitations of the current ORM tools.

Another problem with these tools is that I had no way to place XML comments on the entity properties or classes. XML comments on entity properties and classes is essential for Intellisense and class help files. Yes, you can generate the code with the ORM tools then add the attributes and XML comments, but they will be lost if the entity needs to be regenerated. To me, this is a show stopper. I have written this blog post, Visual Studio 2008 Designers and Code Generators You Have Overlooked (...), that covers this issue in detail.

Two weeks ago, I went to an MSDN event on the Visual Studio Team System. There I learned that Microsoft has more cool ORM tools. However, these tools have the same problems I've mentioned in the above paragraph.

I would like to suggest that a (the) senior level architect at Microsoft convene a group of industry experts on code generation, ORM, and real world business product requirements like I have listed above, and design a metadata system that the Microsoft ORM tools uniformly consume when generating code. It would also be nice to edit the template files that the ORM tools use to generate the code. I would even settle for a data structure that the Microsoft ORM tools use, and we in the development community populate that data structure from our application specification database.

Close

First, thanks for reading this article. I know that it was very long and not sexy at all. But you now have the tools to very quickly, with almost no effort, validate your business entity objects, and report in a unified manner, all form exceptions and entity broken validation rules.

This long article can be distilled down to a few attributes on entities and a few properties in data binding statements. If this statement is true for your development efforts, then I have accomplished my mission.

I hope that you got something from this article in the WPF Business Application Series, and that you have seen some of WPF's potential for writing great business applications.

In addition to this Business Application Series, I also have a WPF Sample Series on my blog. I post several WPF sample applications a month. I won't be posting every sample here on CodeProject, but you can still read them and download the code from my blog.

Have a great day!

History

  • 1 April 2008: Initial release.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here