Skip to content

Commit ba783e4

Browse files
committed
enable easier custom model binding
1 parent 25e6b36 commit ba783e4

26 files changed

+170
-231
lines changed

src/System.CommandLine.DragonFruit.Tests/ConfigureFromMethodTests.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@ public void Parameters_named_arguments_generate_command_arguments_having_the_cor
162162

163163
var rootCommandArgument = parser.Configuration.RootCommand.Arguments.Single();
164164

165-
rootCommandArgument.Type
165+
rootCommandArgument.ValueType
166166
.Should()
167167
.Be(expectedType);
168168
}

src/System.CommandLine.DragonFruit/CommandLine.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -279,7 +279,7 @@ public static IEnumerable<Option> BuildOptions(this MethodInfo type)
279279
};
280280

281281
foreach (var option in descriptor.ParameterDescriptors
282-
.Where(d => !omittedTypes.Contains (d.Type))
282+
.Where(d => !omittedTypes.Contains (d.ValueType))
283283
.Where(d => !_argumentParameterNames.Contains(d.ValueName))
284284
.Select(p => p.BuildOption()))
285285
{
@@ -291,7 +291,7 @@ public static Option BuildOption(this ParameterDescriptor parameter)
291291
{
292292
var argument = new Argument
293293
{
294-
ArgumentType = parameter.Type
294+
ArgumentType = parameter.ValueType
295295
};
296296

297297
if (parameter.HasDefaultValue)

src/System.CommandLine.Tests/Binding/HandlerDescriptorTests.cs

+2-27
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ public void It_provides_the_types_of_the_handler_parameters(Type parameterType)
4141
var descriptor = HandlerDescriptor.FromMethodInfo(method);
4242

4343
descriptor.ParameterDescriptors
44-
.Select(p => p.Type)
44+
.Select(p => p.ValueType)
4545
.Should()
4646
.BeEquivalentSequenceTo(parameterType);
4747
}
@@ -76,7 +76,7 @@ public void It_provides_the_types_of_the_handler_parameters(Type parameterType)
7676
var descriptor = HandlerDescriptor.FromMethodInfo(method);
7777

7878
descriptor.ParameterDescriptors
79-
.Select(p => p.Type)
79+
.Select(p => p.ValueType)
8080
.Should()
8181
.BeEquivalentSequenceTo(parameterType);
8282
}
@@ -85,30 +85,5 @@ public void It_provides_the_types_of_the_handler_parameters(Type parameterType)
8585
public void Handler<T>(T value)
8686
{
8787
}
88-
89-
public class FromExpression
90-
{
91-
[Fact]
92-
public void Handler_descriptor_describes_the_parameter_names_of_the_handler_method()
93-
{
94-
var descriptor = HandlerDescriptor.FromExpression<ClassWithInvokeAndDefaultCtor, string, int, Task<int>>((model, s, i) => model.Invoke(s, i));
95-
96-
descriptor.ParameterDescriptors
97-
.Select(p => p.ValueName)
98-
.Should()
99-
.BeEquivalentSequenceTo("stringParam", "intParam");
100-
}
101-
102-
[Fact]
103-
public void Handler_descriptor_describes_the_parameter_types_of_the_handler_method()
104-
{
105-
var descriptor = HandlerDescriptor.FromExpression<ClassWithInvokeAndDefaultCtor, string, int, Task<int>>((model, s, i) => model.Invoke(s, i));
106-
107-
descriptor.ParameterDescriptors
108-
.Select(p => p.Type)
109-
.Should()
110-
.BeEquivalentSequenceTo(typeof(string), typeof(int));
111-
}
112-
}
11388
}
11489
}

src/System.CommandLine.Tests/Binding/ModelBinderTests.cs

+66-4
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
33

44
using System.CommandLine.Binding;
5+
using System.CommandLine.Builder;
6+
using System.CommandLine.Invocation;
57
using System.CommandLine.Parsing;
68
using System.IO;
79
using FluentAssertions;
@@ -523,8 +525,10 @@ public void Explicit_model_binder_binds_only_to_configured_properties()
523525
var parser = new Parser(intOption, stringOption);
524526

525527
var bindingContext = new BindingContext(parser.Parse("--int-property 42 --string-property Hello"));
526-
var binder = new ModelBinder<ClassWithMultiLetterSetters>()
527-
{ EnforceExplicitBinding = true };
528+
var binder = new ModelBinder<ClassWithMultiLetterSetters>
529+
{
530+
EnforceExplicitBinding = true
531+
};
528532
binder.BindMemberFromValue(obj => obj.IntOption, intOption);
529533
var instance = binder.CreateInstance(bindingContext) as ClassWithMultiLetterSetters;
530534

