All validation in DevForce is performed by a VerifierEngine. Every EntityManager contains its own instance of a VerifierEngine, accessible via its VerifierEngine property. VerifierEngines can also be created and used independently.
The VerifierEngine class is discussed in more detail elsewhere, but the critical idea here is that every VerifierEngine contains a list of verifier instances. A verifier instance is an instance of some subclass of the Verifier class.
The VerifierEngine offers a set of methods that allow collections of these verifier instances to be evaluated sequentially against an instance of a .NET class. This is the process of "performing a validation".
The object to be validated can be a DevForce entity but it doesn’t have to be. The object can be of any concrete type.
Each verifier execution produces a VerifierResult. The engine accumulates these results in a VerifierResultCollection as it proceeds and returns the entire collection as its own result.
Verifiers can be added to a VerifierEngine in two ways:
The application can combine these methods.
Whenever a VerifierEngine is asked to "validate" a type, its first step is to discover all of the verifiers that are applicable to that type. Some of these verifiers are defined using property level attributes that may be applied to the type being validated; but verifiers may also be defined in .NET code, and even in XML. The VerifierEngine discovers all of these verifiers, and creates instances of each in an internal collection. These instances will be used to perform the actual validations.
Most verifier instances are responsible for the validation a single property on a single target type. Validations of this form are usually specified by marking up the target type's properties with a variety of validation attributes.
These validation attributes can either have been automatically generated on the type by DevForce or may have been added directly to the class by the developer ( usually via a metadata buddy class). Many of the most basic "database constraints" that arise from the process of using the Entity Data Model Designer to map a class will automatically appear in the generated code as validation attributes on various properties. These will appear as StringLength or RequiredValue attributes.
Each verifier instance has its own properties which tell the VerifierEngine the conditions under which it is applicable. For example, you can define a verifier so that it runs before a proposed new property value is pushed into the business object; or after; or even both (though that is unusual). You also want most verifiers to run whenever an entire instance of a type is being validated. To specify these things, you specify the ExecutionModes on an instance of the VerifierOptions type.
The VerifierEngine has several overloads available to actually cause a validation to occur. The methods can be called directly, but they will also be called automatically by the DevForce infrastructure at the following points:
Even in a 2-tier application, when saving there are still two VerifierEngines that should be accounted for:
It's important to have this in mind when "loosening" validation rules, so that it be set in both "client" and "server".
For instance, if setting:
| C# | myEM.VerifierEngine.DefaultVerifierOptions.ShouldTreatEmptyStringAsNull = false; |
in the client app, validation will not fail when setting a non-nullable property to "" (empty string).
However, when myEM.SaveChanges() is called, validation will be performed once more in the EntityServer and if ShouldTreatEmptyStringAsNull is not explicitly set to false, the save will fail.
In the EntityServer, we set validation in the SaveInterceptor ValidateSave method:
| C# | protected override bool ValidateSave() { EntityManager.VerifierEngine.DefaultVerifierOptions.ShouldTreatEmptyStringAsNull = false; return base.ValidateSave(); } |
These are two different kinds of validation.
In the case of a change to a property value, we really only want to perform the minimum number of validations that are relevant to the property being changed. This is called a property validation and there are specific methods on the EntityManager that are intended for this purpose.
In the case of a save, we really want to "validate" the entire entity. This will mean "validating" every property along with any validations that cross properties or possibly even involve related objects. This is referred to as an instance validation. Again there is a specific method on the VerifierEngine for this purpose.
Regardless of which form of validation is performed the result of a validation is always a VerifierResultsCollection which as the name suggests, is a collection of VerifierResults. Each VerifierResult contains a reference to the object being validated, the validation instance used to perform the validation and most importantly the "result" of the validation. VerifierResults that represent "errors" are automatically added to each entities EntityAspect.ValidationErrors collection.