Skip to content

Instantly share code, notes, and snippets.

@randyburden
Last active September 1, 2016 16:19
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save randyburden/4517014 to your computer and use it in GitHub Desktop.
Save randyburden/4517014 to your computer and use it in GitHub Desktop.
Provides numerous service interceptors/hooks for a WCF service, endpoint, or method all wrapped up in a single attribute. You can derive from the ServiceInterceptorAttribute class to create your own custom interceptor attribute or use it in conjunction with a class that implements IServiceInterceptor. Update: This has graduated to it's own proje…
using System;
using System.Collections.ObjectModel;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Description;
using System.ServiceModel.Dispatcher;
namespace Utilities.Wcf.Interceptors
{
/// <summary>
/// Provides numerous service interceptors/hooks for a WCF service, endpoint, or method.
/// </summary>
[AttributeUsage( AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true )]
public class ServiceInterceptorAttribute : Attribute, IServiceBehavior, IEndpointBehavior, IOperationBehavior, IParameterInspector
{
private readonly Type _iServiceInterceptor;
public Type ServiceType;
#region Constructors
public ServiceInterceptorAttribute()
{
}
/// <summary>
/// Initializes with an IServiceInterceptor.
/// </summary>
/// <param name="iServiceInterceptor">A type that implements IServiceInterceptor</param>
public ServiceInterceptorAttribute( Type iServiceInterceptor )
{
// Enforce the type to implement IServiceInterceptor
if ( !typeof( IServiceInterceptor ).IsAssignableFrom( iServiceInterceptor ) )
throw new ArgumentException( string.Format( "Wrong type: {0} must implement IServiceInterceptor", iServiceInterceptor ) );
_iServiceInterceptor = iServiceInterceptor;
}
#endregion Constructors
#region Implementation of IServiceBehavior
/// <summary>
/// Provides the ability to inspect the service host and the service description to confirm that the service can run successfully.
/// </summary>
/// <param name="serviceDescription">The service description.</param><param name="serviceHostBase">The service host that is currently being constructed.</param>
public virtual void Validate( ServiceDescription serviceDescription, ServiceHostBase serviceHostBase )
{
}
/// <summary>
/// Provides the ability to pass custom data to binding elements to support the contract implementation.
/// </summary>
/// <param name="serviceDescription">The service description of the service.</param><param name="serviceHostBase">The host of the service.</param><param name="endpoints">The service endpoints.</param><param name="bindingParameters">Custom objects to which binding elements have access.</param>
public virtual void AddBindingParameters( ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, Collection<ServiceEndpoint> endpoints, BindingParameterCollection bindingParameters )
{
}
/// <summary>
/// Provides the ability to change run-time property values or insert custom extension objects such as error handlers, message or parameter interceptors, security extensions, and other custom extension objects.
/// </summary>
/// <param name="serviceDescription">The service description.</param><param name="serviceHostBase">The host that is currently being built.</param>
public virtual void ApplyDispatchBehavior( ServiceDescription serviceDescription, ServiceHostBase serviceHostBase )
{
ServiceType = serviceDescription.ServiceType;
foreach ( var endpoint in serviceDescription.Endpoints )
{
endpoint.Behaviors.Add( this );
foreach ( var operation in endpoint.Contract.Operations )
{
if ( !operation.Behaviors.Contains( GetType() ) )
{
operation.Behaviors.Add( this );
}
}
}
}
#endregion Implementation of IServiceBehavior
#region Implementation of IEndpointBehavior
/// <summary>
/// Implement to confirm that the endpoint meets some intended criteria.
/// </summary>
/// <param name="endpoint">The endpoint to validate.</param>
public virtual void Validate( ServiceEndpoint endpoint )
{
}
/// <summary>
/// Implement to pass data at runtime to bindings to support custom behavior.
/// </summary>
/// <param name="endpoint">The endpoint to modify.</param><param name="bindingParameters">The objects that binding elements require to support the behavior.</param>
public virtual void AddBindingParameters( ServiceEndpoint endpoint, BindingParameterCollection bindingParameters )
{
}
/// <summary>
/// Implements a modification or extension of the service across an endpoint.
/// </summary>
/// <param name="endpoint">The endpoint that exposes the contract.</param><param name="endpointDispatcher">The endpoint dispatcher to be modified or extended.</param>
public virtual void ApplyDispatchBehavior( ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher )
{
}
/// <summary>
/// Implements a modification or extension of the client across an endpoint.
/// </summary>
/// <param name="endpoint">The endpoint that is to be customized.</param><param name="clientRuntime">The client runtime to be customized.</param>
public virtual void ApplyClientBehavior( ServiceEndpoint endpoint, ClientRuntime clientRuntime )
{
}
#endregion Implementation of IEndpointBehavior
#region Implementation of IOperationBehavior
/// <summary>
/// Implement to confirm that the operation meets some intended criteria.
/// </summary>
/// <param name="operationDescription">The operation being examined. Use for examination only. If the operation description is modified, the results are undefined.</param>
public virtual void Validate( OperationDescription operationDescription )
{
}
/// <summary>
/// Implements a modification or extension of the service across an operation.
/// </summary>
/// <param name="operationDescription">The operation being examined. Use for examination only. If the operation description is modified, the results are undefined.</param><param name="dispatchOperation">The run-time object that exposes customization properties for the operation described by <paramref name="operationDescription"/>.</param>
public virtual void ApplyDispatchBehavior( OperationDescription operationDescription, DispatchOperation dispatchOperation )
{
dispatchOperation.ParameterInspectors.Add( this );
}
/// <summary>
/// Implements a modification or extension of the client across an operation.
/// </summary>
/// <param name="operationDescription">The operation being examined. Use for examination only. If the operation description is modified, the results are undefined.</param><param name="clientOperation">The run-time object that exposes customization properties for the operation described by <paramref name="operationDescription"/>.</param>
public virtual void ApplyClientBehavior( OperationDescription operationDescription, ClientOperation clientOperation )
{
}
/// <summary>
/// Implement to pass data at runtime to bindings to support custom behavior.
/// </summary>
/// <param name="operationDescription">The operation being examined. Use for examination only. If the operation description is modified, the results are undefined.</param><param name="bindingParameters">The collection of objects that binding elements require to support the behavior.</param>
public virtual void AddBindingParameters( OperationDescription operationDescription, BindingParameterCollection bindingParameters )
{
}
#endregion Implementation of IOperationBehavior
#region Implementation of IParameterInspector
/// <summary>
/// Called before client calls are sent and after service responses are returned.
/// </summary>
/// <param name="operationName">The name of the operation.</param>
/// <param name="inputs">The objects passed to the method by the client.</param>
/// <returns>The correlation state that is returned as the correlationState parameter in AfterCall. Return null if you do not intend to use correlation state.</returns>
public virtual object BeforeCall( string operationName, object[] inputs )
{
object obj = null;
var interceptor = GetServiceInterceptor();
if ( interceptor != null )
{
obj = interceptor.BeforeCall( operationName, inputs );
}
return obj;
}
/// <summary>
/// Called after client calls are returned and before service responses are sent.
/// </summary>
/// <param name="operationName">The name of the invoked operation.</param>
/// <param name="outputs">Any output objects.</param>
/// <param name="returnValue">The return value of the operation.</param>
/// <param name="correlationState">Any correlation state returned from the BeforeCall method, or null.</param>
public virtual void AfterCall( string operationName, object[] outputs, object returnValue, object correlationState )
{
var interceptor = GetServiceInterceptor();
if ( interceptor != null )
{
interceptor.AfterCall( operationName, outputs, returnValue, correlationState );
}
}
#endregion Implementation of IParameterInspector
#region Public Methods
/// <summary>
/// Attempts to get an IServiceInterceptor.
/// </summary>
/// <remarks>
/// This method attempts to first use a passed in IServiceInterceptor.
/// If one was not passed in, then it attempts to use the current service instance
/// if it implements IServiceInterceptor otherwise it returns null.
/// </remarks>
/// <returns>An IServiceInterceptor</returns>
public IServiceInterceptor GetServiceInterceptor()
{
if ( _iServiceInterceptor != null )
{
var instance = Activator.CreateInstance( _iServiceInterceptor );
var serviceInterceptor = instance as IServiceInterceptor;
if ( serviceInterceptor != null )
{
return serviceInterceptor;
}
}
var serviceInstance = OperationContext.Current.InstanceContext.GetServiceInstance();
if ( serviceInstance != null )
{
var interceptor = serviceInstance as IServiceInterceptor;
if ( interceptor != null )
{
return interceptor;
}
}
return null;
}
#endregion Public Methods
}
/// <summary>
/// When used in conjunction with the ServiceInterceptorAttribute, this interfaces methods
/// will be called at the appropriate time.
/// </summary>
public interface IServiceInterceptor
{
/// <summary>
/// Called before client calls are sent and after service responses are returned.
/// </summary>
/// <param name="operationName">The name of the operation.</param>
/// <param name="inputs">The objects passed to the method by the client.</param>
/// <returns>The correlation state that is returned as the correlationState parameter in AfterCall. Return null if you do not intend to use correlation state.</returns>
object BeforeCall( string operationName, object[] inputs );
/// <summary>
/// Called after client calls are returned and before service responses are sent.
/// </summary>
/// <param name="operationName">The name of the invoked operation.</param>
/// <param name="outputs">Any output objects.</param>
/// <param name="returnValue">The return value of the operation.</param>
/// <param name="correlationState">Any correlation state returned from the BeforeCall method, or null.</param>
void AfterCall( string operationName, object[] outputs, object returnValue, object correlationState );
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment