引言

之前说的做自动记录 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 对象,使用时需要小心。

参考

Spectre.Console-处理依赖注入的更多相关文章

  1. ASP.NET Core 2.2 基础知识(一) 依赖注入

    依赖: 类A用到了类B,我们就说类A依赖类B.如果一个类没有任何地方使用到,那这个类基本上可以删掉了. public class Test { private MyDependency md = ne ...

  2. Console app 里的依赖注入及其实例生命周期

    依赖注入是 ASP.NET Core 里的核心概念之一,我们平常总是愉快地在Startup类的ConfigureServices方法里往IServiceCollection里注册各种类型,以致有一些同 ...

  3. .NET CORE——Console中使用依赖注入

    我们都知道,在 ASP.NET CORE 中通过依赖注入的方式来使用服务十分的简单,而在 Console 中,其实也只是稍微绕了个小弯子而已.不管是内置 DI 组件或者第三方的 DI 组件(如Auto ...

  4. ASP.NET Core中如影随形的”依赖注入”[下]: 历数依赖注入的N种玩法

    在对ASP.NET Core管道中关于依赖注入的两个核心对象(ServiceCollection和ServiceProvider)有了足够的认识之后,我们将关注的目光转移到编程层面.在ASP.NET ...

  5. ASP.NET Core中如影随形的”依赖注入”[上]: 从两个不同的ServiceProvider说起

    我们一致在说 ASP.NET Core广泛地使用到了依赖注入,通过前面两个系列的介绍,相信读者朋友已经体会到了这一点.由于前面两章已经涵盖了依赖注入在管道构建过程中以及管道在处理请求过程的应用,但是内 ...

  6. 模拟AngularJS之依赖注入

    一.概述 AngularJS有一经典之处就是依赖注入,对于什么是依赖注入,熟悉spring的同学应该都非常了解了,但,对于前端而言,还是比较新颖的. 依赖注入,简而言之,就是解除硬编码,达到解偶的目的 ...

  7. angular2系列教程(八)In-memory web api、HTTP服务、依赖注入、Observable

    大家好,今天我们要讲是angular2的http功能模块,这个功能模块的代码不在angular2里面,需要我们另外引入: index.html <script src="lib/htt ...

  8. angularjs 依赖注入--自己学着实现

    在用angular依赖注入时,感觉很好用,他的出现是 为了"削减计算机程序的耦合问题" ,我怀着敬畏与好奇的心情,轻轻的走进了angular源码,看看他到底是怎么实现的,我也想写个 ...

  9. 在.NET Core控制台程序中使用依赖注入

    之前都是在ASP.NET Core中使用依赖注入(Dependency Injection),昨天遇到一个场景需要在.NET Core控制台程序中使用依赖注入,由于对.NET Core中的依赖注入机制 ...

  10. ASP.NET Core中的依赖注入(2):依赖注入(DI)

    IoC主要体现了这样一种设计思想:通过将一组通用流程的控制从应用转移到框架之中以实现对流程的复用,同时采用"好莱坞原则"是应用程序以被动的方式实现对流程的定制.我们可以采用若干设计 ...

随机推荐

  1. MyBatisPlus 整合 SpringBoot 遇见的问题(一)

    [异常]:UnsatisfiedDependencyException: Error creating bean with name 'xxx[类]': Unsatisfied dependency ...

  2. 案例: 利用 Hugging Face 进行复杂文本分类

    Witty 与 Hugging Face 专家加速计划的成功案例 如果你对如何更快构建 ML 解决方案感兴趣,请访问 专家加速计划 登陆页面并通过 填写表单 联系我们! 业务背景 随着 IT 技术不断 ...

  3. 在Jupyter Notebook,沉浸式体验ChatGPT

    大家好,我是章北海mlpy 写代码,修Bug是 ChatGPT 目前最擅长的领域之一 今天向大家推荐一个刚刚开源的Python包 安装后可以直接在IPython和Jupyter Notebook中直接 ...

  4. 基于Mongodb分布式锁简单实现,解决定时任务并发执行问题

    前言 我们日常开发过程,会有一些定时任务的代码来统计一些系统运行数据,但是我们应用有需要部署多个实例,传统的通过配置文件来控制定时任务是否启动又太过繁琐,而且还经常出错,导致一些异常数据的产生 网上有 ...

  5. 【Visual Leak Detector】源码文件概览

    说明 使用 VLD 内存泄漏检测工具辅助开发时整理的学习笔记.本篇对 VLD 源码包中的各文件用途做个概述.同系列文章目录可见 <内存泄漏检测工具>目录 目录 说明 1. 整体概览 2. ...

  6. javasec(五)URLDNS反序列化分析

    这篇文章介绍 URLDNS 就是ysoserial中⼀个利⽤链的名字,但准确来说,这个其实不能称作"利⽤链".因为其参数不是⼀个可以"利⽤"的命令,⽽仅为⼀个U ...

  7. ES的索引结构与算法解析

    作者:京东物流 李洪吉 提到ES,大多数爱好者想到的都是搜索引擎,但是明确一点,ES不等同于搜索引擎.不管是谷歌.百度.必应.搜狗为代表的自然语言处理(NLP).爬虫.网页处理.大数据处理的全文搜索引 ...

  8. 【图解算法使用C++】1.2 生活中的算法

    图解算法使用C++ 一.计算思维与程序设计 1.2 生活中到处都是算法 计算最大公约数(辗转相除法) // C++ #include<iostream> #include<stdio ...

  9. git与github(结合clion操作)

    对自己学习git的一个记录,由于刚开始接触git,所以没有对于git做深入解释和说明,仅供参考,如有理解不对的地方或者需要改进的地方敬请指出.   用到的git命令: git init //初始化 g ...

  10. 讯飞星火大模型 与New Bing实测对比

    昨天科大讯飞发布了讯飞星火认知大模型,在发布会现场实测大模型的7种核心能力,并发布了它在教育.办公.汽车.数字员工领域的应用成果.科大讯飞董事长刘庆峰表示:认知大模型展示了通用人工智能的曙光,讯飞星火 ...