Partager via


Creating Interception Policy Injection Call Handlers

To create a call handler for policy injection, you must understand the way that Unity passes calls through the policy pipeline. This topic explains how the pipeline executes call handlers, and how it can block or abort execution when an error occurs, or on demand (such as when a validation handler detects a validation error or an authorization handler detects an unauthorized user). This topic contains the following sections:

  • The ICallHandler Interface and Pipeline Execution
  • Outline Implementation of a Call Handler
  • Exceptions and Aborted Pipeline Execution

The ICallHandler Interface and Pipeline Execution

The Unity interception mechanism defines an interface named ICallHandler, which all classes that implement handlers for a call handler pipeline must implement. This interface defines two delegates and a single method named Invoke. The first delegate, named InvokeHandlerDelegate, is called by the previous handler to execute this handler—and therefore has the same signature as the Invoke method. The second, named GetNextHandlerDelegate, determines which is the next handler in the chain to invoke (the handler to which this handler should pass control when it completes its own preprocessing stage).

The signature for the InvokeHandlerDelegate and the Invoke method contains an instance of a class that implements the IMethodInvocation interface and a reference to an instance of the GetNextHandlerDelegate class. The IMethodInvocation implementation instance contains information about the current method invocation or property access, including the parameters to pass to the method or property.

The Invoke method returns an instance of a class that implements the IMethodReturn interface. This class instance contains any return value from the method or property accessor and any other information to return to the client. Usually, this is a concrete instance of the ReturnMessage class. The following extract shows the complete ICallHandler interface.

public interface ICallHandler
{
  IMethodReturn Invoke(IMethodInvocation input, GetNextHandlerDelegate  getNext);
  int Order { get; set; }
}

public delegate IMethodReturn InvokeHandlerDelegate(IMethodInvocation input,
                              GetNextHandlerDelegate getNext);

public delegate InvokeHandlerDelegate GetNextHandlerDelegate();
'Usage
Public Interface ICallHandler
  Function Invoke(ByVal input As IMethodInvocation, _
                  ByVal getNext As GetNextHandlerDelegate) As IMethodReturn
  Property Order() As Integer
End Interface

Public Delegate Function InvokeHandlerDelegate(ByVal input As IMethodInvocation, _
                  ByVal getNext As GetNextHandlerDelegate) As IMethodReturn

Public Delegate Function GetNextHandlerDelegate() As InvokeHandlerDelegate

The call handler pipline is part of the policy injection behavior which, if used, is just one behavior in the behaviors pipeline when performing Interception with Unity.

The following schematic shows the process for invocation through the call handler pipeline under normal conditions.

Ff660871.3b2cbde8-a8b0-4b86-aec6-cfe5123934c0(en-us,PandP.20).png

Outline Implementation of a Call Handler

The following extract shows the basic outline of the Invoke method for a custom handler class, indicating where you can add code to perform both the preprocessing and postprocessing tasks you require. The code excludes the definition of the constructors and the Order property.

[ConfigurationElementType(typeof(CustomCallHandlerData))]
public class ExampleHandler : ICallHandler
{
  public IMethodReturn Invoke(IMethodInvocation input,
                               GetNextHandlerDelegate getNext)
  {
    // Declare any variables required for values used in this method here.
    ...
    ...

    // Perform any pre-processing tasks required in the custom handler here.
    // This code executes before control passes to the next handler.
    ...
    ...

    // Use the following line of code in any handler to invoke the next 
    // handler that Unity should execute. This code gets
    // the current return message that you must pass back to the caller:
    IMethodReturn msg = getNext()(input, getNext);  

    // Perform any post-processing tasks required in the custom handler here.
    // This code executes after the invocation of the target object method or
    // property accessor, and before control passes back to the previous
    // handler as the Invoke call stack unwinds. You can modify the return 
    // message if required.
    ...
    ...

    // Return the message to the calling code, which may be the previous 
    // handler or, if this is the first handler in the chain, the client.   
    return msg;
  }

  ... other class members as required here
}
'Usage
<ConfigurationElementType(GetType(CustomCallHandlerData))> _
Public Class ExampleHandler : Implements ICallHandler

  Public Function Invoke(ByVal input As IMethodInvocation, _
                     ByVal getNext As GetNextHandlerDelegate) As IMethodReturn _
         Implements ICallHandler.Invoke
  
    ' Declare any variables required for values used in this method here.
    ...
    ...

    ' Perform any pre-processing tasks required in the custom handler here.
    ' This code executes before control passes to the next handler.
    ...
    ...

    ' Use the following line of code in any handler to invoke the next 
    ' handler that Unity should execute. This code gets
    ' the current return message that you must pass back to the caller:
    Dim msg As IMethodReturn = getNext()(input, getNext)

    ' Perform any post-processing tasks required in the custom handler here.
    ' This code executes after the invocation of the target object method or
    ' property accessor, and before control passes back to the previous
    ' handler as the Invoke call stack unwinds. You can modify the return 
    ' message if required.
    ...
    ...

    ' Return the message to the calling code, which may be the previous 
    ' handler or, if this is the first handler in the chain, the client.   
    Return msg
  End Function

  ... other class members as required here

