Delen via


Implementing Validation in the Stock Trader

Retired Content

This content is outdated and is no longer being maintained. It is provided as a courtesy for individuals who are still using these technologies. This page may contain URLs that were valid when originally published, but now link to sites or pages that no longer exist.

The latest Enterprise Library information can be found at the Enterprise Library site.

patterns & practices Developer Center

On this page:
Validation with WCF Data Services (The Account Holder Info Module) - Account Holder Info Module Scenario, Design of the Account Holder Module | Validation with WCF RIA Services (the Orders Module) - Orders Module Scenario, Design of the Orders Module, Sequence for Validation | Advanced (View) Model Scenarios - Design of the StockTraderRI.Infrastructure.ModelSupport Library, Implementing the INotifyPropertyChanged Interface Using the Property Support Class, Implementing INotifyDataErrorInfo Using the Error Collection Class, Validating a Graph of Objects Using the ModelGraphValidator Class, Using the Model Graph Validator with WCF RIA Services, Automatic Validation When a Property Changes, Displaying Validation Errors That Are Not Bound in the UI, Multi-Threading Support

There are two places where validation has been implemented in the Stock Trader V2. The Account Holder module was built using WCF Data Services and the Orders module was built using WCF RIA Services. In this topic, we'll describe how these modules have been built.

Hh852708.note(en-us,PandP.51).gifEd Says:
Ed
                While building the Account Holder and Order Module, we found that we had to write a lot of repetitive code to build our view models. There are a couple of interfaces that you always have to implement, such as <strong>INotifyPropertyChanged</strong> and <strong>INotifyDataErrorInfo</strong>. To minimize the repetitive code, we have built some infrastructure pieces. They help with the most common scenarios for building views / view models. These infrastructure pieces will be discussed in more detail in the section called Advanced (View) Model Scenarios.</td>

Validation with WCF Data Services (The Account Holder Info Module)

The Stock Trader V2 allows its users to change their own account information through the Account Holder Info Module. In this module, users can register certain information, such as their name or birth date. It also allows users to enter one or more bank accounts.

Account Holder Info Module Scenario

Most of the functionality for the Account Holder Info Module was already present in the previous version of Stock Trader, so one of the most important goals for the new Account Holder Info Module was to reuse the database schema, business entities and validation logic.

Hh852708.note(en-us,PandP.51).gifEd Says:
Ed
                 Two years ago, we invested heavily in improving the account holder functionality in the previous Stock Trader so that it used Entity framework and the Validation Application Block for defining validation rules on its business entities. We couldn't afford to rebuild this logic from scratch, so we were very happy that we were able to reuse that logic when building Stock Trader V2.</td>

Design of the Account Holder Module

The following diagram shows the data model for the Account Holder Info Module. As you can see, an account holder can have several bank accounts. There must be a single primary bank account.

Hh852708.3D818CD1775C22E923BDEE835575CC70(en-us,PandP.51).png

Hh852708.note(en-us,PandP.51).gifEd Says:
Ed
                 In most examples online, you'll only see a single object being validated. In our case, that wasn't enough. We wanted to be able to validate an account holder, including the associated bank accounts information, as a whole. There are some challenges that arise from having to validate a graph of objects, such as where to store the validation results. This is described in more detail in the section called Advanced ViewModel Scenarios.</td>

The following diagram shows a schematic representation of the design of the Account Holder Info Module.

Follow link to expand image

The Account Holder Info Module is built as a set of WCF Data Services that expose two business entities: AccountHolder and BankAccount. An Entity Framework data model called AccountHolderDataModel is used to persist the business entities in the StockTraderData database.

Hh852708.note(en-us,PandP.51).gifSharon Says:
Sharon
                 We could have built the Account Holder Info Module as more traditional WCF services, but we decided not to do that because in the future, we expect other clients to also use these web services. Since these other clients prefer to communicate using a REST API, WCF Data Services seemed a really good candidate. </td>

The Silverlight client is built using the Model View ViewModel (MVVM) pattern. It communicates with the web services through a ServiceAgent class.

Hh852708.note(en-us,PandP.51).gifEd Says:
Ed
                 We have designed the Account Holder Info Module to be easily unit tested. For example, by isolating the UI Logic in the Account Holder View Model and isolating all data access logic in the AccountHolder ServiceAgent, we could write automated unit tests to virtually all UI logic without having to do UI automation tests. </td>

Most of the validation rules are defined as attributes on the business entities. Since the validation rules must be run on the server (for security purposes) but also on the client (to improve the user experience), we have used Visual Studio linked files to share the business entities between the client and the server.

Validation with WCF RIA Services (the Orders Module)

In the Stock Trader V2 Reference Implementation, the Orders module has been built using both WCF RIA Services and the Validation Application Block.

Orders Module Scenario

The previous version of Stock Trader also had order processing functionality. However, it was very complicated and hard to maintain.

Hh852708.note(en-us,PandP.51).gifEd Says:
Ed
                The logic for order processing in the old Stock Trader was not pretty, and its validation logic was even worse. Instead of using a well-designed validation framework, it was a big pile of inconsistent spaghetti code, with lots of duplication. When we started the work on Stock Trader V2, we quickly decided that the best approach was to throw away the old code and start over. </td>

The Orders module for the new Stock Trader V2 has been reworked from scratch. WCF RIA Services was chosen as the platform, largely because of the built-in validation support, and rich Microsoft® Visual Studio® tooling available in WCF RIA Services.

Hh852708.note(en-us,PandP.51).gifSharon Says:
Sharon
                 While most of the validation logic could easily be built using the WCF RIA Services built-in validation support, we did need some of the extra validation support that only the Enterprise Library Validation Application Block provides. <br />For example, the business wanted the ability to experiment with the cost model associated with placing small orders. If an order was placed for penny stock or for less than a certain number of stocks, they wanted to be able to charge an additional fee. But we cannot charge additional fees without warning the users first. Therefore, we decided to implement this using configurable validation rules in in a specific rule set, called "Warnings." </td>

Design of the Orders Module

The Orders Module has the following Data Model: An Order is a logical grouping of several stock transactions that can be managed together or individually.

Follow link to expand image

The following diagram explains the Orders module in more depth.

Follow link to expand image

The heart of the Orders module is the Order Data Model, built using WCF RIA Services. The business entities, Order and OrderDetail, are generated from this data model. Both have validation rules defined in metadata classes, using a mixture of Validation Application Block validation attributes and DataAnnotations built-in attributes.

WCF RIA Services has also generated the business entities on the Silverlight client. These business entities are also decorated with the validation attributes that were defined on the attributes on the Server.

To implement the UI for order entry, the MVVM pattern is used. The Order and OrderDetail business entities are only responsible for containing the data, which are retrieved and stored through the OrderServiceAgent. The UI logic is isolated in the OrderViewModel and OrderDetailViewModel classes, so these classes can be unit tested in isolation. The presentation is handled in the OrderView which uses zero or more OrderDetailViews.

Both the Order and OrderDetail classes in Silverlight inherit from the WCF RIA Services Entity class. Therefore, they already implement INotifyPropertyChanged and INotifyDataErrorInfo and expose a collection of (DataAnnotations) validation results. This makes it very easy to bind these classes to the UI and show validation errors.

Sequence for Validation

There are several points in time when validation is triggered:

  1. When the user changes a value in a textbox, the bound property is automatically validated. For the WCF RIA Services entities, this is done automatically by the generated code. For the view models, this is done calling the ValidateOnPropertyChanged method on the Model Graph Validator.
  2. When you press the submit button, the Validation Application Block's validation is triggered, with the default rule set. This will also trigger the validation of the entire Order and OrderDetails object graph, because the OrderViewModel has an Order property with the ObjectValidator attribute on it. The Order has a collection of Order Details with the ObjectCollectionValidator attribute (defined in the OrderMetadata class) on it.
  3. When the order is valid, then a second validation pass is executed. This time with the 'Warning' rule set. Only the Validation Application Block's validation attributes or attributes defined in configuration can be used here, because DataAnnotations' built-in attributes do not support rule sets. If a validation warning occurs, then we'll show the validation warnings and give the user the chance to stop.
  4. When the order is submitted, WCF RIA Services will automatically trigger validation using the DataAnnotations validator on the server. This will validate using all the DataAnnotations built-in validation attributes and also the Validation Application Block attributes that do not have a rule set specified on them.
  5. On the server, we'll also explicitly call Validation Application Block validation again. We're also executing a 'server only' validation rule, which guards against placing an order that's larger than your risk profile. If you place an order that's greater than 1025 stocks, this will raise a validation error.

Advanced (View) Model Scenarios

When you are building a user interface using the MVVM pattern, you'll quickly find that there are several repetitive tasks to be performed. For example, to create a model or view model that is easily bindable to the UI, you should implement the INotifyPropertyChanged interface and raise this event in every property setter. Then to support validation, you'll want to implement the INotifyDataErrorInfo interface.

