Skip to content
Closed
Original file line number Diff line number Diff line change
Expand Up @@ -458,6 +458,56 @@ public async Task Handler_method_receives_command_arguments_bound_to_the_specifi
c.AssertBoundValue(boundValue);
}

[Theory]
[InlineData(typeof(ClassWithCtorParameter<int>))]
[InlineData(typeof(ClassWithSetter<int>))]
[InlineData(typeof(ClassWithCtorParameter<string>))]
[InlineData(typeof(ClassWithSetter<string>))]
[InlineData(typeof(FileInfo))]
[InlineData(typeof(FileInfo[]))]
[InlineData(typeof(string[]))]
[InlineData(typeof(List<string>))]
[InlineData(typeof(int[]))]
[InlineData(typeof(List<int>))]
public async Task Handler_method_receives_command_arguments_explicitly_bound_to_the_specified_type(
Type type)
{
var c = _bindingCases[type];

var captureMethod = GetType()
.GetMethod(nameof(CaptureMethod), BindingFlags.NonPublic | BindingFlags.Static)
.MakeGenericMethod(c.ParameterType);
var parameter = captureMethod.GetParameters().First();

var handler = CommandHandler.Create(captureMethod);

var argument = new Argument
{
Name = "value",
ArgumentType = c.ParameterType
};

var command = new Command(
"command")
{
argument
};
handler.BindParameter(parameter, argument);
command.Handler = handler;

var parseResult = command.Parse(c.CommandLine);

var invocationContext = new InvocationContext(parseResult);

await handler.InvokeAsync(invocationContext);

var boundValue = ((BoundValueCapturer)invocationContext.InvocationResult).BoundValue;

boundValue.Should().BeOfType(c.ParameterType);

c.AssertBoundValue(boundValue);
}

