-
-
Notifications
You must be signed in to change notification settings - Fork 845
Re-enable RegsiterTypes filtering; better message for RegisterType issues #1389
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
- `ctxt` => `context` everywhere. Community generally uses either `ctx` or `context`, and it didn't make sense to abbreviate. Most .NET examples spell it out. - `parameterised` => `parameterized`. Our default language attribute is en-US so it makes sense to make it common. We have it both ways in here. - Removed unused "using" statements. - Removed unnecessary FxCop suppressions.
- Separate methods into more appropriate owning classes. - Update method names to be more descriptive. - Add documentation to explain non-obvious logic.
@@ -61,6 +62,12 @@ public static class RegistrationBuilder | |||
public static IRegistrationBuilder<TImplementer, ConcreteReflectionActivatorData, SingleRegistrationStyle> ForType<TImplementer>() | |||
where TImplementer : notnull | |||
{ | |||
// Open generics can't be generic type parameters so we don't have to check for that here. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
RegistrationBuilder.ForType
(both overloads) is where all the RegisterType<T>
and RegisterType(Type t)
calls come through. It also gets used a few places directly like in ACTNARS and assembly/type scanning.
Adding the check here means we can catch any issues where someone is trying to do something that probably won't work with reflection activation and provide a more proactive error.
It does mean, though, that we may do the same checks twice on a given type. For example, while scanning the set of types to register, we'll filter off things that don't match this criteria; then when the registration gets created it will be checked again here. Ideally it means we won't get any of these errors during any sort of scanning, but it may have container build performance impacts due to the double-check.
<data name="OnlyRegisterableTypesAllowed" xml:space="preserve"> | ||
<value>The type '{0}' is not concrete. Only concrete types (no interfaces, abstract classes, open generics, etc.) can be registered by type.</value> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the exception message that will be thrown if you register types that generally can't be activated by reflection.
if (!serviceType.IsClass || | ||
serviceType.IsSubclassOf(typeof(Delegate)) || | ||
serviceType.IsAbstract || | ||
if (!serviceType.MayAllowReflectionActivation(allowCompilerGenerated: true) || |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ACTNARS now uses the shared logic for filtering.
@@ -0,0 +1,53 @@ | |||
// Copyright (c) Autofac Project. All rights reserved. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This all came out of the OpenGenericScanningRegistrationExtensions and ScanningRegistrationExtensions classes.
@@ -0,0 +1,168 @@ | |||
// Copyright (c) Autofac Project. All rights reserved. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This all came out of the OpenGenericScanningRegistrationExtensions and ScanningRegistrationExtensions classes.
Codecov ReportPatch coverage:
Additional details and impacted files@@ Coverage Diff @@
## develop #1389 +/- ##
===========================================
- Coverage 78.49% 78.47% -0.02%
===========================================
Files 199 201 +2
Lines 5706 5715 +9
Branches 1166 1169 +3
===========================================
+ Hits 4479 4485 +6
- Misses 714 716 +2
- Partials 513 514 +1
☔ View full report in Codecov by Sentry. |
@@ -13,7 +13,7 @@ namespace Autofac.Features.Scanning; | |||
/// <summary> | |||
/// Helper methods to assist in scanning registration. | |||
/// </summary> | |||
internal static partial class ScanningRegistrationExtensions | |||
internal static class OpenGenericScanningRegistrationExtensions |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Having this just One Big Class was causing some of the confusion with what was consuming what.
@@ -40,22 +40,4 @@ public static IEnumerable<Type> GetLoadableTypes(this Assembly assembly) | |||
return ex.Types.Where(t => t is not null)!; | |||
} | |||
} | |||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is only used in the scanning namespace, so I moved it there.
// If people want only public types, a LINQ Where clause can be used. | ||
// | ||
// Run IsCompilerGenerated check last due to perf. See AssemblyScanningPerformanceTests.MeasurePerformance. | ||
internal static bool MayAllowReflectionActivation(this Type? type, bool allowCompilerGenerated = false) => type != null && type.IsClass && !type.IsAbstract && !type.IsDelegate() && (allowCompilerGenerated || !type.IsCompilerGenerated()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This gets used a lot - ACTNARS, RegisterTypes
, RegistrationBuilder.FromType
, assembly scanning, open generic assembly scanning. The permutations are challenging because sometimes (open generic scanning) you want open generics, but usually you don't; sometimes (ACTNARS) you want compiler generated, but usually you don't.
But at least this gives us a single spot for doing these checks and ensuring the proper order.
@@ -96,6 +106,26 @@ public void RegisterTypeAsUnsupportedService() | |||
Assert.Throws<ArgumentException>(() => builder.Build()); | |||
} | |||
|
|||
[Fact] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added some tests for RegisterType
and RegisterTypes
, both.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For what its worth, this change also improves the error generated if a value type is registered. Before this change, this test:
[Fact]
public void ValueTypeActivation()
{
var cb = new ContainerBuilder();
cb.RegisterType<A>().As<IA>();
cb.RegisterType<MyValueType>();
var c = cb.Build();
c.Resolve<MyValueType>();
}
private struct MyValueType
{
public MyValueType(IA a)
{
A = a;
}
public IA A { get; }
}
Produces a strange and confusing error:
Message:
System.ArgumentException : Expression of type 'Autofac.Specification.Test.Registration.TypeRegistrationTests+MyValueType' cannot be used for return type 'System.Object'
Stack Trace:
Expression.ValidateLambdaArgs(Type delegateType, Expression& body, ReadOnlyCollection`1 parameters, String paramName)
Expression.Lambda[TDelegate](Expression body, String name, Boolean tailCall, IEnumerable`1 parameters)
Expression.Lambda[TDelegate](Expression body, Boolean tailCall, IEnumerable`1 parameters)
Expression.Lambda[TDelegate](Expression body, ParameterExpression[] parameters)
ConstructorBinder.GetConstructorInvoker(ConstructorInfo constructorInfo) line 165
(I believe we generally don't support activating value types, and this isn't just another error.)
Now the error will get raised at RegisterType
.
// If people want only public types, a LINQ Where clause can be used. | ||
// | ||
// Run IsCompilerGenerated check last due to perf. See AssemblyScanningPerformanceTests.MeasurePerformance. | ||
internal static bool MayAllowReflectionActivation(this Type? type, bool allowCompilerGenerated = false) => type != null && type.IsClass && !type.IsAbstract && !type.IsDelegate() && (allowCompilerGenerated || !type.IsCompilerGenerated()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Propose is not null
rather than != null
; Type comparison can be overridden.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated all of our != null
to is not null
since it seems like that'd apply everywhere.
Assert.True(container.TryResolve(typeof(MyOpenGeneric<int>), out object _)); | ||
Assert.False(container.TryResolve(typeof(IMyService), out _)); | ||
Assert.False(container.TryResolve(typeof(MyDelegate), out _)); | ||
Assert.False(container.TryResolve(typeof(MyAbstractClass), out _)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Suggest adding a test case for value types?
[InlineData(typeof(MyDelegate))] | ||
[InlineData(typeof(IMyService))] | ||
[InlineData(typeof(MyAbstractClass))] | ||
[InlineData(typeof(MyOpenGeneric<>))] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As below, suggest a test case for value types.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🐓
RegisterTypes
so if "invalid" types (types that generally can't be created via reflection - interfaces, abstract classes, etc.) get passed in, they will be ignored and not registered.RegisterType(Type t)
andRegisterType<T>()
calls so if someone tries to use a type in there that generally can't be created via reflection, anArgumentException
will come up and explain the issue rather than waiting for the container build to occur and hitting a "no constructors found" exception.parameterised
=>parameterized
. Given the fallback language on the assemblies isen-US
it seemed like fixing up docs and internal naming to also been-US
was reasonable. Also allows spell checkers set to that dictionary to find fewer things.ctxt
=>context
. Another spell checker issue. Community practice generally either usesctx
orcontext
, with most .NET code in the framework being a fully spelled-outcontext
. Updated references to go with the fully spelled-out version.I'll add some comments to the important bits for extra attention.