Autofac 觀念、實務與紀錄

IoC, DI, Autofac

觀念

  • IoC, Inversion of Control ,控制反轉。 一種軟體叫用(或引用)資源的行為,一般來說上層才可引用下層資源,下層一般不需要也沒必要引用到上層的資源,因為會不小心就dead lock且破壞架構階層關係(新鮮人常幹這種事)。 IoC就是反過來,可由下層引用上層一小部份資源。傳統上我們用Callback Function (回呼函式)處理這種下對上的需求。現在可以用lamda函式就可以很簡單地實作 IoC 的效果。

  • DI (Dependency Injection) 依賴注入。

    一種軟體設計模式,介面的實作可在事後更替。用GoF design pattern來類比就是『Bridge pattern』。

  • Autofac為一種實作DI的軟體技術,並可實現IoC行為。 IoC 的實現用 Callback 或 lamda 就可以辦到,為何還要用Autofac 這麼麻煩呢?因為callbacklamda只能反轉函式,而Autofac可以直接反轉整個物件。

在實際上,大部份 Autofac 只用到 DI 程度,很少用到 IoC 這個有點進階的程度。且Autofac 設計的太靈活非常容易爛用,容易造成架構各階層模組的權責混亂。若非必要請不要用,若真的需要請有限度的使用需要的那一部份就好。

一個現實狀況,DI 在 .NET5/MVC6 似手變成了標備,不用都都行啊。

紀錄

開發環境

  • IDE: Visual Studio 2017

  • .NET Fx 4.5.1

  • MVC 5.2.4

  • Autofac 4.9.4

  • Autofac.Mvc5 4.0.2

Autofac 常用三種注射方式.

  1. Constructor injection (基本用法,大部份用這招就能滿足。本例有練習)

  2. Property injection (本例有練習)

  3. Method injection

下層

注入目標介面定義,注入介面只能在下層定義不過可以在上層實作。

IFooService.cs
namespace YourProject.Biz
{
    /// 欲DI注入的目標介面
    public interface IFooService
    {
        void ShowMeTheMoney(int money);
    }
}

下層注入上層資源,一個IoC範例

MySampleBiz.cs
namespace YourProject.Biz
{
    public class MySampleBiz
    {
        /// <summary>
        /// 將會DI注入,Property injection sample
        /// </summary>
        public IFooService _foo { get; set; }

        public MySampleBiz() 
        { 
            /// _foo 將會被注入上層資源,不用建構它。
            /// 此例是用 Property injection
            /// 當然也能用 Constructor injection 或 Method injection。
        }

        /// 一個 IoC 練習
        public void IamRich(int rich)
        {
            _foo.ShowMeTheMoney(rich); /// 將引用上層資源。
            Debug.WriteLine($"I am rich {rich}. Yes");
        }
    }
}

上層

在上層實作下層介面

FooService.cs
namespace YourProject.Models
{
    /// 實作下層的介面,以實現IoC應用。
    public class FooService : Biz.IFooService, IDisposable
    {
        public FooService() {
            Debug.WriteLine($"FooService ctor.");
        }

        public void ShowMeTheMoney(int money) {
            Debug.WriteLine($"ShowMeTheMoney {money}");
        }

        void IDisposable.Dispose()
        {
            Debug.WriteLine($"FooService dispose.");
            /// dispose 未實作完整請自行補滿,因非本主題重點。
        }
    }
}

上層注入應用範例

FooController.cs
namespace YourProject.Areas.Templates.Controllers
{
    public class FooController : Controller
    {
        private Biz.IFooService _foo; // 將注入介面以使用目標物件. 
        private Biz.MySampleBiz _biz; // 將直接注入目標物件。

        public BS3LayoutController(Biz.IFooService foo, Biz.MySampleBiz biz)
        {
            /// 由DI注入成介面引用目標物件,Constructor injection sample。
            _foo = foo;
            /// 由DI直接注入目標物件。也是 Constructor injection sample。
            /// 註:一般下層資源直接建構就好沒有注入的必要。
            _biz = biz;
        }

        public ActionResult Index()
        {
            _foo.ShowMeTheMoney(1000); /// 使用被注入的資源
            _biz.IamRich(9999);        /// 使用被注入的資源
            return View();
        }
    }
}

Autofac 註冊程序 ※ Autofac 註冊語法中,同一目的有多種寫法,說是彈性然而也是混亂源頭之一, 請謹用。

AutofacConfig.cs
using Autofac;
using Autofac.Integration.Mvc;

namespace YourProject.App_Start
{
    public class AutofacConfig
    {
        public static void Bootstrapper()
        {
            // 容器建立者
            var builder = new ContainerBuilder();
            // ------ 開始註冊 ------

            /// 註冊Controllers
            builder.RegisterControllers(Assembly.GetExecutingAssembly());

            /// 註冊Biz,註冊整個Biz模組,其中類別名稱結尾為"Biz"的類別。
            /// 預設就有 Constructor injection 能力。
            builder.RegisterAssemblyTypes(typeof(Biz.MySampleBiz).Assembly)
               .Where(t => t.Name.EndsWith("Biz"))
               .AsSelf()
               .PropertiesAutowired(); // 並啟動 Property injection 能力。

            /// 註冊單一類別 FooService 成介面 IFooService.
            ///※ 註冊上層類別 FooService 並轉型下層介面 IFooService,這樣就可以被注入到下層以實現IoC應用。
            builder.RegisterType<FooService>().As<Biz.IFooService>();

            /// 以下是其他註冊例子,留參用
            
            //builder.RegisterType<DB.MyLabDbConnHelper>()
            //    .As<IDbConnection>()
            //    .InstancePerRequest();

            //// 註冊Service
            //builder.RegisterAssemblyTypes(Assembly.GetExecutingAssembly())
            //       .Where(t => t.Name.EndsWith("Service"))
            //       .AsSelf();

            //// 註冊Repository
            //builder.RegisterAssemblyTypes(Assembly.GetExecutingAssembly())
            //       .Where(t => t.Name.EndsWith("Repository"))
            //       .AsSelf();

            //var services = Assembly.Load("TimidoColor.Services");
            //builder.RegisterAssemblyTypes(services).AsImplementedInterfaces();

            //builder.RegisterFilterProvider();

            // ------ 註冊結束 ------
            // 建立容器
            IContainer container = builder.Build();

            // 建立相依解析器,解析容器內的型別
            DependencyResolver.SetResolver(new AutofacDependencyResolver(container));
        }
    }
}

系統一開始就需啟動註冊程序

Global.asax.cs
using YourProject.App_Start;

namespace YourProject
{
    public class MvcApplication : System.Web.HttpApplication
    {
        protected void Application_Start()
        {
            /// 系統一開始就必需啟動Autofac註冊
            AutofacConfig.Bootstrapper(); 
            
            AreaRegistration.RegisterAllAreas();
            FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
            RouteConfig.RegisterRoutes(RouteTable.Routes);
            BundleConfig.RegisterBundles(BundleTable.Bundles);
        }
    }
}

(EOF)

Last updated