@@ -545,14 +549,72 @@ public void Explicit_model_binder_binds_only_to_configured_ctor_parameters()
545549
var paramInfo = ctor.GetParameters().First();
546550

547551
var bindingContext = new BindingContext(parser.Parse("-a 42 -b Hello"));
548-
var binder = new ModelBinder<ClassWithMultiLetterCtorParameters>()
549-
{ EnforceExplicitBinding = true };
552+
var binder = new ModelBinder<ClassWithMultiLetterCtorParameters>
553+
{
554+
EnforceExplicitBinding = true
555+
};
550556
binder.BindConstructorArgumentFromValue(paramInfo, intOption);
551557
var instance = binder.CreateInstance(bindingContext) as ClassWithMultiLetterCtorParameters;
552558

553559
instance.Should().NotBeNull();
554560
instance.IntOption.Should().Be(42);
555561
instance.StringOption.Should().Be("the default");
556562
}
563+
564+
[Fact]
565+
public void Custom_ModelBinders_specified_via_BindingContext_can_be_used_for_option_binding()
566+
{
567+
ClassWithSetter<int> boundInstance = null;
568+
569+
var rootCommand = new RootCommand
570+
{
571+
new Option<int>("--value")
572+
};
573+
574+
rootCommand.Handler = CommandHandler.Create<ClassWithSetter<int>>(x => boundInstance = x);
575+
576+
var parser = new CommandLineBuilder(rootCommand)
577+
.UseMiddleware(context =>
578+
{
579+
var binder = new ModelBinder<ClassWithSetter<int>>();
580+
581+
binder.BindMemberFromValue(instance => instance.Value, _ => 456);
582+
583+
context.BindingContext.AddModelBinder(binder);
584+
})
585+
.Build();
586+
587+
parser.Invoke("--value 123");
588+
589+
boundInstance.Value.Should().Be(456);
590+
}
591+
592+
[Fact]
593+
public void Custom_ModelBinders_specified_via_BindingContext_can_be_used_for_command_argument_binding()
594+
{
595+
ClassWithSetter<int> boundInstance = null;
596+
597+
var rootCommand = new RootCommand
598+
{
599+
new Argument<int>()
600+
};
601+
602+
rootCommand.Handler = CommandHandler.Create<ClassWithSetter<int>>(x => boundInstance = x);
603+
604+
var parser = new CommandLineBuilder(rootCommand)
605+
.UseMiddleware(context =>
606+
{
607+
var binder = new ModelBinder<ClassWithSetter<int>>();
608+
609+
binder.BindMemberFromValue(instance => instance.Value, _ => 456);
610+
611+
context.BindingContext.AddModelBinder(binder);
612+
})
613+
.Build();
614+
615+
parser.Invoke("123");
616+
617+
boundInstance.Value.Should().Be(456);
618+
}
557619
}
558620
}

src/System.CommandLine.Tests/Binding/TestModels.cs

-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
// Copyright (c) .NET Foundation and contributors. All rights reserved.
22
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
33

4-
using System;
54
using System.Threading.Tasks;
65

76
namespace System.CommandLine.Tests.Binding

src/System.CommandLine/Argument.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,6 @@ public override IEnumerable<string> GetSuggestions(string textToMatch = null)
217217

218218
string IValueDescriptor.ValueName => Name;
219219

220-
Type IValueDescriptor.Type => ArgumentType;
220+
Type IValueDescriptor.ValueType => ArgumentType;
221221
}
222222
}

src/System.CommandLine/Binding/BindingContext.cs

+22-20
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,25 @@
11
// Copyright (c) .NET Foundation and contributors. All rights reserved.
22
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
33

4+
using System.Collections.Generic;
45
using System.CommandLine.Help;
56
using System.CommandLine.Invocation;
67
using System.CommandLine.IO;
78
using System.CommandLine.Parsing;
89
using System.Linq;
910

