Feb 24, 2012

Wcf parameter inspector - IParameterInspector

The parameter inspector is a way of intercepting requests / responses after all the processing of extracting parameters from incoming messages (or before they’re packaged in outgoing messages) is done. Before and after each call, the inspector gets a chance to inspect the operation inputs, outputs and return value, in the same types as defined by the operation contract. There is no conversion required, you just need to cast it to the desired type, since the parameters are passed as objects.

This can be done by implementiong IParameterInspector which has only two methods:
1. public object BeforeCall(string operationName, object[] inputs)
2. public void AfterCall(string operationName, object[] outputs, object returnValue, object correlationState)

Real world scenario
1. Some common validation which need to be done for large number of operations. For example name field which accepts only [a-zA-Z]. It would't be hard to implement this in the method (operation) itself, but if you need to do this for lot of cases then it might make sense to implement the validation logic as an IParameterInspector extension that can be declaratively applied to any operation.

2. To capture performance timing. The parameter inspector interface methods are called really close to the actual method invocation both on the client and on the server, so the time difference between before and after call can be calculated as operation duration.
  

 class NameValidationParameterInspector : IParameterInspector
    {
        readonly int _nameParamIndex;
        const string NameFormat ="[a-zA-Z]";
  
        public NameValidationParameterInspector() : this(0) { }

        public NameValidationParameterInspector(int nameParamIndex)
        {
            _nameParamIndex = nameParamIndex;
        }

        public object BeforeCall(string operationName, object[] inputs)
        {
   //Access the parameter. you need to know the index of the parameter. In this case we will always validate the 
   //first parameter which is being passed
            string nameParam = inputs[_nameParamIndex] as string;

            if (nameParam != null)
                if (!Regex.IsMatch(
                    nameParam, NameFormat, RegexOptions.None))
                    throw new FaultException(
                        "Invalid name. Only alphabetical character");
            return null;
        }

        public void AfterCall(string operationName, object[] outputs, object returnValue, object correlationState){}
    }
And now add this to behavior
 

 public class NameValidationOperationBehavior : Attribute, IOperationBehavior
    {
        public void ApplyClientBehavior(OperationDescription operationDescription, ClientOperation clientOperation)
        {
            NameValidationParameterInspector nameValidationParameterInspector = new NameValidationParameterInspector();
            clientOperation.ParameterInspectors.Add(nameValidationParameterInspector);
        }

        public void AddBindingParameters(OperationDescription operationDescription, BindingParameterCollection bindingParameters){}
  
        public void Validate(OperationDescription operationDescription){}

        public void ApplyDispatchBehavior(OperationDescription operationDescription, DispatchOperation dispatchOperation)
        {
            NameValidationParameterInspector nameValidationParameterInspector = new NameValidationParameterInspector();
            dispatchOperation.ParameterInspectors.Add(nameValidationParameterInspector);
        }
    }
And now you can decorate any operation with attribute NameValidationOperationBehavior.
In this example I calculate the operation time
 
 public class OperationProfilerParameterInspector : IParameterInspector
    {
        public object BeforeCall(string operationName, object[] inputs)
        {
                return DateTime.Now;
        }

        public void AfterCall(string operationName, object[] outputs, object returnValue, object correlationState)
        {
            DateTime endCall = DateTime.Now;
            DateTime startCall = (DateTime)correlationState;
            TimeSpan operationDuration = endCall.Subtract(startCall);
        }
    }
And now add to the EndpointBehavior
 
 public class OperationProfilerEndpointBehavior : IEndpointBehavior
    {
        public void Validate(ServiceEndpoint endpoint){}
        public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters){}
  
        public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
        {
            foreach (DispatchOperation operation in endpointDispatcher.DispatchRuntime.Operations)
            {
                operation.ParameterInspectors.Add(new OperationProfilerParameterInspector());
            }
        }

        public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
        {
            foreach (ClientOperation operation in clientRuntime.Operations)
            {
                operation.ParameterInspectors.Add(new OperationProfilerParameterInspector());
            }
        }
    }

    public class OperationProfilerBehaviorExtensionElement : BehaviorExtensionElement
    {
        protected override object CreateBehavior()
        {
            return new OperationProfilerEndpointBehavior();
        }

        public override Type BehaviorType
        {
            get { return typeof(OperationProfilerEndpointBehavior); }
        }
    }
configuration change for the new behavior
 
 
  
    
   
    
  
    
    
    
      
        
          
          
          
          
        
      

      
        
          
        
      
    

    
      
        
      
    
   
    
  
The same can be done at the client side.
    Service1Client client = new Service1Client();
    client.Endpoint.Behaviors.Add(new     OperationProfilerEndpointBehavior());

or in configuration like this


        
          
        
      
      
        
          
            
          
        

      

            
        

No comments:

Post a Comment