Friday, January 27, 2012

Validation in WCF application with NValidator

I have been working on a heavy validation messaging project for 3 months. Basically, my company had built a workflow based system to process incoming WCF messages from other business partners. These WCF messages are supposed to be validated before being queued in a RabbitMQ queue store. And I was responsible for implementing the validation stuff for the application. As mentioned in previous post, I wanted to have something similar to ASP.NET MVC such as Validattion Provider, Validation Factory and using DataAnnotation for message contracts. But after a first draft implementation I has seen some potential issues:

1/ It's hard to have a clean contract DLL if using DataAnnotation. The validation logic for the messaging application is quite complicated. We just perform field validation without touching database at this point. However, the rules are so many and quite complicated. So some basic DataAnnotation attributes cannot fit all requirements. Many custom DataAnnotation attributes have been implemented and I don't want to embedded them in the same assembly with the Contracts. As a result, the contract dll had to reference to 1 or 2 other assemblies which contains the custom DataAnnotation classes. I hate this drawback since the contract we built will be provided to other internal teams, even our business partners.

2/ It's possible but quite ugly to perform cross field validations Using DataAnnotation, we can only perform validation rules on the "reader property" of a contract. Believe me, it's not gonna work for public field since the TypeDescriptor .NET class couldn't reflect the fields. To perform validation on crossfields, either we implement an attribute and decorate on the class it self, or implement interface IValidatableObject. I don't like both approaches since the first choice seems to be so complicated and not a straightforward way. The second choice pollutes my POCO classes. So, it's the second drawback to give up DataAnnotation

3/ Validation rules could be different between different trading partners Using DataAnnotation, the validation logics are tight to the contract classes so it's almost impossible to changes the rules since the data annotation attributes are decorated on the contract's properties or the class itself.

4/ And similar to the above reason, validation rules should support versioning. DataAnnotation makes it impossible to deploy new validaton logic without compilation the application eventhough we're hardly need this level of support.

Based on above reasons, separating the validation logics to different assembly seems to be a proper decision. I implemented NValidator and changed my code to using NValidator and it works perfectly. The validation errors are so meanningfull and easy to debug. After applying the library a few days, I got 90 bugs regarding these validations but believe me or not, I fixed all of them just in 1 day.

<a:Result>
    <a:MessageCreatedDT>20120105 12170001</a:MessageCreatedDT>
    <a:MessageReceivedDT>20120127 13250974</a:MessageReceivedDT>
    <a:MessageSuccess>Fail</a:MessageSuccess>
    <a:ProcessingErrors>
        <a:ProcessingError>   
            <a:ErrorDataField>CreateProfile.ApplicationNumber</a:ErrorDataField>
            <a:ErrorDescription>ApplicationNumber is not a valid application number. Received value: "abcdef"</a:ErrorDescription>
            <a:ErrorUniqueID>2d31f33f-44df-48aa-a682-61e3ca7d5566</a:ErrorUniqueID>
        </a:ProcessingError>
        <a:ProcessingError>   
            <a:ErrorDataField>CreateProfile.Entities[0].CompanyName</a:ErrorDataField>
            <a:ErrorDescription>CompanyName must be null because ‘Entity Type’ = “Person”. Received value: ""</a:ErrorDescription>
            <a:ErrorUniqueID>854e68c1-7604-412f-991c-20a6fc31b611</a:ErrorUniqueID>
        </a:ProcessingError>
        <a:ProcessingError>   
            <a:ErrorDataField>CreateProfile.Entities[1].CompanyName</a:ErrorDataField>
            <a:ErrorDescription>Mandatory because ‘Entity Type’ = “Company”. Received value: ""</a:ErrorDescription>
            <a:ErrorUniqueID>cca94d9d-6ea5-4846-8109-091285ae03e4</a:ErrorUniqueID>
        </a:ProcessingError>
    </a:ProcessingErrors>
</a:Result>

Above is a sample message response when validation fails. The 2 last erros are examples of cross field validation. If EntityType = "Person" the CompanyName must be null. CompanyName is mandatory if EntityType = "Company".
Reading enough? Here are the steps for using NValidator for validation on WCF service:
  1. Assume that you have your contract defined in an assembly, add another class library project just for validation rules. Using Nuget to get latest NValidator and add your validator classes.
  2. Register your validator classes in the application bootstrapper or anywhere of your app where the code is executed before other stuff.
  3. Create a validation attribute to decorate on your WCF service. The method is pretty similar to this post.