private static void CaptureMethod<T>(T value, InvocationContext invocationContext)
{
invocationContext.InvocationResult = new BoundValueCapturer(value);
Expand Down
41 changes: 24 additions & 17 deletions src/System.CommandLine/Binding/ModelBinder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -84,13 +84,13 @@ public void BindConstructorArgumentFromValue(ParameterInfo parameter,
if (ctorDesc is null)
throw new ArgumentException(paramName: nameof(parameter),
message: "Parameter is not described by any of the model constructor descriptors.");

var paramDesc = ctorDesc.ParameterDescriptors[parameter.Position];
ConstructorArgumentBindingSources[paramDesc] =
new SpecificSymbolValueSource(valueDescriptor);
}

public void BindMemberFromValue(PropertyInfo property,
public void BindMemberFromValue(PropertyInfo property,
IValueDescriptor valueDescriptor)
{
var propertyDescriptor = FindModelPropertyDescriptor(
Expand All @@ -108,9 +108,9 @@ public void BindMemberFromValue(PropertyInfo property,
var values = GetValues(
// No binding sources, as were are attempting to bind a value
// for the model itself, not for its ctor args or its members.
bindingSources: null,
bindingContext: context,
new[] { ValueDescriptor },
bindingSources: null,
bindingContext: context,
new[] { ValueDescriptor },
includeMissingValues: false);

if (values.Count == 1 &&
Expand Down Expand Up @@ -140,7 +140,7 @@ private bool TryDefaultConstructorAndPropertiesStrategy(
{
var boundConstructorArguments = GetValues(
ConstructorArgumentBindingSources,
context,
context,
constructor.ParameterDescriptors,
true);

Expand Down Expand Up @@ -201,14 +201,7 @@ private IReadOnlyList<BoundValue> GetValues(

var valueSource = GetValueSource(bindingSources, bindingContext, valueDescriptor);

BoundValue? boundValue;
if (!bindingContext.TryBindToScalarValue(
valueDescriptor,
valueSource,
out boundValue) && valueDescriptor.HasDefaultValue)
{
boundValue = BoundValue.DefaultForValueDescriptor(valueDescriptor);
}
BoundValue? boundValue = GetBoundValue(valueSource, bindingContext, valueDescriptor);

if (boundValue is null)
{
Expand All @@ -219,13 +212,12 @@ private IReadOnlyList<BoundValue> GetValues(
{
if (parameterDescriptor.HasDefaultValue)
boundValue = BoundValue.DefaultForValueDescriptor(parameterDescriptor);
else if (parameterDescriptor.AllowsNull &&
else if (parameterDescriptor.AllowsNull &&
ShouldPassNullToConstructor(constructorDescriptor.Parent, constructorDescriptor))
boundValue = BoundValue.DefaultForType(valueDescriptor);
Comment on lines 213 to 217

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like this block should also be part of the extracted GetBoundValue method.

}
}
}

if (boundValue != null)
{
values.Add(boundValue);
Expand All @@ -235,6 +227,21 @@ private IReadOnlyList<BoundValue> GetValues(
return values;
}

internal static BoundValue? GetBoundValue(IValueSource valueSource, BindingContext bindingContext,
IValueDescriptor valueDescriptor)
{
BoundValue? boundValue;
if (!bindingContext.TryBindToScalarValue(
valueDescriptor,
valueSource,
out boundValue) && valueDescriptor.HasDefaultValue)
{
boundValue = BoundValue.DefaultForValueDescriptor(valueDescriptor);
}

return boundValue;
}

private IValueSource GetValueSource(
IDictionary<IValueDescriptor, IValueSource>? bindingSources,
BindingContext bindingContext,
Expand Down Expand Up @@ -264,7 +271,7 @@ private IValueSource GetValueSource(
public override string ToString() =>
$"{ModelDescriptor.ModelType.Name}";

private bool ShouldPassNullToConstructor(ModelDescriptor modelDescriptor,
private static bool ShouldPassNullToConstructor(ModelDescriptor modelDescriptor,
ConstructorDescriptor? ctor = null)
{
if (!(ctor is null))
Expand Down
26 changes: 26 additions & 0 deletions src/System.CommandLine/Invocation/InvocationExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using System.Reflection;

namespace System.CommandLine.Invocation
{
public static class InvocationExtensions
{
public static void BindParameter(this ICommandHandler handler, ParameterInfo param, Option option)
{
// check for nulls
if (!(handler is ModelBindingCommandHandler bindingHandler))
{
throw new InvalidOperationException("Cannot bind to this type of handler");

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we should just make ModelBindingCommandHandler public?

}
bindingHandler.BindParameter(param, option);
}

public static void BindParameter(this ICommandHandler handler, ParameterInfo param, Argument argument)
{
if (!(handler is ModelBindingCommandHandler bindingHandler))
{
throw new InvalidOperationException("Cannot bind to this type of handler");
}
bindingHandler.BindParameter(param, argument);
}
}
}
55 changes: 48 additions & 7 deletions src/System.CommandLine/Invocation/ModelBindingCommandHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ internal class ModelBindingCommandHandler : ICommandHandler
private readonly ModelBinder? _invocationTargetBinder;
private readonly MethodInfo? _handlerMethodInfo;
private readonly IReadOnlyList<ParameterDescriptor> _parameterDescriptors;
private Dictionary<IValueDescriptor, IValueSource> _invokeArgumentBindingSources { get; } =
new Dictionary<IValueDescriptor, IValueSource>();

public ModelBindingCommandHandler(
MethodInfo handlerMethodInfo,
Expand Down Expand Up @@ -51,14 +53,24 @@ public async Task<int> InvokeAsync(InvocationContext context)
{
var bindingContext = context.BindingContext;

var parameterBinders = _parameterDescriptors
.Select(p => bindingContext.GetModelBinder(p))
.ToList();
var invocationArguments = new object?[_parameterDescriptors.Count()];
var length = _parameterDescriptors.Count();

var invocationArguments =
parameterBinders
.Select(binder => binder.CreateInstance(bindingContext))
.ToArray();
for (int i = 0; i < length; i++)
{
var paramDesc = _parameterDescriptors[i];
if (_invokeArgumentBindingSources.TryGetValue(paramDesc, out var valueSource))
{
var boundValue = ModelBinder.GetBoundValue(valueSource, bindingContext, paramDesc);
if (!(boundValue is null))
{
invocationArguments[i] = boundValue.Value;
continue;
}
}
var binder = bindingContext.GetModelBinder(paramDesc);
invocationArguments[i] = binder.CreateInstance(bindingContext);
}

var invocationTarget = _invocationTarget ??
_invocationTargetBinder?.CreateInstance(bindingContext);
Expand All @@ -77,5 +89,34 @@ public async Task<int> InvokeAsync(InvocationContext context)

return await CommandHandler.GetResultCodeAsync(result, context);
}

public void BindParameter(ParameterInfo param, Argument argument)
{
var _ = argument ?? throw new InvalidOperationException("You must specify an argument to bind");
BindValueSource(param, new SpecificSymbolValueSource(argument));
}

public void BindParameter(ParameterInfo param, Option option)
{
var _ = option ?? throw new InvalidOperationException("You must specify an argument to bind");
BindValueSource(param, new SpecificSymbolValueSource(option));
}

private void BindValueSource(ParameterInfo param, IValueSource valueSource)
{
var paramDesc = FindParameterDescriptor(param);
if (paramDesc is null)
{
throw new InvalidOperationException("You must bind to a parameter on this handler");
}
_invokeArgumentBindingSources.Add(paramDesc, valueSource);
}

private ParameterDescriptor? FindParameterDescriptor(ParameterInfo? param)
=> param == null
? null
: _parameterDescriptors
.FirstOrDefault(x => x.ValueName == param.Name &&
x.ValueType == param.ParameterType);
}
}