I was recently surprised to find out first hand that the Enterprise Library Validation Block throws a somewhat obscure exception: ArgumentNullException(“operation.SyncMethod”) when the WCF AsyncPattern is employed.
By debugging into the Enterprise Library source code (EntLib 3.1), I came to understand that the Validation Block was coded to handle synchronous service operations only (using the FaultContract<ValidationFault> attribute on the service operation signature . In our case, some of our operations also implement the asynchronous pattern and we have Begin and End method pairs for each.
While I was aware that one-way operations do not allow faults to be returned, I was’t sure about asynchronous service operations (implemented as asynchronous on the service side). I checked the .NET documentation and was able to confirm that FaultContract<T> is supported on service operations implemented asynchronously using the AsyncPattern property. A difficult to find comment exists in the documentation of the FaultException(TDetail) Generic Class: “The FaultContractAttribute can be used to specify SOAP faults for both two-way service methods and for asynchronous method pairs.” See http://msdn.microsoft.com/en-us/library/ms576199.aspx.
With this information in hand, I reasonned that I should be able to modify the validation block. I made the code changes, ran the NUnit tests included in the validation block, and employed the modified assembly on our asynchronous service operations. I made modifications to ValidationParameterInspector.cs and ValidationBehavior.cs. The scope of the modifications included the following methods:
- ValidationParameterInspector.ValidationParameterInspector
- ValidationParameterInspector.BeforeCall
- ValidationBehavior.HasValidationAssertions
Note that the modifications apply to both EntLib 3.1 and EntLib 4.0. The modifications are included below:
Changes to ValidationParameterInspector.cs
/// <summary>
/// Constructor to initialize <see cref=”InputValidators”/>.
/// </summary>
/// <remarks>
/// When the operation is a <see cref=”OperationDescription.BeginMethod”/> because it implements the
/// the <see cref=”OperationContractAttribute.AsyncPattern”/>,
/// it will cause the <see cref=”InputValidators”/> to have empty validators for the
/// <see cref=”AsyncCallback”>callback</see> and <see cref=”Object”>state</see> method
/// arguments.
/// </remarks>
/// <param name=”operation”>The <see cref=”OperationDescription”/> of the operation.</param>
/// <param name=”ruleSet”>A <see cref=”String”/> naming the ruleset to be used.</param>
public ValidationParameterInspector(OperationDescription operation, string ruleSet)
{
// Check the synchronous method of the service first. If the operation implements the
// OperationContractAttribute.AsyncPattern property, set the method to the BeginMethod.
MethodInfo method = operation.SyncMethod;
if (method == null) method = operation.BeginMethod;
foreach (ParameterInfo param in method.GetParameters())
{
switch (param.Attributes)
{
case ParameterAttributes.Out:
case ParameterAttributes.Retval:
break;
default:
inputValidators.Add(CreateInputParameterValidator(param, ruleSet));
inputValidatorParameterNames.Add(param.Name);
break;
}
}
}
/// <summary>
/// Performs the validations.
/// </summary>
/// <param name=”operationName”>A <see cref=”String”/> containing the name of the operation.</param>
/// <param name=”inputs”>
/// An <see cref=”object”/> array containing the input parameters of the method and expected as described on
/// the operation, and thus excluding input parameters resulting from the service’s implementation of the
/// <see cref=”OperationContractAttribute.AsyncPattern”/> property.
/// </param>
/// <returns>null</returns>
public object BeforeCall(string operationName, object[] inputs)
{
ValidationFault fault = new ValidationFault();
// Because there could be fewer inputs than inputValidators, iterate
// through the inputs to run the corresponding validations. This accounts
// for asynchronous methods where the callback and state are defined in the
// method but are not exposed outside the boundary of the service.
for (int i = 0; i < inputs.Length; ++i)
{
ValidationResults results = inputValidators[i].Validate(inputs[i]);
AddFaultDetails(fault, inputValidatorParameterNames[i], results);
}
if (!fault.IsValid)
{
throw new FaultException<ValidationFault>(fault);
}
return null;
}
Changes to ValidationBehavior.cs
/// <summary>
/// Returns a <see cref=”Boolean”/> to indicate whether or not an operation has validation assertions.
/// </summary>
/// <remarks>
/// If the method implements the <see cref=”OperationContractAttribute.AsyncPattern”/> property,
/// the BeginMethod is used instead of the SyncMethod because the SyncMethod is null.
/// </remarks>
/// <param name=”operation”></param>
/// <returns></returns>
private bool HasValidationAssertions(OperationDescription operation)
{
if (operation == null) throw new ArgumentNullException(“operation”);
// Check the synchronous method of the service first. If the operation implements the
// OperationContractAttribute.AsyncPattern property, set the method to the BeginMethod.
MethodInfo methodInfo = operation.SyncMethod;
if (methodInfo == null) methodInfo = operation.BeginMethod;
return methodInfo.GetCustomAttributes(typeof(ValidatorAttribute), false).Length > 0 ||
HasParametersWithValidationAssertions(methodInfo.GetParameters());
}
I created a copy of this post on CodePlex. See http://www.codeplex.com/entlib/Thread/View.aspx?ThreadId=31493.