Alan Tsai 的學習筆記


學而不思則罔,思而不學則殆,不思不學則“網貸” 記錄軟體開發的點點滴滴 著重於微軟技術、網頁開發、DevOps、C#, Asp .net Mvc、Azure、AI、Chatbot、Docker、Data Science

[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


如果文章對您有幫助,就請我喝杯飲料吧
街口支付QR Code
街口支付QR Code
台灣 Pay QR Code
台灣 Pay QR Code
Line Pay 一卡通 QR Code
Line Pay 一卡通 QR Code
街口支付QR Code
支付寶QR Code
街口支付QR Code
微信支付QR Code
comments powered by Disqus