farlee2121.System.CommandLine.PropertyMapBinder 1.0.0
dotnet add package farlee2121.System.CommandLine.PropertyMapBinder --version 1.0.0
NuGet\Install-Package farlee2121.System.CommandLine.PropertyMapBinder -Version 1.0.0
<PackageReference Include="farlee2121.System.CommandLine.PropertyMapBinder" Version="1.0.0" />
paket add farlee2121.System.CommandLine.PropertyMapBinder --version 1.0.0
#r "nuget: farlee2121.System.CommandLine.PropertyMapBinder, 1.0.0"
// Install farlee2121.System.CommandLine.PropertyMapBinder as a Cake Addin #addin nuget:?package=farlee2121.System.CommandLine.PropertyMapBinder&version=1.0.0 // Install farlee2121.System.CommandLine.PropertyMapBinder as a Cake Tool #tool nuget:?package=farlee2121.System.CommandLine.PropertyMapBinder&version=1.0.0
System.CommandLine.PropertyMapBinding
动机/这是什么
目标是创建一个直观的处理器绑定体验,针对 System.CommandLine。一些子目标包括
- 直观绑定复杂类型
- 混合多个绑定规则以提供可定制且一致的绑定体验
- 简单地扩展绑定管道
- 支持将处理器的声明作为自包含的表达式(不引用符号实例)
示例
所有示例都假定以下定义可用
Option<int> frequencyOpt = new Option<int>(new string[] { "--frequency", "-f" }, "such description");
RootCommand rootCommand = new RootCommand("Test Test")
{
new Argument<string>("print-me", "gets printed"),
frequencyOpt,
new Option<IEnumerable<int>>(new string[] { "--list", "-l" }, "make sure lists work")
{
Arity = ArgumentArity.ZeroOrMore
};
};
public static async Task SuchHandler(SuchInput input)
{
Console.WriteLine($"printme: {input.PrintMe}; \nfrequency: {input.Frequency}; \nlist:{string.Join(",",input.SuchList)}");
}
public class SuchInput {
public int Frequency { get; set; }
public string? PrintMe { get; set; }
public IEnumerable<int> SuchList { get; set; } = Enumerable.Empty<int>();
}
管道
骨干构造是 BinderPipeline
。
rootCommand.Handler = new BinderPipeline<SuchInput>()
.MapFromName("print-me", model => model.PrintMe)
.MapFromReference(frequencyOpt, model => model.Frequency)
.MapFromName("-l", model => model.SuchList)
.ToHandler(SuchHandler);
BinderPipeline
实际上是 IPropertyBinder
的集合。每个 IPropertyBinder
定义将输入分配给目标对象的策略。管道按给定顺序执行每个绑定器。这意味着后续的绑定器将覆盖先前的绑定器。这也意味着我们可以
- 使用多个规则来绑定属性
- 为任何给定的属性定义优先级/回退链
混合约定
管道可以处理许多绑定输入的方法。以下是一个使用简单的命名约定和显式映射回退的示例
rootCommand.Handler = new BinderPipeline<SuchInput>()
.MapFromNameConvention(NameConvention.PascalCaseComparer)
.MapFromName("-l", model => model.SuchList)
.ToHandler(SuchHandler);
可以为此管道添加更多约定。以下是一些我没有实现但很容易添加的情况:
- 从配置中映射默认值
- 询问用户任何缺失的输入
- 可以使用现有的设置器重载来完成,但可以使用类似于
.PromptIfMissing(name, selector)
的签名来自动化提示
- 可以使用现有的设置器重载来完成,但可以使用类似于
- 根据类型匹配属性
参见如何扩展获取更多详细信息。
绑定到现有模型
有时我们可能希望将我们的输入模型与输入绑定过程分离初始化(例如,从配置中默认模型)。
这很简单
SuchInput existingModelInstance = //...
rootCommand.Handler = new BinderPipeline<SuchInput>()
.ToHandler(SuchHandler, existingModelInstance);
使用所需数据初始化模型
某些模型可能希望通过构造函数强制保证数据,或者某些字段在初始化后不允许修改。
这可以与System.Commandline的SetHandler
类似处理。
IModelFactory<SuchInput> modelFactory = ModelFactory.FromSymbolMap((int frequency, string printMe) => new InputModel(frequency, printMe), frequencyOpt, printMeArg);
rootCommand.Handler = new BinderPipeline<SuchInput>()
.ToHandler(SuchHandler, modelFactory);
这可以通过选项和参数别名实现相同的操作
IModelFactory<SuchInput> modelFactory = ModelFactor.FromNameMap((int frequency, string printMe) => new InputModel(frequency, printMe), "-f", "print-me");
rootCommand.Handler = new BinderPipeline<SuchInput>()
.ToHandler(SuchHandler, modelFactory);
如何扩展
扩展管道相当简单。
核心合同是
public interface IPropertyBinder<InputModel>
{
InputModel Bind(InputModel InputModel, InvocationContext context);
}
IPropertyBinder
接受目标输入类的实例和由解析器提供的调用上下文。
输入定义(即选项和参数)可以在context.ParserResult.CommandResult.Symbol.Children
中找到,可以通过诸如context.ParseResult.GetValueForOption
之类的函数获取值。
另一个关键步骤是在BinderPipeline
上注册扩展方法。需要考虑的主要行为
- 扩展应将其绑定器添加到管道的末尾(例如,
pipeline.Add(yourBinder)
) - 扩展应返回管道的修改副本(即始终具有返回类型
BinderPipeline<T>
)
// Example pipeline extension
public static class BinderPipelineExtensions{
public static BinderPipeline<InputModel> MapFromNameConvention<InputModel>(this BinderPipeline<InputModel> pipeline, NameConventionComparer comparer)
{
pipeline.Add(new NameConventionBinder<InputModel>(comparer)); // this adds an IPropertyBinder<T>
return pipeline; // be sure to return the pipeline for further chaining
}
}
如何处理依赖注入?
简而言之:在处理函数中调用依赖容器(即ToHandler(handlerFunction)
)
该库的位置是将依赖注入与模型绑定分离开来。一些原因包括
- 保持两个活动的分离可以简化错误诊断并提高代码清晰度
- 依赖容器可以很容易地在处理函数内部调用
- 输入值可能需要与依赖容器注册,这需要在依赖容器之前完成输入模型
- 输入模型应只为可能输入建模。它不负责组合或行为。
- 处理函数的存在是为了在输入模型和消费者之间建立桥梁。
项目状态
一次成功的实验。核心构建器的体验可能稳定,但API可能会根据反馈/经验而更改。
该库可使用,已有测试,但没有保证(包括支持)。
产品 | 版本 兼容和附加的计算目标框架版本。 |
---|---|
.NET | net5.0 已计算。 net5.0-windows 已计算。 net6.0 已计算。 net6.0-android 已计算。 net6.0-ios 已计算。 net6.0-maccatalyst 已计算。 net6.0-macos 已计算。 net6.0-tvos 已计算。 net6.0-windows 已计算。 net7.0 已计算。 net7.0-android 已计算。 net7.0-ios 已计算。 net7.0-maccatalyst 已计算。 net7.0-macos 已计算。 net7.0-tvos 已计算。 net7.0-windows 已计算。 net8.0 已计算。 net8.0-android 已计算。 net8.0-browser 已计算。 net8.0-ios 已计算。 net8.0-maccatalyst 已计算。 net8.0-macos 已计算。 net8.0-tvos 已计算。 net8.0-windows 已计算。 |
.NET Core | netcoreapp2.0 已计算。 netcoreapp2.1 已计算。 netcoreapp2.2 已计算。 netcoreapp3.0 已计算。 netcoreapp3.1 已计算。 |
.NET Standard | netstandard2.0 兼容。 netstandard2.1 已计算。 |
.NET Framework | net461已计算。 net462已计算。 net463已计算。 net47已计算。 net471已计算。 net472已计算。 net48已计算。 net481已计算。 |
MonoAndroid | monoandroid已计算。 |
MonoMac | monomac已计算。 |
MonoTouch | monotouch已计算。 |
Tizen | tizen40已计算。 tizen60已计算。 |
Xamarin.iOS | xamarinios已计算。 |
Xamarin.Mac | xamarinmac已计算。 |
Xamarin.TVOS | xamarintvos已计算。 |
Xamarin.WatchOS | xamarinwatchos已计算。 |
-
.NETStandard 2.0
- System.CommandLine (>= 2.0.0-beta2.21617.1)
NuGet包 (1)
显示依赖farlee2121.System.CommandLine.PropertyMapBinder的顶级1个NuGet包
包 | 下载 |
---|---|
farlee2121.System.CommandLine.PropertyMapBinder.NameConventionBinder
一个用于通过简单的命名约定将控制台输入映射到属性的System.CommandLine.PropertyMapBinder扩展 |
GitHub仓库
此包没有被任何流行GitHub仓库使用。
版本 | 下载 | 最后更新 |
---|---|---|
1.0.0 | 1,585 | 3/23/2022 |
1.0.0-preview1 | 1,061 | 3/5/2022 |
0.1.0-beta1 | 959 | 1/13/2022 |
0.1.0-alpha1 | 139 | 1/9/2022 |