.net控制台应用中使用依赖注入


对依赖注入比较熟悉的小伙伴推荐直接阅读第二节 # 控制台应用

.Net中的依赖注入

使用过ASP.Net Core的小伙伴对以下的代码一定不会陌生。
Startup.cs

        public void ConfigureServices(IServiceCollection services)
        {
        	...
            services.AddSingleton<ILogger>((sp) =>
            {
                var factory = sp.GetService<ILoggerFactory>();
                return factory.CreateLogger("AppLogger");
            });
            ...
        }

TestController.cs

public class TestController
{
	private ILogger m_Logger;
	
	public TestController(ILogger logger)
	{
		m_Logger = logger;
	}
}

在ASP.Net Core中默认就为我们准备好了依赖注入框架,在Startup中配置好需要用到的类型,后续直接在构造方法里加上就能直接使用了,从而免去了很多新建对象的new语句。
依赖注入能使程序中的耦合度降低,简化对象的创建,几乎已经成为现代.Net程序中必备的东西。那么如果我们不是编写ASP.Net Core,要在其他程序使用依赖注入要怎么做呢?

示例代码

首先我们需要引用微软的扩展包:Microsoft.Extensions.DependencyInjection。然后在程序初始化的时候配置好依赖注入容器。

public class TestConsole
{
	/// <summary>
	/// 依赖注入容器
	/// </summary>
	protected IServiceProvider m_Service;

	public void Initialize()
	{
		// 依赖注入配置集合
		var serviceArr = new ServiceCollection();
		// 像ASP.Net Core一样配置依赖注入
		serviceArr.AddScoped<ClassB>();
		serviceArr.AddSingleton<ClassA>();
		...
		// 构建依赖注入容器
		m_Service = serviceArr.BuildServiceProvider();
	}
	
	public void Run()
	{
		//需要构建对象时就使用m_Service 
		ClassA a =  m_Service .ServiceProvider.GetRequiredService<ClassA>();
		...
	}
}

以上就是.Net依赖注入最原始的所有代码。其实步骤非常简单:

  1. 配置容器里的类型和生命周期
  2. 调用容器获取对象

基本要点

要使用依赖注入,无论任何依赖注入框架都必须要遵循一些规则。

从头用到尾

依赖注入必须从头用到尾,即从程序的开头开始就要初始化,所有的用到类型都以依赖注入的形式构建。不能说某个模块想用到依赖注入,而其他模块又不用,也许代码上是可以这样实现,但是从整个程序的结构上看来就有点不伦不类的样子,而且如果后面某个没使用依赖注入的模块想获得某个使用依赖注入的类型时将会非常痛苦。

生命周期

.Net依赖注入获取对象的生命周期有3种。由短到长分别是
Transient(瞬时),Scoped(作用域),Singleton(单例)
详情可见以下文档
依赖注入生命周期

Singleton(单例)

单例比较好理解,类型的对象只有1个,每次获取的都是同一个。

Transient(瞬时)

瞬时的也不难理解,类型的对象在每次获取时都会重新创建,所以,每次获取的都是不同的对象。

Scoped(作用域)

作用域就有点难理解了,在IServiceProvider创建的IServiceScope的生命周期中每次获取的对象都是同一个。对应ASP.Net Core中,每个http请求都是一个Scope;对应我们自己写的程序,如下面的代码所示,在using语句中就是一个Scope,即IServiceScope 对象Dispose之前通过它获取的对象都是同一个。

public void Test()
{
	...
	using (IServiceScope scope = m_Service.CreateScope())
	{
	    try
	    {
	        ClassA c = scope.ServiceProvider.GetRequiredService<ClassA>();
	        c.Test();
	        if (c is IDisposable dis)
	        {
	            dis.Dispose();
	        }
	    }
	    catch (Exception ex)
	    {
	        Console.WriteLine(ex.Message);
	    }
	}
	...
}

控制台应用