End Class

The Invoke method receives a reference to an instance of a concrete implementation of the IMethodInvocation class. Your handler can access the properties of this instance to get information about the current method or property accessor call. For example, you can access the target object instance (Target), the name of the target method or property (MethodBase.Name), a count of the number of inputs (Inputs.Count), and a collection of all the parameter values (Arguments).

Note

Your code can change the values of the properties of the IMethodInvocation instance. However, be aware that changing the values of some properties may cause unexpected behavior. You should avoid changing any properties that affect the name, type, or signature of the target member.

Unity reuses handler instances in different pipelines to minimize the number of objects it must create. Therefore, handlers should not store any per-call state in member variables. In multi-threaded applications, the action of multiple calls passing through the same handler instance is likely to corrupt the internal state.

Exceptions and Aborted Pipeline Execution

Exceptions may occur in a call handler, or a call handler may wish to abort or short-circuit execution so that the subsequent handlers in the pipeline do not execute and Unity does not invoke the target method or property accessor. In this case, your code should simply avoid calling the next handler and generate a suitable return message. In other words, you omit the code line shown in the previous example that calls the getNext method. This causes all the previous handlers to execute their postprocessing tasks as the Invoke stack unwinds.

If your handler has to abort processing completely without allowing previous handlers to execute, you can create and raise a suitable exception type—depending on the tasks that your handler carries out. However, in most cases, you should avoid this approach and, instead, add to the message any exceptions you want to raise to the client so that previous handlers (which may perform logging or process exceptions) can execute their postprocessing tasks.

For example, the following schematic illustrates a scenario where a handler in the handler pipeline aborts execution. In this scenario, the handler pipeline contains—in execution order—a handler that performs logging, a handler that performs authorization, and a handler that validates the values in the parameters of the method call.

Ff660871.445d4b5d-e8fd-4ed7-9d2b-fee41ca197dd(en-us,PandP.20).png

If, as shown in the schematic, the authorization handler detects an authorization failure, it can return an exception to the original caller by adding it to the message that passes along the handler pipeline. This allows handlers earlier in the pipeline to carry out their post-processing tasks (such as, in this example, creating and writing a log message) as the stack unwinds. If, instead of passing it back through the message, a handler raises an unhandled exception, the previous handlers will not be able to execute their post-processing tasks.

To add an exception to the message, you create a new instance of the ReturnMessage class using the constructor that accepts a System.Exception instance or a subclass of System.Exception. For example, this code shows how you can add an exception to the return message when the current method request for some business-related activity occurs on a Saturday or Sunday.

public IMethodReturn Invoke(IMethodInvocation input, GetNextHandlerDelegate getNext)
{
  GregorianCalendar cal = new GregorianCalendar();
  DayOfWeek weekDay = cal.GetDayOfWeek(DateTime.Now);
  if ((weekDay == DayOfWeek.Saturday) || (weekDay == DayOfWeek.Sunday))  
  {
    // Create the exception to return and the return message.
    Exception ex = new Exception("Available on weekdays only");
    IMethodReturn msg = input.CreateExceptionMethodReturn(ex);
    return msg;  
  }
  else
  {
    // Do nothing except invoke the next handler. 
    return getNext()(input, getNext);  
  } 
}
'Usage
Public Function Invoke(ByVal input As IMethodInvocation, _
                  ByVal getNext As GetNextHandlerDelegate) As IMethodReturn _
       Implements ICallHandler.Invoke

  Dim cal As New GregorianCalendar()
  Dim weekDay As DayOfWeek = cal.GetDayOfWeek(DateTime.Now)
  If (weekDay = DayOfWeek.Saturday) Or (weekDay = DayOfWeek.Sunday) Then
  
    ' Create the exception to return and the return message.
    Dim ex As New Exception("Available on weekdays only")
    Dim msg As IMethodReturn = input.CreateExceptionMethodReturn(ex)
    Return msg  
  
  Else
    ' Do nothing except invoke the next handler. 
    return getNext()(input, getNext)  
  End If 
End Function