在上一篇我們介紹了Autofac的基本概念,還有它裡面比較常用的專有名詞,詳細對於Autofac有了一些瞭解。
在這一篇,我將會介紹如何讓我們在Asp .Net Mvc裡面,簡單的使用Autofac作為我們的DI Container。
在介紹Autofac如何提供簡單和Mvc結合之前,我們先來看一下Mvc有了DI Container的好處。
通常來說在我們的Controller裡面至少都會depend別的library。有可能是Data Access Layer(DAL)或者是Business Logic Layer。我們以預設的Mvc Scaffolding搭配Entity Framework來看,可能會有類似如下的程式碼:
public class LinksController : Controller
{
private ApplicationDbContext db = new ApplicationDbContext();
// GET: Links
public ActionResult Index()
{
return View(db.Links.ToList());
}
....
}
這個有一個問題,那個ApplicationDbContext等於是被寫死在裡面了,假設今天我要做單元測試,每一個測試都實際對DB執行其實是很沒有效率。
因此,有一種做法是我們把db這個參數實際的實例寫在建構子裡面,然後透過Controller在被建立的時候注入我們要的服務,這樣,如果在做單元測試的時候可以丟進去 mock出來的db,在實際運行的時候,DI Container又會真的注入真的db進去。
// psuedo 範例程式
public class LinksController : Controller
{
private ApplicationDbContext db;
public LinksController(ApplicationDbContext inDb)
{
db = inDb;
}
}
public class UnitTest
{
LinksController c = new LinksController(new FakeDbContext());
}
上面,是一個範例程式碼,表示在單元測試的時候可以替換db。
不過如果這樣直接執行,也會出錯,因為預設的Mvc是只會實例化預設建構子(沒有參數的建構子),而以我們上面的例子來說,我們沒有預設建構子,因此,Mvc無法實例Controller 就會出錯。
沒有預設建構子的錯誤
這個時候就要靠我們的DI Container了。Mvc只能夠實例化預設建構子是因為它不知道每一個參數是對應到什麼class,因此沒有辦法做下去。但是DI Container會有我們註冊的 Service和Component,因此DI Container知道如何實例化那些對應的建構子參數,達到幫我們注入正確的Component進去。
Mvc開發之間,就有考慮到這個問題,因此有一個靜態方法:DependencyResolver.SetResolver(IDependencyResolver)可以讓我們告訴Mvc,當需要建立Controller的時候,不要用你自己的來建立,用註冊進去的IDependencyResolver物件來做建立的動作。
到這裡就需要來提一下Autofac的Mvc 5 Integration套件,基本上概念非常簡單這個部分的套件幫助我們是做好了IDependencyResolver,並且讓我們能夠為Mvc不同的功能提供注入的服務。
首先,需要先去nuget下載安裝,這個Integration的nuget指令如下:
Install-Package Autofac.Mvc5
這個integration提供了幾個helper方便我們整合Autofac和Mvc:
更多詳細的介紹,請參考文件:Mvc Integration
這邊我用一個簡單的例子來介紹有了DI的強大。
我們假設要做一個很陽春的Log功能,他會把進來的Request QueryString記錄下來。
public interface ILog
{
void Write(string message);
}
public class TextWriterLog : ILog
{
public void Write(string message)
{
File.AppendAllText(@"R:\logFile.txt", message);
}
}
上面沒有太特別的地方,一個interface,和一個寫到檔案的實作。
public class HomeController : Controller
{
ILog logger;
public HomeController(ILog inLogger)
{
logger = inLogger;
}
// GET: Home
public ActionResult Index()
{
logger.Write("進入 Home/Index");
return View();
}
}
設定DI Container和Mvc結合
到了這邊,我們就需要去設定DI Container和要註冊的物件
// 在global.asax.cs
protected void Application_Start()
{
var builder = new ContainerBuilder();
// 註冊所有的Controller作為Service
builder.RegisterControllers(typeof(HomeController).Assembly);
// 註冊TextWriterLog作為ILog的Service
builder.RegisterType<TextWriterLog>().As<Ilog>();
// 建立 DI Container
var container = builder.Build();
// 用DI Container作為建立Controller時候的DI Resolver。
DependencyResolver.SetResolver(new AutofacDependencyResolver(container));
// 其他Mvc設定
TextWriterLog輸出的結果內容
一開是說會把QueryString的東西也輸出來,這還需要一點設定。
首先在global.asax.cs做點增加:
// .. 其他內容
// 註冊TextWriterLog作為ILog的Service
builder.RegisterType<TextWriterLog>().As<Ilog>();
// 註冊Http 相關內容
builder.RegisterModule(new AutofacWebTypesModule());
// 建立 DI Container
var container = builder.Build();
// .. 其他內容
再來,修改我們的TextWriterLog
public class TextWriterLog : ILog
{
HttpRequestBase request;
// 注入Request
public TextWriterLog(HttpRequestBase inRequest)
{
request = inRequest;
}
public void Write(string message)
{
var queryString = string.Empty;
foreach (var item in request.QueryString.AllKeys)
{
queryString = queryString + item + "=" +
request.QueryString[item] + "&";
}
var result = string.Format("訊息:{0}{1} QueryString:{2}{3}",
message, Environment.NewLine, queryString, Environment.NewLine);
File.AppendAllText(@"R:\logFile.txt", result);
}
}
最後我們帶上Querystring瀏覽:
最後輸出結果的截圖
這邊結果有兩次,第一次是執行起來沒有Querystring,第二次則是有帶上QueryString。
希望透過這一篇對於Autofac和Mvc的整合有些瞭解。
也希望透過簡單的範例,能夠看到有DI的強大,畢竟我們不需要做什麼,我們只是說我需要一個Request,在方法裡面就會有Request的Instance。
下一篇我們講開始打造我們的框架,首先從建立我們自己的Log服務開始。
如果想要知道更多關於 Autofac Mvc Integration其他用法,請參考文件: Mvc Integration