Created
May 9, 2014 18:15
-
-
Save default-kramer/f8a8a212d94387741eca to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.Linq; | |
using Microsoft.VisualStudio.TestTools.UnitTesting; | |
using Autofac; | |
using Autofac.Core; | |
using System.Reflection; | |
using System.Linq.Expressions; | |
namespace test_autofac_generic_source | |
{ | |
public class GenericSource : IRegistrationSource | |
{ | |
[AttributeUsage(AttributeTargets.Method, Inherited = false, AllowMultiple = true)] | |
protected sealed class RegisterAsAttribute : Attribute | |
{ | |
public readonly Type openGenericType; | |
public RegisterAsAttribute(Type openGenericType) | |
{ | |
this.openGenericType = openGenericType; | |
} | |
} | |
public bool IsAdapterForIndividualComponents { get { return true; } } | |
private List<Tuple<RegisterAsAttribute, MethodInfo>> resolveMethods = new List<Tuple<RegisterAsAttribute, MethodInfo>>(); | |
public GenericSource() | |
{ | |
foreach (var method in this.GetType().GetMethods(BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance)) | |
{ | |
foreach (var att in method.GetCustomAttributes(true).OfType<RegisterAsAttribute>()) | |
{ | |
var attRoot = att.openGenericType.GetGenericTypeDefinition(); | |
var methodRoot = method.ReturnType.GetGenericTypeDefinition(); | |
if (!attRoot.IsAssignableFrom(methodRoot)) | |
{ | |
throw new Exception("cannot register {0} as {1}"); | |
} | |
resolveMethods.Add(Tuple.Create(att, method)); | |
} | |
} | |
} | |
public IEnumerable<IComponentRegistration> RegistrationsFor(Service service, Func<Service, IEnumerable<IComponentRegistration>> registrationAccessor) | |
{ | |
var swt = service as IServiceWithType; | |
if (swt == null) | |
{ | |
return Enumerable.Empty<IComponentRegistration>(); | |
} | |
return resolveMethods.Select(entry => CreateRegistration(service, entry.Item1, entry.Item2)) | |
.Where(x => x != null) | |
.ToList(); | |
} | |
private static Type OpenType(Type t) | |
{ | |
return t.IsGenericType ? t.GetGenericTypeDefinition() : t; | |
} | |
private static bool CouldBeAssignable(Type requested, Type actual) | |
{ | |
if (requested.IsGenericParameter) | |
{ | |
throw new Exception("how can requested ever be generic?"); | |
} | |
else if (actual.IsGenericParameter) | |
{ | |
return actual.GetGenericParameterConstraints().All(c => c.IsAssignableFrom(requested)); | |
} | |
else | |
{ | |
return requested.IsAssignableFrom(actual); | |
} | |
} | |
private IComponentRegistration CreateRegistration(Service service, RegisterAsAttribute att, MethodInfo method) | |
{ | |
var swt = (IServiceWithType)service; | |
var swtRoot = OpenType(swt.ServiceType); | |
var methodRoot = OpenType(method.ReturnType); | |
if (!swtRoot.IsAssignableFrom(methodRoot)) | |
{ | |
return null; | |
} | |
var requestedGenerics = swt.ServiceType.GetGenericArguments(); | |
var actualGenerics = method.ReturnType.GetGenericArguments(); | |
for (int i = 0; i < requestedGenerics.Length; i++) | |
{ | |
if (!CouldBeAssignable(requestedGenerics[i], actualGenerics[i])) | |
{ | |
return null; | |
} | |
} | |
var activator = BuildActivator(swt.ServiceType, method); | |
var guid = Guid.NewGuid(); | |
var services = new[] { service }; | |
return Autofac.Builder.RegistrationBuilder.CreateRegistration( | |
Guid.NewGuid(), | |
new Autofac.Builder.RegistrationData(service), | |
activator, | |
services); | |
} | |
private IInstanceActivator BuildActivator(Type serviceType, MethodInfo method) | |
{ | |
var type = typeof(CompiledActivator<>).MakeGenericType(serviceType); | |
var ctor = type.GetConstructor(new[] { typeof(MethodInfo), typeof(GenericSource) }); | |
object activator = ctor.Invoke(new object[] { method, this }); | |
return (IInstanceActivator)activator; | |
} | |
class CompiledActivator<TClosedReturnType> : IInstanceActivator | |
{ | |
object IInstanceActivator.ActivateInstance(IComponentContext context, IEnumerable<Parameter> parameters) | |
{ | |
return invoker(context, parameters); | |
} | |
Type IInstanceActivator.LimitType { get { return typeof(TClosedReturnType); } } | |
void IDisposable.Dispose() { } | |
private readonly Func<IComponentContext, IEnumerable<Parameter>, TClosedReturnType> invoker; | |
public CompiledActivator(MethodInfo openMethod, GenericSource instance) | |
{ | |
if (!openMethod.IsStatic && instance == null) | |
{ | |
throw new ArgumentNullException("instance"); | |
} | |
// TODO we haven't done enough validation to ensure that this will work. | |
// For example, if the generic parameters are swapped (e.g. "public Func<A, B> DoIt<B, A>()" will fail) | |
// Perhaps GenericParameterPosition is the key? | |
var methodGenerics = openMethod.ReturnType.GetGenericArguments(); | |
List<Type> makeGeneric = new List<Type>(); | |
for (int i = 0; i < methodGenerics.Length; i++) | |
{ | |
if (methodGenerics[i].IsGenericParameter) | |
{ | |
makeGeneric.Add(typeof(TClosedReturnType).GetGenericArguments()[i]); | |
} | |
} | |
var method = openMethod.MakeGenericMethod(makeGeneric.ToArray()); | |
// build a lambda of the form | |
// | |
// (IComponentContext context, IEnumerable<Parameter> parameters) => method(arg1, arg2, arg3, ...) | |
// | |
var ctx = Expression.Parameter(typeof(IComponentContext), "context"); | |
var parms = Expression.Parameter(typeof(IEnumerable<Parameter>), "parameters"); | |
// If argN is of type IComponentContext or IEnumerable<Parameter> it gets special treatment - just pass "context" or "parameters" along. | |
// Otherwise, do "context.Resolve<T>()" where T is the type of argN. | |
var args = new List<Expression>(); | |
foreach (var arg in method.GetParameters()) | |
{ | |
if (arg.ParameterType == ctx.Type) | |
{ | |
args.Add(ctx); | |
} | |
else if (arg.ParameterType == parms.Type) | |
{ | |
args.Add(parms); | |
} | |
else | |
{ | |
var resolveMethod = genericResolveMethod.MakeGenericMethod(arg.ParameterType); | |
args.Add(Expression.Call(resolveMethod, ctx)); | |
} | |
} | |
// handle static or instance method | |
Expression call; | |
if (method.IsStatic) | |
{ | |
call = Expression.Call(method, args); | |
} | |
else | |
{ | |
call = Expression.Call(Expression.Constant(instance), method, args); | |
} | |
var lambda = Expression.Lambda<Func<IComponentContext, IEnumerable<Parameter>, TClosedReturnType>>(call, ctx, parms); | |
invoker = lambda.Compile(); | |
} | |
// this is the common "context.Resolve<T>()" extension method | |
private static MethodInfo genericResolveMethod = typeof(Autofac.ResolutionExtensions) | |
.GetMethods(BindingFlags.Public | BindingFlags.Static) | |
.Where(m => m.Name == "Resolve") | |
.Where(m => m.IsGenericMethod) | |
.Where(m => m.GetParameters().Length == 1) | |
.Where(m => m.GetParameters().Single().ParameterType == typeof(IComponentContext)) | |
.Single(); | |
} | |
} | |
[TestClass] | |
public class tests | |
{ | |
// ------------------------------------------ | |
// IDbSet<T> example | |
// based on http://stackoverflow.com/questions/16224895/resolving-idbsett-from-autofac | |
// using classes instead of interfaces for simplicity | |
// ------------------------------------------ | |
public class DbSet<T> { } | |
public class DbContext<T> | |
{ | |
public DbSet<T> Set() { return new DbSet<T>(); } | |
} | |
public class DbSetExample : GenericSource | |
{ | |
[RegisterAs(typeof(DbSet<>))] | |
public static DbSet<T> ResolveDbSet<T>(DbContext<T> context) | |
{ | |
return context.Set(); | |
} | |
} | |
[TestMethod] | |
public void DbSet_example() | |
{ | |
var cb = new ContainerBuilder(); | |
cb.RegisterSource(new DbSetExample()); | |
cb.RegisterGeneric(typeof(DbContext<>)); | |
var container = cb.Build(); | |
var context = container.Resolve<DbContext<object>>(); | |
Assert.IsNotNull(context); | |
var set = container.Resolve<DbSet<object>>(); | |
Assert.IsNotNull(set); | |
} | |
// ------------------------------------------ | |
// generic delegate Pipe example | |
// based on http://stackoverflow.com/questions/7984944/is-it-possible-to-register-an-open-generic-delegate-in-autofac | |
// but modified because the OP's intent isn't clear to me | |
// ------------------------------------------ | |
delegate TOutput Pipe<in TInput, out TOutput>(TInput input); | |
abstract class AnonymousPipe<TInput, TOutput> | |
{ | |
public abstract TOutput Execute(TInput input); | |
} | |
class NotRandomPipe : AnonymousPipe<Random, int> | |
{ | |
public override int Execute(Random input) { return 42; } | |
} | |
class PipeExample : GenericSource | |
{ | |
[RegisterAs(typeof(Pipe<,>))] | |
public static Pipe<TInput, TOutput> ResolvePipe<TInput, TOutput>(AnonymousPipe<TInput, TOutput> pipe) | |
{ | |
return input => pipe.Execute(input); | |
} | |
} | |
[TestMethod] | |
public void pipe_example() | |
{ | |
var cb = new ContainerBuilder(); | |
cb.RegisterSource(new PipeExample()); | |
cb.RegisterAssemblyTypes(this.GetType().Assembly) | |
.AsClosedTypesOf(typeof(AnonymousPipe<,>)); | |
var container = cb.Build(); | |
var pipe = container.Resolve<Pipe<Random, int>>(); | |
int result = pipe(new Random()); | |
Assert.AreEqual(42, result); | |
} | |
// ------------------------------------------ | |
// generic delegate example using IHandle<TCommand> pattern | |
// ------------------------------------------ | |
delegate void CommandInvoker<in TCommand>(TCommand command); | |
interface IHandle<in TCommand> | |
{ | |
void Handle(TCommand command); | |
} | |
class SomeCommand { } | |
class Handler1 : IHandle<SomeCommand> | |
{ | |
public static int callCount = 0; | |
public void Handle(SomeCommand command) { callCount++; } | |
} | |
class Handler2 : IHandle<SomeCommand> | |
{ | |
public static int callCount = 0; | |
public void Handle(SomeCommand command) { callCount++; } | |
} | |
class CommandExample : GenericSource | |
{ | |
[RegisterAs(typeof(CommandInvoker<>))] | |
public static CommandInvoker<TCommand> ResolveInvoker<TCommand>(ILifetimeScope parentScope) | |
{ | |
return command => | |
{ | |
using (var childScope = parentScope.BeginLifetimeScope("commandScope")) | |
{ | |
foreach (var handler in childScope.Resolve<IEnumerable<IHandle<TCommand>>>()) | |
{ | |
handler.Handle(command); | |
} | |
} | |
}; | |
} | |
} | |
[TestMethod] | |
public void command_example() | |
{ | |
var cb = new ContainerBuilder(); | |
cb.RegisterSource(new CommandExample()); | |
cb.RegisterAssemblyTypes(this.GetType().Assembly) | |
.AsClosedTypesOf(typeof(IHandle<>)); | |
var container = cb.Build(); | |
var invoker = container.Resolve<CommandInvoker<SomeCommand>>(); | |
invoker(new SomeCommand()); | |
Assert.AreEqual(1, Handler1.callCount); | |
Assert.AreEqual(1, Handler2.callCount); | |
} | |
// ------------------------------------------ | |
// simple Tuple example | |
// ------------------------------------------ | |
class TupleExample : GenericSource | |
{ | |
[RegisterAs(typeof(Tuple<,>))] | |
public static Tuple<A, B> ResolveTuple2<A, B>(A a, B b) | |
{ | |
return Tuple.Create(a, b); | |
} | |
} | |
[TestMethod] | |
public void tuple_example() | |
{ | |
var cb = new ContainerBuilder(); | |
cb.RegisterSource(new TupleExample()); | |
cb.Register(c => new NullReferenceException("null ref")).AsSelf(); | |
cb.Register(c => new ArithmeticException("div 0")).AsSelf(); | |
var container = cb.Build(); | |
var tuple = container.Resolve<Tuple<NullReferenceException, ArithmeticException>>(); | |
Assert.AreEqual("null ref", tuple.Item1.Message); | |
Assert.AreEqual("div 0", tuple.Item2.Message); | |
} | |
// ------------------------------------------ | |
// generic constraint example | |
// ------------------------------------------ | |
class GenericConstraintExample : GenericSource | |
{ | |
[RegisterAs(typeof(Tuple<>))] | |
public static Tuple<T> ResolveTuple<T>(T item) where T : Exception | |
{ | |
return Tuple.Create(item); | |
} | |
} | |
[TestMethod] | |
public void generic_constraint_example() | |
{ | |
var cb = new ContainerBuilder(); | |
cb.RegisterSource(new GenericConstraintExample()); | |
cb.RegisterType<ArithmeticException>().AsSelf(); | |
cb.RegisterType<Random>().AsSelf(); | |
var container = cb.Build(); | |
var t1 = container.Resolve<Tuple<ArithmeticException>>(); | |
Assert.IsNotNull(t1); | |
// System.Random does not satisfy the constraint "where T : Exception" | |
// so it should not be registered | |
Assert.IsTrue(container.IsRegistered<Random>()); | |
Assert.IsFalse(container.IsRegistered<Tuple<Random>>()); | |
} | |
// ------------------------------------------ | |
// "partially open" generic example | |
// ------------------------------------------ | |
class PartiallyOpenExample : GenericSource | |
{ | |
private readonly string str; | |
public PartiallyOpenExample(string str) { this.str = str; } | |
[RegisterAs(typeof(Tuple<,,>))] | |
public Tuple<A, string, B> ResolveTuple3<A, B>(A a, B b) | |
{ | |
return Tuple.Create(a, str, b); | |
} | |
} | |
[TestMethod] | |
public void partially_open_example() | |
{ | |
var cb = new ContainerBuilder(); | |
cb.RegisterSource(new PartiallyOpenExample("a string")); | |
cb.Register(c => new NullReferenceException("null ref")).AsSelf(); | |
cb.Register(c => new ArithmeticException("div 0")).AsSelf(); | |
var container = cb.Build(); | |
var tuple = container.Resolve<Tuple<ArithmeticException, string, NullReferenceException>>(); | |
Assert.AreEqual("div 0", tuple.Item1.Message); | |
Assert.AreEqual("a string", tuple.Item2); | |
Assert.AreEqual("null ref", tuple.Item3.Message); | |
// if the middle parameter is not string, it is not registered | |
Assert.IsFalse(container.IsRegistered<Tuple<ArithmeticException, ArithmeticException, NullReferenceException>>()); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment