当前位置: 首页 >  要文 > 正文

天天热讯:Spectre.Console-处理依赖注入

2023-06-01 23:03:32 来源:博客园
引言

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

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

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


(资料图片)

await _commandApp.RunAsync(cmd.Split(" "));
依赖注入问题
static void Main(string[] args)        {            CreateHostBuilder(args).Build().Run();        }        public static IHostBuilder CreateHostBuilder(string[] args) =>    Host.CreateDefaultBuilder(args)        .ConfigureServices((hostContext, services) =>        {            services.AddSingleton();            services.AddHostedService();            services.AddCommandApp();        });

最后一个是拓展方法:

internal static IServiceCollection AddCommandApp(this IServiceCollection services){return services.AddSingleton(w =>{var app = new CommandApp();app.Configure(config =>{config.CaseSensitivity(CaseSensitivity.None);config.AddBranch("del", del =>{del.SetDefaultCommand>();del.AddCommand>("todo");del.AddCommand>("pro");del.AddCommand>("tag");});}return app;}}

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

放弃 Host

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

using Microsoft.Extensions.DependencyInjection;using Spectre.Console.Cli;namespace TodoTrack.Cli{    public sealed class TypeRegistrar : ITypeRegistrar    {        private readonly IServiceCollection _builder;        public TypeRegistrar(IServiceCollection builder)        {            _builder = builder;        }        public ITypeResolver Build()        {            return new TypeResolver(_builder.BuildServiceProvider());        }        public void Register(Type service, Type implementation)        {            _builder.AddSingleton(service, implementation);        }        public void RegisterInstance(Type service, object implementation)        {            _builder.AddSingleton(service, implementation);        }        public void RegisterLazy(Type service, Func func)        {            if (func is null)            {                throw new ArgumentNullException(nameof(func));            }            _builder.AddSingleton(service, (provider) => func());        }    }}
using Spectre.Console.Cli;namespace TodoTrack.Cli{    public sealed class TypeResolver : ITypeResolver, IDisposable    {        private readonly IServiceProvider _provider;        public TypeResolver(IServiceProvider provider)        {            _provider = provider ?? throw new ArgumentNullException(nameof(provider));        }        public object? Resolve(Type? type)        {            if (type == null)            {                return null;            }            return _provider.GetService(type);        }        public void Dispose()        {            if (_provider is IDisposable disposable)            {                disposable.Dispose();            }        }    }}

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

public static int Main(string[] args)    {        // Create a type registrar and register any dependencies.        // A type registrar is an adapter for a DI framework.        var registrations = new ServiceCollection();        registrations.AddSingleton();        var registrar = new TypeRegistrar(registrations);        // Create a new command app with the registrar        // and run it with the provided arguments.        var app = new CommandApp(registrar);        return app.Run(args);    }

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

修改注册器行为

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

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

public void Dispose()        {            //if (_provider is IDisposable disposable)            //{               // disposable.Dispose();            //}        }
private ITypeResolver _typeResolver;        public ITypeResolver Build()        {            return _typeResolver ??= new TypeResolver(_builder.BuildServiceProvider());        }

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

参考Spectre.Console - Introduction (spectreconsole.net)

标签: