2014年9月26日 星期五

[iThome 第七屆鐵人賽 04] Autofac和Asp .Net Mvc結合

在上一篇我們介紹了Autofac的基本概念,還有它裡面比較常用的專有名詞,詳細對於Autofac有了一些了解。

在這一篇,我將會介紹如何讓我們在Asp .Net Mvc裡面,簡單的使用Autofac作為我們的DI Container。

Mvc能夠用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 就會出錯。 image

沒有預設建構子的錯誤

這個時候就要靠我們的DI Container了。Mvc只能夠實例化預設建構子是因為它不知道每一個參數是對應到什麼class,因此沒有辦法做下去。但是DI Container會有我們註冊的 Service和Component,因此DI Container知道如何實例化那些對應的建構子參數,達到幫我們注入正確的Component進去。


Asp .Net Mvc如何在建構Controller的時候,讓DI container介入


Mvc開發之間,就有考慮到這個問題,因此有一個靜態方法:DependencyResolver.SetResolver(IDependencyResolver)可以讓我們告訴Mvc,當需要建立Controller的時候,不要用你自己的來建立,用註冊進去的IDependencyResolver物件來做建立的動作。


到這裡就需要來提一下Autofac的Mvc 5 Integration套件,基本上概念非常簡單這個部分的套件幫助我們是做好了IDependencyResolver,並且讓我們能夠為Mvc不同的功能提供注入的服務。


Autofac Asp.net Mvc Integration


首先,需要先去nuget下載安裝,這個Integration的nuget指令如下:

Install-Package Autofac.Mvc5

這個integration提供了幾個helper方便我們整合Autofac和Mvc:



  1. 第一個當然是IDependencyResolver的實作,方便使用Autofac作為Mvc的DI
  2. ContainerBuilder有多一個RegisterControllers的擴充方法,只要把Controller所在的Assembly給他, 它就會幫我們把所有Controller都註冊成為Component
  3. 之前講註冊Service的時候,沒有提到Component的Lifetime Scope(這方面比較細部因此不會介紹,有興趣可以看文件),在裝了這個Integration, 會多一個Lifetime Scope是InstancePerHttpRequest,表示每一次Request都是新的物件
  4. 有提供能夠動態注入ModelBinder到Mvc
  5. 提供一個Autofac的Module方便注入Http相關 - 因此要取得像Session、Request等變的簡單
  6. 提供注入Property到View和ActionFilter裡面

更多詳細的介紹,請參考文件:Mvc Integration

Demo


這邊我用一個簡單的例子來介紹有了DI的強大。


我們假設要做一個很陽春的Log功能,他會把進來的Request QueryString記錄下來。


先定義Log服務的Interface和一支實作

public interface ILog
{
void Write(string message);
}

public class TextWriterLog : ILog
{
public void Write(string message)
{
File.AppendAllText(@"R:\logFile.txt", message);
}
}

上面沒有太特別的地方,一個interface,和一個寫到檔案的實作。


在Controller裡面,允許透過建構子注入ILog

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設定

這時候把我們的程式run起來,之後就會發現多了一個檔案: image

TextWriterLog輸出的結果內容

取得Request裡面的QueryString並且輸出去到檔案


一開是說會把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瀏覽:


 image

最後輸出結果的截圖

這邊結果有兩次,第一次是執行起來沒有Querystring,第二次則是有帶上QueryString。


結語


希望透過這一篇對於Autofac和Mvc的整合有些了解。


也希望透過簡單的範例,能夠看到有DI的強大,畢竟我們不需要做什麼,我們只是說我需要一個Request,在方法裡面就會有Request的Instance。


下一篇我們講開始打造我們的框架,首先從建立我們自己的Log服務開始。


Reference


如果想要知道更多關於 Autofac Mvc Integration其他用法,請參考文件: Mvc Integration

沒有留言 :

張貼留言