0.前言 通過前面幾篇,我們瞭解到瞭如何實現項目的基本架構:數據源、路由設置、加密以及身份驗證。那麼在實現的時候,我們還會遇到這樣的一個問題:當我們業務類和數據源越來越多的時候,我們無法通過普通的構造對象的方法為每個實例進行賦值。同時,傳統意義上的賦值遇到底層切換或者其他修改的時候,就需要修改大量的 ...
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框架的核心秘密。
更多內容煩請關註我的博客《高先生小屋》