背景介紹

依賴注入(Dependency Injection), 是面向物件程式設計中的一種設計原則,可以用來減低程式碼之間的耦合度。在.NET Core MVC中 我們可以在Startup.cs檔案的ConfigureService方法中使用服務容器IServiceCollection註冊介面及其實現類的對映。

例如,當我們需要訪問Http上下文時,我們需要配置IHttpContextAccessor介面及其實現類HttpContextAccessor

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
        services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
    }

那麼當我們編寫一個.NET Core控制檯程式的時候,我們該如何使用依賴注入呢?

使用內建依賴注入

在.NET Core中,內建依賴注入模組使用的程式集是Microsoft.Extensions.DependencyInjection

所以如果希望在控制檯程式中使用內建依賴注入,我們首先需要使用NUGET新增對Microsoft.Extensions.DependencyInjection程式集的引用。

PM> Install-Package Microsoft.Extensions.DependencyInjection

這裡為了說明如何使用.NET Core內建的依賴注入模組, 我們建立以下2個服務介面。

    public interface IFooService
    {
        void DoThing(int number);
    }

    public interface IBarService
    {
        void DoSomeRealWork();
    }

然後我們針對這2個服務介面,新增2個對應的實現類

    public class BarService : IBarService
    {
        private readonly IFooService _fooService;
        public BarService(IFooService fooService)
        {
            _fooService = fooService;
        }

        public void DoSomeRealWork()
        {
            for (int i = 0; i < 10; i++)
            {
                _fooService.DoThing(i);
            }
        }
    }

    public class FooService : IFooService
    {
        private readonly ILogger<FooService> _logger;
        public FooService(ILoggerFactory loggerFactory)
        {
            _logger = loggerFactory.CreateLogger<FooService>();
        }

        public void DoThing(int number)
        {
            _logger.LogInformation($"Doing the thing {number}");
        }
    }

程式碼解釋

  • BarService類建構函式依賴了一個IFooService介面的實現
  • FooService類建構函式依賴一個ILoggerFactory介面的實現
  • FooService中,我們輸出了一個Information級別的日誌

在以上實現類程式碼中,我們使用了.NET Core內建的日誌模組, 所以我們還需要使用NUGET新增對應的程式集Microsoft.Extensions.Logging.Console

PM> Install-Package Microsoft.Extensions.Logging.Console

最後我們來修改Program.cs, 程式碼如下


using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;

    public class Program
    {
        public static void Main(string[] args)
        {
            //setup our DI
            var serviceProvider = new ServiceCollection()
                .AddLogging()
                .AddSingleton<IFooService, FooService>()
                .AddSingleton<IBarService, BarService>()
                .BuildServiceProvider();

            //configure console logging
            serviceProvider
                .GetService<ILoggerFactory>()
                .AddConsole(LogLevel.Debug);

            var logger = serviceProvider.GetService<ILoggerFactory>()
                .CreateLogger<Program>();
            logger.LogInformation("Starting application");

            //do the actual work here
            var bar = serviceProvider.GetService<IBarService>();
            bar.DoSomeRealWork();

            logger.LogInformation("All done!");

        }
    }

程式碼解釋

  • 這裡我們手動例項化了一個ServiceCollection類, 這個類是IServiceCollection>介面的一個實現類,它就是一個.NET Core內建服務容器。
  • 然後我們在服務容器中註冊了IFooService介面的實現類FooService以及IBarService介面的實現類BarService
  • 當時需要從服務容器中獲取介面類的對應實現類時,我們只需要呼叫服務容器類的GetSerivce方法。

最終效果

執行程式,我們期望的日誌,正確的輸出了

info: DIInConsoleApp.Program[0]
      Start application.
info: DIInConsoleApp.FooService[0]
      Doing the thing 0
info: DIInConsoleApp.FooService[0]
      Doing the thing 1
info: DIInConsoleApp.FooService[0]
      Doing the thing 2
info: DIInConsoleApp.FooService[0]
      Doing the thing 3
info: DIInConsoleApp.FooService[0]
      Doing the thing 4
info: DIInConsoleApp.FooService[0]
      Doing the thing 5
info: DIInConsoleApp.FooService[0]
      Doing the thing 6
info: DIInConsoleApp.FooService[0]
      Doing the thing 7
info: DIInConsoleApp.FooService[0]
      Doing the thing 8
info: DIInConsoleApp.FooService[0]
      Doing the thing 9
info: DIInConsoleApp.Program[0]
      All done!

使用第三方依賴注入

除了使用內建的依賴注入模組,我們還可以直接使用一些第三方的依賴注入框架,例如Autofac, StructureMap。

這裡我們來使用StructureMap來替換當前的內建的依賴注入框架。

首先我們需要先新增程式集引用。

PM> Install-Package StructureMap.Microsoft.DependencyInjection

然後我們來修改Program.cs檔案,程式碼如下

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using StructureMap;
using System;

namespace DIInConsoleApp
{
    class Program
    {
        static void Main(string[] args)
        {
            var services = new ServiceCollection().AddLogging();

            var container = new Container();
            container.Configure(config =>
            {
                config.Scan(_ =>
                {
                    _.AssemblyContainingType(typeof(Program));
                    _.WithDefaultConventions();
                });

                config.Populate(services);
            });

            var serviceProvider = container.GetInstance<IServiceProvider>();

            serviceProvider.GetService<ILoggerFactory>().AddConsole(LogLevel.Debug);

            var logger = serviceProvider.GetService<ILoggerFactory>().CreateLogger<Program>();
            logger.LogInformation("Start application.");

            var bar = serviceProvider.GetService<IBarService>();
            bar.DoSomeRealWork();

            logger.LogInformation("All done!");
            Console.Read();
        }
    }
}

程式碼解釋

  • 這裡我們例項化了一個StructureMap的服務容器Container, 並在其Configure方法中配置了介面類及其實現類的自動搜尋。這裡使用的是一種約定,介面類必須以字母“I”開頭, 實現類的名字和介面類只相差一個字母“I”, 例IFooService, FooService, IBarService, BarService
  • 後續程式碼和前一個例子基本一樣。雖然看起來程式碼多了很多,但是實際上這種使用約定的注入方式非常強力,可以省去很多手動配置的程式碼。

最終效果

執行程式,程式碼和之前的效果一樣

info: DIInConsoleApp.Program[0]
      Start application.
info: DIInConsoleApp.FooService[0]
      Doing the thing 0
info: DIInConsoleApp.FooService[0]
      Doing the thing 1
info: DIInConsoleApp.FooService[0]
      Doing the thing 2
info: DIInConsoleApp.FooService[0]
      Doing the thing 3
info: DIInConsoleApp.FooService[0]
      Doing the thing 4
info: DIInConsoleApp.FooService[0]
      Doing the thing 5
info: DIInConsoleApp.FooService[0]
      Doing the thing 6
info: DIInConsoleApp.FooService[0]
      Doing the thing 7
info: DIInConsoleApp.FooService[0]
      Doing the thing 8
info: DIInConsoleApp.FooService[0]
      Doing the thing 9
info: DIInConsoleApp.Program[0]
      All done!

本篇原始碼