0%

【Autofac】生命週期

2019/08/21 補充 : 將之前在公司分享的 Demo Code 整理並放到 Github,有興趣的可以下載來試試看。

#什麼是Autofac

用來實作控制反轉(Inversion of Control, IoC)的套件,幫我們管理抽象與實體之間的對應關係, 除了Autofac之外還有Unity等其它套件可以實作,但概念都大同小異。

#Autofac的生命週期有哪些

  • InstancePerDependency
  • SingleInstance
  • InstancePerLifetimeScope
  • InstancePerRequest

下面會一一解說跟實作測試,在這之前先準備一個WebAPI的專案,並且將Autofac與Autofac WebAPI2裝起來

/images/20180712/1.jpg

然後在App_Start資料夾新增AutofacConfig類別

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public static class AutofacConfig
{
public static IContainer Container { get; private set; }

public static void BuildContainer()
{
var builder = new ContainerBuilder();

Register(builder);

Container = builder.Build();
GlobalConfiguration.Configuration.DependencyResolver = new AutofacWebApiDependencyResolver(Container);
}

private static void Register(ContainerBuilder builder)
{
var assembly = typeof(AutofacConfig).Assembly;
builder.RegisterApiControllers(assembly);
}
}

Global.asax加上這行

1
2
3
4
5
6
7
8
9
10
11
protected void Application_Start()
{
//Autofac Init
AutofacConfig.BuildContainer();

AreaRegistration.RegisterAllAreas();
GlobalConfiguration.Configure(WebApiConfig.Register);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
}

InstancePerDependency

Also called ‘transient’ or ‘factory’ in other containers. Using per-dependency scope, a unique instance will be returned from each request for a service.

每次呼叫都回傳一個新的實體,產生的Instance隨著呼叫者的生命週期消滅

1
2
3
4
5
6
7
8
9
10
11
12
public class LiftTimeModel
{
public static int Count;

public int ID { get; private set; }

public LiftTimeModel()
{
Count++;
this.ID = LiftTimeModel.Count;
}
}

AutofacConfig

1
2
3
4
5
6
7
8
9
10
11
12
public static class AutofacConfig
{
....省略....

private static void Register(ContainerBuilder builder)
{
var assembly = typeof(AutofacConfig).Assembly;
builder.RegisterApiControllers(assembly);

builder.RegisterType<LiftTimeModel>().InstancePerDependency();
}
}

TestController

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class TestController : ApiController
{
private LifeTimeModel _lifeTimeModel;
public TestController(LifeTimeModel lifeTimeModel)
{
this._lifeTimeModel = lifeTimeModel;
}

// GET api/<controller>
public string Get()
{
StringBuilder sb = new StringBuilder();
sb.AppendLine(string.Concat("PerDependencyModel ID :", this._lifeTimeModel.ID));

return sb.ToString();
}
}

呼叫/api/test會發現隨著呼叫的次數增加,LifeTimeModel ID 也會一直增加,因為每次Request呼叫開始都會產生新的TestController Instance,隨著Request結束TestController也會被消滅,依賴在它身上的LifeTimeModel也隨之產生與消滅。而每次產生新的LifeTimeModel Instance都會在建構子將static的Count加1,這是數字一直增加的緣故。

此外Autofac預設使用的生命週期即為InstancePerDependency,所以這兩行的意思是一樣的

1
2
builder.RegisterType<LiftTimeModel>().InstancePerDependency();
builder.RegisterType<LiftTimeModel>();

InstancePerDependency圖解

SingleInstance

This is also known as ‘singleton.’ Using single instance scope, one instance is returned from all requests in the root and all nested scopes.

不管呼叫幾次都只有產生一個Instance,以WebAPI為例只有當Application Shut down的時候會消滅

1
2
3
4
5
6
7
8
9
10
11
12
public static class AutofacConfig
{
...省略....

private static void Register(ContainerBuilder builder)
{
var assembly = typeof(AutofacConfig).Assembly;
builder.RegisterApiControllers(assembly);
//改成SingleInstance
builder.RegisterType<LifeTimeModel>().SingleInstance();
}
}

會發現不管呼叫/api/test幾次ID永遠都是1,原因是LifeTimeModel只會產生一次

SingleInstance圖解

InstancePerLifetimeScope

This scope applies to nested lifetimes. A component with per-lifetime scope will have at most a single instance per nested lifetime scope.

這個非常容易跟InstancePerRequest搞混,簡單理解方式就是,每個Scope只會最多產生一個Instance。

所以在Root只會產生一個,在Autofac WebRequest只會產生一組

新增TestPerLifetimeScopeModel做為觀察標的

1
2
3
4
5
6
7
8
public class TestPerLifetimeScopeModel
{
public LifeTimeModel LifeTimeModel { get; set; }
public TestPerLifetimeScopeModel(LifeTimeModel lifeTimeModel)
{
this.LifeTimeModel = lifeTimeModel;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public static class AutofacConfig
{
...省略....

private static void Register(ContainerBuilder builder)
{
var assembly = typeof(AutofacConfig).Assembly;
builder.RegisterApiControllers(assembly);

builder.RegisterType<TestPerLifetimeScopeModel>();
//改成InstancePerLifetimeScope
builder.RegisterType<LifeTimeModel>().InstancePerLifetimeScope();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class TestController : ApiController
{
private LifeTimeModel _lifeTimeModel;
private TestPerLifetimeScopeModel _testPerLifetimeScopeModel;
public TestController(LifeTimeModel lifeTimeModel, TestPerLifetimeScopeModel testPerLifetimeScopeModel)
{
this._lifeTimeModel = lifeTimeModel;
this._testPerLifetimeScopeModel = testPerLifetimeScopeModel;
}

// GET api/<controller>
public string Get()
{
StringBuilder sb = new StringBuilder();
sb.AppendLine(string.Concat("PerDependencyModel ID :", this._lifeTimeModel.ID));
sb.AppendLine(string.Concat("TestPerLifetimeScopeModel's LifeTimeModel ID :", this._testPerLifetimeScopeModel.LifeTimeModel.ID));

return sb.ToString();
}
}

會發現兩個得到的ID都是相同的,原因是Autofac WebRequest Scope中,一次Request只會產生一組LifeTimeModel,所以ID都會一樣

/images/20180712/4.jpg

更進一步說,如果我們今天有使用WebAPI DelegatingHandler,因為Handler只會在Application起來時產生一次Instance,所以在這產生的LifeTimeModel也會變成Singleton *註1

1
2
3
4
5
6
7
8
9
10
public class TestHandler: DelegatingHandler
{
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var Model = AutofacConfig.Container.Resolve<LifeTimeModel>();
var TestID = Model.ID;

return base.SendAsync(request, cancellationToken);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Web API 設定和服務

// Web API 路由
config.MapHttpAttributeRoutes();

// 註冊Handler
config.MessageHandlers.Add(new TestHandler());

config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}

會發現Handler裡面的ID永遠拿到的都是1,而API回傳的結果則會慢慢增加,兩邊是各自獨立的實體

/images/20180712/6.jpg

/images/20180712/7.jpg

InstancePerLifetimeScope圖解

註1

這邊觀念有錯誤,會Singleton的原因不在於DelegatingHandler的問題,而是因為使用AutofacConfig.Container來產生實體,Container是Root層級也是Singleton,請它產生LifeTimeModel (InstancePerLifetimeScope),所以會變成Singleton。

交叉測試的方法是把這段 AutofacConfig.Container.Resolve()放到Controller的Action之中,產出來的實體也會是Singleton,所以癥結在AutofacConfig.Container而非DelegatingHandler或Controller Action的生命週期,特此更正

InstancePerRequest

Some application types naturally lend themselves to “request” type semantics, for example ASP.NET web forms and MVC applications. In these application types, it’s helpful to have the ability to have a sort of “singleton per request.”

基本上跟InstancePerLifetimeScope認知是一致的,唯一差別是它無法Root Scope產生Instance,因為生命週期隨著Request產生與消滅,不會有Singleton的可能,所以如果改成InstancePerRequest,剛剛的TestHandler就會產生Exception。

/images/20180712/8.jpg

/images/20180712/9.jpg

Unity跟Autofac的生命週期一直都不是很好懂所以之前也踩了不少雷,筆記起來希望對大家能有幫助

補充

產生在AutofacWebRequest Scope的實體,一定會在每次Request結束後被消滅