2013年9月7日 星期六

ASP.NET MVC 使用 Autofac

在這之前有關 IoC Container 已經為各位說明了 Enterprise Library Unity, Simple Injector 這兩種,其中 Enterprise Library 是介紹了第三方所製作的 ASP.NET MVC 與 Unity 整合套件「Unity.MVC」,以及 Enterprise Library 於 6.0 版 Release 時所推出的「Unity bootstrapper for ASP.NET MVC」,除了前面所說的兩種 IoC Container 之外,其實還有很多 IoC Container 也是有許多開發者在使用,各種 IoC Container 都有其優缺點,但也沒有辦法說那一種IoC Container 比較好或比較壞,只有適合不適合的 IoC Container,因為專案與開發者或團隊的因素佔絕大部分。

Autofac 是我第一個接觸的 IoC Container,也應該是比較多人知道或是使用的 IoC Container,Autofac 使用上有很多的優點,再加上官網以及網路上的相關文章、討論也相當多,所以就變成很多人第一個學習或是專案優先採用的 IoC Container,這篇內容一樣是拿「分層架構」的範例程式來做說明,IoC Container 改使用 Autofac,不過 IoC 的概念與內容再其他文章裡有講過,所以就不會講太多理論的內容,直接切入實作。

 


autofac

https://code.google.com/p/autofac/

image

 

專案實作

首先準備了一個還沒有導入 IoC 的專案(可參考「ASP.NET MVC 專案分層架構 Part.5 - 建立 Service 層」),再這個專案裡我們已經完成了 Model 層 Repositopry 以及 Service 層的建立,並且在 Web 裡使用了 Service 來進行資料處理,

image

在 Web 的 Controller 會直接建立 Service 的 instance,

image

上面的作法在「ASP.NET MVC 專案分層架構 Part.6 - DI/IoC 使用 Unity.MVC」這一篇的內容有說到,為了避免這種直接依賴的作法,所以專案使用了 IoC 來降低物件之間的耦合問題,那篇文章採用的是 Unity.MVC4 ( Enterprise Library Unity 2.1 ),現在同樣的專案,現在則是要改用 Autofac。

 

網站專案加入 Autofac

這邊我們透過 NuGet 要為網站專案加入 AutofacAutofac ASP.NET MVC4 Integration

image

 

建立 IDbContextFactory 與 DbContextFactory

因為稍後會在 Autofac 處理註冊類別時,GenericRepository 會需要在建構式傳入 DbContext,而 Autofac 設定註冊類別是一個靜態方法,不能夠在靜態方法內去建立一個 DbContext intance(可參閱:「錯誤更正:有關 ASP.NET MVC 分層架構使用 Unity 的 DbContext 處理」的內容說明),所以會需要在 Model 層建立 IDbContextFactory 以及實作,

image

IDbContextFactory.cs

using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
 
namespace Mvc_Repository.Models
{
    public interface IDbContextFactory
    {
        DbContext GetDbContext();
    }
}

DbContextFactory.cs

using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
 
namespace Mvc_Repository.Models
{
    public class DbContextFactory : IDbContextFactory
    {
        private string _ConnectionString = string.Empty;
 
        public DbContextFactory(string connectionString)
        {
            this._ConnectionString = connectionString;
        }
 
        private DbContext _dbContext;
        private DbContext dbContext
        {
            get
            {
                if (this._dbContext == null)
                {
                    Type t = typeof(DbContext);
                    this._dbContext = 
                        (DbContext)Activator.CreateInstance(t, this._ConnectionString);
                }
                return _dbContext;
            }
        }
 
        public DbContext GetDbContext()
        {
            return this.dbContext;
        }
 
    }
}

上面 DbContextFactory 的實作內容與「錯誤更正:有關 ASP.NET MVC 分層架構使用 Unity 的 DbContext 處理」這一篇裡的 DbContextFactory 略有不同,修改後的作法是為了要確保每一次的 HttpRequest 所建立的 DbContext 為同一個,而不是個別的 DbContext instance,所以稍後 Autofac 在註冊 DbContextFactory 類別時,LifeTime 需要使用 PerHttpRequest,這樣 DbContextFactory 在每次 HttpRequest 只會建立一個 instance,使得建立的 DbContext 也只會有一個。

 

修改 GenericRepository

