Skip to content

Commit

Permalink
Don't immediately throw in ConstructorInjectionConvention (#181)
Browse files Browse the repository at this point in the history
  • Loading branch information
TheConstructor authored and natemcmaster committed Nov 28, 2018
1 parent a0e8d52 commit df0d28b
Show file tree
Hide file tree
Showing 2 changed files with 32 additions and 42 deletions.
62 changes: 26 additions & 36 deletions src/CommandLineUtils/Conventions/ConstructorInjectionConvention.cs
Original file line number Diff line number Diff line change
Expand Up @@ -57,46 +57,33 @@ private void ApplyImpl<TModel>(ConventionContext context)
.GetTypeInfo()
.GetConstructors(BindingFlags.Public | BindingFlags.Instance);

if (constructors.Length == 0)
{
throw new InvalidOperationException(Strings.NoAnyPublicConstuctorFound(typeof(TModel)));
}

var (matchedCtor, matchedArgs) = (constructors.Length == 1)
// shortcut for single constructor
? FindMatchedConstructor(constructors, context.Application,
throwIfNoParameterTypeRegistered: true)
// find the constructor with the most parameters first
: FindMatchedConstructor(constructors, context.Application,
throwIfNoParameterTypeRegistered: false);
var factory = FindMatchedConstructor<TModel>(constructors, context.Application,
constructors.Length == 1);

var parameterLessCtor = Array.Find(constructors, c => c.GetParameters().Length == 0);

if (matchedCtor == null && parameterLessCtor != null)
if (factory != null)
{
return;
}

if (matchedCtor == null && parameterLessCtor == null)
{
throw new InvalidOperationException(Strings.NoMatchedConstructorFound(typeof(TModel)));
}

if (matchedCtor != null)
{
(context.Application as CommandLineApplication<TModel>).ModelFactory =
() => (TModel)matchedCtor.Invoke(matchedArgs);
((CommandLineApplication<TModel>) context.Application).ModelFactory = factory;
}
}

private (ConstructorInfo matchedCtor, object[] matchedArgs) FindMatchedConstructor(
private static Func<TModel> FindMatchedConstructor<TModel>(
ConstructorInfo[] constructors,
IServiceProvider services,
bool throwIfNoParameterTypeRegistered = false)
{
if (constructors.Length == 0)
{
return () => throw new InvalidOperationException(Strings.NoAnyPublicConstuctorFound(typeof(TModel)));
}

foreach (var ctorCandidate in constructors.OrderByDescending(c => c.GetParameters().Length))
{
var parameters = ctorCandidate.GetParameters().ToArray();
if (parameters.Length == 0)
{
return null;
}

var args = new object[parameters.Length];
for (var i = 0; i < parameters.Length; i++)
{
Expand All @@ -106,22 +93,25 @@ private void ApplyImpl<TModel>(ConventionContext context)
{
if (!throwIfNoParameterTypeRegistered)
{
continue;
goto nextCtor;
}
throw new InvalidOperationException(Strings.NoParameterTypeRegistered(ctorCandidate.DeclaringType, paramType));

return () =>
throw new InvalidOperationException(
Strings.NoParameterTypeRegistered(ctorCandidate.DeclaringType, paramType));
}

args[i] = service;
if (i == parameters.Length - 1)
{
var matchedArgsLength = args.Count(x => x != null);
if (parameters.Length == matchedArgsLength)
{
return (ctorCandidate, args);
}
return () => (TModel) ctorCandidate.Invoke(args);
}
}

nextCtor: ;
}
return (null, null);

return () => throw new InvalidOperationException(Strings.NoMatchedConstructorFound(typeof(TModel)));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -130,9 +130,9 @@ private TestAppWithoutPublicConstructor()
public void ThrowsWhenNoAnyPublicConstructorFound()
{
var app = new CommandLineApplication<TestAppWithoutPublicConstructor>();
var ex = Assert.Throws<TargetInvocationException>(() => app.Conventions.UseConstructorInjection());
Assert.IsType<InvalidOperationException>(ex.InnerException);
Assert.Equal(Strings.NoAnyPublicConstuctorFound(typeof(TestAppWithoutPublicConstructor)), ex.InnerException.Message);
app.Conventions.UseConstructorInjection();
var ex = Assert.Throws<InvalidOperationException>(() => app.Model);
Assert.Equal(Strings.NoAnyPublicConstuctorFound(typeof(TestAppWithoutPublicConstructor)), ex.Message);
}

private class TestAppWithoutMatchedConstructor
Expand All @@ -149,9 +149,9 @@ public TestAppWithoutMatchedConstructor(TestConsole console)
public void ThrowsWhenNoMatchedConstructorFound()
{
var app = new CommandLineApplication<TestAppWithoutMatchedConstructor>();
var ex = Assert.Throws<TargetInvocationException>(() => app.Conventions.UseConstructorInjection());
Assert.IsType<InvalidOperationException>(ex.InnerException);
Assert.Equal(Strings.NoParameterTypeRegistered(typeof(TestAppWithoutMatchedConstructor), typeof(TestConsole)), ex.InnerException.Message);
app.Conventions.UseConstructorInjection();
var ex = Assert.Throws<InvalidOperationException>(() => app.ModelFactory());
Assert.Equal(Strings.NoParameterTypeRegistered(typeof(TestAppWithoutMatchedConstructor), typeof(TestConsole)), ex.Message);
}

private class TestAppWithAlternativeConstructor
Expand Down

0 comments on commit df0d28b

Please sign in to comment.