相信你們對IOC和DI都耳熟能詳,它們在項目裏面帶來的便利你們也都知道,微軟新出的.NetCore也大量採用了這種手法。html
現在.NetCore也是大勢所趨了,基本上以.Net爲技術主導的公司都在向.NetCore轉型了,我也一直在想抽時間寫幾篇.NetCore的文章,可無奈最近的項目實在太趕,也沒時間寫什麼文章。前端
但今天這篇文章不是專門講.NetCore的。jquery
算了,廢話很少說,開始今天的主題吧。git
本篇文章主要講解Autofac的基本使用和高級用法,以及可能會踩到的一些坑。web
相信你們都知道IOC、DIP和DI是什麼,用卻是網上抄點代碼過來,項目就能跑起來了,但真要你講出個花樣,估計仍是有點懸吧,這也是找工做面試時候常常被問起的。面試
誰控制誰?c#
IoC/DI容器控制應用主程序。後端
控制什麼?bash
IoC/DI容器控制對象自己的建立、實例化;控制對象之間的依賴關係。服務器
何謂反轉(對應於正向)?
由於如今應用程序不能主動去建立對象了,而是被動等待對象容器給它注入它所須要的資源,因此稱之爲反轉。
哪些方面反轉了?
1.建立對象
2.程序獲取資源的方式反了
爲什麼須要反轉?
1.引入IoC/DI容器事後,體系更爲鬆散,並且管理和維護以及項目升級更有序;
2.類之間真正實現瞭解耦
什麼是依賴(按名稱理解、按動詞理解)?
依賴(按名稱理解):依賴關係;依賴(按動詞理解):依賴的動做
誰依賴於誰?
應用程序依賴於IoC/DI容器
爲何須要依賴?
由於發生了反轉,應用程序依賴的資源都是IoC/DI容器裏面
依賴什麼東西?
應用程序依賴於IoC/DI容器爲它注入所須要的資源。(好比:依賴關係)
誰注入於誰?
IoC/DI容器注入於應用程序。
注入什麼東西?
注入應用程序須要的對象,好比依賴關係。
爲什麼要注入?
由於程序要正常運行須要訪問這些對象。
控制反轉(Inversion of Control)就是使用對象容器反過來控制應用程序所須要的外部資源,這樣的一種程序開發思想,調用者再也不建立被調用者的實例,由IOC框架實現(容器建立)因此稱爲控制反轉;建立對象和對象非託管資源的釋放都由外部容器去完成,實現項目層與層之間的解耦的一種設計思想。
相信不少人還分不清楚DI和DIP這兩個詞,甚至認爲它們就是同一個詞。
依賴倒置原則(Dependency Inversion Principle)爲咱們提供了下降模塊間耦合度的一種思路,而依賴注入(Dependency Injection)是一種具體的實施方法,容器建立好實例後再注入調用者稱爲依賴注入,就是應用程序依賴IOC容器來注入所須要的外部資源,這樣一種程序的開發思想。
能作什麼(What)?鬆散耦合對象,解耦項目架構層。
怎麼作(How)?使用Autofac/Unity/Spring等框架類庫,裏面有實現好了的IoC/DI容器。
用在什麼地方(Where)?凡是程序裏面須要使用外部資源的狀況,好比建立對象,均可以考慮使用IoC/DI容器。
確定不是同一律唸啊,但它們兩個描述的是同一件事件,從不一樣的角度來講:IOC是從對象容器的角度;DI是從應用程序的角度。
控制反轉的描述:對象容器反過來控制應用程序,控制應用程序鎖所須要的一些對象,好比DbContext。
依賴注入的描述:應用程序依賴對象容器,依賴它注入所須要的外部資源。
a. 應用程序無需主動new對象,而是描述對象應該如何被建立(構造方法、屬性、方法參數等)。
b. 應用程序不須要主動裝配對象之間的依賴關係,而是描述須要哪一個服務,IoC容器會幫你裝配,被動接受裝配。
c. 主動變被動,是一種讓服務消費者不直接依賴於服務提供者的組件設計方式,是一種減小類與類之間依賴的設計原則。
Autofac是.NET領域最爲流行的IOC框架之一,傳說是速度最快的一個,而今微軟也很青睞的一個輕量高效的IOC框架,簡單易上手且讓人着迷;
Unity是微軟官方出品的IOC框架,用法和Autofac大體差很少。
經過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
>>();
|
你卻說搞這麼多過場,就爲了建立一個對象?!咱不着急,接下來的纔是重頭戲
接着剛纔的例子,添加個接口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註冊的緣由之一。
從容器中的可用對象中選擇一個構造方法來建立對象,這個過程叫作自動裝配。它是經過反射實現的,因此實際上容器創造對象的行爲比較適合用在配置環境中。
改造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()的時候提供的參數會覆蓋全部名字相同的參數,在註冊階段提供的參數會覆蓋容器中全部可能的服務。
固然是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);
}
}
|
有時候咱們須要對對象的屬性進行注入,好比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"
,
"李四"
);
|
能夠實現方法注入的方式有兩種。
若是你使用委託來激活,只要調用這個方法在激活中:
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);
}
|
若是你使用另一種激活,好比反射激活,建立激活的事件接口OnActivating,這種方式僅需一行代碼:
1
|
builder.RegisterType<Person>().OnActivating(e => e.Instance.SayHello(
"my name is van!"
));
|
有時候咱們還須要注入一些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);
}
}
|
若是咱們項目中還用到了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"
);
}
|
若是咱們項目中還用到了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每請求的生命週期依賴。
然而大多數時候咱們的項目不少代碼是直接用代碼生成器生成的,像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();
|
這裏真的只有一句話!
當註冊或者檢索component的時候可使用參數。
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;
}
|
Autofac提供幾種不一樣的參數對應策略:
1
2
3
4
|
NamedParameter :像上面那樣對應的參數名字
TypedParameter:對應到參數的類型(必須是具體的類型)
ResolvedParameter:靈活的參數匹配
NamedParameter 和TypedParameter:只能提供常量參數
|
若是使用表達式註冊的方式,可使用第二個可用的委託參數來得到參數。
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));
|
對每個依賴或每一次調用建立一個新的惟一的實例。也稱做瞬態或者工廠,使用PerDependency做用域,服務對於每次請求都會返回互補影響實例。在沒有指定其餘參數的狀況下,這也是默認的建立實例的方式。
官方文檔解釋:Configure the component so that every dependent component or call to Resolve() gets a new, unique instance (default.)
在一個生命週期域中,每個依賴或調用建立一個單一的共享的實例,且每個不一樣的生命週期域,實例是惟一的,不共享的,也就是線程內惟一對象。
官方文檔解釋: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.
在一個作標識的生命週期域中,每個依賴或調用建立一個單一的共享的實例。打了標識了的生命週期域中的子標識域中能夠共享父級域中的實例。若在整個繼承層次中沒有找到打標識的生命週期域,則會拋出異常: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.
在一個生命週期域中所擁有的實例建立的生命週期中,每個依賴組件或調用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.
每一次依賴組件或調用Resolve()方法都會獲得一個相同的共享的實例。其實就是單例模式。
官方文檔解釋:Configure the component so that every dependent component or call to Resolve() gets the same, shared instance.
在一次Http請求上下文中,共享一個組件實例。僅適用於asp.net mvc開發。
像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容器去釋放。
在對象生命週期的不一樣階段使用事件。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(
"在釋放佔用的資源以前調用!"
));
|
以上示例輸出以下:
組件被建立以前調用,在這裏你能夠:
1
2
3
|
1)將實例轉向另一個或者使用代理封裝它
2)進行屬性注入
3)執行其餘初始化工做
|
在component被徹底建立的時候調用一次。在這個時候你能夠執行程序級別的一些工做(這些工做依賴於對象被徹底建立)-這種狀況很罕見。
替代component的標準清理方法。實現了IDisposable 接口的標準清理方法(沒有標記爲ExternallyOwned) 經過調用Dispose 方法。沒有實現IDisposable或者被標記爲ExternallyOwned的清理方法是一個空函數-不執行任何操做。OnRelease 就是用來覆蓋默認的清理行爲的。
在 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);
}
|