Skip to content

Commit d7c46ee

Browse files
committed
PR autofac#1 - Fix for KeyNotFoundException when using open generics. Includes some code cleanup.
1 parent c411fad commit d7c46ee

File tree

6 files changed

+221
-155
lines changed

6 files changed

+221
-155
lines changed
Lines changed: 161 additions & 155 deletions
Original file line numberDiff line numberDiff line change
@@ -1,156 +1,162 @@
1-
// This software is part of the Autofac IoC container
2-
// Copyright (c) 2007 - 2010 Autofac Contributors
3-
// http://autofac.org
4-
//
5-
// Permission is hereby granted, free of charge, to any person
6-
// obtaining a copy of this software and associated documentation
7-
// files (the "Software"), to deal in the Software without
8-
// restriction, including without limitation the rights to use,
9-
// copy, modify, merge, publish, distribute, sublicense, and/or sell
10-
// copies of the Software, and to permit persons to whom the
11-
// Software is furnished to do so, subject to the following
12-
// conditions:
13-
//
14-
// The above copyright notice and this permission notice shall be
15-
// included in all copies or substantial portions of the Software.
16-
//
17-
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18-
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
19-
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20-
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
21-
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
22-
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
23-
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
24-
// OTHER DEALINGS IN THE SOFTWARE.
25-
26-
using System;
27-
using System.Collections.Generic;
28-
using System.Diagnostics.CodeAnalysis;
29-
using System.Linq;
30-
using System.Reflection;
31-
using Autofac;
32-
using Autofac.Core;
33-
using Castle.DynamicProxy;
34-
35-
namespace Autofac.Extras.AggregateService
36-
{
37-
/// <summary>
38-
/// Interceptor that resolves types of properties and methods using a <see cref="IComponentContext"/>.
39-
/// </summary>
40-
public class ResolvingInterceptor : IInterceptor
41-
{
42-
private readonly IComponentContext _context;
43-
private readonly Dictionary<MethodInfo, Action<IInvocation>> _invocationMap;
44-
45-
///<summary>
46-
/// Initialize <see cref="ResolvingInterceptor"/> with an interface type and a component context.
47-
///</summary>
48-
public ResolvingInterceptor(Type interfaceType, IComponentContext context)
49-
{
50-
_context = context;
51-
_invocationMap = SetupInvocationMap(interfaceType);
52-
}
53-
54-
/// <summary>
55-
/// Intercepts a method invocation.
56-
/// </summary>
57-
/// <param name="invocation">
58-
/// The method invocation to intercept.
59-
/// </param>
60-
public void Intercept(IInvocation invocation)
61-
{
62-
if (invocation == null)
63-
{
64-
throw new ArgumentNullException("invocation");
65-
}
66-
var invocationHandler = _invocationMap[invocation.Method];
67-
invocationHandler(invocation);
68-
}
69-
70-
private Dictionary<MethodInfo, Action<IInvocation>> SetupInvocationMap(Type interfaceType)
71-
{
72-
var methods = interfaceType
73-
.GetUniqueInterfaces()
74-
.SelectMany(x => x.GetMethods())
75-
.ToArray();
76-
77-
var methodMap = new Dictionary<MethodInfo, Action<IInvocation>>(methods.Count());
78-
foreach (var method in methods)
79-
{
80-
var returnType = method.ReturnType;
81-
82-
if (returnType == typeof(void))
83-
{
84-
// Any method with 'void' return type (includes property setters) should throw an exception
85-
methodMap.Add(method, InvalidReturnTypeInvocation);
86-
}
87-
else if (GetProperty(method) != null)
88-
{
89-
// All properties should be resolved at proxy instantiation
90-
var propertyValue = _context.Resolve(returnType);
91-
methodMap.Add(method, invocation => invocation.ReturnValue = propertyValue);
92-
}
93-
else
94-
{
95-
// For methods with parameters, cache parameter info for use at invocation time
96-
var parameters = method.GetParameters()
97-
.OrderBy(parameterInfo => parameterInfo.Position)
98-
.Select(parameterInfo => new { parameterInfo.Position, parameterInfo.ParameterType })
99-
.ToArray();
100-
101-
if (parameters.Length > 0)
102-
{
103-
methodMap.Add(method, invocation =>
104-
{
105-
var arguments = invocation.Arguments;
106-
var typedParameters = parameters
107-
.Select(info => (Parameter)new TypedParameter(info.ParameterType, arguments[info.Position]));
108-
109-
invocation.ReturnValue = _context.Resolve(returnType, typedParameters);
110-
});
111-
}
112-
else
113-
{
114-
var methodWithoutParams = GetType()
115-
.GetMethod("MethodWithoutParams", BindingFlags.Instance | BindingFlags.NonPublic)
116-
.MakeGenericMethod(new[] { returnType });
117-
118-
var methodWithoutParamsDelegate = (Action<IInvocation>)Delegate.CreateDelegate(typeof(Action<IInvocation>), this, methodWithoutParams);
119-
methodMap.Add(method, methodWithoutParamsDelegate);
120-
}
121-
}
122-
}
123-
124-
return methodMap;
125-
}
126-
127-
// ReSharper disable UnusedMember.Local
128-
[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "This method gets called via reflection.")]
129-
private void MethodWithoutParams<TReturnType>(IInvocation invocation)
130-
// ReSharper restore UnusedMember.Local
131-
{
132-
invocation.ReturnValue = _context.Resolve<TReturnType>();
133-
}
134-
135-
private static void InvalidReturnTypeInvocation(IInvocation invocation)
136-
{
137-
throw new InvalidOperationException("The method " + invocation.Method + " has invalid return type System.Void");
138-
}
139-
140-
private static PropertyInfo GetProperty(MethodInfo method)
141-
{
142-
var takesArg = method.GetParameters().Length == 1;
143-
var hasReturn = method.ReturnType != typeof(void);
144-
if (takesArg == hasReturn) return null;
145-
// Ignore setters
146-
//if (takesArg)
147-
//{
148-
// return method.DeclaringType.GetProperties()
149-
// .Where(prop => prop.GetSetMethod() == method).FirstOrDefault();
150-
//}
151-
152-
return method.DeclaringType.GetProperties()
153-
.Where(prop => prop.GetGetMethod() == method).FirstOrDefault();
154-
}
155-
}
1+
// This software is part of the Autofac IoC container
2+
// Copyright (c) 2007 - 2010 Autofac Contributors
3+
// http://autofac.org
4+
//
5+
// Permission is hereby granted, free of charge, to any person
6+
// obtaining a copy of this software and associated documentation
7+
// files (the "Software"), to deal in the Software without
8+
// restriction, including without limitation the rights to use,
9+
// copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
// copies of the Software, and to permit persons to whom the
11+
// Software is furnished to do so, subject to the following
12+
// conditions:
13+
//
14+
// The above copyright notice and this permission notice shall be
15+
// included in all copies or substantial portions of the Software.
16+
//
17+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18+
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
19+
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20+
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
21+
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
22+
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
23+
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
24+
// OTHER DEALINGS IN THE SOFTWARE.
25+
26+
using System;
27+
using System.Collections.Generic;
28+
using System.Diagnostics.CodeAnalysis;
29+
using System.Globalization;
30+
using System.Linq;
31+
using System.Reflection;
32+
using Autofac;
33+
using Autofac.Core;
34+
using Castle.DynamicProxy;
35+
36+
namespace Autofac.Extras.AggregateService
37+
{
38+
/// <summary>
39+
/// Interceptor that resolves types of properties and methods using a <see cref="IComponentContext"/>.
40+
/// </summary>
41+
public class ResolvingInterceptor : IInterceptor
42+
{
43+
private readonly IComponentContext _context;
44+
45+
private readonly Dictionary<MethodInfo, Action<IInvocation>> _invocationMap;
46+
47+
/// <summary>
48+
/// Initialize <see cref="ResolvingInterceptor"/> with an interface type and a component context.
49+
/// </summary>
50+
public ResolvingInterceptor(Type interfaceType, IComponentContext context)
51+
{
52+
_context = context;
53+
_invocationMap = SetupInvocationMap(interfaceType);
54+
}
55+
56+
/// <summary>
57+
/// Intercepts a method invocation.
58+
/// </summary>
59+
/// <param name="invocation">
60+
/// The method invocation to intercept.
61+
/// </param>
62+
public void Intercept(IInvocation invocation)
63+
{
64+
if (invocation == null)
65+
{
66+
throw new ArgumentNullException("invocation");
67+
}
68+
69+
// Generic methods need to use the open generic method definition.
70+
var method = invocation.Method.IsGenericMethod ? invocation.Method.GetGenericMethodDefinition() : invocation.Method;
71+
var invocationHandler = _invocationMap[method];
72+
invocationHandler(invocation);
73+
}
74+
75+
private static PropertyInfo GetProperty(MethodInfo method)
76+
{
77+
var takesArg = method.GetParameters().Length == 1;
78+
var hasReturn = method.ReturnType != typeof(void);
79+
80+
if (takesArg == hasReturn)
81+
{
82+
return null;
83+
}
84+
85+
return method
86+
.DeclaringType
87+
.GetProperties()
88+
.Where(prop => prop.GetGetMethod() == method)
89+
.FirstOrDefault();
90+
}
91+
92+
private static void InvalidReturnTypeInvocation(IInvocation invocation)
93+
{
94+
throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "The method {0} has invalid return type System.Void", invocation.Method));
95+
}
96+
97+
[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "This method gets called via reflection.")]
98+
private void MethodWithoutParams(IInvocation invocation)
99+
{
100+
// To handle open generics, this resolves the return type of the invocation rather than the scanned method.
101+
invocation.ReturnValue = _context.Resolve(invocation.Method.ReturnType);
102+
}
103+
104+
private Dictionary<MethodInfo, Action<IInvocation>> SetupInvocationMap(Type interfaceType)
105+
{
106+
var methods = interfaceType
107+
.GetUniqueInterfaces()
108+
.SelectMany(x => x.GetMethods())
109+
.ToArray();
110+
111+
var methodMap = new Dictionary<MethodInfo, Action<IInvocation>>(methods.Count());
112+
foreach (var method in methods)
113+
{
114+
var returnType = method.ReturnType;
115+
116+
if (returnType == typeof(void))
117+
{
118+
// Any method with 'void' return type (includes property setters) should throw an exception
119+
methodMap.Add(method, InvalidReturnTypeInvocation);
120+
continue;
121+
}
122+
123+
if (GetProperty(method) != null)
124+
{
125+
// All properties should be resolved at proxy instantiation
126+
var propertyValue = _context.Resolve(returnType);
127+
methodMap.Add(method, invocation => invocation.ReturnValue = propertyValue);
128+
continue;
129+
}
130+
131+
// For methods with parameters, cache parameter info for use at invocation time
132+
var parameters = method.GetParameters()
133+
.OrderBy(parameterInfo => parameterInfo.Position)
134+
.Select(parameterInfo => new { parameterInfo.Position, parameterInfo.ParameterType })
135+
.ToArray();
136+
137+
if (parameters.Length > 0)
138+
{
139+
// Methods with parameters
140+
methodMap.Add(method, invocation =>
141+
{
142+
var arguments = invocation.Arguments;
143+
var typedParameters = parameters
144+
.Select(info => (Parameter)new TypedParameter(info.ParameterType, arguments[info.Position]));
145+
146+
// To handle open generics, this resolves the return type of the invocation rather than the scanned method.
147+
invocation.ReturnValue = _context.Resolve(invocation.Method.ReturnType, typedParameters);
148+
});
149+
150+
continue;
151+
}
152+
153+
// Methods without parameters
154+
var methodWithoutParams = this.GetType().GetMethod("MethodWithoutParams", BindingFlags.Instance | BindingFlags.NonPublic);
155+
var methodWithoutParamsDelegate = (Action<IInvocation>)Delegate.CreateDelegate(typeof(Action<IInvocation>), this, methodWithoutParams);
156+
methodMap.Add(method, methodWithoutParamsDelegate);
157+
}
158+
159+
return methodMap;
160+
}
161+
}
156162
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
using Xunit;
2+
3+
namespace Autofac.Extras.AggregateService.Test
4+
{
5+
public class AggregateServiceGenericsFixture
6+
{
7+
private IContainer _container;
8+
9+
public AggregateServiceGenericsFixture()
10+
{
11+
var builder = new ContainerBuilder();
12+
builder.RegisterAggregateService<IOpenGenericAggregate>();
13+
builder.RegisterGeneric(typeof(OpenGenericImpl<>))
14+
.As(typeof(IOpenGeneric<>));
15+
16+
this._container = builder.Build();
17+
}
18+
19+
/// <summary>
20+
/// Attempts to resolve an open generic by a method call
21+
/// </summary>
22+
[Fact]
23+
public void Method_ResolveOpenGeneric()
24+
{
25+
var aggregateService = this._container.Resolve<IOpenGenericAggregate>();
26+
27+
var generic = aggregateService.GetOpenGeneric<object>();
28+
Assert.NotNull(generic);
29+
30+
var ungeneric = aggregateService.GetResolvedGeneric();
31+
Assert.NotNull(ungeneric);
32+
Assert.NotSame(generic, ungeneric);
33+
}
34+
}
35+
}

