Showing posts with label VAB. Show all posts
Showing posts with label VAB. Show all posts

Sunday, May 06, 2007

EntLib3 should apply convention over configuration

After using the EntLib3 validation application block (VAB) for a while now, and also some of the new configuration mechanisms such as external configuration source and environmental overrides for different build types; it occurs to me that there is a bit to much XML noise in the config.

EntLib3 should do as in Castle Windsor and MonoRail and Ruby on Rails; apply "convention over configuration". In EntLib3 config files, you have to add mandatory attributes to identify the default configuration when there are multiple config options. Using "first is default" as in the Windsor 'constructor injection' mechanism would make configuration simpler and less error prone (see VAB issue below). I understand the reasons for having a default-identifier as a separate attribute, but with the new config override and merging mechanisms in EntLib3, there should be less need for compulsory config "switching" attributes.

The convention "first is default" would prevent silly omission errors such as not setting the default rule set in VAB less drastic. As it is now, if you forget to set the VAB default rule set, no validation will be applied, neither will there be any "no rules" exception - and your code will run as if all validations passed, even if there are plenty of broken rules in the input.

While I'm at it, the caching of connection strings and service URLs in the Settings class and the service proxies, is also really silly; you will not be able to detect that some configuration is missing until you move the solution to an isolated staging environment that has no access to the databases/service resources referenced in the development environment. Most test environments are not that isolated from the development environment, and such config errors can go undetected for a long time during testing. This is one area where it would be better if Microsoft could make configuration compulsory.

Tuesday, April 10, 2007

EntLib3 April 2007: still some VAB issues

It's been quiet around here the last month, as I've been "busy" finishing the March sprint of our current project - after skiing in Avoriaz, France for one week first. Today I'm back after a one week Easter vacation, skiing in Trysil, Norway.

The production version of EntLib 3 was released last week, and I have downloaded it and tested the validation application block with our WCF services. The first thing I noticed when recompiling was that a breaking change has been introduced in EntLib3 April 2007: the attribute parameters for specifying a custom validation message are gone from the attribute constructors. You now need to use named parameters to specify your message in a VAB attribute, e.g. MessageTemplate = "message" as shown here:

[RegexValidator(@"^.*$", RegexOptions.IgnoreCase, MessageTemplate = "Invalid ticket")]

When my code again compiled, I checked if some of the issues I've reported in the February CTP had been fixed. The most serious issue for my current project is validating recursive object collections, e.g. a structure of folders that can contain files and sub-folders.

The [ObjectCollectionValidator] issue has not been fixed, the NUnit tests still silently fails - caused by a stack overflow due to an infinite loop when building the validator tree for the recursive object structure.

The second issue is that the [IgnoreNulls] attribute should get a sibling [IgnoreNullOrEmpty] attribute for strings. This is especially important for validating WCF messages and data contracts; if an optional string element is not specified (null) in the message on-the-wire, the DataContractSerializer will deserialize the missing string element by applying the default value for strings: String.Empty. So, now the null value sent by the client is actually String.Empty when applying the validation rules on the server, and thus the [IgnoreNulls] rule is not sufficient for easily validating WCF messages.

Specifying a set of validation attributes to build a ruleset for covering "ignore if null or empty, otherwise string must be 5-10 chars long" using the [ValidatorComposition] attribute is not simple, thus I have logged the need for [IgnoreNullOrEmpty] as an issue.

Thursday, March 01, 2007

WCF: Validating Message and Data Contracts using VAB

Last year I wrote about how we used a 'broken rules' engine and the Noogen validation component for doing validation across all layers including the business logic and the WinForms client. Using a validation engine allows for better design and separation of concerns in your components, and makes the code easier to understand as the validation logic is clearly separated from your domain logic.

These days I have started to use the Validation application block (VAB) from the upcoming Enterprise Library 3 provided by the patterns & practices team at Microsoft. VAB works just like our custom 'broken rules' engine, it allows you to declaratively add validation rules as [attributes] to your objects instead of implementing the rules as code; and then passing an object instance to the Validation.Validate() method to interpret the declared rules and provide a list of those rules that are broken, i.e. the rules that the object instance does not comply with.

The nice feature provided by VAB is that you can actually completely separate the validation rules from your domain logic code by allowing you to declare the rules in an external XML config file instead of adding [attributes] on your domain objects. It is really nice as there is a VS2005 editor tool that allows you to create and edit the rules instead of hand-coding XML. The figure shows how easy the editor makes it to e.g. choose which objects and properties to validate (click to enlarge):


Remember to set the "default rule set" property for each type/class that you add. Forgetting to set it will give no warning, but neither will your rules be applied unless you actually specify a rule set name in the call to
Validation.Validate() (more on this problem below).

By having the validation rules in config, you can actually modify the rules without having to recompile and redeploy your solution to keep up with the ever changing business requirements.
Read more about this and other VAB features at David Hayden's blog. Also read his four part VAB introduction series to see some code.

I use VAB to validate the incoming message and data contracts of our WCF service. I have decorated all the message and data contracts with VAB attributes in addition to the WCF attributes. This allows for validating just only a single data contract or for validating the complete structure of a message including contained data contracts. The capability of validating an object graph in a single operation is what I love most about VAB. These are the object graph validators:
  • [ObjectValidator] is for validating a composite child object of the current object
  • [ObjectCollectionValidator(typeof(...))] is for validating a collection of child objects