平时的开发中总会遇到要写控制台应用的时候,比如:定时任务(爬虫)、命令行交互、通讯等场景。众所周知.Net控制台应用创建出来几乎什么都没有,这时候如果我们想在控制台应用中使用依赖注入就需要稍微对程序加一点设计进去。
首先,如果你的控制台应用是用来定时执行一下特定的方法,这里推荐你使用Quartz这个类库。当然如果你和我一样是不怕折腾的程序猿,欢迎你继续往下阅读。
Quartz中使用依赖注入

稍加设计

这里开始我们以定时任务控制台为例子开始踩坑。
首先我们需要将程序抽象一下,把定时任务控制台程序实现为一个类,定时任务就用System.Threading.Timer来实现。

程序雏形

/// <summary>
/// 控制台程序基类
/// </summary>
public abstract class BaseConsole
{
	/// <summary>
	/// 依赖注入集合
	/// </summary>
	protected IServiceProvider m_Service;
	
	/// <summary>
	/// 配置依赖注入
	/// </summary>
	/// <param name="services">容器集合</param>
	protected abstract void ConfigureServices(IServiceCollection services);
	
 	public void Run()
	{
	    // 初始化
	    Initialize();

	    while (true)
	    {
	        // 阻塞主线程
	        string input = Console.ReadLine();
	        // 如果输入了exit就退出
	        if(input.ToLower() == "exit")
	        {
	            break;
	        }
	    }
	}
	
	/// <summary>
	/// 初始化启动配置
	/// </summary>
	private void Initialize()
	{
	    var serviceArr = new ServiceCollection();
	    // 配置依赖注入
	    ConfigureServices(serviceArr);
	
	    m_Service = serviceArr.BuildServiceProvider();
	}
}

程序的雏形如上所示,后续的实际使用中将我们的具体业务程序类继承BaseConsole,重写ConfigureServices方法以配置我们自己的依赖注入即可。

定时任务

定时任务我们可以用System.Threading.Timer这个类型来实现。
首先,抽象出一个定时任务接口ICron,然后Timer每隔一段时间就以依赖注入的形式创建ICron对象并执行业务。

    /// <summary>
    /// 定时任务接口
    /// </summary>
    public interface ICron
    {
        /// <summary>
        /// 任务开始
        /// </summary>
        /// <remarks>程序开始时执行一次,可进行定时任务的准备工作</remarks>
        /// <returns></returns>
        Task Start();

        /// <summary>
        /// 定时任务执行
        /// </summary>
        /// <remarks>每隔一段时间会以新对象执行该方法</remarks>
        /// <returns></returns>
        Task<string> Execute();

        /// <summary>
        /// 程序结束前进行清理工作
        /// </summary>
        /// <remarks>程序结束时执行一次,可进行定时任务的清理工作</remarks>
        /// <returns></returns>
        Task Exit();
    }

因为程序里的定时任务肯定不会只有一个,这里为了方便就创建了一个定时任务集合的类型方便管理。这里每向集合添加一个定时任务都会以AddScpoed的形式加入到依赖注入中。

    /// <summary>
    /// 定时任务集合
    /// </summary>
    public partial class CronCollection : IEnumerable<CronCollection.Cron>
    {
        /// <summary>
        /// 定时任务描述
        /// </summary>
        public class Cron
        {
            /// <summary>
            /// 执行间隔
            /// </summary>
            public TimeSpan Interval;

            /// <summary>
            /// 定时任务逻辑类型
            /// </summary>
            public Type ExecType;
        }

        private IServiceCollection m_Services;

        /// <summary>
        /// 内部集合
        /// </summary>
        protected List<Cron> m_List;

        public CronCollection(IServiceCollection services)
        {
            m_Services = services;
            m_List = new List<Cron>();
        }

        public CronCollection AddCron<T>(TimeSpan span) where T : class, ICron
        {
            m_Services.AddScoped<T>();
            Cron c = new Cron { Interval = span, ExecType = typeof(T) };
            m_List.Add(c);
            return this;
        }

        public CronCollection AddCron<T>(TimeSpan span, Func<IServiceProvider, T> func) where T : class, ICron
        {
            m_Services.AddScoped<T>(func);
            Cron c = new Cron { Interval = span, ExecType = typeof(T) };
            m_List.Add(c);
            return this;
        }

        public IEnumerator<Cron> GetEnumerator()
        {
            return ((IEnumerable<Cron>)m_List).GetEnumerator();
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return ((IEnumerable)m_List).GetEnumerator();
        }
    }

