翼度科技»论坛 编程开发 .net 查看内容

Spectre.Console-处理依赖注入

11

主题

11

帖子

33

积分

新手上路

Rank: 1

积分
33
引言

之前说的做自动记录 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.                 }
  18.                 return app;
  19.         }
  20. }
复制代码
一切显得非常美好,但是棘手的问题就来了。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,依赖注入的行为会由 TypeRegistrar 与 TypeResolver 控制。
修改注册器行为

由于 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/archive/2023/06/01/17450308.html
免责声明:由于采集信息均来自互联网,如果侵犯了您的权益,请联系我们【E-Mail:cb@itdo.tech】 我们会及时删除侵权内容,谢谢合作!

举报 回复 使用道具