Spectre.Console-处理依赖注入

Spectre,Console · 浏览次数 : 181

小编点评

## Summary of the code This code shows how to register and use a custom command app in Spectre.Console.Cli. **Key points:** * **Spectre.Console.Cli does not provide registration mechanisms for external dependencies.** * **The code uses a `TypeRegistrar` class to manage dependencies.** * **The `TypeResolver` class provides dependency injection and manages lifetimes.** * **The `Main` method demonstrates how to configure and run the command app with a custom type registrar.** **Details:** **`TypeRegistrar`** * Provides registration methods for services and instances. * Uses `IServiceCollection` to build and configure services. * Offers methods to register lazy dependencies, including registration and disposal. * Provides `Build` method to return an `ITypeResolver` instance for dependency injection. **`TypeResolver`** * Provides implementation of `ITypeResolver` interface. * Uses `IServiceCollection` to resolve dependencies. * Provides methods to resolve dependencies for specific types and register them. * Offers `Dispose` method to release resources. **`Main` method example:** * Creates a `TypeRegistrar` instance. * Registers dependencies for `HelloWorldGreeter` service. * Creates a `CommandApp` instance with the registrar. * Starts the app and runs it with provided arguments. **Benefits of the approach:** * **Loose coupling between components.** * **Flexibility in dependency registration.** * **Reduced boilerplate code.** * **Maintainability and separation of concerns.** **Limitations:** * **External dependencies require special handling.** * **Registering and injecting in `TypeResolver` can be cumbersome.** * **`TypeResolver` may not be suitable for all scenarios.** **Overall, the code demonstrates a practical approach to managing dependencies and using external command apps with Spectre.Console.Cli.**

正文

引言

之前说的做自动记录 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<TodoHolder>();
            services.AddHostedService<TodoCommandService>();
            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<MethodSettings>("del", del =>
			{
				del.SetDefaultCommand<DelCommand<TodoItem>>();
				del.AddCommand<DelCommand<TodoItem>>("todo");
				del.AddCommand<DelCommand<Project>>("pro");
				del.AddCommand<DelCommand<Tag>>("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<object> 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<IGreeter, HelloWorldGreeter>();
        var registrar = new TypeRegistrar(registrations);

        // Create a new command app with the registrar
        // and run it with the provided arguments.
        var app = new CommandApp<DefaultCommand>(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-处理依赖注入相似的内容:

Spectre.Console-处理依赖注入

## 引言 之前说的做自动记录 Todo 执行过程中消耗的时间的[Todo 项目]( https://github.com/circler3/TodoTrack ),由于想持续保持程序执行,就放弃了 `Spectre.Console.Cli`,后来随着命令越来越多,自己处理觉得很是麻烦,想了想要不试

Spectre.Console-实现自己的CLI

## 引言 最近发现自己喜欢用的 Todo 软件总是差点意思,毕竟每个人的习惯和工作流不太一样,我就想着自己写一个小的[Todo 项目]( https://github.com/circler3/TodoTrack ),核心的功能是自动记录 Todo 执行过程中消耗的时间(尤其面向程序员),按照自己

使用Terminal.Gui构建功能强大的.NET控制台应用

前言 前段时间分享了一个库帮你轻松的创建漂亮的.NET控制台应用程序 - Spectre.Console的文章教程,然后就有小伙伴提问:.NET控制台应用需要应对强交互性的场景,有什么好的解决方案?,今天大姚给大家分享一款适用于.NET的跨平台终端 UI 工具包,帮助大家快速构建功能强大的.NET控

[转帖]迟到的Meltdown/Spectre分析

https://zhuanlan.zhihu.com/p/263081764 Meltdown/Spectre在2018年初闹得沸沸扬扬, 可以说是有史以来最有影响的cpu漏洞了. 当时有过简单了解, 但是不够深入, 这两天重新又看了一下. 背景知识 乱序执行 cpu的乱序执行一般都使用Tomasu

[转帖]【文章导读】什么是旁道攻击?Meltdown Redux英特尔漏洞(MDS攻击);KAISER:从用户空间隐藏内核(KAISER);Meltdown/Spectre分析

Table of Contents 黑客词典:什么是旁道攻击? Meltdown Redux:Intel缺陷使黑客窃取了数百万台PC的秘密 三重熔毁:有多少研究人员同时发现了20年的芯片缺陷 KAISER:从用户空间隐藏内核 迟到的Meltdown/Spectre分析 黑客词典:什么是旁道攻击? h

一个库帮你轻松的创建漂亮的.NET控制台应用程序

前言 做过.NET控制台应用程序的同学应该都知道原生的.NET控制台应用程序输出的内容都比较的单调,假如要编写漂亮且美观的控制台输出内容或者样式可能需要花费不少的时间去编写代码和调试。今天大姚给大家分享一个.NET开源且免费的类库帮你轻松的创建漂亮、美观的.NET控制台应用程序:Spectre.Co