原本的 GenericRepository 在建構式是需要傳入 DbContext,不過這地方要改為使用 IDbContextFactory,再經由 DbContextFactory 取得 DbContext instance,

修改前:

image

修改後:

image

 

修改 Service

原本 Service 裡也是直接建立要用到的 Repository instance,改用建構式來處理 Repository,而 Repository 則會交由 Autofac 來處理建立 instance 的工作,

修改前:

image

修改後:

image

 

修改 Web 專案的 Controller

與上面的 Service 修改方式一樣,原本 Controller 內直接建立 Service instance,也改為使用建構式然後再由 Autofac 建立 Service instance 並注入到 Controller,

修改前:

image

修改後:

image

 

建立 AutofacConfig.cs

其實相關 Autofac 的註冊類別設定大部分都會放在 Global.asax 裡面,但是如果什麼設定都放在 Global.asax 裡面就會越來越雜亂,所以就跟其他的 IoC Container 一樣,在 ~/App_Start 目錄下建立 AutofacConfig.cs 檔案,

image

Global.asax 的 Application_Start() 方法裡再去呼叫 AutofacConfig.Bootstrapper() 方法,

image

接著就是要完成 AutofacConfig.Bootstrapper() 的內容,如下:

image

在處理泛型類別的時候,並不需要為每個 Model 類別去個別註冊,可以使用 RegisterGeneric() ,

image

在註冊 Service 時卻需要每個 Service 都需要個別處理,如果 Service 有很多,萬一漏了幾個 Service 沒有註冊時,那麼程式就一定會出現錯誤,下圖所顯示的就是對每個 Service 做個別註冊的處理,這樣的作法雖然直接,但卻不是很聰明,

image

所以可以改用以下的方式,

image

可以指定載入「Mvc_Repository.Service」組件,然後使用 Autofac 提供的 RegisterAssemblyTypes() 與 AsImplementedInterfaces() 來完成 Service 類別的註冊。

 

其實 Autofac 的使用方式還有很多更進階的操作,而這一篇只是拿現有專案來導入使用,現有專案並不是特別複雜,所以使用 Autofac 的註冊類別設定看起來就蠻簡單的,不過 Autofac 也是有比較進階的操作處理方式,可以在註冊類別的使用 Lambda 語法將註冊的類別做過濾,節省系統找尋可註冊類別的時間,如下:

image

 

這邊並沒有講得很深,接下來就是各位自己嘗試以及練習與研究啦!

 


autofac

官網:https://code.google.com/p/autofac/

Wiki:https://code.google.com/p/autofac/w/list

autofac MvcIntegration - Interating with ASP.NET MVC 4.0

 

延伸閱讀

Autofac筆記 1 - 黑暗執行緒

Dependency Injection with Autofac - CodeProject

MVC Repository Pattern with Entity Framework and solving Dependency Injection using Autofac: Part-I

 

以上

