Wednesday, July 23, 2008

Validation Application Block

Starting to implement the Microsoft Validation Application block which I believe is somewhat cool, the following links gave me a good kick start








http://www.codeproject.com/KB/cs/VAB.aspx



http://msdn.microsoft.com/en-us/library/cc511770.aspx
http://www.codeproject.com/KB/WCF/WCFVAB.aspx

In general the validation application block allows you to add configurability to your service and UI validations. in this post I will talk briefly about what you need to do to implement VAB in your service and UI starting with the service.



Service Validation using VAB





So you have a service that contains some operations, to implement VAB you will have to modify every operation to make a call to

ServiceValidator.Validate(InsertRequest.AccountObject, MethodBase.GetCurrentMethod().Name)

all of the above is a custom code that you actually write, the ServiceValidator is a class that you should have built, and it containts the Validate method.

The first parameter to Validate is the object that you want its members validated. Here I want to mention one of the VAB limitations. the limitation is that you need to provide the object that you want to validate which is "InsertRequest.AccountObject", this object should be the direct parent of all the members (Fields, Properties) that you want to validate. VAB configuration will not let you start from "InsertRequest", it will only show one level down from the Type that you select in the configuration file and you can not drill down more. so if you start with InsertRequest, then you will only be able to set validation rules on AccountObject which is the direct child of InsertRequest class. but you can not set rules on members of AccountObject itslef.

To illustrate this in further consider the diagram below.























































In the diagram above, you see under the InsertAccount "rule set" you can choose the members of the parent type (Account). in case your parent type is InsertRequest you will not be able to access the grand children (AccountNameInternal), and this is why you will have to start with the parent object of the members you want to validate.








the validate method itself should call the enterprise library validate method, here is a sample code




public static class ServiceValidator
{
public static void Validate(T validationTarget, String ruleSet)
{
ValidationResults results = Validation.Validate(validationTarget, ruleSet);
if (!results.IsValid)
{
StringBuilder validationMsg = new StringBuilder();
int msgNumber = 1;
foreach (ValidationResult valResult in results)
{
validationMsg.Append("Service Reply " + msgNumber + ": ");
validationMsg.Append(valResult.Message + " {Key= "+ valResult.Key + ", Tag= " + valResult.Tag +"}\n");
msgNumber++;
}
throw new Exception(validationMsg.ToString());
}
}
}


the Validation class referenced above is located at
Microsoft.Practices.EnterpriseLibrary.Validation


when you want o send the validation result to the caller, you usually do that via Faults. so you need to send a Fault back to the caller.

the exception thrown in the code above will need to be captured at the service boundary and convereted to fault and this is the only way it can travel safely thru the wire back to the caller.

you can use srvcconfig and srvcmonitor applications to monitor what is actually travelling thru the wire.

the code below shows how to capture and convert the exception to a Fault.

public void ProvideFault(Exception error, MessageVersion version, ref Message fault)
{
ValidationFault validationFault = new ValidationFault();
FaultException fe = new FaultException(validationFault, error.Message);
MessageFault mf = fe.CreateMessageFault();
fault = Message.CreateMessage(version, mf, fe.Action);
}

he above function receives your exception and converts it to a Fault and sends it over.

The best practice in handling Exceptions in the service is to handle the exception at the service boundary. A good way of doing this is to create an attribute as follows


public class ServiceExceptionHandlerAttribute : Attribute,IServiceBehavior
{
#region IServiceBehavior Members
public void AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, System.Collections.ObjectModel.Collection endpoints, BindingParameterCollection bindingParameters)
{
}
public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
{
foreach (ChannelDispatcher dispatcher in serviceHostBase.ChannelDispatchers)
{
dispatcher.ErrorHandlers.Add(new ServiceExceptionHandler());
}
}
public void Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
{
}
#endregion
}

the attribute references the class ServiceExceptionHandler which is implemented as follows


public class ServiceExceptionHandler : IErrorHandler
{
public bool HandleError(Exception error)
{
//ASCLogger.LogError(string.Format("Uncaught exception of type {0} was thrown. Message: {1}", error.GetType(), error.Message),true );
return true;
}
public void ProvideFault(Exception error, MessageVersion version, ref Message fault)
{
ValidationFault validationFault = new ValidationFault();
FaultException fe = new FaultException(validationFault, error.Message);
MessageFault mf = fe.CreateMessageFault();
fault = Message.CreateMessage(version, mf, fe.Action);
}
}

Now if you decorate your service with this attribute you're all set in handling every single exception and converting it to a fault and passing it back to the caller.


Validating User Interface

To use VAB in the UI you need to add an ErrorProvider which comes with .Net and a ValidationProvider which comes with the enterprise library to your form. Attach the ValidtionProvider to the ErrorProvider and attach your control to the ValidationProvider.

Couple of things to note here.
1- UI and service can not share the same validation file if they do not share the data contracts. In your UI you will most probably have a service agent that copies the service objects in the UI. When you try to add the same type to the config file it will be refused.
Also there is a nice little trick about using the load assembly functionality when you add a new type to the validation config file. if you are like me irritated by the long list of objects that appears after you add an assembly, you can use this trick







after adding your assembly, select any object from the list and then press ok, then delete the object that you just added to the config file and then click to add a type again, you will now see the same diagram above but all the types will be in a collapse mode instead of an expand mode.

VAB will only help you validate simple things like text box, drop down. but if you have a grid for example, you will have to write a custom validation, which I did not like so much.

No comments: