【asp.net core 系列】14 .net core 中的IOC

月影西下發表於2020-06-28

0.前言

通過前面幾篇,我們瞭解到瞭如何實現專案的基本架構:資料來源、路由設定、加密以及身份驗證。那麼在實現的時候,我們還會遇到這樣的一個問題:當我們業務類和資料來源越來越多的時候,我們無法通過普通的構造物件的方法為每個例項進行賦值。同時,傳統意義上的賦值遇到底層切換或者其他修改的時候,就需要修改大量的程式碼,對改變不友好。為了改變這種現狀,我們基於面向介面程式設計,然後使用一些DI功能和IOC框架。

1. IOC和DI

先來給大家解釋幾個概念,IOC全稱Inversion of Control,翻譯過來就是控制反轉,是物件導向程式設計的一種設計原則,用來降低程式碼之間的耦合度。所謂的控制反轉簡單來講就是將類中屬性或者其他引數的初始化交給其他方處理,而不是直接使用建構函式。

public class Demo1
{
}

public class Demo2
{
	public Demo1 demo;
}

對於以上簡單示例程式碼中,在Demo2類中持有了一個Demo1的例項。如果按照之前的情況來講,我們會通過以下方法為demo賦值:

// 方法一
public Demo1 demo = new Demo1();
// 方法二
public Demo2()
{
    demo = new Demo1();
}

這時候,如果Demo1變成下面的樣子:

public class Demo1
{
    public Demo1(Demo3 demo3)
    {
        // 隱藏
    }
}
public class Demo3
{
}

那麼,如果Demo2 沒有持有一個Demo3的例項物件,這時候建立Demo1的時候就需要額外構造一個Demo3。如果Demo3需要持有另外一個類的物件,那麼Demo2中就需要多建立一個物件。最後就會發現這樣就陷入了一個構造“地獄”(我發明的詞,指這種為了一個物件卻得構造一大堆其他型別的物件)。

實際上,對於Demo2並不關心Demo1的例項物件是如何獲取的,甚至都不關心它是不是Demo1的子類或者介面實現類。我在示例中使用了類,但這裡可以同步替換成Interface,替換之後,Demo2在呼叫Demo1的時候,還需要知道Demo1有實現類,以及實現類的資訊。

為了解決這個問題,一些高明的程式設計師們提出了將物件的建立這一過程交給第三方去操作,而不是呼叫類來建立。於是乎,上述程式碼就變成了:

public class Demo2
{
    public Demo1 Demo {get;set;}
    public Demo2(Demo1 demo)
    {
        Demo = demo;
    }
}

似乎並沒有什麼變化?對於Demo2來說,Demo2從此不再負責Demo1的建立,這個步驟交由Demo2的呼叫方去建立,Demo2從此從負責維護Demo1這個物件的大麻煩中解脫了。

但實際上構造地獄的問題還是沒有解決,只不過是通過IOC的設計將這一步後移了。這時候,那些大神們想了想,不如開發一個框架這些實體物件吧。所以就出現了很多IOC框架:AutoFac、Sping.net、Unity等。

說到IOC就不得不提一下DI(Dependency Injection)依賴注入。所謂的依賴注入就是屬性對應例項通過建構函式或者使用屬性由第三方進行賦值。也就是最後Demo2的示例程式碼中的寫法。

早期IOC和DI是指一種技術,後來開始確定這是不同的描述。IOC描述的是一種設計模式,而DI是一種行為。

2. 使用asp.net core的預設IOC

在之前的ASP.NET 框架中,微軟並沒有提供預設的IOC支援。在最新的asp.net core中微軟提供了一套IOC支援,該支援在名稱空間:

Microsoft.Extensions.DependencyInjection

裡,在程式碼中引用即可。

主要通過以下幾組方法實現:

public static IServiceCollection AddScoped<TService>(this IServiceCollection services) where TService : class;
public static IServiceCollection AddSingleton<TService>(this IServiceCollection services) where TService : class;
public static IServiceCollection AddTransient<TService>(this IServiceCollection services) where TService : class;

這裡只列出了這三組方法的一種過載版本。

這三組方法分別代表三種生命週期:

  • AddScored 表示物件的生命週期為整個Request請求
  • AddTransient 表示每次從服務容器進行請求時建立的,適合輕量級、 無狀態的服務
  • AddSingleton 表示該物件在第一次從服務容器請求後獲取,之後就不會再次初始化了

這裡每組方法只介紹了一個版本,但實際上每個方法都有以下幾個版本:

public static IServiceCollection AddXXX<TService>(this IServiceCollection services) where TService : class;
public static IServiceCollection AddXXX(this IServiceCollection services, Type serviceType, Type implementationType);
public static IServiceCollection AddXXX(this IServiceCollection services, Type serviceType, Func<IServiceProvider, object> implementationFactory);
public static IServiceCollection AddXXX<TService, TImplementation>(this IServiceCollection services)
            where TService : class
            where TImplementation : class, TService;
public static IServiceCollection AddXXX(this IServiceCollection services, Type serviceType);
public static IServiceCollection AddXXX<TService>(this IServiceCollection services, Func<IServiceProvider, TService> implementationFactory) where TService : class;
public static IServiceCollection AddXXX<TService, TImplementation>(this IServiceCollection services, Func<IServiceProvider, TImplementation> implementationFactory)
            where TService : class
            where TImplementation : class, TService;

其中:implementationFactory 表示通過一個Provider實現TService/TImplementation 的工廠方法。當方法指定了泛型的時候,會自動依據泛型引數獲取要注入的型別資訊,如果沒有使用泛型則必須手動傳入引數型別。

asp.net core如果使用依賴注入的話,需要在Startup方法中設定,具體內容可以參照以下:

public void ConfigureServices(IServiceCollection services)
{
    //省略其他程式碼
    services.AddScoped<ISysUserAuthRepository,SysUserAuthRepository>();
}

asp.net core 為DbContext提供了不同的IOC支援,AddDbContext:

public static IServiceCollection AddDbContext<TContext>(
      this IServiceCollection serviceCollection,
      Action<DbContextOptionsBuilder> optionsAction = null,
      ServiceLifetime contextLifetime = ServiceLifetime.Scoped,
      ServiceLifetime optionsLifetime = ServiceLifetime.Scoped)
      where TContext : DbContext;

使用方法如下:

services.AddDbContext<DefaultContext>();

3. AutoFac 使用

理論上,asp.net core的IOC已經足夠好了,但是依舊原諒我的貪婪。如果有二三百個業務類需要我來設定的話,我寧願不使用IOC。因為那配置起來就是一場極其痛苦的過程。不過,可喜可賀的是AutoFac可以讓我免收這部分的困擾。

這裡簡單介紹一下如何使用AutoFac作為IOC管理:

cd Web  # 切換目錄到Web專案
dotnet package add Autofac.Extensions.DependencyInjection # 新增 AutoFac的引用

因為asp.net core 版本3更改了一些邏輯,AutoFac的引用方式發生了改變,現在不介紹之前版本的內容,以3為主。使用AutoFac需要先在 Program類裡設定以下程式碼:

public static IHostBuilder CreateHostBuilder(string[] args) =>
    		Host.CreateDefaultBuilder(args)
    		.UseServiceProviderFactory(new AutofacServiceProviderFactory()) // 新增這行程式碼
    		.ConfigureWebHostDefaults(webBuilder =>
			{
                webBuilder.UseStartup<Startup>();
            });

在Program類裡啟用AutoFac的一個Service提供工廠類。然後在Startup類裡新增如下方法:

public void ConfigureContainer(ContainerBuilder builder)
{
    builder.RegisterType<DefaultContext>().As<DbContext>()
                .WithParameter("connectStr","Data Source=./demo.db")
                .InstancePerLifetimeScope();
            

    builder.RegisterAssemblyTypes(Assembly.Load("Web"))
        .Where(t => t.BaseType.FullName.Contains("Filter"))
        .AsSelf();

    builder.RegisterAssemblyTypes(Assembly.Load("Domain"),
                    Assembly.Load("Domain.Implements"), Assembly.Load("Service"), Assembly.Load("Service.Implements"))
                .AsSelf()
                .AsImplementedInterfaces()
                .InstancePerLifetimeScope()
                .PropertiesAutowired();
}

修改:

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllersWithViews(options =>
            {
                options.Filters.Add<UnitOfWorkFilterAttribute>();
            }).AddControllersAsServices();// 這行新增
    // 省略其他
}

4. 總結

這一篇簡單介紹瞭如何在Asp.net Core中啟用IOC支援,並提供了兩種方式,可以說是各有優劣。小夥伴們根據自己需要選擇。後續會為大家詳細深入AutoFac之類IOC框架的核心祕密。

更多內容煩請關注我的部落格《高先生小屋》

file

相關文章