To make implementing these interfaces easier, a custom library called StockTraderRI.Infrastructure.ModelSupport was developed. This library supports the most common scenarios when building models and view models that should be easily data bound to the user interface.

This chapter stars with an overview of the Model Support library and will then describe the individual scenarios that it supports.

Design of the StockTraderRI.Infrastructure.ModelSupport Library

The ModelSupport Library was designed like the layers of an onion. If you want to get started quickly, you can take the whole onion, but you can also peel back the layers and only use the pieces that you like.

The following diagram shows the most important pieces of the library:

Follow link to expand image

On the left of the diagram are the three core pieces:

  • The PropertyValue class helps you to implement the INotifyPropertyChanged interface. It can help you implement quasi-automatic properties, which raise the property changed event when they are changed. But if you want more control, you can also use it just to raise the property changed event explicitly.
  • The ErrorsCollection class helps you to implement the INotifyDataErrorInfo interface. It is a collection of validation errors with properties and methods to match the INotifyDataErrorInfo interface.
  • The ModelGraphValidator class helps you to validate a graph of objects. It uses a pluggable mechanism for writing validation errors into models, so that these validation errors easily bind to the user interface. The model validator uses the Validator class from the Validation Application Block to execute the actual validation logic.
  • In the middle there are two support classes:
  • ModelSupport combines the functionality for the Property Support and Errors Collection. Since it is very common to have to implement both the INotifyPropertyChanged and INotifyDataErrorInfo interfaces on your models, it would again be very repetitive to have to use both these classes on each model or view model.
  • ValidatingModelSupport extends the functionality of the ModelSupport class with the ability to validate a graph of objects. This helps if you wish to implement validation in your ViewModels, but do not wish to have to explicitly add the ModelGraphValidator to each ViewModel.

On the right, there are two base classes. These base classes have the same functionality as their Support counterparts. In fact they use them internally, but they expose their functionality through methods on the base class.

  • The NotifyingModel is an abstract base class that uses the ModelSupport class to implement INotifyDataErrorInfo and INotifyPropertyChanged.
  • The ValidatingModel is an abstract base class that extends the functionality for the Notifying Model with validation support. It uses the Validating Model Support class to implement that support. It also implements the custom IWriteDataErrorInfo interface for writing error information into the model.
Hh852708.note(en-us,PandP.51).gifEd Says:
Ed
                Using the base classes provided in the Model Support library is a very fast way to get started with building models and view models. However, if you don't want to use a base class or need to use a different base class, then you can still use either the support classes or the core pieces to help you implement the most common scenarios. </td>

Implementing the INotifyPropertyChanged Interface Using the Property Support Class

The INotifyPropertyChanged interface is used by the Silverlight data binding engine to detect if a property that is bound to the view has changed, so that the view can be updated. The PropertyValue class can help you to implement INotifyPropertyChanged in your models. The following example demonstrates how to use the PropertyValue class:

public class ExampleModel : INotifyPropertyChanged
{
    private PropertyValue propertyValue;

    public ExampleModel()
    {
        this.propertyValue = new PropertyValue(() => PropertyChanged);
    }

    private int exampleProperty;
    public int ExampleProperty
    {
        get { return exampleProperty; }
        set
        {
            if (exampleProperty == value)
                return;

            exampleProperty = value;

            propertyValue.RaisePropertyChanged(
                () => ExamplePropertyUsingExpression);
        }
    }

    public int ExampleAutomaticProperty
    {
        get { return propertyValue.GetProperty(() => ExampleAutomaticProperty); }
        set { propertyValue.SetProperty(() => ExampleAutomaticProperty, value); }
    }

    public event PropertyChangedEventHandler PropertyChanged;
}

In the constructor, an instance of the PropertyValue class is created, and it gets a lambda expression that retrieves the PropertyChanged event when it needs to be raised. Normally, you can't pass an event to a different class, but you can pass a lambda expression that retrieves the event handler when it's needed.

The first example property shows a regular property that uses the PropertyValue class to raise the PropertyChanged event. You can either pass a string with the name of the property, or pass an expression that targets the property.

Hh852708.note(en-us,PandP.51).gifSharon Says:
Sharon In thebeginning I had to get used to writing all these lambda expressions, as opposed to specifying strings for the property names when implementing the INotifyPropertyChanged interface without this infrastructure. However, once I got used to them, I found that they offer a lot of advantages. You get compile-time checking for the property name and type and you'll get much better refactoring support within Visual Studio.

