Spectre.Console.Cli注入服务的几种姿势

  • Spectre.Console.Cli注入服务的几种姿势已关闭评论
  • 54 次浏览
  • A+
所属分类:.NET技术
摘要

Spectre.Console大家可能都不陌生,写控制台程序美化还是不错的,支持着色,表格,图标等相当nice,如果对这个库不熟悉我强烈推荐你了解一下,对于写一些CLI小工具还是相当方便的, 本文主要讲讲 Spectre.Console.Cli的服务注入, TA是 Spectre.Console 库的一部分,用于创建命令行界面(CLI)应用程序。它提供了一个强大且易于使用的API来定义命令、参数和选项,同时支持 Spectre.Console 的丰富输出格式化功能。

Spectre.Console大家可能都不陌生,写控制台程序美化还是不错的,支持着色,表格,图标等相当nice,如果对这个库不熟悉我强烈推荐你了解一下,对于写一些CLI小工具还是相当方便的, 本文主要讲讲 Spectre.Console.Cli的服务注入, TA是 Spectre.Console 库的一部分,用于创建命令行界面(CLI)应用程序。它提供了一个强大且易于使用的API来定义命令、参数和选项,同时支持 Spectre.Console 的丰富输出格式化功能。

一个官方极简的例子,定义一个GreetCommand:

public class GreetCommand : Command<GreetCommand.Settings> {     public class Settings : CommandSettings     {         [CommandArgument(0, "<name>")]         [Description("The name of the person to greet.")]         public string Name { get; set; }          [CommandOption("-r|--repeat <times>")]         [Description("The number of times to repeat the greeting.")]         [DefaultValue(1)]         public int Repeat { get; set; }     }     public override int Execute(CommandContext context, Settings settings)     {         for (int i = 0; i < settings.Repeat; i++)         {             Console.WriteLine($"Hello, {settings.Name}!");         }         return 0;     } } 

接下来,在程序的入口点配置Command

public class Program {     public static int Main(string[] args)     {         var app = new CommandApp();         app.Configure(config =>         {             config.AddCommand<GreetCommand>("greet");         });         return app.Run(args);     } } 

对于Spectre.Console.Cli的常规使用我这里不做过多介绍,感兴趣的同学可以移步官方文档,本文主要讲一下在CLI程序中如何注入服务

那么我们需要在GreetCommand中注入服务应该怎么做呢? 比如下面的一个服务:

public class HelloService(ILogger<HelloService> logger) {     public Task<string> SayHello(string name, int age)     {         //注入的logger         logger.LogInformation("SayHello called with name:{name},age:{age}", name, age);         return Task.FromResult($"Hello,My name is {name}, I`m {age} years old!");     } } 

其实Spectre.Console.Cli内置了最简单的方式,我们只需要在app.Configure中完成:

var services = new ServiceCollection(); //添加服务 services.AddSingleton<HelloService>(); //添加日志 services.AddLogging(config => {     config.AddConsole(); }); var sp = services.BuildServiceProvider();  app.Configure(config => {     //添加Commands     config.AddCommand<OneCommand>("one");     config.AddCommand<AnotherCommand>("another");     //注册Services     config.Settings.Registrar.RegisterInstance(sp.GetRequiredService<HelloService>()); }); 

注册的服务就可以直接使用了:

public class HelloCommand(HelloService helloService) : AsyncCommand<HelloCommand.HelloSettings> {     private readonly HelloService _helloService = helloService;     public class HelloSettings : CommandSettings     {         [CommandArgument(0, "<name>")]         [Description("The target to say hello to.")]         public string Name { get; set; } = null!;     }     public override async Task<int> ExecuteAsync(CommandContext context, HelloSettings settings)     {         var message = await _helloService.SayHello(settings.Name, settings.Age);         AnsiConsole.MarkupLine($"[blue]{message}[/]");         return 0;     } } 

另外的一个注入方式是实现ITypeRegistrar,官方提供MSDI的用例,自己也可以实现Autofac等其他DI,下面是MSDI的实现:

namespace Infrastructure {     public sealed class MsDITypeRegistrar(IServiceCollection services) : ITypeRegistrar     {         private readonly IServiceCollection _services =             services ?? throw new ArgumentNullException(nameof(services));         public ITypeResolver Build()         {             return new TypeResolver(_services.BuildServiceProvider());         }         public void Register(Type service, Type implementation)         {             _services.AddSingleton(service, implementation);         }         public void RegisterInstance(Type service, object implementation)         {             _services.AddSingleton(service, implementation);         }         public void RegisterLazy(Type service, Func<object> factory)         {             _services.AddSingleton(service, (provider) => factory());         }     }     internal sealed class TypeResolver(IServiceProvider provider) : ITypeResolver     {         public object? Resolve(Type? type)         {             if (provider is null || type is null)             {                 return null;             }             return ActivatorUtilities.GetServiceOrCreateInstance(provider, type);         }     } } 

使用的话只需要实例化CommandApp时候传入MsDITypeRegistrar即可:

var services = new ServiceCollection(); //添加服务...  var app = new CommandApp(new MsDITypeRegistrar(services)); app.Configure(config => {    //... }); return app.Run(args); 

除了上面的方式,我们其实还可以使用ICommandInterceptor切面的方式来完成注入的操作:

下面我们定义一个AutoDIAttribute特性,实现一个AutoDIInterceptor的拦截器,后者主要给标记了AutoDI的属性服务赋值

[AttributeUsage(AttributeTargets.Property, Inherited = true)] public class AutoDIAttribute : Attribute{ }  /// <summary> /// 自动注入的拦截器 /// </summary> internal class AutoDIInterceptor(IServiceProvider serviceProvider) : ICommandInterceptor {     public void Intercept(CommandContext context, CommandSettings settings)     {         var type = settings.GetType();         var properties = type.GetProperties();         foreach (var property in properties)         {             var isAutoInject = property.GetCustomAttributes<AutoDIAttribute>(true).Any();             if (isAutoInject)             {                 var service = ActivatorUtilities.GetServiceOrCreateInstance(serviceProvider, property.PropertyType);                 property.SetValue(settings, service);             }         }     } } 

接下来在CommandSettings中标记需要自动注入服务的属性,如下面的HelloService:

internal class AutoDICommand : AsyncCommand<AutoDICommand.AnotherInjectSettings> {     public class AnotherInjectSettings : CommandSettings     {         /// <summary>         /// 使用切面装载的服务         /// </summary>         [AutoDI]         public HelloService HelloService { get; set; } = null!;          [Description("user name")]         [DefaultValue("vipwan"), CommandOption("-n|--name")]         public string Name { get; set; } = null!;          [Description("user age")]         [DefaultValue(12), CommandOption("-a|--age")]         public int Age { get; set; }     }      public override async Task<int> ExecuteAsync(CommandContext context, AnotherInjectSettings settings)     {         var message = await settings.HelloService.SayHello(settings.Name, settings.Age);         AnsiConsole.MarkupLine($"[green]{message}[/]");         return 0;     } } 

然后在app.Configure中使用AutoDIInterceptor切面:

var services = new ServiceCollection(); //添加服务 services.AddSingleton<HelloService>(); var sp = services.BuildServiceProvider();  app.Configure(config => {     //设置自动注入的拦截器     config.SetInterceptor(new AutoDIInterceptor(sp));     config.AddCommand<AutoDICommand>("di");     //... }); 

然后测试运行程序:

dotnet run -- di -n "vipwan" 

大功告成:
Spectre.Console.Cli注入服务的几种姿势

以上就介绍了几种在Spectre.Console.Cli注入服务的方式,当然没有最优的只有最适合自己的,如果代码存在不足,或者你有更好的建议 欢迎留言交流!