Monday, November 7, 2011

WCF Validation Engine with Data Annotation style like ASP.NET MVC

    There are many articles about WCF validation. Some are about using WCF with Microsoft Application Block, many others are about using Data Annotation. But there is not any thing like the validation engine of ASP.NET MVC. Honestly, I'm very impressed of MVC Team's work. The code is very SOLID that makes it too easy to extend without changing the implementation. There are still something i don't like in the Framework but overall, I think it's an awesome library. I have been working with ASP.NET MVC for over 2 years and now I'm having a chance to go back to WCF. I realized that WCF is designed a lot as open as MVC, there are so many extensible points in WCF that allows users to customize the services behavior, validation is one of them. I expected to find some existing code to use for WCF Validation like the one in MVC for me to copy and paste but there is not. I wanted to have a framework that we can switch any validation logic any time, just change the provider. Therefore, I decided to copy some of the implementation from MVC source code and use for this WCF validation demo.

    Firstly, I want to say about the way MVC Validation engine work. If you have ever read the MVC source code, you mush have known that in MVC, there is a ModelMetadata provider which will read the view model class and make a ModelMetaData. Then there is one or many ModelValidators which are registered before to create validator objects base the model metadata. Each validator will produce ValidationResults when validate the object. The validation process will happing during model binding and before Action method is executed. If there is an error, the error information will be append to Model State of current Controller context. And finally, in side the action method, we'll check the ModelState.IsValid to decide what to do.

    So I guess the MVC validation engine will not be able to validate nested object. But in MVC application, we still can see the error if nested object has validation error. I'm pretty sure that error is found while model binding process happen. In WCF, we don't have to worry about the model binder thing so one of the problem I have to solve is making the code validate nested object and child objects in an enumerable.

    Secondly, the ModelMetadata class and the ModelMetadataProvider classes in MVC aware of ControllerContext object which I don't want to use and there is not any reason to use it in WCF application so the next problem for me is trimming any thing related to ControllerContext.

    And finally, The MVC validation engine itself contains logic for Client validation. Again, these things will add no value for a WCF application. My aim would be copying only the code needed for validation the contract parameters in WCF.

    You may ask me why not using the classes in System.Web.Mvc.dll instead of rewriting/copying them. The answer is we cannot use that dll outside a web environment due to the assembly setting of that library.

    Alright, the way I inject validation logic into WCF method is making a IOperationBehavior as an Attribute to decorate on the action method. It's pretty much the same as other's guide. Here is the implementation:

[AttributeUsage(AttributeTargets.Method)]
public sealed class ParameterValidatorAttribute : Attribute, IOperationBehavior
{
    public ParameterValidatorAttribute()
    {
        ThrowErrorOnFirstError = false;
        ThrowErrorAfterValidation = true;
    }

    public bool ThrowErrorOnFirstError { get; set; }
    public bool ThrowErrorAfterValidation { get; set; }

    void IOperationBehavior.Validate(OperationDescription description)
    {
    }

    void IOperationBehavior.AddBindingParameters(OperationDescription description, BindingParameterCollection parameters)
    {
    }

    void IOperationBehavior.ApplyClientBehavior(OperationDescription description, ClientOperation proxy)
    {
    }

    void IOperationBehavior.ApplyDispatchBehavior(OperationDescription description, DispatchOperation dispatch)
    {
        dispatch.ParameterInspectors.Add(new ParameterValidatorBehavior(ThrowErrorOnFirstError, ThrowErrorAfterValidation));
    }
}

    The ParameterValidatorBehavior implements IParameterInspector, before a service method is called, it will invoke the validation engine to validate every single input parameter. If there is a validation error, the error will be appended to ModelState object. That requires the service implementation must implement the following interface
public interface IHasModelStateService
{
    ModelState ModelState { get; set; }
}

    Here is the code of the parameter inspector:
public object BeforeCall(string operationName, object[] inputs)
{
    // validate parameters before call
    var serviceIntance = OperationContext.Current.InstanceContext.GetServiceInstance() as IHasModelStateService;
    if (serviceIntance != null)
    {
        if (serviceIntance.ModelState == null)
        {
            serviceIntance.ModelState = new ModelState();
        }
        if (serviceIntance.ModelState.Errors == null)
        {
            serviceIntance.ModelState.Errors = new List<ModelError>();
        }

        IEnumerable<ModelValidationResult> validationResults = new ModelValidationResult[] { };
        foreach (object input in inputs)
        {
            if (input != null)
            {
                ModelMetadata metadata = ModelMetadataProviders.Current.GetMetadataForType(() => input, input.GetType());

                validationResults = ModelValidator.GetModelValidator(metadata).Validate(null);
                foreach (ModelValidationResult validationResult in validationResults)
                {
                    var temp = validationResult;

                    if (ThrowErrorOnFirstError)
                    {
                        throw new FaultException<ValidationFault>(new ValidationFault(new[] { temp }), "Validation error");
                    }

                    serviceIntance.ModelState.Errors.Add(new ModelError
                    {
                        MemberName = temp.MemberName,
                        Message = temp.Message
                    });
                }
            }
        }
        if (ThrowErrorAfterValidation && !serviceIntance.ModelState.IsValid)
        {
            throw new FaultException<ValidationFault>(new ValidationFault(validationResults), "Validation error");
        }
    }
    return null;
}

    Summary, to use this library. We need to do following steps:
  • Decorate the service method with [FaultContract(typeof(ValidationFault))]
  • Decorate the service method implementation or contract with [ParameterValidator], by default it will throw fault exception if there is validation error
  • Make service implementation implement interface IHasModelStateService
  • If we dont want to throw exception when validation error, we can check the ModelState.IsValid like MVC way and do whatever we want:

    if (!ModelState.IsValid)
    {
        var error = new StringBuilder();
        foreach (var e in ModelState.Errors)
        {
            error.Append(string.Format("Validation error on {0}:{1}\n", e.MemberName, e.Message));
        }
        throw new FaultException(error.ToString());
    }

    That's it. If we want to use Microsoft Validation Application Block or Fluent Validation, just replace the ModelMetadataProvider and ModelValidatorProvider like what people did with ASP.NET MVC. There is definitely alot of things to improve such as implementing an IgnoreValidationAttribute on a certain complex property of a view model or supporting the validation with validation attribute decorated inside the method contract, etc. I would happy to implement all of them when I have a chance to apply this stuff in my real project. Now it's pretty enough for my birthday night :D

Please refer to full source code here: https://github.com/vanthoainguyen/Blog/tree/master/WCF.Validation.Demo Cheers.

2 comments:

George K said...

Hi Van,
Is there any way to apply the behavior via configuration and avoid placing the same attribute over each and every operation inside the service contract/implementation?

Second question regarding the simple models. Like an operation taking just an integer. How would you validate that?

Van Nguyen said...

For first question, I believe there is. You properly need to implement some more things to achieve that: http://stackoverflow.com/questions/6201249/wcf-how-add-me-custom-servicebehavior-into-wcf-configuration


For second question, I know that C# allow you to assign attributes to method parameters? So you can extend this small library if you want.


I made this package long time ago as an experiment. I ended up not using this one for many reasons which I described here: http://thoai-nguyen.blogspot.com.au/2012/01/validation-wcf-with-nvalidator.html . You can try it.

Cheers

Post a Comment