Having to implement properties as shown with the ExampleProperty sample above still requires a lot of coding. You have to have a backing variable for the property, check if the property has changed, update the value, and raise an event. You will need eight lines of code for each property. To reduce that amount of code, you can also use quasi-automatic properties, as shown with the ExampleAutomaticProperty example above. The PropertyValue class acts as a backing store for the property values, so you don't have to create a private variable. The SetProperty method will check if the property has changed and will only raise the NotifyPropertyChanged event if it has changed.

Implementing INotifyDataErrorInfo Using the Error Collection Class

The INotifyDataErrorInfo interface is used by Silverlight to notify the data binding engine when validation errors have occurred. The data binding engine will then query the model for the validation errors and show the validation errors in the view.

Hh852708.note(en-us,PandP.51).gifSharon Says:
Sharon The data binding engine will only pick up validation errors from objects that implement INotifyDataErrorInfo. If you use a graph of objects, such as the AccountHolder and BankAccount classes from the Account Holder Info Module, then both these classes need to implement INotifyDataErrorInfo.

You can use the ErrorsCollection class to easily implement INofityDataErrorInfo on your models. The following code example shows how this works:

public class ExampleINotifyDataErrorInfoModel : INotifyDataErrorInfo
{
    private ErrorsCollection<string> validationErrors;

    public ExampleINotifyDataErrorInfoModel()
    {
        validationErrors = new ErrorsCollection<string>(this, 
            () => ErrorsChanged);
    }

    public void AddValidationErrorExample()
    {
        this.validationErrors.SetErrors(() => BoundProperty,
            new []{"Error has occurred"});
    }

    public string BoundProperty { get; set; }

    public IEnumerable GetErrors(string propertyName)
    {
        return validationErrors.GetErrors(propertyName);
    }

    public bool HasErrors
    {
        get { return validationErrors.HasErrors; }
    }

    public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
}

The INotifyDataErrorInfo interface makes no assumption on what a validation error is, so you can use anything as a validation error. In this example, we're using a string to represent a validation error.

The ErrorsCollection class is created in the constructor. Using a lambda expression, the ErrorsChanged event is passed to the ErrorsCollection so that it can raise the ErrorsChanged event when validation errors are added or removed.

Using the SetErrors method, you can add or remove validation errors for a particular property.

Validating a Graph of Objects Using the ModelGraphValidator Class

The ModelGraphValidator class helps you to validate a graph of objects and to assign the validation errors to the object that has the actual error. For example, if you have a Person class that has a property called Address classes and a collection of PhoneNumbers classes, then you might want to validate the entire graph of objects, rather than each object individually.

When you call the Validator class from the Validation Application Block on this graph, it will return a list of validation errors, without changing the objects being validated. If you are only validating a single object, you can simply populate the errors collection for that object with the validation result. However, if you are validating a graph of objects, then this approach does not work. The INotifyDataErrorInfo interface has to be implemented on all objects that are bound to the user interface and have their error information displayed.

The INotifyDataErrorInfo is a read-only interface. It only supports methods for reading error information from that object, but does not define how you can populate your models with validation errors. To support writing validation errors into objects, the Model Support library also contains the custom IWriteDataErrorInfo interface. This interface allows you to externally add validation errors into an object so that they can be displayed properly in a view.

The following example shows how you can validate a graph of objects:

public class PersonModel : NotifyingModel
{
    private ModelGraphValidator modelGraphValidator;

    public PersonModel()
    {
        this.modelGraphValidator = new ModelGraphValidator(this);
    }

    public void Save()
    {
        var result = this.modelGraphValidator.Validate();
        if (!result.Any())
        {
            // perform the actual save operation
        }
    }

    [ObjectValidator]
    public AddressModel Address 
    {
        get { return GetProperty(() => Address); }
        set { SetProperty(() => Address, value); }
    }

    [ObjectCollectionValidator]
    public IEnumerable<PhoneNumberModel> PhoneNumbers
    {
        get { return GetProperty(() => PhoneNumbers); }
        set { SetProperty(() => PhoneNumbers, value); }
    }
}

public class AddressModel : NotifyingModel
{
    [StringLengthValidator(0, 100)]
    public string StreetName
    {
        get { return GetProperty(() => StreetName); }
        set { SetProperty(() => StreetName, value); }
    }

}

public class PhoneNumberModel : NotifyingModel
{
    [StringLengthValidator(0, 3)]
    public string AreaCode
    {
        get { return GetProperty(() => AreaCode); }
        set { SetProperty(() => AreaCode, value); }
    }
}