And here is the code. In this demo, I want to print out the value of the validated object so I create a custom Validation result as below. You don't have to do this if you don't want this function. The default ValidationResult and ValidationBuilder work just fine
public class CustomValidationResult : FormattableMessageResult
{
    public CustomValidationResult() : base(new Dictionary<string, object>())
    {
    }

    public CustomValidationResult(ValidationResult originalResult)
        : base(originalResult is FormattableMessageResult
                ? (originalResult as FormattableMessageResult).Params
                : new Dictionary<string, object>())
    {
        CopyValues(originalResult);
    }

    private void CopyValues(ValidationResult originalResult)
    {
        var customValidationResult = originalResult as CustomValidationResult;
        MemberName = originalResult.MemberName;
        Message = originalResult.Message;
        PropertyName = originalResult.PropertyName;

        if (customValidationResult != null && customValidationResult.ValidatedValueWasSet)
        {
            ValidatedValue = customValidationResult.ValidatedValue;
        }
    }

    public bool ValidatedValueWasSet { get; private set; }

    private object _validatedValue;
    public object ValidatedValue
    {
        get
        {
            return _validatedValue;
        }
        set
        {
            if (!ValidatedValueWasSet)
            {
                _validatedValue = value;
                ValidatedValueWasSet = true;
            }
        }
    }
}

I also have to make a customization of ValidationBuilder to make it return my custom ValidationResult:
/// <summary>
/// This class customizes the behavior of ValidationBuilder and return CustomValidationResult
/// </summary>
/// <typeparam name="T"></typeparam>
/// <typeparam name="TProperty">The type of the property.</typeparam>
public class CustomValidationBuilder<T, TProperty> : ValidationBuilder<T, TProperty>
{
    private object _validatedObject;
    public CustomValidationBuilder(Expression<Func<T, TProperty>> expression)
        : base(expression)
    {
    }

    protected override NValidator.ValidationResult FormatValidationResult(NValidator.ValidationResult result, string propertyChain)
    {
        var baseResult = base.FormatValidationResult(result, propertyChain);
        var customResult = new CustomValidationResult(baseResult);
        if (!customResult.ValidatedValueWasSet)
        {
            customResult.ValidatedValue = _validatedObject;
        }
        return customResult;
    }

    protected override object GetObjectToValidate(T value)
    {
        _validatedObject = base.GetObjectToValidate(value);
        return _validatedObject;
    }
}

And here is the ParameterValidatorBehavior which invokes validation on every WCF message call:
public class ParameterValidatorBehavior : IParameterInspector
{
    public void AfterCall(string operationName, object[] outputs, object returnValue, object correlationState)
    {
    }

    public object BeforeCall(string operationName, object[] inputs)
    {
        // validate parameters before call
        var validationResults = new List<ValidationResult>();
        var serviceIntance = OperationContext.Current.InstanceContext.GetServiceInstance();
        if (serviceIntance != null)
        {
            foreach (var input in inputs)
            {
                if (input != null)
                {
                    var validator = ValidatorFactory.Current.GetValidatorFor(input.GetType());
                    if (validator == null)
                    {
                        ColorConsole.WriteLine(ConsoleColor.Yellow, "Validator for {0} not found.", input.GetType().Name);
                        continue;
                    }
                    validationResults.AddRange(validator.GetValidationResult(input));
                }
            }
            if (validationResults.Count > 0)
            {
                throw new FaultException<ValidationFault>(new ValidationFault(validationResults), "Validation error");
            }
        }
        return null;
    }
}

As mentioned in the NValidator topic, the above CustomValidationBuilder has to be registered in BootStrapper:
internal class BootStrapper
{
    public static void Start()
    {
        ValidatorFactory.Config.DefaultValidationBuilderType = typeof (CustomValidationBuilder<,>);

        ValidatorFactory.Current.Register<OrderValidator>(true);
        ValidatorFactory.Current.Register<OrderDetailValidator>(true);
    }
}

That cannot be easier. Again, the full source code can be check out here at GitHub. Cheers.

2 comments:

Vishwa said...

Hi,

Is it possible to post the Source Code Link for this Sample.

Its some thing I am looking for

Thanks in advance
Visu

vantheshark said...

Sorry mate, forgot to upload the code:

https://github.com/vanthoainguyen/Blog/tree/master/NValidatorDemo


Cheers

Post a Comment