雏形升级

有了定时任务之后,就需要为雏形加入定时任务让它执行。

    /// <summary>
    /// 控制台程序类
    /// </summary>
    public abstract class BaseConsole
    {
        /// <summary>
        /// 依赖注入集合
        /// </summary>
        protected IServiceProvider m_Service;

        /// <summary>
        /// 定时任务集合
        /// </summary>
        protected CronCollection m_Crons;

        /// <summary>
        /// 定时任务定时器集合
        /// </summary>
        private List<Timer> m_CronTimers;

        public BaseConsole()
        {
        }

        /// <summary>
        /// 配置依赖注入
        /// </summary>
        /// <param name="services">容器集合</param>
        protected abstract void ConfigureServices(IServiceCollection services);

        /// <summary>
        /// 配置定时任务
        /// </summary>
        /// <param name="crons">定时任务集合</param>
        /// <param name="config">配置文件</param>
        protected abstract void ConfigureCron(CronCollection crons);

        public void Run()
        {
            // 初始化
            Initialize();
            // 开启定时任务 or 执行其他业务
            CronRun();
            while (true)
            {
                // 阻塞主线程
                string input = Console.ReadLine();
                // 如果输入了exit就退出
                if(input.ToLower() == "exit")
                {
                    break;
                }
            }
            // 释放定时器
            CronEnd();
        }

        /// <summary>
        /// 初始化启动配置
        /// </summary>
        private void Initialize()
        {
            m_CronTimers = new List<Timer>();
            var serviceArr = new ServiceCollection();
            // 配置依赖注入
            ConfigureServices(serviceArr);

            var crons = new CronCollection(serviceArr);
            // 配置定时任务
            ConfigureCron(crons);
            m_Service = serviceArr.BuildServiceProvider();

            m_Crons = crons;
        }

        private async void CronRun()
        {
            ICron c = default;
            foreach (var cron in m_Crons)
            {
                using (var scope = m_Service.CreateScope())
                {
                    try
                    {
                        c = scope.ServiceProvider.GetRequiredService(cron.ExecType) as ICron;
                        await c.Start();
                        if (c is IDisposable dis)
                        {
                            dis.Dispose();
                        }
                    }
                    catch (Exception ex)
                    {
                        Console.WriteLine("CRON start error:" + ex.Message);
                    }
                }

                Timer timer = new Timer(ExecCron, cron.ExecType, TimeSpan.FromSeconds(5), cron.Interval);
                m_CronTimers.Add(timer);
            }
        }

        private void ExecCron(object state)
        {
            Type tp = state as Type;
            ICron cron = default;
            if (tp != null)
            {
                string print = string.Empty;
                using (var scope = m_Service.CreateScope())
                {
                    try
                    {
                        cron = scope.ServiceProvider.GetRequiredService(tp) as ICron;
                        print = cron.Execute().GetAwaiter().GetResult();
                        if (cron is IDisposable dis)
                        {
                            dis.Dispose();
                        }
                    }
                    catch (Exception ex)
                    {
                        print = "CRON execute error:" + ex.Message;
                    }
                }
                Console.WriteLine(print);
            }
        }

        private void CronEnd()
        {
            Task[] dispTks = new Task[m_CronTimers.Count];
            // 关闭定时器
            for (int i = 0; i < m_CronTimers.Count; i++)
            {
                var timer = m_CronTimers[i];
                // 等待正在执行的任务完成
                dispTks[i] = Task.Run(async () =>
                {
                    await timer.DisposeAsync();
                });
            }
            Task.WaitAll(dispTks);

            // 调用定时任务的结束方法
            ICron c;
            foreach (var cron in m_Crons)
            {
                using (var scope = m_Service.CreateScope())
                {
                    try
                    {
                        c = scope.ServiceProvider.GetRequiredService(cron.ExecType) as ICron;
                        c.Exit().Wait();
                        if (c is IDisposable dis)
                        {
                            dis.Dispose();
                        }
                    }
                    catch (Exception ex)
                    {
                        Console.WriteLine("CRON exit error:" + ex.Message);
                    }
                }
            }
        }
    }