test/Autofac.Extras.AggregateService.Test/Autofac.Extras.AggregateService.Test.csproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,17 +72,21 @@
7272
</Reference>
7373
</ItemGroup>
7474
<ItemGroup>
75+
<Compile Include="AggregateServiceGenericsFixture.cs" />
7576
<Compile Include="AggregateServiceInheritanceFixture.cs" />
7677
<Compile Include="ContainerBuilderExtensionsFixture.cs" />
7778
<Compile Include="AggregateServiceFixture.cs" />
7879
<Compile Include="AggregateServiceGeneratorFixture.cs" />
80+
<Compile Include="IOpenGeneric.cs" />
81+
<Compile Include="IOpenGenericAggregate.cs" />
7982
<Compile Include="ISomeOtherDependency.cs" />
8083
<Compile Include="ISubService.cs" />
8184
<Compile Include="IMyService.cs" />
8285
<Compile Include="IMyContext.cs" />
8386
<Compile Include="ISuperService.cs" />
8487
<Compile Include="ISomeDependency.cs" />
8588
<Compile Include="MyServiceImpl.cs" />
89+
<Compile Include="OpenGenericImpl.cs" />
8690
<Compile Include="Properties\AssemblyInfo.cs" />
8791
</ItemGroup>
8892
<ItemGroup>
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
namespace Autofac.Extras.AggregateService.Test
2+
{
3+
public interface IOpenGeneric<T>
4+
{
5+
}
6+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
namespace Autofac.Extras.AggregateService.Test
2+
{
3+
public interface IOpenGenericAggregate
4+
{
5+
IOpenGeneric<T> GetOpenGeneric<T>();
6+
7+
IOpenGeneric<string> GetResolvedGeneric();
8+
}
9+
}

0 commit comments

Comments
 (0)