In this example, a person has one address and several phone numbers. For simplicity, we have used the NotifyingModel base class to implement INotifyDataErrorInfo and IWriteDataErrorInfo on these classes, but we could have also implemented these interfaces without using a base class. In order for a validation error to be displayed in the User Interface, it must be added to the error collection of the correct object. For example, if the second phone number for a user has a validation error, then the validation error needs to be added to the second phone number instance. You can't add it to the Person Model's error collection, because then the validation error will not display in the user interface.

Now when you call the save method, you would like to validate the person, but also his address and his phone numbers. You can use the Object Validator attribute and Object Collection Validator attribute from the Validation Application Block to ensure that these classes also get validated when you call validate.

When you call validate on the Model Graph Validator, it will first retrieve the list of validation errors for the entire graph. Then it will distribute those validation errors to the correct instances so that they display correctly in the user interface.

Using the Model Graph Validator with WCF RIA Services

You can also use the Model Graph Validator to validate business entities generated by WCF RIA Services. The business entities generated by the WCF RIA Service code generator derive from the Entity class. This class implements INotifyPropertyChanged and INotifyDataErrorInfo for you. It also exposes a collection of validation errors.

The Model Graph Validator uses adapters to write validation errors into the models. There is an adapter for classes that implement IWriteDataErrorInfo, such as the NotifyingModel and an adapter for classes that derive from the WCF RIA Services Entity class.

This feature is very useful when you're building View Models that use WCF RIA Services entities. To implement the view models, you can use IWriteDataErrorInfo and validate them together with the WCF RIA Services entities.

Automatic Validation When a Property Changes

It's usually a good idea to validate a user's input as soon as he leaves the input field. This will give the user early feedback about potential validation errors, which often results in a better user experience.

To avoid having to add validation logic to each property setter, you can use the ValidateOnPropertyChanged method from the Model Graph Validator. If your model implements INotifyPropertyChanged, then you can use this to trigger validation as soon as your properties change.

Displaying Validation Errors That Are Not Bound in the UI

The Silverlight Control Toolkit ships with a very useful control called the ValidationSummary. This control will pick up any validation error that is bound to the user interface and display them in a summary. This works great for properties that are actually bound to the user interface. However, it's not uncommon for some validation errors to occur that are not bound to the user interface.

Hh852708.note(en-us,PandP.51).gifSharon Says:
Sharon In the Account Holder Info Module, when you validate an account holder, there are certain validation rules that are not bound to any particular piece of the user interface. For example the rule that you should have at least one primary bank account. You can't say: "this control that's bound to this property of my model is invalid," because it's more a problem of the state of the entire object graph, rather than a single property that has an error.

To ensure that validation errors that are not bound to the UI are also displayed in the ValidationSummary, a custom behavior was created, called the ValidationResultsSyncBehavior. When the validation errors collection of the data context changes, it will find the validation error messages have not yet been added automatically to the validation summary and add them explicitly.

The following code sample, from the EditAccountHolderView, demonstrates how you can add a validation summary control with the Validation Results Sync Behavior:

        <sdk:ValidationSummary>
            <i:Interaction.Behaviors>
                <Validation:ValidationResultsSyncBehavior Source="{Binding}" />
            </i:Interaction.Behaviors>
        </sdk:ValidationSummary>

Multi-Threading Support

All the classes in the model support library help to make it easier to create multi-threaded applications. Silverlight does not allow you to manipulate the user interface from any thread other than the UI thread. If you have a model that's bound to the user interface, and you change one of its properties from a different thread, this also causes an indirect user interface manipulation from a different thread, resulting in an exception.

For this reason, all the classes in the Model Support library will raise their events on the UI thread. The most important reason these events exist is to update the user interface. For example the PropertyChanged event from INotifyDataErrorInfo is often used to notify the view of changed data.

So if you use the Property Value class to implement INotifyPropertyChanged, it will always trigger the PropertyChanged event on the UI thread, regardless of from which thread you made the change. While you will still need to watch out for the typical multi-threaded issues, such as race conditions or deadlocks, this can make multi-threaded programming a little easier.

Hh852708.note(en-us,PandP.51).gifEd Says:
Ed In the Account Holder View Model, the Submit action is performed on a different thread, to improve user interface performance. If the Model Support library didn't raise its events on the UI thread, then the code would be riddled with dispatcher statements, which would make the code very hard to read. Now, the code is very clean, and you can hardly tell that it is being executed asynchronously.

Next Topic | Previous Topic | Home

Last built: July 8, 2011