Skip to content

Commit

Permalink
PR #1 - Fix for KeyNotFoundException when using open generics. Includ…
Browse files Browse the repository at this point in the history
…es some code cleanup.
  • Loading branch information
tillig committed Nov 4, 2015
1 parent c411fad commit d7c46ee
Show file tree
Hide file tree
Showing 6 changed files with 221 additions and 155 deletions.
316 changes: 161 additions & 155 deletions src/Autofac.Extras.AggregateService/ResolvingInterceptor.cs
Original file line number Diff line number Diff line change
@@ -1,156 +1,162 @@
// This software is part of the Autofac IoC container
// Copyright (c) 2007 - 2010 Autofac Contributors
// http://autofac.org
//
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following
// conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.

using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reflection;
using Autofac;
using Autofac.Core;
using Castle.DynamicProxy;

namespace Autofac.Extras.AggregateService
{
/// <summary>
/// Interceptor that resolves types of properties and methods using a <see cref="IComponentContext"/>.
/// </summary>
public class ResolvingInterceptor : IInterceptor
{
private readonly IComponentContext _context;
private readonly Dictionary<MethodInfo, Action<IInvocation>> _invocationMap;

///<summary>
/// Initialize <see cref="ResolvingInterceptor"/> with an interface type and a component context.
///</summary>
public ResolvingInterceptor(Type interfaceType, IComponentContext context)
{
_context = context;
_invocationMap = SetupInvocationMap(interfaceType);
}

/// <summary>
/// Intercepts a method invocation.
/// </summary>
/// <param name="invocation">
/// The method invocation to intercept.
/// </param>
public void Intercept(IInvocation invocation)
{
if (invocation == null)
{
throw new ArgumentNullException("invocation");
}
var invocationHandler = _invocationMap[invocation.Method];
invocationHandler(invocation);
}

private Dictionary<MethodInfo, Action<IInvocation>> SetupInvocationMap(Type interfaceType)
{
var methods = interfaceType
.GetUniqueInterfaces()
.SelectMany(x => x.GetMethods())
.ToArray();

var methodMap = new Dictionary<MethodInfo, Action<IInvocation>>(methods.Count());
foreach (var method in methods)
{
var returnType = method.ReturnType;

if (returnType == typeof(void))
{
// Any method with 'void' return type (includes property setters) should throw an exception
methodMap.Add(method, InvalidReturnTypeInvocation);
}
else if (GetProperty(method) != null)
{
// All properties should be resolved at proxy instantiation
var propertyValue = _context.Resolve(returnType);
methodMap.Add(method, invocation => invocation.ReturnValue = propertyValue);
}
else
{
// For methods with parameters, cache parameter info for use at invocation time
var parameters = method.GetParameters()
.OrderBy(parameterInfo => parameterInfo.Position)
.Select(parameterInfo => new { parameterInfo.Position, parameterInfo.ParameterType })
.ToArray();

if (parameters.Length > 0)
{
methodMap.Add(method, invocation =>
{
var arguments = invocation.Arguments;
var typedParameters = parameters
.Select(info => (Parameter)new TypedParameter(info.ParameterType, arguments[info.Position]));
invocation.ReturnValue = _context.Resolve(returnType, typedParameters);
});
}
else
{
var methodWithoutParams = GetType()
.GetMethod("MethodWithoutParams", BindingFlags.Instance | BindingFlags.NonPublic)
.MakeGenericMethod(new[] { returnType });

var methodWithoutParamsDelegate = (Action<IInvocation>)Delegate.CreateDelegate(typeof(Action<IInvocation>), this, methodWithoutParams);
methodMap.Add(method, methodWithoutParamsDelegate);
}
}
}

return methodMap;
}

// ReSharper disable UnusedMember.Local
[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "This method gets called via reflection.")]
private void MethodWithoutParams<TReturnType>(IInvocation invocation)
// ReSharper restore UnusedMember.Local
{
invocation.ReturnValue = _context.Resolve<TReturnType>();
}

private static void InvalidReturnTypeInvocation(IInvocation invocation)
{
throw new InvalidOperationException("The method " + invocation.Method + " has invalid return type System.Void");
}

private static PropertyInfo GetProperty(MethodInfo method)
{
var takesArg = method.GetParameters().Length == 1;
var hasReturn = method.ReturnType != typeof(void);
if (takesArg == hasReturn) return null;
// Ignore setters
//if (takesArg)
//{
// return method.DeclaringType.GetProperties()
// .Where(prop => prop.GetSetMethod() == method).FirstOrDefault();
//}

return method.DeclaringType.GetProperties()
.Where(prop => prop.GetGetMethod() == method).FirstOrDefault();
}
}
// This software is part of the Autofac IoC container
// Copyright (c) 2007 - 2010 Autofac Contributors
// http://autofac.org
//
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following
// conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.

using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Linq;
using System.Reflection;
using Autofac;
using Autofac.Core;
using Castle.DynamicProxy;

