1.寫在前面
相信大家對IOC和DI都耳熟能詳,它們在項目里面帶來的便利大家也都知道,微軟新出的.NetCore也大量采用了這種手法。
如今.NetCore也是大勢所趨了,基本上以.Net為技術主導的公司都在向.NetCore轉型了,我也一直在想抽時間寫幾篇.NetCore的文章,可無奈最近的項目實在太趕,也沒時間寫什么文章。
但今天這篇文章不是專門講.NetCore的。
算了,廢話不多說,開始今天的主題吧。
本篇文章主要講解Autofac的基本使用和高級用法,以及可能會踩到的一些坑。
2.基本概念
相信大家都知道IOC、DIP和DI是什么,用倒是網上抄點代碼過來,項目就能跑起來了,但真要你講出個花樣,估計還是有點懸吧,這也是找工作面試時候經常被問起的。
控制反轉
誰控制誰?
IoC/DI容器控制應用主程序。
控制什么?
IoC/DI容器控制對象本身的創建、實例化;控制對象之間的依賴關系。
何謂反轉(對應於正向)?
因為現在應用程序不能主動去創建對象了,而是被動等待對象容器給它注入它所需要的資源,所以稱之為反轉。
哪些方面反轉了?
1.創建對象
2.程序獲取資源的方式反了
為何需要反轉?
1.引入IoC/DI容器過后,體系更為松散,而且管理和維護以及項目升級更有序;
2.類之間真正實現了解耦
依賴
什么是依賴(按名稱理解、按動詞理解)?
依賴(按名稱理解):依賴關系;依賴(按動詞理解):依賴的動作
誰依賴於誰?
應用程序依賴於IoC/DI容器
為什么需要依賴?
因為發生了反轉,應用程序依賴的資源都是IoC/DI容器里面
依賴什么東西?
應用程序依賴於IoC/DI容器為它注入所需要的資源。(比如:依賴關系)
注入
誰注入於誰?
IoC/DI容器注入於應用程序。
注入什么東西?
注入應用程序需要的對象,比如依賴關系。
為何要注入?
因為程序要正常運行需要訪問這些對象。
IOC(控制反轉Inversion of Control)
控制反轉(Inversion of Control)就是使用對象容器反過來控制應用程序所需要的外部資源,這樣的一種程序開發思想,調用者不再創建被調用者的實例,由IOC框架實現(容器創建)所以稱為控制反轉;創建對象和對象非托管資源的釋放都由外部容器去完成,實現項目層與層之間的解耦的一種設計思想。
DI(依賴注入)和DIP(依賴倒置原則)
相信很多人還分不清楚DI和DIP這兩個詞,甚至認為它們就是同一個詞。
依賴倒置原則(Dependency Inversion Principle)為我們提供了降低模塊間耦合度的一種思路,而依賴注入(Dependency Injection)是一種具體的實施方法,容器創建好實例后再注入調用者稱為依賴注入,就是應用程序依賴IOC容器來注入所需要的外部資源,這樣一種程序的開發思想。
能做什么(What)?松散耦合對象,解耦項目架構層。
怎么做(How)?使用Autofac/Unity/Spring等框架類庫,里面有實現好了的IoC/DI容器。
用在什么地方(Where)?凡是程序里面需要使用外部資源的情況,比如創建對象,都可以考慮使用IoC/DI容器。
DI和IOC是同一概念嗎?
肯定不是同一概念啊,但它們兩個描述的是同一件事件,從不同的角度來說:IOC是從對象容器的角度;DI是從應用程序的角度。
控制反轉的描述:對象容器反過來控制應用程序,控制應用程序鎖所需要的一些對象,比如DbContext。
依賴注入的描述:應用程序依賴對象容器,依賴它注入所需要的外部資源。
對IoC的理解:
a. 應用程序無需主動new對象,而是描述對象應該如何被創建(構造方法、屬性、方法參數等)。
b. 應用程序不需要主動裝配對象之間的依賴關系,而是描述需要哪個服務,IoC容器會幫你裝配,被動接受裝配。
c. 主動變被動,是一種讓服務消費者不直接依賴於服務提供者的組件設計方式,是一種減少類與類之間依賴的設計原則。
3.Autofac/Unity簡介
Autofac是.NET領域最為流行的IOC框架之一,傳說是速度最快的一個,而今微軟也很青睞的一個輕量高效的IOC框架,簡單易上手且讓人着迷;
Unity是微軟官方出品的IOC框架,用法和Autofac大致差不多。
4.基本使用
通過nuget引入autofac;
准備幾個實例對象:
1
2
3
4
5
6
7
8
9
10
11
12
|
public
class
Doge
{
public
void
SayHello()
{
Console.WriteLine(
"我是小狗,汪汪汪~"
);
}
}
public
class
Person
{
public
string
Name {
get
;
set
; }
public
int
Age {
get
;
set
; }
}
|
我們傳統的做法當然是直接new啦,但現在有了IOC容器,怎么還能那樣做呢!
接下來准備IOC容器,通過IOC容器來實例化對象;
1
2
3
4
5
|
var builder =
new
ContainerBuilder();
//准備容器
builder.RegisterType<Doge>();
//注冊對象
var container = builder.Build();
//創建容器完畢
var dog = container.Resolve<Doge>();
//通過IOC容器創建對象
dog.SayHello();
|
還可以直接實例注入:
1
|
builder.RegisterInstance(
new
Doge());
//實例注入
|
單例托管:
1
|
builder.RegisterInstance(Singleton.GetInstance()).ExternallyOwned();
//將單例對象托管到IOC容器
|
還可以Lambda表達式注入:
1
2
|
builder.Register(c =>
new
Person() { Name =
"張三"
, Age = 20 });
//Lambda表達式創建
Person p = container.Resolve<Person>();
|
還可以注入泛型類:
1
2
|
builder.RegisterGeneric(
typeof
(List<>));
List<
string
> list = container.Resolve<List<
string
>>();
|
你卻說搞這么多過場,就為了創建一個對象?!咱不着急,接下來的才是重頭戲
5.以接口方式注入
接着剛才的例子,添加個接口IAnimal,讓Doge來實現它;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
public
interface
IAnimal
{
void
SayHello();
}
public
class
Doge : IAnimal
{
public
void
SayHello()
{
Console.WriteLine(
"我是小狗,汪汪汪~"
);
}
}
public
class
Cat : IAnimal
{
public
void
SayHello()
{
Console.WriteLine(
"我是小貓,喵喵喵~"
);
}
}
public
class
Pig : IAnimal
{
public
void
SayHello()
{
Console.WriteLine(
"我是小豬,呼呼呼~"
);
}
}
|
然后IOC注冊對象的方式改變為:
1
2
3
4
5
|
var builder =
new
ContainerBuilder();
//准備容器
builder.RegisterType<Doge>().As<IAnimal>();
//映射對象
var container = builder.Build();
//創建容器完畢
var dog = container.Resolve<IAnimal>();
//通過IOC容器創建對象
dog.SayHello();
|
如果一個類型被多次注冊,以最后注冊的為准。通過使用PreserveExistingDefaults() 修飾符,可以指定某個注冊為非默認值。
1
2
3
4
5
|
builder.RegisterType<Doge>().As<IAnimal>();
//映射對象
builder.RegisterType<Cat>().As<IAnimal>().PreserveExistingDefaults();
//指定Cat為非默認值
var dog = container.Resolve<IAnimal>();
//通過IOC容器創建對象
dog.SayHello();
|
如果一個接口類被多個實例對象實現,可以進行命名,注入的時候使用名字進行區分
1
2
3
4
5
|
builder.RegisterType<Doge>().Named<IAnimal>(
"doge"
);
//映射對象
builder.RegisterType<Pig>().Named<IAnimal>(
"pig"
);
//映射對象
var dog = container.ResolveNamed<IAnimal>(
"pig"
);
//通過IOC容器創建對象
dog.SayHello();
|
ResolveNamed()只是Resolve()的簡單重載,指定名字的服務其實是指定鍵的服務的簡單版本。有Named的方式很方便,但是只支持字符串,但有時候我們可能需要通過其他類型作鍵,比如枚舉。
先聲明一個枚舉類:
1
2
3
4
|
public
enum
AnumalType
{
Doge, Pig, Cat
}
|
然后將上面的代碼改造成:
1
2
3
4
5
|
builder.RegisterType<Doge>().Keyed<IAnimal>(AnumalType.Doge);
//映射對象
builder.RegisterType<Pig>().Keyed<IAnimal>(AnumalType.Pig);
//映射對象
var dog = container.ResolveKeyed<IAnimal>(AnumalType.Cat);
//通過IOC容器創建對象
dog.SayHello();
|
不過這種方式是不推薦使用的,因為autofac容器會被當作Service Locator使用,推薦的做法是通過索引類型來實現,Autofac.Features.Indexed.IIndex<K,V>是Autofac自動實現的一個關聯類型。使用IIndex<K,V>作為參數的構造函數從基於鍵的服務中選擇需要的實現:
1
2
3
|
var animal = container.Resolve<IIndex<AnumalType,IAnimal>>();
var cat = animal[AnumalType.Cat];
cat.SayHello();
|
IIndex中第一個泛型參數要跟注冊時一致,在例子中是AnimalType枚舉。其他兩種注冊方法沒有這樣的索引查找功能,這也是為什么設計者推薦Keyed注冊的原因之一。
6.自動裝配
從容器中的可用對象中選擇一個構造方法來創建對象,這個過程叫做自動裝配。它是通過反射實現的,所以實際上容器創造對象的行為比較適合用在配置環境中。
改造Person類:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
public
class
Person
{
public
Person() { }
public
Person(
string
name)
{
Name = name;
}
public
Person(
string
name,
int
age) :
this
(name)
{
Age = age;
}
public
string
Name {
get
;
set
; }
public
int
Age {
get
;
set
; }
}
|
注入時指定構造函數
Autofac默認從容器中選擇參數最多的構造函數。如果想要選擇一個不同的構造函數,就需要在注冊的時候就指定它:
1
|
builder.RegisterType<Person>().UsingConstructor(
typeof
(
string
));
|
這種寫法將指定調用Person(string)構造函數,如該構造函數不存在則報錯。
額外的構造函數參數:
有兩種方式可以添加額外的構造函數參數,在注冊的時候和在檢索的時候。在使用自動裝配實例的時候這兩種都會用到。
注冊時添加參數,使用WithParameters()方法在每一次創建對象的時候將組件和參數關聯起來。
1
2
|
List<NamedParameter> pars =
new
List<NamedParameter>() {
new
NamedParameter(
"Age"
, 20),
new
NamedParameter(
"Name"
,
"張三"
) };
builder.RegisterType<Person>().WithParameters(pars);
|
在檢索階段添加參數:
在Resolve()的時候提供的參數會覆蓋所有名字相同的參數,在注冊階段提供的參數會覆蓋容器中所有可能的服務。
7.MVC控制器和WebAPI控制器注入
當然是web MVC項目了,要在MVC或WebApi項目中用autofac,當然需要以下nuget包了,
准備幾個Repository和Service;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
|
public
class
Person
{
public
int
Id {
get
;
set
; }
public
string
Name {
get
;
set
; }
public
int
Age {
get
;
set
; }
}
public
interface
IRepository
{
List<Person> GetPersons();
}
public
class
RepositoryBase : IRepository
{
public
List<Person> Persons {
get
;
set
; } =
new
List<Person>();
public
RepositoryBase()
{
for
(
int
i = 0; i < 10; i++)
{
Persons.Add(
new
Person()
{
Id = i + 1,
Name =
"張三"
+ i,
Age = 10 + i * 2
});
}
}
public
List<Person> GetPersons()
{
return
Persons;
}
}
public
class
PersonRepository : RepositoryBase
{
}
public
interface
IService
{
List<Person> GetPersons();
}
public
class
ServiceBase : IService
{
public
IRepository Repository {
get
;
set
; }
public
ServiceBase(IRepository repository)
{
Repository = repository;
}
public
List<Person> GetPersons()
{
return
Repository.GetPersons();
}
}
public
class
PersonService : ServiceBase
{
public
PersonService(IRepository repository) :
base
(repository)
{
}
}
|
網站啟動時注冊容器,在Global的Application_Start方法中注冊IOC容器;
1
2
3
4
5
6
7
8
9
10
11
12
|
//注冊IOC容器
var builder =
new
ContainerBuilder();
//告訴autofac將來要創建的控制器類存放在哪個程序集
builder.RegisterControllers(Assembly.GetExecutingAssembly());
//注冊MVC控制器
builder.RegisterApiControllers(Assembly.GetExecutingAssembly());
//注冊WebAPI控制器
//注冊Repository和Service
builder.RegisterType<PersonRepository>().As<IRepository>().InstancePerDependency();
builder.RegisterType<PersonService>().As<IService>().InstancePerDependency();
var container = builder.Build();
//將當前容器交給MVC底層,保證容器不被銷毀,控制器由autofac來創建
GlobalConfiguration.Configuration.DependencyResolver =
new
AutofacWebApiDependencyResolver(container);
//先給WebAPI注冊
DependencyResolver.SetResolver(
new
AutofacDependencyResolver(container));
//再給MVC注冊
|
InstancePerDependency:為你注入的這個服務的生命周期.(注:生命周期我們后面講)
現在就可以在控制器中通過構造函數注入對象了
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
public
class
HomeController : Controller
{
public
IService Service {
get
;
set
; }
public
HomeController(IService service)
{
Service = service;
}
// GET: Home
public
ActionResult Index()
{
var ps = Service.GetPersons();
return
Json(ps, JsonRequestBehavior.AllowGet);
}
}
|
8.屬性注入
有時候我們需要對對象的屬性進行注入,比如EF上下文對象DbContext,很簡單,兩句話搞定;
我們先來模擬一個DbContext:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
public
class
DataContext
{
public
ICollection<Person> Persons {
get
;
set
; } =
new
List<Person>();
public
DataContext()
{
for
(
int
i = 0; i < 10; i++)
{
Persons.Add(
new
Person()
{
Id = i + 1,
Name =
"張三"
+ i,
Age = 10 + i * 2
});
}
}
}
|
在IOC容器中注入;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
//注冊IOC容器
var builder =
new
ContainerBuilder();
//告訴autofac將來要創建的控制器類存放在哪個程序集
builder.RegisterControllers(Assembly.GetExecutingAssembly()).PropertiesAutowired(PropertyWiringOptions.AllowCircularDependencies).InstancePerDependency();
//注冊MVC控制器
builder.RegisterApiControllers(Assembly.GetExecutingAssembly()).PropertiesAutowired(PropertyWiringOptions.AllowCircularDependencies).InstancePerDependency();
//注冊WebAPI控制器
builder.RegisterFilterProvider();
//特性注入
builder.RegisterType<DataContext>().InstancePerRequest();
//注冊Repository和Service
builder.RegisterType<PersonRepository>().As<IRepository>().PropertiesAutowired(PropertyWiringOptions.AllowCircularDependencies).InstancePerDependency();
builder.RegisterType<PersonService>().As<IService>().PropertiesAutowired(PropertyWiringOptions.AllowCircularDependencies).InstancePerDependency();
var container = builder.Build();
//將當前容器交給MVC底層,保證容器不被銷毀,控制器由autofac來創建
GlobalConfiguration.Configuration.DependencyResolver =
new
AutofacWebApiDependencyResolver(container);
//先給WebAPI注冊
DependencyResolver.SetResolver(
new
AutofacDependencyResolver(container));
//再給MVC注冊
|
PropertiesAutowired(PropertyWiringOptions.AllowCircularDependencies)表示屬性注入,當實例對象存在有被IOC容器對象托管的時候,IOC容器會自動給屬性實例化,Controller則可以不通過構造函數注入,直接屬性注入;
1
2
3
4
5
6
7
8
|
public
class
HomeController : Controller
{
public
DataContext DataContext {
get
;
set
; }
public
ActionResult GetPersons()
{
return
Json(DataContext.Persons, JsonRequestBehavior.AllowGet);
}
}
|
關於參數PropertyWiringOptions的三個選項:
PropertyWiringOptions.None:默認的注入方式,當發現注入的對象有屬性依賴的時候,會注入一個新的對象;
PropertyWiringOptions.AllowCircularDependencies:循環依賴注入方式,當發現注入的對象有循環依賴關系的時候,會循環注入;
PropertyWiringOptions.PreserveSetValues:保留預設值注入,當注入時發現屬性已經有初始值,會自動忽略。
同樣,我們也可以改造剛才的Repository,通過屬性注入DataContext;
1
2
3
4
5
6
7
8
|
public
class
RepositoryBase : IRepository
{
public
DataContext DataContext {
get
;
set
; }
public
List<Person> GetPersons()
{
return
DataContext.Persons.ToList();
}
}
|
上面是自動注入屬性,有時候我們還可以手動去注入屬性:
1
|
builder.RegisterType<Person>().OnActivated(e => e.Instance.Name =
"李四"
);
|
如果你預先知道屬性的名字和值,你還可以使用:
1
|
builder.RegisterType<Person>().WithProperty(
"Name"
,
"李四"
);
|
9.方法注入
可以實現方法注入的方式有兩種。
1、使用Activator
如果你使用委托來激活,只要調用這個方法在激活中:
1
2
3
4
5
6
|
builder.Register(c =>
{
var result =
new
Person();
result.SayHello(
"my name is van"
);
return
result;
});
|
注意,使用這種方法,Person類里必須要有這個方法:
1
2
3
4
|
public
void
SayHello(
string
hello)
{
Console.WriteLine(hello);
}
|
2、使用Activating Handler
如果你使用另外一種激活,比如反射激活,創建激活的事件接口OnActivating,這種方式僅需一行代碼:
1
|
builder.RegisterType<Person>().OnActivating(e => e.Instance.SayHello(
"my name is van!"
));
|
10. Attribute注入
有時候我們還需要注入一些Attribute特性,在使用MVC的時候,肯定會用到特性,比如ActionFilter,肯定會有一些自己定義的特性,我們想在里面做一些邏輯操作,比如用戶登錄狀態檢查,我們就需要在ActionFilter里面實例化Service對象,那么這些特性里面要用到相關的服務,該怎么注入呢?
很簡單,我們只需要在IOC容器構建時再加上builder.RegisterFilterProvider()即可;
1
2
3
4
5
6
7
8
9
10
11
12
13
|
//注冊IOC容器
var builder =
new
ContainerBuilder();
//告訴autofac將來要創建的控制器類存放在哪個程序集
builder.RegisterControllers(Assembly.GetExecutingAssembly());
//注冊MVC控制器
builder.RegisterApiControllers(Assembly.GetExecutingAssembly());
//注冊WebAPI控制器
builder.RegisterFilterProvider();
//特性注入
//注冊Repository和Service
builder.RegisterType<PersonRepository>().As<IRepository>().InstancePerDependency();
builder.RegisterType<PersonService>().As<IService>().InstancePerDependency();
var container = builder.Build();
//將當前容器交給MVC底層,保證容器不被銷毀,控制器由autofac來創建
GlobalConfiguration.Configuration.DependencyResolver =
new
AutofacWebApiDependencyResolver(container);
//先給WebAPI注冊
DependencyResolver.SetResolver(
new
AutofacDependencyResolver(container));
//再給MVC注冊
|
然后就可以直接通過屬性的方式注入到ActionFilter中,是的,你沒看錯,就只需要這一行代碼就可以了,特性里面就可以取到想要的服務了;
1
2
3
4
5
6
7
8
9
10
11
|
public
class
MyActionFilterAttribute : ActionFilterAttribute
{
public
IService Service {
get
;
set
; }
/// <summary>在執行操作方法之前由 ASP.NET MVC 框架調用。</summary>
/// <param name="filterContext">篩選器上下文。</param>
public
override
void
OnActionExecuting(ActionExecutingContext filterContext)
{
var ps = Service.GetPersons();
base
.OnActionExecuting(filterContext);
}
}
|
11.Hangfire注入
如果我們項目中還用到了hangfire這樣的分布式任務調度框架,我們也可以通過autofac來進行對象的依賴注入;
首先我們引入hangfire的一些基礎包,並配置好hangfire;
當然,還要引入hangfire的autofac支持庫:
Startup.cs類:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
public
class
Startup
{
public
void
Configuration(IAppBuilder app)
{
//配置任務持久化到內存
GlobalConfiguration.Configuration.UseMemoryStorage();
//啟用dashboard
app.UseHangfireServer(
new
BackgroundJobServerOptions { WorkerCount = 10 });
app.UseHangfireDashboard(
"/taskcenter"
,
new
DashboardOptions()
{
Authorization =
new
[] {
new
MyRestrictiveAuthorizationFilter() }
});
//注冊dashboard的路由地址
}
}
public
class
MyRestrictiveAuthorizationFilter : IDashboardAuthorizationFilter
{
//public RedisHelper RedisHelper { get; set; } = new RedisHelper();
public
bool
Authorize(DashboardContext context)
{
return
true
;
}
}
|
Global.Application_Start():
1
2
3
4
5
6
7
8
9
|
Hangfire.GlobalConfiguration.Configuration.UseMemoryStorage();
Hangfire.GlobalConfiguration.Configuration.UseAutofacActivator(container);
//注冊IOC容器
Server =
new
BackgroundJobServer(
new
BackgroundJobServerOptions
{
ServerName = $
"{Environment.MachineName}"
,
//服務器名稱
SchedulePollingInterval = TimeSpan.FromSeconds(1),
ServerCheckInterval = TimeSpan.FromSeconds(1),
WorkerCount = Environment.ProcessorCount * 2,
});
|
配置hangfire后台任務;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
public
interface
IHangfireBackJob
{
DataContext DataContext {
get
;
set
; }
void
UpdatePersons();
}
public
class
HangfireBackJob : IHangfireBackJob
{
public
DataContext DataContext {
get
;
set
; }
public
void
UpdatePersons()
{
DataContext.Persons.Clear();
for
(
int
i = 0; i < 10; i++)
{
DataContext.Persons.Add(
new
Person()
{
Name =
"李四"
+ i,
Age = 20 + i * 2,
Id = i + 1
});
}
}
}
|
配置IOC容器:
1
2
3
|
builder.RegisterType<DataContext>().InstancePerBackgroundJob(MatchingScopeLifetimeTags.RequestLifetimeScopeTag, AutofacJobActivator.LifetimeScopeTag);
//指定生命周期為每個后台任務依賴,並且每次http請求內單例
builder.RegisterType<BackgroundJobClient>().SingleInstance();
//指定生命周期為單例
builder.RegisterType<HangfireBackJob>().As<IHangfireBackJob>().PropertiesAutowired(PropertyWiringOptions.AllowCircularDependencies).InstancePerDependency();
|
創建任務:
1
2
3
4
5
6
7
|
public
ActionResult CreateJob()
{
var type =
typeof
(IHangfireBackJob);
var job =
new
Job(type, type.GetMethod(
"UpdatePersons"
));
BackgroundJobClient.Create(job,
new
EnqueuedState());
return
Content(
"ok"
);
}
|
12. SignalR注入
如果我們項目中還用到了SignalR這樣的socket通信框架,我們也可以通過autofac來進行對象的依賴注入;按照剛才hangfire的套路,我們先引入SignalR的一些基礎包,並配置好SignalR;當然,還要引入SignalR的autofac支持庫:
注冊容器的方式和上面的例子都有些不同了,SignalR的IOC容器注入必須在Startup里面注入,不能在Global中注入,因為SignalR是Owin項目,OWIN 集成常見錯誤為使用GlobalHost。OWIN中配置你會抓狂. OWIN集成中,任何地方你都不能引用 。當年博主我也不太清楚,反正當時也踩了這一大坑。至於hangfire也是owin項目,為什么可以在Global里面注入,因為hangfire不是用GlobalHost去注入的,而是GlobalConfiguration。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
public
class
Startup
{
public
void
Configuration(IAppBuilder app)
{
var builder =
new
ContainerBuilder();
var config =
new
HubConfiguration();
builder.RegisterHubs(Assembly.GetExecutingAssembly()).PropertiesAutowired(PropertyWiringOptions.AllowCircularDependencies);
builder.RegisterType<DataContext>().InstancePerLifetimeScope();
var container = builder.Build();
config.Resolver =
new
AutofacDependencyResolver(container);
app.UseAutofacMiddleware(container);
app.MapSignalR(
"/signalr"
, config);
}
}
|
然后,hub就可以用屬性注入,構造函數注入等方式了;
1
2
3
4
5
6
7
8
9
|
[HubName(
"myhub"
)]
//聲明hub的顯式名字
public
class
MyHub : Hub
{
public
DataContext DataContext {
get
;
set
; }
public
void
Send()
{
Clients.All.hello(DataContext.Persons.ToJsonString());
}
}
|
前端代碼:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
<!DOCTYPE html>
<
html
>
<
head
>
<
meta
name
=
"viewport"
content
=
"width=device-width"
/>
<
title
>WebSocket</
title
>
<
script
src
=
"~/Scripts/jquery-3.3.1.js"
></
script
>
<
script
src
=
"~/Scripts/jquery.signalR-2.2.3.js"
></
script
>
<
script
src
=
"~/signalr/hubs"
></
script
>
<!--后端SignalR根據注冊的路由生成的js腳本-->
</
head
>
<
body
>
<
span
class
=
"msg"
></
span
>
</
body
>
</
html
>
<
script
>
$(function () {
//客戶端都以駝峰命名法使用
let hubProxy = $.connection["myhub"]; //hub代理對象
var $msg = $(".msg");
//注冊客戶端方法
hubProxy.client.hello = function (msg) {
$msg.text(msg);
}
//向服務端發數據
$.connection.hub.start().done(function () {
hubProxy.server.send();
});
});
</
script
>
|
注意:由於 SignalR 是內部構件,所以不支持SignalR每請求的生命周期依賴。
13. 注冊程序集
然而大多數時候我們的項目很多代碼是直接用代碼生成器生成的,像Repository和Service並非完全手寫的,並不想這么一個一個類的去builder.RegisterType,那多麻煩啊,所以autofac還提供了程序集批量注入的選項;一句話搞定:
1
|
builder.RegisterAssemblyTypes(Assembly.GetExecutingAssembly()).Where(t => t.Name.EndsWith(
"Repository"
) || t.Name.EndsWith(
"Service"
)).AsSelf().AsImplementedInterfaces().PropertiesAutowired(PropertyWiringOptions.PreserveSetValues).InstancePerDependency();
|
這里真的只有一句話!
14. Resolve的參數
當注冊或者檢索component的時候可以使用參數。
1、傳遞參數給Resolve
Resolve接受可變參數或IEnumerable<T>傳入多個值:
1
|
Person p = container.Resolve<Person>(
new
NamedParameter(
"name"
,
"王五"
),
new
NamedParameter(
"age"
, 22));
|
Person下必須添加如下構造函數:
1
2
3
4
5
|
public
Person(
string
name,
int
age) :
this
(name)
{
Name = name;
Age = age;
}
|
2、可用的參數類型
Autofac提供幾種不同的參數對應策略:
1
2
3
4
|
NamedParameter :像上面那樣對應的參數名字
TypedParameter:對應到參數的類型(必須是具體的類型)
ResolvedParameter:靈活的參數匹配
NamedParameter 和TypedParameter:只能提供常量參數
|
3、從表達式中使用參數
如果使用表達式注冊的方式,可以使用第二個可用的委托參數來獲得參數。
1
2
|
builder.Register((c, p) =>
new
Person(p.Named<
string
>(
"name"
), p.Named<
int
>(
"age"
)));
Person pp = container.Resolve<Person>(
new
NamedParameter(
"name"
,
"王五"
),
new
NamedParameter(
"age"
, 22));
|
15. 對象生命周期InstancePerDependency
對每一個依賴或每一次調用創建一個新的唯一的實例。也稱作瞬態或者工廠,使用PerDependency作用域,服務對於每次請求都會返回互補影響實例。在沒有指定其他參數的情況下,這也是默認的創建實例的方式。
官方文檔解釋:Configure the component so that every dependent component or call to Resolve() gets a new, unique instance (default.)
InstancePerLifetimeScope
在一個生命周期域中,每一個依賴或調用創建一個單一的共享的實例,且每一個不同的生命周期域,實例是唯一的,不共享的,也就是線程內唯一對象。
官方文檔解釋:Configure the component so that every dependent component or call to Resolve() within a single ILifetimeScope gets the same, shared instance. Dependent components in different lifetime scopes will get different instances.
InstancePerMatchingLifetimeScope
在一個做標識的生命周期域中,每一個依賴或調用創建一個單一的共享的實例。打了標識了的生命周期域中的子標識域中可以共享父級域中的實例。若在整個繼承層次中沒有找到打標識的生命周期域,則會拋出異常:DependencyResolutionException。
官方文檔解釋:Configure the component so that every dependent component or call to Resolve() within a ILifetimeScope tagged with any of the provided tags value gets the same, shared instance. Dependent components in lifetime scopes that are children of the tagged scope will share the parent's instance. If no appropriately tagged scope can be found in the hierarchy an DependencyResolutionException is thrown.
InstancePerOwned
在一個生命周期域中所擁有的實例創建的生命周期中,每一個依賴組件或調用Resolve()方法創建一個單一的共享的實例,並且子生命周期域共享父生命周期域中的實例。若在繼承層級中沒有發現合適的擁有子實例的生命周期域,則拋出異常:DependencyResolutionException。
官方文檔解釋:Configure the component so that every dependent component or call to Resolve() within a ILifetimeScope created by an owned instance gets the same, shared instance. Dependent components in lifetime scopes that are children of the owned instance scope will share the parent's instance. If no appropriate owned instance scope can be found in the hierarchy an DependencyResolutionException is thrown.
SingleInstance
每一次依賴組件或調用Resolve()方法都會得到一個相同的共享的實例。其實就是單例模式。
官方文檔解釋:Configure the component so that every dependent component or call to Resolve() gets the same, shared instance.
InstancePerRequest
在一次Http請求上下文中,共享一個組件實例。僅適用於asp.net mvc開發。
16. 需要Dispose的對象的注入
像RedisClient、DbContext這類對象需要用完之后被Dispose的,也很簡單;
改造成可Dispose的DataContext:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
public
class
DataContext : IDisposable
{
public
ICollection<Person> Persons {
get
;
set
; } =
new
List<Person>();
public
DataContext()
{
for
(
int
i = 0; i < 10; i++)
{
Persons.Add(
new
Person()
{
Id = i + 1,
Name =
"張三"
+ i,
Age = 10 + i * 2
});
}
}
/// <summary>執行與釋放或重置非托管資源關聯的應用程序定義的任務。</summary>
public
void
Dispose()
{
Persons =
null
;
}
}
|
然后在創建IOC容器的時候:
1
|
builder.RegisterType<DataContext>().OnRelease(db => db.Dispose());
|
給對象注冊OnRelease事件,每次使用完的時候會由IOC容器去釋放。
17.綁定事件
在對象生命周期的不同階段使用事件。Autofac暴露五個事件接口供實例的按如下順序調用
1
2
3
4
5
|
1)OnRegistered
2)OnPreparing
3)OnActivated
4)OnActivating
5)OnRelease
|
這些事件會在注冊的時候被訂閱,或者被附加到IComponentRegistration 的時候。
1
|
builder.RegisterType<Person>().OnRegistered(e => Console.WriteLine(
"在注冊的時候調用!"
)).OnPreparing(e => Console.WriteLine(
"在准備創建的時候調用!"
)).OnActivating(e => Console.WriteLine(
"在創建之前調用!"
)).OnActivated(e => Console.WriteLine(
"創建之后調用!"
)).OnRelease(e => Console.WriteLine(
"在釋放占用的資源之前調用!"
));
|
以上示例輸出如下:
OnActivating
組件被創建之前調用,在這里你可以:
1
2
3
|
1)將實例轉向另外一個或者使用代理封裝它
2)進行屬性注入
3)執行其他初始化工作
|
OnActivated
在component被完全創建的時候調用一次。在這個時候你可以執行程序級別的一些工作(這些工作依賴於對象被完全創建)-這種情況很罕見。
OnRelease
替代component的標准清理方法。實現了IDisposable 接口的標准清理方法(沒有標記為ExternallyOwned) 通過調用Dispose 方法。沒有實現IDisposable或者被標記為ExternallyOwned的清理方法是一個空函數-不執行任何操作。OnRelease 就是用來覆蓋默認的清理行為的。
18. .NetCore中使用Autofac
在 Starpup 中 配置 Autofac,注意的是要將 ConfigureServices 的返回類型從void類型 改成IServiceProvider,並 return new AutofacServiceProvider(ApplicationContainer); 官方解釋是,讓第三方容器接管Core的默認DI。
1
2
3
4
5
6
7
8
9
10
11
|
public
IServiceProvider ConfigureServices(IServiceCollection services)
{
services.AddMvc().AddControllersAsServices().AddViewComponentsAsServices().AddTagHelpersAsServices();
//將控制器以及視圖開啟屬性注入方式
var builder =
new
ContainerBuilder();
builder.RegisterType<DataContext>().InstancePerLifetimeScope();
builder.RegisterAssemblyTypes(Assembly.GetExecutingAssembly()).Where(t => t.Name.EndsWith(
"Repository"
) || t.Name.EndsWith(
"Service"
)).AsSelf().AsImplementedInterfaces().PropertiesAutowired(PropertyWiringOptions.PreserveSetValues).InstancePerDependency();
builder.RegisterAssemblyTypes(Assembly.GetExecutingAssembly()).Where(t => t.Name.EndsWith(
"Controller"
)).AsSelf().PropertiesAutowired().InstancePerDependency();
//注冊控制器為屬性注入,如果你想要控制器支持屬性注入,這句很重要
builder.Populate(services);
var container = builder.Build();
return
new
AutofacServiceProvider(container);
}
|