经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 程序设计 » C# » 查看文章
Spectre.Console-处理依赖注入
来源:cnblogs  作者:波多尔斯基  时间:2023/6/2 10:52:05  对本文有异议

引言

之前说的做自动记录 Todo 执行过程中消耗的时间的Todo 项目,由于想持续保持程序执行,就放弃了 Spectre.Console.Cli,后来随着命令越来越多,自己处理觉得很是麻烦,想了想要不试试怎么将这个东西嵌入程序,然后手动传递参数?

本文完整代码可以从项目中获取。

说干就干,研究了一下,发现核心的 CommandApp 并不需要独占的控制台,我们可以随时 new,参数直接将 ReadLine() 获得的参数传递 args 就可以了。

  1. await _commandApp.RunAsync(cmd.Split(' '));

依赖注入问题

  1. static void Main(string[] args)
  2. {
  3. CreateHostBuilder(args).Build().Run();
  4. }
  5. public static IHostBuilder CreateHostBuilder(string[] args) =>
  6. Host.CreateDefaultBuilder(args)
  7. .ConfigureServices((hostContext, services) =>
  8. {
  9. services.AddSingleton<TodoHolder>();
  10. services.AddHostedService<TodoCommandService>();
  11. services.AddCommandApp();
  12. });

最后一个是拓展方法:

  1. internal static IServiceCollection AddCommandApp(this IServiceCollection services)
  2. {
  3. return services.AddSingleton(w =>
  4. {
  5. var app = new CommandApp();
  6. app.Configure(config =>
  7. {
  8. config.CaseSensitivity(CaseSensitivity.None);
  9. config.AddBranch<MethodSettings>("del", del =>
  10. {
  11. del.SetDefaultCommand<DelCommand<TodoItem>>();
  12. del.AddCommand<DelCommand<TodoItem>>("todo");
  13. del.AddCommand<DelCommand<Project>>("pro");
  14. del.AddCommand<DelCommand<Tag>>("tag");
  15. });
  16. }
  17. return app;
  18. }
  19. }

一切显得非常美好,但是棘手的问题就来了。Spectre.Console.Cli 自带依赖注入功能,会自动管理 Command 中的依赖关系,如果我们的 Command 需要依赖外部的类,那么需要在 Spectre.Console.Cli 中注册才能正常工作。但是这个东西也不自带注册器,我们在外部 DI 中注册的 TodoHolder 并没有什么用。

放弃 Host

虽然 Spectre.Console.Cli 不提供注册的办法,但是提供了一个构造函数,支持接受一个 ITypeRegistrar 作为参数,直接传递 IServiceCollection 就可以,这样在外部注册的类就传递进去了注册系统。官方提供了这个两个类的实现示例:

  1. using Microsoft.Extensions.DependencyInjection;
  2. using Spectre.Console.Cli;
  3. namespace TodoTrack.Cli
  4. {
  5. public sealed class TypeRegistrar : ITypeRegistrar
  6. {
  7. private readonly IServiceCollection _builder;
  8. public TypeRegistrar(IServiceCollection builder)
  9. {
  10. _builder = builder;
  11. }
  12. public ITypeResolver Build()
  13. {
  14. return new TypeResolver(_builder.BuildServiceProvider());
  15. }
  16. public void Register(Type service, Type implementation)
  17. {
  18. _builder.AddSingleton(service, implementation);
  19. }
  20. public void RegisterInstance(Type service, object implementation)
  21. {
  22. _builder.AddSingleton(service, implementation);
  23. }
  24. public void RegisterLazy(Type service, Func<object> func)
  25. {
  26. if (func is null)
  27. {
  28. throw new ArgumentNullException(nameof(func));
  29. }
  30. _builder.AddSingleton(service, (provider) => func());
  31. }
  32. }
  33. }
  1. using Spectre.Console.Cli;
  2. namespace TodoTrack.Cli
  3. {
  4. public sealed class TypeResolver : ITypeResolver, IDisposable
  5. {
  6. private readonly IServiceProvider _provider;
  7. public TypeResolver(IServiceProvider provider)
  8. {
  9. _provider = provider ?? throw new ArgumentNullException(nameof(provider));
  10. }
  11. public object? Resolve(Type? type)
  12. {
  13. if (type == null)
  14. {
  15. return null;
  16. }
  17. return _provider.GetService(type);
  18. }
  19. public void Dispose()
  20. {
  21. if (_provider is IDisposable disposable)
  22. {
  23. disposable.Dispose();
  24. }
  25. }
  26. }
  27. }

CommandApp 的初始化语句还得改成这个形式:

  1. public static int Main(string[] args)
  2. {
  3. // Create a type registrar and register any dependencies.
  4. // A type registrar is an adapter for a DI framework.
  5. var registrations = new ServiceCollection();
  6. registrations.AddSingleton<IGreeter, HelloWorldGreeter>();
  7. var registrar = new TypeRegistrar(registrations);
  8. // Create a new command app with the registrar
  9. // and run it with the provided arguments.
  10. var app = new CommandApp<DefaultCommand>(registrar);
  11. return app.Run(args);
  12. }

这种方法放弃了 Host 创建 HostedService,依赖注入的行为会由 TypeRegistrarTypeResolver 控制。

修改注册器行为

由于 Spectre.Console.Cli 是依照 CLI 工具设计的,这类工具往往执行一次就自动退出返回控制台。因此它的注册器会在每次调用时重新创建 IServiceProvider,如果直接将其改成多次执行,我们会发现所有对象都会重新初始化一遍,和 AddSingleton 之类的行为不同。

修改注册器行为,将其作为一个长期运行的单例执行,这样我们可以继续使用拓展方法注册,并注入到 HostedService 中。

  1. public void Dispose()
  2. {
  3. //if (_provider is IDisposable disposable)
  4. //{
  5. // disposable.Dispose();
  6. //}
  7. }
  1. private ITypeResolver _typeResolver;
  2. public ITypeResolver Build()
  3. {
  4. return _typeResolver ??= new TypeResolver(_builder.BuildServiceProvider());
  5. }

这种方式下,外部的 DI 无法识别 CommandApp 内部注册的 Command 对象,使用时需要小心。

参考

原文链接:https://www.cnblogs.com/podolski/p/17450308.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号