Skip to content

Instantly share code, notes, and snippets.

@default-kramer
Created May 9, 2014 18:15
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save default-kramer/f8a8a212d94387741eca to your computer and use it in GitHub Desktop.
Save default-kramer/f8a8a212d94387741eca to your computer and use it in GitHub Desktop.
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