I want to decorate my services with attributes for interception, and then have conventions based binding set the interceptors up for me. I don't want my attributes to inherit from the interception attributes... if I can avoid it.
For example, I have the following class:
[Log]
public class SomeClassToLog
{
public void DoSomething() { ... }
}
I understand I can bind this as follows:
var kernel = new StandardKernel();
kernel.Bind(x => x.FromAssembliesMatching("SomeProject.*")
.SelectAllClasses()
.WithAttribute(typeof(LogAttribute))
.BindToSelf().Configure(syntax => syntax.Intercept().With(LogInterceptor)));
How can I do this with different combinations of attributes and interceptors? For example:
If I have Log and Authorize attributes I would have to configure 3 sets of bindings? (1 for log without authorize, 1 for authorize without log and one for both log and authorize).
Updated: While I couldn't find a solution based on my original question parameters, I did stumble upon a similar question which lead me to the solution I ended up going with. Here is the source code:
Notes: Common.Interception.Interceptors namespace is in an assembly which has a reference to Ninject.Extensions.Interception (and all of its required dependencies). My attributes are defined in a separate assembly with no dependencies of their own.
MiscExtensions.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
namespace Common.Interception.Interceptors
{
// TODO: Remove the dependence on these eventually
/// <summary>
/// A bundle of extension methods which I needed to borrow from ninject source since they were internal:
/// Ninject.Extensions.Interception.Infrastructure.Language
/// ExtensionsForIEnumerable
/// ExtensionsForMethodInfo
/// ExtensionsForICustomAttributeProvider
/// </summary>
internal static class MiscExtensions
{
/// <summary>
/// Converts all of the items in the specified series using the specified converter.
/// </summary>
/// <typeparam name="TInput">The type of items contained in the input list.</typeparam>
/// <typeparam name="TOutput">The type of items to return.</typeparam>
/// <param name="items">The series of items to convert.</param>
/// <param name="converter">The converter to use to convert the items.</param>
/// <returns>A list of the converted items.</returns>
public static IEnumerable<TOutput> Convert<TInput, TOutput>(this IEnumerable<TInput> items,
Func<TInput, TOutput> converter)
{
return items.Select(converter);
}
/// <summary>
/// Skips the last items where the count of skipped items is given by count.
/// </summary>
/// <typeparam name="T">The type of the enumerable.</typeparam>
/// <param name="source">The source.</param>
/// <param name="count">The count of skipped items.</param>
/// <returns>An enumerable that skippes the last items from the source enumerable.</returns>
public static IEnumerable<T> SkipLast<T>(this IEnumerable<T> source, int count)
{
var enumerator = source.GetEnumerator();
var items = new Queue<T>();
while (enumerator.MoveNext())
{
if (count-- <= 0)
{
yield return items.Dequeue();
}
items.Enqueue(enumerator.Current);
}
}
private const BindingFlags DefaultBindingFlags =
BindingFlags.Public |
BindingFlags.NonPublic |
BindingFlags.Instance;
public static PropertyInfo GetPropertyFromMethod(this MethodInfo method, Type implementingType)
{
if (!method.IsSpecialName)
{
return null;
}
var isGetMethod = method.Name.Substring(0, 3) == "get";
var returnType = isGetMethod ? method.ReturnType : method.GetParameterTypes().Last();
var indexerTypes = isGetMethod ? method.GetParameterTypes() : method.GetParameterTypes().SkipLast(1);
return implementingType.GetProperty(method.Name.Substring(4), DefaultBindingFlags, null, returnType, indexerTypes.ToArray(), null);
}
public static PropertyInfo GetPropertyFromMethod(this MethodInfo method)
{
if (!method.IsSpecialName)
{
return null;
}
return method.DeclaringType.GetProperty(method.Name.Substring(4), DefaultBindingFlags);
}
/// <summary>
/// Gets the types of the parameters of the method.
/// </summary>
/// <param name="method">The method in question.</param>
/// <returns>An array containing the types of the method's parameters.</returns>
public static IEnumerable<Type> GetParameterTypes(this MethodBase method)
{
return method.GetParameters().Convert(p => p.ParameterType);
}
/// <summary>
/// Gets the method handle of either the method or its generic type definition, if it is
/// a generic method.
/// </summary>
/// <param name="method">The method in question.</param>
/// <returns>The runtime method handle for the method or its generic type definition.</returns>
public static RuntimeMethodHandle GetMethodHandle(this MethodBase method)
{
var mi = method as MethodInfo;
if (mi != null &&
mi.IsGenericMethod)
{
return mi.GetGenericMethodDefinition().MethodHandle;
}
return method.MethodHandle;
}
/// <summary>
/// Gets the first attribute of a specified type that decorates the member.
/// </summary>
/// <typeparam name="T">The type of attribute to search for.</typeparam>
/// <param name="member">The member to examine.</param>
/// <returns>The first attribute matching the specified type.</returns>
public static T GetOneAttribute<T>(this ICustomAttributeProvider member)
where T : Attribute
{
var attributes = member.GetCustomAttributes(typeof(T), true) as T[];
return (attributes == null) ||
(attributes.Length == 0)
? null
: attributes[0];
}
/// <summary>
/// Gets the first attribute of a specified type that decorates the member.
/// </summary>
/// <param name="member">The member to examine.</param>
/// <param name="type">The type of attribute to search for.</param>
/// <returns>The first attribute matching the specified type.</returns>
public static object GetOneAttribute(this ICustomAttributeProvider member, Type type)
{
object[] attributes = member.GetCustomAttributes(type, true);
return (attributes == null) ||
(attributes.Length == 0)
? null
: attributes[0];
}
/// <summary>
/// Gets an array of attributes matching the specified type that decorate the member.
/// </summary>
/// <typeparam name="T">The type of attribute to search for.</typeparam>
/// <param name="member">The member to examine.</param>
/// <returns>An array of attributes matching the specified type.</returns>
public static T[] GetAllAttributes<T>(this ICustomAttributeProvider member)
where T : Attribute
{
return member.GetCustomAttributes(typeof(T), true) as T[];
}
/// <summary>
/// Gets an array of attributes matching the specified type that decorate the member.
/// </summary>
/// <param name="member">The member to examine.</param>
/// <param name="type">The type of attribute to search for.</param>
/// <returns>An array of attributes matching the specified type.</returns>
public static object[] GetAllAttributes(this ICustomAttributeProvider member, Type type)
{
return member.GetCustomAttributes(type, true);
}
/// <summary>
/// Determines whether the member is decorated with one or more attributes of the specified type.
/// </summary>
/// <typeparam name="T">The type of attribute to search for.</typeparam>
/// <param name="member">The member to examine.</param>
/// <returns><see langword="True"/> if the member is decorated with one or more attributes of the type, otherwise <see langword="false"/>.</returns>
public static bool HasAttribute<T>(this ICustomAttributeProvider member)
where T : Attribute
{
return member.IsDefined(typeof(T), true);
}
/// <summary>
/// Determines whether the member is decorated with one or more attributes of the specified type.
/// </summary>
/// <param name="member">The member to examine.</param>
/// <param name="type">The type of attribute to search for.</param>
/// <returns><see langword="True"/> if the member is decorated with one or more attributes of the type, otherwise <see langword="false"/>.</returns>
public static bool HasAttribute(this ICustomAttributeProvider member, Type type)
{
return member.IsDefined(type, true);
}
/// <summary>
/// Determines whether the member is decorated with an attribute that matches the one provided.
/// </summary>
/// <typeparam name="T">The type of attribute to search for.</typeparam>
/// <param name="member">The member to examine.</param>
/// <param name="attributeToMatch">The attribute to match against.</param>
/// <returns><see langword="True"/> if the member is decorated with a matching attribute, otherwise <see langword="false"/>.</returns>
public static bool HasMatchingAttribute<T>(this ICustomAttributeProvider member, T attributeToMatch)
where T : Attribute
{
T[] attributes = member.GetAllAttributes<T>();
if ((attributes == null) ||
(attributes.Length == 0))
{
return false;
}
return attributes.Any(attribute => attribute.Match(attributeToMatch));
}
}
}
AlternateInterceptorRegistrationStrategy.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Ninject;
using Ninject.Components;
using Ninject.Extensions.Interception;
using Ninject.Extensions.Interception.Advice;
using Ninject.Extensions.Interception.Planning.Directives;
using Ninject.Extensions.Interception.Registry;
using Ninject.Planning;
using Ninject.Planning.Strategies;
namespace Common.Interception.Interceptors
{
/// <summary>
/// This is a derivation of InterceptorRegistrationStrategy from Ninject.Extensions.Interception.Planning.Strategies, merged with
/// http://stackoverflow.com/questions/6386461/ninject-intercept-any-method-with-certain-attribute
/// </summary>
public class AlternateInterceptorRegistrationStrategy<TAttribute, TInterceptor> : NinjectComponent, IPlanningStrategy
where TAttribute : Attribute
where TInterceptor : IInterceptor
{
protected const BindingFlags DefaultBindingFlags =
BindingFlags.Public |
BindingFlags.NonPublic |
BindingFlags.Instance;
public AlternateInterceptorRegistrationStrategy(IAdviceFactory adviceFactory, IAdviceRegistry adviceRegistry, IKernel kernel)
{
AdviceFactory = adviceFactory;
AdviceRegistry = adviceRegistry;
Kernel = kernel;
}
public IKernel Kernel { get; set; }
public IAdviceFactory AdviceFactory { get; set; }
public IAdviceRegistry AdviceRegistry { get; set; }
public virtual void Execute(IPlan plan)
{
IEnumerable<MethodInfo> candidates = GetCandidateMethods(plan.Type);
RegisterClassInterceptors(plan.Type, plan, candidates);
foreach (MethodInfo method in candidates)
{
PropertyInfo property = method.GetPropertyFromMethod(plan.Type);
ICustomAttributeProvider provider = (ICustomAttributeProvider)property ?? method;
TAttribute[] attributes = provider.GetAllAttributes<TAttribute>();
if (attributes.Length == 0)
{
continue;
}
RegisterMethodInterceptor(plan.Type, method);
// Indicate that instances of the type should be proxied.
if (!plan.Has<ProxyDirective>())
{
plan.Add(new ProxyDirective());
}
}
}
protected virtual void RegisterClassInterceptors(Type type, IPlan plan, IEnumerable<MethodInfo> candidates)
{
var attributes = type.GetAllAttributes<TAttribute>();
if (attributes.Length == 0)
{
return;
}
foreach (MethodInfo method in candidates)
{
PropertyInfo property = method.GetPropertyFromMethod(type);
ICustomAttributeProvider provider = (ICustomAttributeProvider) property ?? method;
var config = Kernel.Get<IInterceptorConfig>();
if (config.DoNotInterceptAttribute == null)
{
// A "do not intercept" attribute wasn't defined in the config, so go ahead and register
RegisterMethodInterceptor(type, method);
}
else if (!provider.IsDefined(config.DoNotInterceptAttribute, true))
{
// The method wasn't decorated with the "do not intercept" attribute, so go ahead and register
RegisterMethodInterceptor(type, method);
}
}
if (!plan.Has<ProxyDirective>())
{
plan.Add(new ProxyDirective());
}
}
protected virtual void RegisterMethodInterceptor(Type type, MethodInfo method)
{
IAdvice advice = AdviceFactory.Create(method);
advice.Callback = request => request.Context.Kernel.Get<TInterceptor>();
var config = Kernel.TryGet<IInterceptorConfig>();
if (config != null)
{
advice.Order = config.GetOrder<TInterceptor>();
}
AdviceRegistry.Register(advice);
}
protected virtual IEnumerable<MethodInfo> GetCandidateMethods(Type type)
{
MethodInfo[] methods = type.GetMethods(DefaultBindingFlags);
return methods.Where(ShouldIntercept);
}
protected virtual bool ShouldIntercept(MethodInfo methodInfo)
{
return methodInfo.DeclaringType != typeof(object) &&
!methodInfo.IsPrivate;// &&
//!methodInfo.IsFinal;
}
}
}
IInterceptorConfig.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Ninject.Extensions.Interception;
namespace Common.Interception.Interceptors
{
public interface IInterceptorConfig
{
IInterceptorConfig SpecifyOrder<TInterceptor>(int order) where TInterceptor : IInterceptor;
IInterceptorConfig SpecifyDoNotInterceptAttribute<TAttribute>() where TAttribute : Attribute;
int GetOrder<TInterceptor>() where TInterceptor : IInterceptor;
Type DoNotInterceptAttribute { get; }
}
}
InterceptorConfig.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Ninject.Extensions.Interception;
namespace Common.Interception.Interceptors
{
public class InterceptorConfig : IInterceptorConfig
{
private readonly Dictionary<Type, int> _orderDictionary = new Dictionary<Type, int>();
public IInterceptorConfig SpecifyOrder<TInterceptor>(int order) where TInterceptor : IInterceptor
{
_orderDictionary.Add(typeof(TInterceptor), order);
return this;
}
public IInterceptorConfig SpecifyDoNotInterceptAttribute<TAttribute>() where TAttribute : Attribute
{
DoNotInterceptAttribute = typeof(TAttribute);
return this;
}
public int GetOrder<TInterceptor>() where TInterceptor : IInterceptor
{
return _orderDictionary[typeof(TInterceptor)];
}
public Type DoNotInterceptAttribute { get; private set; }
}
}
TraceInterceptor.cs - just a sample interceptor
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Ninject.Extensions.Interception;
namespace Common.Interception.Interceptors
{
public class TraceInterceptor : IInterceptor
{
public void Intercept(IInvocation invocation)
{
Console.WriteLine("Enter Method");
invocation.Proceed();
Console.WriteLine("Exit Method");
}
}
}
Notes: Here is a simple console app that shows how to wire up the attributes/interceptors. This has dependencies on both Ninject.Extensions.Interception.DynamicProxy and Ninject.Extensions.Conventions (and all their required dependencies)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using Common.Interception.Attributes;
using Common.Interception.Interceptors;
using Ninject;
using Ninject.Extensions.Conventions;
using Ninject.Planning.Strategies;
using SomeProject.Infrastructure;
namespace SomeProject.ConsoleApp
{
class Program
{
static void Main(string[] args)
{
var kernel = new StandardKernel();
kernel.Components.Add<IPlanningStrategy, AlternateInterceptorRegistrationStrategy<TraceAttribute, TraceInterceptor>>();
kernel.Components.Add<IPlanningStrategy, AlternateInterceptorRegistrationStrategy<AuthorizeAttribute, AuthorizeInterceptor>>();
// I needed a way to specify execution order and "DoNotIntercept" without deriving from attributes that would force ninject references all over my domain
// not 100% confident this is the best way - but it works
kernel.Bind<IInterceptorConfig>().ToConstant(new InterceptorConfig()
.SpecifyOrder<TraceInterceptor>(1)
.SpecifyOrder<AuthorizeInterceptor>(0)
.SpecifyDoNotInterceptAttribute<DoNotInterceptAttribute>());
// SomeProject.Infrastructure contains my service classes decorated with my custom attributes
kernel.Bind(x => x.FromAssembliesMatching("SomeProject.Infrastructure")
.SelectAllClasses()
.BindToSelf());
var a = kernel.Get<SomeServiceA>();
var b = kernel.Get<SomeServiceB>();
Console.WriteLine("Calling a.DoSomeStuff()...");
a.DoSomeStuff();
Console.WriteLine("Calling b.DoMyThing()...");
b.DoMyThing();
Console.WriteLine("Calling b.NowTraceThis()...");
b.NowTraceThis();
Console.ReadLine();
}
}
}
After scouring and scouring, I found that a similar question had been asked and answered here: Ninject Intercept any method with certain attribute?
I used this in conjunction with the source code of the InterceptorRegistrationStrategy class within Ninject.Extensions.Interception.Planning.Strategies to create a derivation which I am now using.
crickets