namespace Autofac.Extras.AggregateService
{
/// <summary>
/// Interceptor that resolves types of properties and methods using a <see cref="IComponentContext"/>.
/// </summary>
public class ResolvingInterceptor : IInterceptor
{
private readonly IComponentContext _context;

private readonly Dictionary<MethodInfo, Action<IInvocation>> _invocationMap;

/// <summary>
/// Initialize <see cref="ResolvingInterceptor"/> with an interface type and a component context.
/// </summary>
public ResolvingInterceptor(Type interfaceType, IComponentContext context)
{
_context = context;
_invocationMap = SetupInvocationMap(interfaceType);
}

/// <summary>
/// Intercepts a method invocation.
/// </summary>
/// <param name="invocation">
/// The method invocation to intercept.
/// </param>
public void Intercept(IInvocation invocation)
{
if (invocation == null)
{
throw new ArgumentNullException("invocation");
}

// Generic methods need to use the open generic method definition.
var method = invocation.Method.IsGenericMethod ? invocation.Method.GetGenericMethodDefinition() : invocation.Method;
var invocationHandler = _invocationMap[method];
invocationHandler(invocation);
}

private static PropertyInfo GetProperty(MethodInfo method)
{
var takesArg = method.GetParameters().Length == 1;
var hasReturn = method.ReturnType != typeof(void);

if (takesArg == hasReturn)
{
return null;
}

return method
.DeclaringType
.GetProperties()
.Where(prop => prop.GetGetMethod() == method)
.FirstOrDefault();
}

private static void InvalidReturnTypeInvocation(IInvocation invocation)
{
throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "The method {0} has invalid return type System.Void", invocation.Method));
}

[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "This method gets called via reflection.")]
private void MethodWithoutParams(IInvocation invocation)
{
// To handle open generics, this resolves the return type of the invocation rather than the scanned method.
invocation.ReturnValue = _context.Resolve(invocation.Method.ReturnType);
}

private Dictionary<MethodInfo, Action<IInvocation>> SetupInvocationMap(Type interfaceType)
{
var methods = interfaceType
.GetUniqueInterfaces()
.SelectMany(x => x.GetMethods())
.ToArray();

var methodMap = new Dictionary<MethodInfo, Action<IInvocation>>(methods.Count());
foreach (var method in methods)
{
var returnType = method.ReturnType;

if (returnType == typeof(void))
{
// Any method with 'void' return type (includes property setters) should throw an exception
methodMap.Add(method, InvalidReturnTypeInvocation);
continue;
}

if (GetProperty(method) != null)
{
// All properties should be resolved at proxy instantiation
var propertyValue = _context.Resolve(returnType);
methodMap.Add(method, invocation => invocation.ReturnValue = propertyValue);
continue;
}

// For methods with parameters, cache parameter info for use at invocation time
var parameters = method.GetParameters()
.OrderBy(parameterInfo => parameterInfo.Position)
.Select(parameterInfo => new { parameterInfo.Position, parameterInfo.ParameterType })
.ToArray();

if (parameters.Length > 0)
{
// Methods with parameters
methodMap.Add(method, invocation =>
{
var arguments = invocation.Arguments;
var typedParameters = parameters
.Select(info => (Parameter)new TypedParameter(info.ParameterType, arguments[info.Position]));
// To handle open generics, this resolves the return type of the invocation rather than the scanned method.
invocation.ReturnValue = _context.Resolve(invocation.Method.ReturnType, typedParameters);
});

continue;
}

// Methods without parameters
var methodWithoutParams = this.GetType().GetMethod("MethodWithoutParams", BindingFlags.Instance | BindingFlags.NonPublic);
var methodWithoutParamsDelegate = (Action<IInvocation>)Delegate.CreateDelegate(typeof(Action<IInvocation>), this, methodWithoutParams);
methodMap.Add(method, methodWithoutParamsDelegate);
}

return methodMap;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
using Xunit;

namespace Autofac.Extras.AggregateService.Test
{
public class AggregateServiceGenericsFixture
{
private IContainer _container;

public AggregateServiceGenericsFixture()
{
var builder = new ContainerBuilder();
builder.RegisterAggregateService<IOpenGenericAggregate>();
builder.RegisterGeneric(typeof(OpenGenericImpl<>))
.As(typeof(IOpenGeneric<>));

this._container = builder.Build();
}

/// <summary>
/// Attempts to resolve an open generic by a method call
/// </summary>
[Fact]
public void Method_ResolveOpenGeneric()
{
var aggregateService = this._container.Resolve<IOpenGenericAggregate>();

var generic = aggregateService.GetOpenGeneric<object>();
Assert.NotNull(generic);

var ungeneric = aggregateService.GetResolvedGeneric();
Assert.NotNull(ungeneric);
Assert.NotSame(generic, ungeneric);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -72,17 +72,21 @@
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="AggregateServiceGenericsFixture.cs" />
<Compile Include="AggregateServiceInheritanceFixture.cs" />
<Compile Include="ContainerBuilderExtensionsFixture.cs" />
<Compile Include="AggregateServiceFixture.cs" />
<Compile Include="AggregateServiceGeneratorFixture.cs" />
<Compile Include="IOpenGeneric.cs" />
<Compile Include="IOpenGenericAggregate.cs" />
<Compile Include="ISomeOtherDependency.cs" />
<Compile Include="ISubService.cs" />
<Compile Include="IMyService.cs" />
<Compile Include="IMyContext.cs" />
<Compile Include="ISuperService.cs" />
<Compile Include="ISomeDependency.cs" />
<Compile Include="MyServiceImpl.cs" />
<Compile Include="OpenGenericImpl.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
Expand Down
6 changes: 6 additions & 0 deletions test/Autofac.Extras.AggregateService.Test/IOpenGeneric.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace Autofac.Extras.AggregateService.Test
{
public interface IOpenGeneric<T>
{
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace Autofac.Extras.AggregateService.Test
{
public interface IOpenGenericAggregate
{
IOpenGeneric<T> GetOpenGeneric<T>();

IOpenGeneric<string> GetResolvedGeneric();
}
}
Loading

0 comments on commit d7c46ee

Please sign in to comment.