11+
#nullable enable
12+
1013
namespace System.CommandLine.Binding
1114
{
1215
public sealed class BindingContext
1316
{
1417
private IConsole _console;
18+
private readonly Dictionary<Type, ModelBinder> _modelBindersByValueDescriptor = new Dictionary<Type, ModelBinder>();
1519

1620
public BindingContext(
1721
ParseResult parseResult,
18-
IConsole console = null)
22+
IConsole? console = default)
1923
{
2024
_console = console ?? new SystemConsole();
2125

@@ -25,7 +29,7 @@ public BindingContext(
2529

2630
public ParseResult ParseResult { get; set; }
2731

28-
internal IConsoleFactory ConsoleFactory { get; set; }
32+
internal IConsoleFactory? ConsoleFactory { get; set; }
2933

3034
internal IHelpBuilder HelpBuilder => (IHelpBuilder)ServiceProvider.GetService(typeof(IHelpBuilder));
3135

@@ -46,18 +50,16 @@ public IConsole Console
4650

4751
internal ServiceProvider ServiceProvider { get; }
4852

49-
public void AddService(Type serviceType, Func<IServiceProvider, object> factory)
50-
{
51-
if (serviceType == null)
52-
{
53-
throw new ArgumentNullException(nameof(serviceType));
54-
}
53+
public void AddModelBinder(ModelBinder binder) =>
54+
_modelBindersByValueDescriptor.Add(binder.ValueDescriptor.ValueType, binder);
5555

56-
if (factory == null)
57-
{
58-
throw new ArgumentNullException(nameof(factory));
59-
}
56+
public ModelBinder GetModelBinder(IValueDescriptor valueDescriptor) =>
57+
_modelBindersByValueDescriptor.GetOrAdd(
58+
valueDescriptor.ValueType,
59+
_ => new ModelBinder(valueDescriptor));
6060

61+
public void AddService(Type serviceType, Func<IServiceProvider, object> factory)
62+
{
6163
ServiceProvider.AddService(serviceType, factory);
6264
}
6365

@@ -68,31 +70,31 @@ public void AddService<T>(Func<IServiceProvider, T> factory)
6870
throw new ArgumentNullException(nameof(factory));
6971
}
7072

71-
ServiceProvider.AddService(typeof(T), s => factory(s));
73+
ServiceProvider.AddService(typeof(T), s => factory(s)!);
7274
}
7375

7476
internal bool TryGetValueSource(
7577
IValueDescriptor valueDescriptor,
76-
out IValueSource valueSource)
78+
out IValueSource? valueSource)
7779
{
78-
if (ServiceProvider.AvailableServiceTypes.Contains(valueDescriptor.Type))
80+
if (ServiceProvider.AvailableServiceTypes.Contains(valueDescriptor.ValueType))
7981
{
8082
valueSource = new ServiceProviderValueSource();
8183
return true;
8284
}
8385

84-
valueSource = null;
86+
valueSource = default;
8587
return false;
8688
}
8789

8890
internal bool TryBindToScalarValue(
8991
IValueDescriptor valueDescriptor,
9092
IValueSource valueSource,
91-
out BoundValue boundValue)
93+
out BoundValue? boundValue)
9294
{
9395
if (valueSource.TryGetValue(valueDescriptor, this, out var value))
9496
{
95-
if (value == null || valueDescriptor.Type.IsInstanceOfType(value))
97+
if (value == null || valueDescriptor.ValueType.IsInstanceOfType(value))
9698
{
9799
boundValue = new BoundValue(value, valueDescriptor, valueSource);
98100
return true;
@@ -101,7 +103,7 @@ internal bool TryBindToScalarValue(
101103
{
102104
var parsed = ArgumentConverter.ConvertObject(
103105
valueDescriptor as IArgument ?? new Argument(valueDescriptor.ValueName),
104-
valueDescriptor.Type,
106+
valueDescriptor.ValueType,
105107
value);
106108

107109
if (parsed is SuccessfulArgumentConversionResult successful)
@@ -112,7 +114,7 @@ internal bool TryBindToScalarValue(
112114
}
113115
}
114116

115-
boundValue = null;
117+
boundValue = default;
116118
return false;
117119
}
118120
}

src/System.CommandLine/Binding/BoundValue.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@ internal BoundValue(
1111
IValueSource valueSource)
1212
{
1313
if (value != null &&
14-
!valueDescriptor.Type.IsInstanceOfType(value))
14+
!valueDescriptor.ValueType.IsInstanceOfType(value))
1515
{
16-
throw new ArgumentException($"Value {value} ({value.GetType()}) must be an instance of type {valueDescriptor.Type}");
16+
throw new ArgumentException($"Value {value} ({value.GetType()}) must be an instance of type {valueDescriptor.ValueType}");
1717
}
1818

1919
Value = value;

src/System.CommandLine/Binding/DelegateHandlerDescriptor.cs

+1-5
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,9 @@ public DelegateHandlerDescriptor(Delegate handlerDelegate)
1818

1919
public override ICommandHandler GetCommandHandler()
2020
{
21-
var parameterBinders = ParameterDescriptors
22-
.Select(p => new ModelBinder(p))
23-
.ToList();
24-
2521
return new ModelBindingCommandHandler(
2622
_handlerDelegate,
27-
parameterBinders);
23+
ParameterDescriptors);
2824
}
2925

3026
public override ModelDescriptor Parent => null;

src/System.CommandLine/Binding/ExpressionHandlerDescriptor.cs

-60
This file was deleted.

0 commit comments

Comments
 (0)