15 則留言:

  1. 不好意思,請問一下,我照著您最後說的做過濾,但是卻出現錯誤訊息。
    無法載入檔案或組件 'WebErp.Service' 或其相依性的其中之一。 系統找不到指定的檔案。
    我附上下面的圖片給您看看,看是否我哪方面出什麼問題了
    http://i.imgur.com/3Wx4Fpo.png?1

    回覆刪除
    回覆
    1. Hello, 因為你給的擷圖只有程式碼的部份,舊程式碼來說是沒有問題的,
      但我不確定你的專案是否有加入你所指定「WebErp.Service」的參考呢?

      刪除
    2. 感謝您熱心的回答,後來我發現,我把名稱改成Service就可以了,主方案加入了反而找不到。

      刪除
    3. 你要看一下你的 Service 專案的內容,看看是不是組件的命名就是使用「Service」而並非「WebErp.Service」.

      刪除
  2. 我在您的程式碼裡面並沒有看到context從service傳進repository裡面,請問這樣子有辦法達到在service裡面對多個新增修改刪除做savechange嗎??就我的認知如果有分成這樣子的話,都會用到unit of work,但在您這邊不知道是我見識淺還是??好像跟我所認知的不太一樣。

    回覆刪除
    回覆
    1. Hello, 沒錯,繼續做下去就是往 Unit of Work 方向開發,只是有關 UOW 的部份我一直沒有辦法抽出時間做整理,有關系統分層架構的文章都是需要有足夠的時間來做詳細的分析與說明,也或許是我目前還沒有足夠的能力來說明這部份。

      刪除
    2. 其實是我在unit of work有遇到一些困難點,所以想從您的文章看出一道暏光,所以才會提出疑問...........網路上unit of work的原文文章很多,但是差異性也都頗大.........

      刪除
    3. 其實沒有什麼困難的,繼續往下做到使用 Unit of Work 的作法,其實可以看看 ASP.NET MVC 官網的教學課程內容,
      雖然那個內容與我所作的架構有蠻大的差異,但是方式都是差不多的,我想你已經思考到了要做 UOW 的階段,
      那麼所涉獵的相關內容應該不少

      Implementing the Repository and Unit of Work Patterns in an ASP.NET MVC Application (9 of 10)
      http://goo.gl/jTyMpD

      Repository Pattern and Unit of Work with Entity Framework in ASP.NET MVC
      http://goo.gl/tS3lve

      另外你可以直接到 Github 上面找資料,其實也很多人將自己所作的系統給開放了,以下就是其中一個,
      Sample web app for ASP.NET MVC 5, EF 6 Code First, AutoMapper, Autofac and TDD
      https://github.com/MarlabsInc/SocialGoal

      你的回覆也是提醒了我,耽擱蠻久的分層架構系列文章是該趕快動一動了。

      刪除
    4. 問題就是架構不太一樣,參考別人的架構之後,實作自己的,卻怎麼樣也無法savechang..............完全不知道為何會這樣子........也不知道要上哪去問人...............

      刪除
    5. 我前前後後看了數十個在 CodePlex 與 GitHub 以及其他地方所能找到有使用 UOW 的專案,
      在看過這麼多專案時,也動手做了數十個專案,希望找出一個適合我用在開發上的架構,
      常常會推翻之前所作的版本,然後再從頭開始,這樣的過程直到現在還在進行中,
      有時候不見得一定要在工作上所開發的專案就動手下去作,畢竟工作的專案有其目的性以及時效性與功能性,
      所以並不能夠讓我們大玩特玩,我都是先自己找個題目或是試做幾個小專案來做架構開發的試驗,
      一有時間就會持續作這樣的事情,從 2009 年一直到現在還是一直這麼做。
      我也是時常找不到人來問,所以大部分都是上網找文章、找專案來看,
      再來認識了 twMVC 的夥伴之後,就有人可以一起來討論相關的技術與架構,
      每個人的見解與認知都有所不同,而架構設計沒有一個正確答案,就如同我一直說的依照專案的狀況來選擇適合的方式。

      刪除
    6. 感謝您的回覆,因為工作專案有時程壓力,我已經找了一堆文章和四份CODE下來看過了,已改了五遍以上有了吧.....每次專案連線順利了,都會卡在儲存的這一關..........可惜我身邊也找不到人討論,雖然FB有加MVP好友,但不熟也不好意思去問人..........因為我當初對架構的觀念大部份是從您的文章學來的,所以在這邊就廢話多了一點,請見諒...............

      刪除
  3. 問一下哦 因為我 根據你的generic repository 的 寫法 改寫 加了 db context factory 的 類別 可是 他出現了 不包含使用0引數的建構函式

    回覆刪除
    回覆
    1. 這要看你的程式怎麼寫的才會知道,我沒有天眼通 ^.<

      刪除
  4. 版主:
    你好,不好意思打擾。
    目前同一個專案剛好使用MVC 與WebAPI,而且兩者使用不同的資料庫,
    請問能在Autofac 的RegisterType 同時註冊兩個不同資料庫來源字串嗎?
    如果不行,能否建議其他相關作法? 謝謝。 不好意思。

    回覆刪除
    回覆
    1. 不好意思,最後還是個別註冊(apibuilder,mvcbuilder),
      貌似可以正常運作,不好意思打擾了。
      如果版主有其他建議在煩請不吝告知。 謝謝

      刪除

提醒

千萬不要使用 Google Talk (Hangouts) 或 Facebook 及時通訊與我聯繫、提問,因為會掉訊息甚至我是過了好幾天之後才發現到你曾經傳給我訊息過,請多多使用「詢問與建議」(在左邊,就在左邊),另外比較深入的問題討論,或是有牽涉到你實作程式碼的內容,不適合在留言板裡留言討論,請務必使用「詢問與建議」功能(可以夾帶檔案),謝謝。