Combine the object validators with the [NotNullValidator] to make the composite objects required. Note that there seems to be a bug in the current VAB beta when using the collection validator on recursive structures (e.g. folders containing folders), your app-domain will just silently die. [UPDATE] More about this bug here.

All messages derive from a common base class that ensures that all messages contain the same set of [MessageHeader] info required by our service. This allows us to validate all messages using a single method:

public static void ValidateMessage<T>(T message)
where T : DefaultMessage, new()
{
ValidationResults results = Validation.Validate(message);

//NOTE: DO NOT EXPOSE IMPLEMENTATION DETAILS
if(results.IsValid==false)
{
FaultContracts.DefaultFaultContract fault = new FaultContracts.DefaultFaultContract();

fault.ErrorId = (int)EApprovalErrorCode.InvalidMessage;

fault.ErrorMessage = Utilities.MakeDetailedErrorMessage(results);

throw new FaultException<DefaultFaultContract> (fault, new FaultReason(InvalidMessageReason));
}
}

Note that as VAB uses generics in the
Validation.Validate(<T>) method, the code must ensure that type fidelity is not lost before calling the method. I.e. you must use a generic <T> type input and not the base class DefaultMessage in your message validator method input. The problem by using a base class type input is that even if you pass a derived class at run-time, the compile-time type will be used to deduce the validation rules - thus only the base class will get validated.

Using VAB gives us better control over the validity of the input than the DataContractSerializer is able to provide. E.g. it allows for checking string lengths and controlling that string elements are actually not just empty values; and validating e-mail addresses, guids, etc; rules that cannot be expressed using WCF attributes. These are some of the field level validators:
  • [NotNullValidator] ensures that required fields are not null; just got to love this, no more String.IsNullOrEmpty() checks in my domain logic
  • [StringLengthValidator(min, max)] controls the string length; must be combined with [IgnoreNulls] for non-required strings; no more 'field would be truncated' exceptions
  • [RegexValidator] validates the field against a regular expression; useful for validating e-mail addresses, phone numbers, guids, etc.
  • A guid validator example, with optional { and }: [RegexValidator(@ "^(\{){0,1}
    [0-9A-F]{8}\-[0-9A-F]{4}\-[0-9A-F]{4}\-[0-9A-F]{4}\-[0-9A-F]{12}
    (\}){0,1}$", RegexOptions.IgnoreCase)]
  • E-mail validator for 99% of all the possible formats: [RegexValidator(@ "^[A-Z0-9._%-]+@(?:[A-Z0-9-]+\.)+[A-Z]{2,4}$", RegexOptions.IgnoreCase)]
Note that you must apply the validators to public class members only; validators on private class members will not be interpreted, and neither will they cause an exception.

Validators can be composed into Boolean expressions using [AndCompositeValidator] and [OrCompositeValidator]. In addition, there are plans to provide cross-property validation in the final release of the validation application block. This will allow for comparing class members against each other instead of just literals, values sets and type/conversions. Sometimes, however, this is not sufficient for complex rules.

When your rules are too complex to be supported by the standard validators, VAB allows you to do self validation by providing [SelfValidation] methods:

[HasSelfValidation]
[MessageContract]
public class ImportProjectDocumentsMessage : DefaultMessage
{
[SelfValidation]
public void ValidateFileXorData(ValidationResults results)
{
//XOR operation
if ( (_fieldRootNode == null ^ String.IsNullOrEmpty(_fieldImportXmlFilename) ) == false)
{
results.AddResult(new ValidationResult("One of the parameters ProjectDocuments or ImportXmlFilename must be specified", this, "ImportProjectDocumentsMessage", null, null));
}
}
. . .
}

All classes containing self-validation must be marked with the [HasSelfValidation] attribute. VAB allows you to mix standard validators with self-validation. You just got to love this kind of flexibility.

Note that as there will be no exceptions if your validation rules are not interpreted by VAB for some reason (such as the above lost type fidelity problem, missing "default rule", etc), I recommend hard-coding one of the rules in your logic to ensure that such mishaps are detected during development and testing. This fail-safe can be enclosed in #if DEBUG so that it is not included in your final solution. Regardless, you should always have a unit-test expected to fail when validating; if the test does not fail, you know that the rules are not being interpreted. Use [ExpectedException] or Assert.IsFalse() to verify that invalid input actually causes the validation to fail.

Download the EntLib3 Feb2007 CTP beta including VAB from CodePlex. Read the documentation and check the 'quick start' samples to get started.

[UPDATE] Watch the 'Taking Advantage of the Enterprise Library in Your Site' web-cast for an introduction to EntLib3.

[UPDATE] Watch the 'New Capabilities in Enterprise Library 3.0' web-cast for even more details about the new features in EntLib3.

Another part of enterprise library 3 to look forward to, is the Policy Injection block. Read more about PIAB at Tom Hollander's blog. This looks good for adding cross-cutting concerns such as logging and exception handling to the domain logic without actually modifying the logic to add non-domain stuff. Alas, this is in fact just 'aspect oriented programming' (AOP) under a new name - read Patrik Löwendahl's comments and the response from the p&p team at his blog.

[UPDATE] Also check out the new WCF stuff in the Orcas March CTP: integration of WF and WCF with new activity types and a new WorkFlowServiceHost; plus JSON endcoding and webHttpBinding for better REST + POX/JSON support.