这里我们把每次获取ICron的代码都写到一个Scope里,所以定时任务的每次执行都是在同一个作用域里面。并且调用完成之后还需要判断这个对象是否需要Dispose,如果需要,则Dispose掉它,保证不占用非托管资源。

正式使用

至此,一个使用依赖注入的微型定时任务控制台应用框架已经实现了。
使用时只需实现ICron接口写我们自己的定时任务业务,然后把这个定时任务类型加到派生自BaseConsole的控制台应用中即可。

    /// <summary>
    /// 测试定时任务
    /// </summary>
    public class HelloCron : ICron
    {
        public async Task<string> Execute()
        {
            // 假装工作了100ms
            await Task.Delay(100);
            return "Hello_World";
        }

        public Task Exit()
        {
            return Task.CompletedTask;
        }

        public Task Start()
        {
            return Task.CompletedTask;
        }
    }
    
    /// <summary>
    /// 派生的控制台应用类
    /// </summary>
    public class TestConsole : BaseConsole
    {
        protected override void ConfigureCron(CronCollection crons)
        {
            // 加入定时任务并配置执行间隔
            crons.AddCron<HelloCron>(TimeSpan.FromSeconds(5));
        }

        protected override void ConfigureServices(IServiceCollection services)
        {
            // 这里像ASP.Net Core一样配置依赖注入
            // services.AddScoped<T>();
        }
    }

Main方法

    class Program
    {
        static void Main(string[] args)
        {
            TestConsole console = new TestConsole();
            console.Run();
        }
    }

完善实现

这个雏形,没有日志,没有交互,没有配置文件,作为一个控制台应用是远远不够的。为此可以参考本人对以上代码的进一步完善。

https://github.com/YinRunhao/TimingConsole

参考文章

依赖注入生命周期
Quartz中使用依赖注入

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
.NET Framework 使用 MVC 架构时,可以使用依赖注入(Dependency Injection,简称 DI)来解决对象之间的依赖关系。依赖注入是一种设计模式,通过将对象的创建和管理委托给外部容器来实现。 在 MVC 使用依赖注入的好处是可以降低代码的耦合性,提高可维护性和可测试性。以下是在 .NET Framework MVC 使用依赖注入的一般步骤: 1. 配置依赖注入容器:您可以使用第三方的依赖注入容器,如 Autofac、Unity、Ninject 等,或者使用 .NET Framework 自带的简单容器(如 `UnityContainer`)。 2. 注册依赖项:在配置阶段,您需要将控制器、服务、存储库等需要被注入的对象注册到容器。这样容器就能够识别和管理这些对象。 3. 声明依赖关系:在需要使用依赖项的地方(如控制器的构造函数),通过构造函数或属性注入等方式声明依赖关系。容器会自动解析和提供所需的对象实例。 4. 解析依赖项:在运行时,容器会根据对象的声明和注册信息来解析依赖关系,并将所需的对象实例提供给相应的对象。 下面是一个使用 .NET Framework MVC 和 Unity 容器的示例: 首先,安装 Unity 容器的 NuGet 包(例如通过 NuGet 包管理器控制台运行 `Install-Package Unity`)。 然后,在 Global.asax.cs 文件的 Application_Start 方法配置 Unity 容器: ```csharp protected void Application_Start() { // 创建 Unity 容器 var container = new UnityContainer(); // 注册依赖项 container.RegisterType<IMyService, MyService>(); // 设置 MVC 依赖解析器为 UnityDependencyResolver DependencyResolver.SetResolver(new UnityDependencyResolver(container)); // 其他 MVC 配置... } ``` 接下来,在控制器声明依赖关系: ```csharp public class MyController : Controller { private readonly IMyService _myService; public MyController(IMyService myService) { _myService = myService; } // 控制器动作方法... } ``` 现在,当 MVC 框架创建 MyController 实例时,Unity 容器会自动解析 IMyService 接口的实现(如 MyService),并将其注入到控制器的构造函数。 这样,您就可以通过依赖注入来实现对象之间的解耦和灵活性,使代码更加可测试和可扩展。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值