【半小時大話.net依賴注入】(下)詳解AutoFac+實戰Mvc、Api以及.NET Core的依賴注入

系列目錄

  1. 上|理論基礎+實戰控制檯程序實現AutoFac注入html

  2. 下|詳解AutoFac+實戰Mvc、Api以及.NET Core的依賴注入ios

前言

原本計劃是五篇文章的,每章發個半小時隨便翻翻就能懂,可是第一篇發了以後,我發現.NET環境下不少人對IoC和DI都很排斥,搞得評論區異常熱鬧。git

同一個東西,在Java下和在.NET下能有這麼大的差別,也是挺有意思的一件事情。github

因此我就把剩下四篇內容精簡再精簡,合成一篇了,權當是寫給本身的一個備忘記錄了。web

GitHub源碼地址:https://github.com/WangRui321/Ray.EssayNotes.AutoFac數據庫

源碼是一個虛構的項目框架,相似於樣例性質的代碼或者測試程序,裏面不少註釋,對理解DI,或怎麼在MVC、WebApi和Core Api分別實現依賴注入有很好的幫助效果。
因此,如下內容,配合源碼食用效果更佳~api

第一部分:詳解AutoFac用法

名詞解釋

老規矩,理論先行。mvc

組件(Components)

一串聲明瞭它所提供服務和它所消費依賴的代碼。app

能夠理解爲容器內的基本單元,一個容器內會被註冊不少個組件,每一個組件都有本身的信息:好比暴露的服務類型、生命週期域、綁定的具象對象等。框架

服務(Services)

一個在提供和消費組件之間明肯定義的行爲約定。

和項目中的xxxService不一樣,AutoFac的服務是對容器而言的,能夠簡單的理解爲上一章講的組件的暴露類型(即對外開放的服務類型),也就是As方法裏的東西:

builder.RegisterType<CallLogger>()
       .As<ILogger>()
       .As<ICallInterceptor>();

這裏,針對同一個註冊對象(CallLogger),容器就對外暴露了兩個服務(service),ILogger服務和ICallInterceptor服務。

生命週期做用域(LifeTimeScope)

  • 生命週期

指服務實例在你的應用中存在的時長:從開始實例化到最後釋放結束。

  • 做用域

指它在應用中能共享給其餘組件並被消費的做用域。例如, 應用中有個全局的靜態單例,那麼該全局對象實例的 "做用域" 將會是整個應用。

  • 生命週期做用域

實際上是把這兩個概念組合在了一塊兒, 能夠理解爲應用中的一個工做單元。後面詳細講。

怎麼理解它們的關係

容器是一個自動售貨機,組件是放在裏面的在售商品,服務是商品的出售名稱
把商品(項目裏的具象對象)放入自動售貨機(容器)上架的過程叫註冊
註冊的時候會給商品貼上標籤,標註該商品的名稱,這個名稱就叫服務
咱們還能夠標註這個商品的適用人羣和過時時間等(生命週期做用域);
把這個包裝後的商品放入自動售貨機後,它就變成了在售商品(組件)。
當有顧客須要某個商品時,他只要對着售貨機報一個商品名(服務名),自動售貨機找到對應商品,拋出給客戶,這個拋給你的過程,就叫作注入你;
並且這個售貨機比較智能,拋出前還能夠先判斷商品是否是過時了,該不應拋給你。

註冊組件

即在容器初始化時,向容器內添加對象的操做。AutoFac封裝瞭如下幾種便捷的註冊方法:

反射註冊

直接指定注入對象與暴露類型,使用RegisterType<T>()或者RegisterType(typeof(T))方法:

builder.RegisterType<StudentRepository>()
    .As<IStudentRepository>();
builder.RegisterType(typeof(StudentService))
    .As(typeof(IStudentService));

實例註冊

將實例註冊到容器,使用RegisterInstance()方法,一般有兩種:

  • new出一個對象註冊:
var output = new StringWriter();
builder.RegisterInstance(output).As<TextWriter>();
  • 註冊項目已存在單例:
builder.RegisterInstance(MySingleton.Instance).ExternallyOwned();

Lambda表達式註冊

builder.Register(x => new StudentRepository())
    .As<IStudentRepository>();
builder.Register(x => new StudentService(x.Resolve<IStudentRepository>()))
    .As<IStudentService>();

利用拉姆達註冊能夠實現一些常規反射沒法實現的操做,好比一些複雜參數註冊。

泛型註冊

最多見的就是泛型倉儲的註冊:

builder.RegisterGeneric(typeof(BaseRepository<>))
    .As(typeof(IBaseRepository<>))
    .InstancePerLifetimeScope();

條件註冊

經過加上判斷條件,來決定是否執行該條註冊語句。

  • IfNotRegistered

表示:若是沒註冊過xxx,就執行語句:

builder.RegisterType<TeacherRepository>()
    .AsSelf()
    .IfNotRegistered(typeof(ITeacherRepository));

只有當ITeacherRepository服務類型沒有被註冊過,纔會執行該條註冊語句。

  • OnlyIf

表示:只有...,纔會執行語句:

builder.RegisterType<TeacherService>()
    .AsSelf()
    .As<ITeacherService>()
    .OnlyIf(x => 
            x.IsRegistered(new TypedService(typeof(ITeacherRepository)))||
            x.IsRegistered(new TypedService(typeof(TeacherRepository))));

只有當ITeacherRepository服務類型或者TeacherRepository服務類型被註冊過,纔會執行該條註冊語句。

程序集批量註冊

最經常使用,也最實用的一個註冊方法,使用該方法最好要懂點反射的知識。

/// <summary>
        /// 經過反射程序集批量註冊
        /// </summary>
        /// <param name="builder"></param>
        public static void BuildContainerFunc8(ContainerBuilder builder)
        {
            Assembly[] assemblies = Helpers.ReflectionHelper.GetAllAssemblies();

            builder.RegisterAssemblyTypes(assemblies)//程序集內全部具象類(concrete classes)
                .Where(cc =>cc.Name.EndsWith("Repository")|//篩選
                            cc.Name.EndsWith("Service"))
                .PublicOnly()//只要public訪問權限的
                .Where(cc=>cc.IsClass)//只要class型(主要爲了排除值和interface類型)
                //.Except<TeacherRepository>()//排除某類型
                //.As(x=>x.GetInterfaces()[0])//反射出其實現的接口,默認以第一個接口類型暴露
                .AsImplementedInterfaces();//自動以其實現的全部接口類型暴露(包括IDisposable接口)

            builder.RegisterGeneric(typeof(BaseRepository<>))
                .As(typeof(IBaseRepository<>));
        }

如上會批量註冊項目中全部的Repository和Service。

屬性注入

講屬性注入以前,要先看下構造注入。

  • 構造注入
    即解析的時候,利用構造函數注入,形式以下:
/// <summary>
    /// 學生邏輯處理
    /// </summary>
    public class StudentService : IStudentService
    {
        private readonly IStudentRepository _studentRepository;
        /// <summary>
        /// 構造注入
        /// </summary>
        /// <param name="studentRepository"></param>
        public StudentService(IStudentRepository studentRepository)
        {
            _studentRepository = studentRepository;
        }
    }

在構造函數的參數中直接寫入服務類型,AutoFac解析該類時,就會去容器內部已存在的組件中查找,而後將匹配的對象注入到構造函數中去。

  • 屬性注入
    屬性注入與構造注入不一樣,是將容器內對應的組件直接注入到類內的屬性中去,形式以下:
/// <summary>
    /// 教師邏輯處理
    /// </summary>
    public class TeacherService : ITeacherService
    {
        /// <summary>
        /// 用於屬性注入
        /// </summary>
        public ITeacherRepository TeacherRepository { get; set; }

        public string GetTeacherName(long id)
        {
            return TeacherRepository?.Get(111).Name;
        }
    }

要使用這種屬性注入,在註冊該屬性所屬類的時候,須要使用PropertiesAutowired()方法額外標註,以下:

builder.RegisterType<TeacherService>().PropertiesAutowired();

這樣,容器在解析並實例化TeacherService類時,便會將容器內的組件與類內的屬性作映射,若是相同則自動將組件注入到類內屬性種。

  • 注意

屬性注入爭議性很大,不少人稱這是一種_反模式_,事實也確實如此。
使用屬性注入會讓代碼可讀性變得極其複雜(而複雜難懂的代碼必定不是好的代碼,無論用的技術有多高大上)。
可是屬性注入也不是一無可取,由於屬性注入有一個特性:
在構造注入的時候,若是構造函數的參數中有一個對象在容器不存在,那麼解析就會報錯。
可是屬性注入就不同了,當容器內沒有與該屬性類型對應的組件時,這時解析不會報異常,只會讓這個屬性保持爲空類型(null)。
利用這個特性,能夠實現一些特殊的操做。

暴露服務

即上面提到的As<xxx>()函數,AutoFac提供瞭如下三種標註暴露服務類型的方法:

以其自身類型暴露服務

使用AsSelf()方法標識,表示以其自身類型暴露,也是當沒有標註暴露服務的時候的默認選項。
以下四種寫法是等效的:

builder.RegisterType<StudentService>();//不標註,默認以自身類型暴露服務
builder.RegisterType<StudentService>().AsSelf();
builder.RegisterType<StudentService>().As<StudentService>();
builder.RegisterType<StudentService>().As(typeof(StudentService));

以其實現的接口(interface)暴露服務

使用As()方法標識,暴露的類型能夠是多個,好比CallLogger類實現了ILogger接口和ICallInterceptor接口,那麼能夠這麼寫:

builder.RegisterType<CallLogger>()
       .As<ILogger>()
       .As<ICallInterceptor>()
       .AsSelf();

程序集批量註冊時指定暴露類型

  • 方法1:本身指定
public static void BuildContainerFunc8(ContainerBuilder builder)
        {
            Assembly[] assemblies = Helpers.ReflectionHelper.GetAllAssemblies();

            builder.RegisterAssemblyTypes(assemblies)//程序集內全部具象類(concrete classes)
                .Where(cc =>cc.Name.EndsWith("Repository")|//篩選
                            cc.Name.EndsWith("Service"))
                .As(x=>x.GetInterfaces()[0])//反射出其實現的接口,並指定以其實現的第一個接口類型暴露
        }
  • 方法2:以其實現的全部接口類型暴露

使用AsImplementedInterfaces()函數實現,至關於一個類實現了幾個接口(interface)就會暴露出幾個服務,等價於上面連寫多個As()的做用。

public static void BuildContainerFunc8(ContainerBuilder builder)
        {
            Assembly[] assemblies = Helpers.ReflectionHelper.GetAllAssemblies();

            builder.RegisterAssemblyTypes(assemblies)//程序集內全部具象類(concrete classes)
                .Where(cc =>cc.Name.EndsWith("Repository")|//篩選
                            cc.Name.EndsWith("Service"))
                .AsImplementedInterfaces();//自動以其實現的全部接口類型暴露(包括IDisposable接口)
        }

生命週期做用域

至關於UnitWork(工做單元)的概念。下面羅列出了AutoFac與.NET Core的生命週期做用域,並做了簡要的對比。

AutoFac的生命週期做用域

下面講下AutoFac定義的幾種生命週期做用域,上一篇評論裏也有人提了,關於生命週期做用域這塊確實不是很好理解,因此下面每中類型我都寫了一個例子程序,這些例子程序對理解頗有幫助,只要能讀懂這些例子程序,就必定能弄懂這些生命週期做用域。(例子項目源碼裏都有,能夠去試着實際運行下,更易理解)

瞬時單例(Instance Per Dependency)

也叫每一個依賴一個實例。
即每次從容器裏拿出來的都是全新對象,至關於每次都new出一個。
在其餘容器中也被標識爲 'Transient'(瞬時) 或 'Factory'(工廠)。

  • 註冊

使用InstancePerDependency()方法標註,若是不標註,這也是默認的選項。如下兩種註冊方法是等效的:

//不指定,默認就是瞬時的
builder.RegisterType<Model.StudentEntity>();

//指定其生命週期域爲瞬時
builder.RegisterType<Model.StudentEntity>().InstancePerDependency();
  • 解析:
using (var scope = Container.Instance.BeginLifetimeScope())
{
    var stu1 = scope.Resolve<Model.StudentEntity>();
    Console.WriteLine($"第1次打印:{stu1.Name}");
    stu1.Name = "張三";
    Console.WriteLine($"第2次打印:{stu1.Name}");

    var stu2 = scope.Resolve<Model.StudentEntity>();
    Console.WriteLine($"第2次打印:{stu2.Name}");
}

上面解析了2次,有兩個實例,stu1和stu2指向不一樣的兩塊內存,彼此之間沒有關係。
打印結果:

全局單例(Single Instance)

即全局只有一個實例,在根容器和全部嵌套做用域內,每次解析返回的都是同一個實例。

  • 註冊

使用SingleInstance()方法標識:

builder.RegisterType<Model.StudentEntity>().SingleInstance();
  • 解析:
//直接從根域內解析(單例下可使用,其餘不建議這樣直接從根域內解析)
var stu1 = Container.Instance.Resolve<Model.StudentEntity>();
stu1.Name = "張三";
Console.WriteLine($"第1次打印:{stu1.Name}");

using (var scope1 = Container.Instance.BeginLifetimeScope())
{
    var stu2 = scope1.Resolve<Model.StudentEntity>();
    Console.WriteLine($"第2次打印:{stu2.Name}");

    stu1.Name = "李四";
}
using (var scope2 = Container.Instance.BeginLifetimeScope())
{
    var stu3 = scope2.Resolve<Model.StudentEntity>();
    Console.WriteLine($"第3次打印:{stu3.Name}");
}

上面的stu一、stu二、stu3都是同一個實例,在內存上它們指向同一個內存塊。
打印結果:

域內單例(Instance Per Lifetime Scope)

即在每一個生命週期域內是單例的。

  • 註冊
    使用InstancePerLifetimeScope()方法標識:
x.RegisterType<Model.StudentEntity>().InstancePerLifetimeScope();
  • 解析
//子域一
using (var scope1 = Container.Instance.BeginLifetimeScope())
{
    var stu1 = scope1.Resolve<Model.StudentEntity>();
    Console.WriteLine($"第1次打印:{stu1.Name}");
    
    stu1.Name = "張三";

    var stu2 = scope1.Resolve<Model.StudentEntity>();
    Console.WriteLine($"第2次打印:{stu2.Name}");
}
//子域二
using (var scope2 = Container.Instance.BeginLifetimeScope())
{
    var stuA = scope2.Resolve<Model.StudentEntity>();
    Console.WriteLine($"第3次打印:{stuA.Name}");
    
    stuA.Name = "李四";

    var stuB = scope2.Resolve<Model.StudentEntity>();
    Console.WriteLine($"第4次打印:{stuB.Name}");
}

如上,在子域一中,雖然解析了2次,可是2次解析出的都是同一個實例,即stu1和stu2指向同一個內存塊Ⅰ。
子域二也同樣,stuA和stuB指向同一個內存塊Ⅱ,可是內存塊Ⅰ和內存塊Ⅱ卻不是同一塊。
打印結果以下,第1次和第3次爲null:

匹配域內單例(Instance Per Matching Lifetime Scope)

即每一個匹配的生命週期做用域一個實例。
該域類型實際上是上面的「域內單例」的其中一種,不同的是它容許咱們給域「打標籤」,只要在這個特定的標籤域內就是單例的。

  • 註冊
    使用InstancePerMatchingLifetimeScope(string tagName)方法註冊:
builder.RegisterType<Worker>().InstancePerMatchingLifetimeScope("myTag");
  • 解析
//myScope標籤子域一
using (var myScope1 = Container.Instance.BeginLifetimeScope("myTag"))
{
    var stu1 = myScope1.Resolve<Model.StudentEntity>();
    stu1.Name = "張三";
    Console.WriteLine($"第1次打印:{stu1.Name}");

    var stu2 = myScope1.Resolve<Model.StudentEntity>();
    Console.WriteLine($"第2次打印:{stu2.Name}");
    //解析了2次,但2次都是同一個實例(stu1和stu2指向同一個內存塊Ⅰ)
}
//myScope標籤子域二
using (var myScope2 = Container.Instance.BeginLifetimeScope("myTag"))
{
    var stuA = myScope2.Resolve<Model.StudentEntity>();
    Console.WriteLine($"第3次打印:{stuA.Name}");
    //由於標籤域內已註冊過,因此能夠解析成功
    //可是由於和上面不是同一個子域,因此解析出的實例stuA與以前的並非同一個實例,指向另外一個內存塊Ⅱ
}
//無標籤子域三
using (var noTagScope = Container.Instance.BeginLifetimeScope())
{
    try
    {
        var stuOne = noTagScope.Resolve<Model.StudentEntity>();//會報異常
        Console.WriteLine($"第4次正常打印:{stuOne.Name}");
    }
    catch (Exception e)
    {
        Console.WriteLine($"第4次異常打印:{e.Message}");
    }
    //由於StudentEntity只被註冊到帶有myScope標籤域內,因此這裏解析不到,報異常
}

打印結果:

須要注意:

  • 第3次打印爲null,不一樣子域即便標籤相同,但也是不一樣子域,因此域之間不是同一個實例
  • 在其餘標籤的域內(包括無標籤域)解析,會報異常
每次請求內單例(Instance Per Request)

該種類型適用於「request」類型的應用,好比MVC和WebApi。
其實質其實又是上一種的「指定域內單例」的一種特殊狀況:AutoFac內有一個靜態字符串叫Autofac.Core.Lifetime.MatchingScopeLifetimeTags.RequestLifetimeScopeTag,其值爲"AutofacWebRequest",當「指定域內單例」打的標籤是這個字符串時,那它就是「每次請求內單例」了。

  • 註冊
    使用InstancePerRequest()方法標註:
builder.RegisterType<Model.StudentEntity>().InstancePerRequest();

也可使用上面的域內單例的註冊法(可是不建議):

//使用靜態字符串標記
builder.RegisterType<Model.StudentEntity>().InstancePerMatchingLifetimeScope(Autofac.Core.Lifetime.MatchingScopeLifetimeTags.RequestLifetimeScopeTag);
//或者直接寫明字符串
builder.RegisterType<Model.StudentEntity>().InstancePerMatchingLifetimeScope("AutofacWebRequest");

這裏用控制檯程序很差舉例子就不寫解析代碼了,要理解「每次請求內單例」的做用,最好的例子就是EF中的DBContext,咱們在一次request請求內,即便是用到了多個Service和多個Repository,也只須要一個數據庫實例,這樣即能減小數據庫實例初始化的消耗,還能實現事務的功能。

.NET Core的生命週期做用域(Service lifetimes)

相比於AutoFac的豐富複雜,.NET Core就比較簡單粗暴了,只要3種類型:

瞬時實例(Transient)

與AutoFac的瞬時實例(Instance Per Dependency)相同,每次都是全新的實例。
使用AddTransient()註冊:

services.AddTransient<IStudentService, StudentService>();
請求內單例(Scoped)

其意義與AutoFac的請求內單例(Instance Per Request)相同,但實際若是真正在.NET Core中使用使用AutoFac的話,應該使用AutoFac的域內單例(Instance Per LifetimeScope)來代替。
緣由是.NET Core框架自帶的DI(Microsoft.Extensions.DependencyInjection)全權接管了請求和生命週期做用域的建立,因此AutoFac沒法控制,可是使用域內單例(Instance Per LifetimeScope)能夠實現相同的效果。
使用AddScoped()註冊:

services.AddScoped<IStudentService, StudentService>();
單例(Singleton)

與AutoFac的單例(Single Instance)相同。
使用AddSingleton();註冊:

services.AddSingleton<StudentEntity>();

第二部分:.NET Framework Web程序AutoFac注入

MVC項目

思路很簡單,三步走:

  1. 新建AutoFac容器

  2. 初始化容器,向容器註冊全部須要的依賴對象

  3. 將AutoFac解析器設置爲系統的依賴解析器(Dependency Resolver)

MVC容器

除了AutoFac主包以外,還須要Nuget導入AutoFac.Mvc5包:

容器代碼:

using System;
using System.Linq;
using System.Reflection;
//
using Autofac;
using Autofac.Integration.Mvc;
//
using Ray.EssayNotes.AutoFac.Repository.IRepository;
using Ray.EssayNotes.AutoFac.Repository.Repository;


namespace Ray.EssayNotes.AutoFac.Infrastructure.Ioc
{
    /// <summary>
    /// .net framework MVC程序容器
    /// </summary>
    public static class MvcContainer
    {
        public static IContainer Instance;

        /// <summary>
        /// 初始化MVC容器
        /// </summary>
        /// <param name="func"></param>
        /// <returns></returns>
        public static System.Web.Mvc.IDependencyResolver Init(Func<ContainerBuilder, ContainerBuilder> func = null)
        {
            //新建容器構建器,用於註冊組件和服務
            var builder = new ContainerBuilder();
            //註冊組件
            MyBuild(builder); 
            func?.Invoke(builder);
            //利用構建器建立容器
            Instance = builder.Build();

            //返回針對MVC的AutoFac解析器
            return new AutofacDependencyResolver(Instance);
        }

        public static void MyBuild(ContainerBuilder builder)
        {
            Assembly[] assemblies = Helpers.ReflectionHelper.GetAllAssembliesWeb();

            //註冊倉儲 && Service
            builder.RegisterAssemblyTypes(assemblies)//程序集內全部具象類(concrete classes)
                .Where(cc => cc.Name.EndsWith("Repository") |//篩選
                             cc.Name.EndsWith("Service"))
                .PublicOnly()//只要public訪問權限的
                .Where(cc => cc.IsClass)//只要class型(主要爲了排除值和interface類型)
                .AsImplementedInterfaces();//自動以其實現的全部接口類型暴露(包括IDisposable接口)

            //註冊泛型倉儲
            builder.RegisterGeneric(typeof(BaseRepository<>)).As(typeof(IBaseRepository<>));

            //註冊Controller
            //方法1:本身根據反射註冊
            //builder.RegisterAssemblyTypes(assemblies)
            //    .Where(cc => cc.Name.EndsWith("Controller"))
            //    .AsSelf();
            //方法2:用AutoFac提供的專門用於註冊MvcController的擴展方法
            Assembly mvcAssembly = assemblies.FirstOrDefault(x => x.FullName.Contains(".NetFrameworkMvc"));
            builder.RegisterControllers(mvcAssembly);
        }
    }
}

這裏Init()初始化函數返回類型變成了System.Web.Mvc.IDependencyResolver接口,即MVC的系統依賴解析器。
AutoFac本身封裝了一個AutofacDependencyResolver類(AutoFac依賴解析器類)實現了這個接口,因此直接new一個AutofacDependencyResolver類返回,等下把這個AutoFac依賴解析器類設置爲MVC的系統依賴解析器就能夠了。

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Reflection;
using System.Web.Mvc;

namespace Autofac.Integration.Mvc
{
  /// <summary>
  /// Autofac implementation of the <see cref="T:System.Web.Mvc.IDependencyResolver" /> interface.
  /// </summary>
  public class AutofacDependencyResolver : IDependencyResolver
  {
        //內部實現
        //......
  }

項目主程序:

  • Global.asax啓動項

啓動時初始化容器,並把AutoFac生成的解析器設置爲系統依賴解析器:

using System.Web.Mvc;
using System.Web.Optimization;
using System.Web.Routing;
//
using Ray.EssayNotes.AutoFac.Infrastructure.Ioc;


namespace Ray.EssayNotes.AutoFac.NetFrameworkMvc
{
    public class MvcApplication : System.Web.HttpApplication
    {
        protected void Application_Start()
        {
            AreaRegistration.RegisterAllAreas();
            FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
            RouteConfig.RegisterRoutes(RouteTable.Routes);
            BundleConfig.RegisterBundles(BundleTable.Bundles);

            //初始化容器,並返回適用於MVC的AutoFac解析器
            System.Web.Mvc.IDependencyResolver autoFacResolver = MvcContainer.Init();
            //將AutoFac解析器設置爲系統DI解析器
            DependencyResolver.SetResolver(autoFacResolver);
        }
    }
}

其中DependencyResolver.SetResolver()爲MVC封裝的一個靜態方法,用於設置MVC的依賴解析器。
其參數只要是實現了System.Web.Mvc.IDependencyResolver接口的對象均可以,AutoFac本身封裝的解析器AutofacDependencyResolver類實現了這個接口,因此能夠傳進來,從而實現了讓AutoFac接管MVC的依賴注入。

  • 學生控制器:

直接利用構造注入就能夠了:

using System.Web.Mvc;
//
using Ray.EssayNotes.AutoFac.Service.IService;


namespace Ray.EssayNotes.AutoFac.NetFrameworkMvc.Controllers
{
    /// <summary>
    /// 學生Api
    /// </summary>
    public class StudentController : Controller
    {
        private readonly IStudentService _studentService;

        /// <summary>
        /// 構造注入
        /// </summary>
        /// <param name="studentService"></param>
        public StudentController(IStudentService studentService)
        {
            _studentService = studentService;
        }

        /// <summary>
        /// 獲取學生姓名
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        public string GetStuNameById(long id)
        {
            return _studentService.GetStuName(id);
        }
    }
}

運行調用Api

WebApi項目

和MVC同樣,思路很簡單,三步走:

  1. 新建AutoFac容器

  2. 初始化容器,向容器註冊全部須要的依賴對象

  3. 將AutoFac解析器設置爲系統的依賴解析器(Dependency Resolver)

Api容器

除了AutoFac主包以外,還須要Nuget導入AutoFac.WebApi2包:

容器代碼:

using System;
using System.Linq;
using System.Reflection;
//
using Autofac;
using Autofac.Integration.WebApi;
//
using Ray.EssayNotes.AutoFac.Repository.Repository;
using Ray.EssayNotes.AutoFac.Repository.IRepository;


namespace Ray.EssayNotes.AutoFac.Infrastructure.Ioc
{
    /// <summary>
    /// .NET Framework WebApi容器
    /// </summary>
    public static class ApiContainer
    {
        public static IContainer Instance;

        /// <summary>
        /// 初始化Api容器
        /// </summary>
        /// <param name="func"></param>
        public static System.Web.Http.Dependencies.IDependencyResolver Init(Func<ContainerBuilder, ContainerBuilder> func = null)
        {
            //新建容器構建器,用於註冊組件和服務
            var builder = new ContainerBuilder();
            //註冊組件
            MyBuild(builder);
            func?.Invoke(builder);
            //利用構建器建立容器
            Instance = builder.Build();

            //返回針對WebApi的AutoFac解析器
            return new AutofacWebApiDependencyResolver(Instance);
        }

        public static void MyBuild(ContainerBuilder builder)
        {
            var assemblies = Helpers.ReflectionHelper.GetAllAssembliesWeb();

            //註冊倉儲 && Service
            builder.RegisterAssemblyTypes(assemblies)//程序集內全部具象類(concrete classes)
                .Where(cc => cc.Name.EndsWith("Repository") |//篩選
                             cc.Name.EndsWith("Service"))
                .PublicOnly()//只要public訪問權限的
                .Where(cc => cc.IsClass)//只要class型(主要爲了排除值和interface類型)
                .AsImplementedInterfaces();//自動以其實現的全部接口類型暴露(包括IDisposable接口)

            //註冊泛型倉儲
            builder.RegisterGeneric(typeof(BaseRepository<>)).As(typeof(IBaseRepository<>));

            //註冊ApiController
            //方法1:本身根據反射註冊
            //Assembly[] controllerAssemblies = assemblies.Where(x => x.FullName.Contains(".NetFrameworkApi")).ToArray();
            //builder.RegisterAssemblyTypes(controllerAssemblies)
            //    .Where(cc => cc.Name.EndsWith("Controller"))
            //    .AsSelf();
            //方法2:用AutoFac提供的專門用於註冊ApiController的擴展方法
            Assembly mvcAssembly = assemblies.FirstOrDefault(x => x.FullName.Contains(".NetFrameworkApi"));
            builder.RegisterApiControllers(mvcAssembly);
        }
    }
}

這裏Init()初始化函數返回類型變成了System.Web.Http.Dependencies.IDependencyResolver接口,即WebApi的系統依賴解析器。
AutoFac本身封裝了一個AutofacWebApiDependencyResolver類(AutoFac針對WebApi的依賴解析器類)實現了這個接口,因此直接new一個AutofacWebApiDependencyResolver類返回,等下把這個AutoFac依賴解析器類設置爲WebApi的系統依賴解析器就能夠了。

WebApi主程序

  • Global.asax啓動項

在項目啓動時初始化容器:

using System.Web.Http;
using System.Web.Mvc;
using System.Web.Optimization;
using System.Web.Routing;
//
using Ray.EssayNotes.AutoFac.Infrastructure.Ioc;


namespace Ray.EssayNotes.AutoFac.NetFrameworkApi
{
    public class WebApiApplication : System.Web.HttpApplication
    {
        protected void Application_Start()
        {
            AreaRegistration.RegisterAllAreas();
            GlobalConfiguration.Configure(WebApiConfig.Register);
            FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
            RouteConfig.RegisterRoutes(RouteTable.Routes);
            BundleConfig.RegisterBundles(BundleTable.Bundles);

            //初始化容器,並返回適用於WebApi的AutoFac解析器
            System.Web.Http.Dependencies.IDependencyResolver autoFacResolver = ApiContainer.Init();
            //獲取HttpConfiguration
            HttpConfiguration config = GlobalConfiguration.Configuration;
            //將AutoFac解析器設置爲系統DI解析器
            config.DependencyResolver = autoFacResolver;
        }
    }
}

這裏跟上面的MVC項目不太同樣,是經過HttpConfiguration對象來設置依賴解析器的,可是原理相同,不贅述了。

  • 學生控制器:

直接利用構造函數注入便可:

using System.Web.Http;
//
using Ray.EssayNotes.AutoFac.Service.IService;


namespace Ray.EssayNotes.AutoFac.NetFrameworkApi.Controllers
{
    /// <summary>
    /// 學生Api
    /// </summary>
    public class StudentController : ApiController
    {
        private readonly IStudentService _studentService;

        /// <summary>
        /// 構造注入
        /// </summary>
        /// <param name="studentService"></param>
        public StudentController(IStudentService studentService)
        {
            _studentService = studentService;
        }

        /// <summary>
        /// 獲取學生姓名
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        [HttpGet]
        [Route("Student/GetStuNameById")]
        public string GetStuNameById(long id)
        {
            return _studentService.GetStuName(123);
        }
    }
}

運行調用接口

第三部分:.NET Core的DI

自帶的DI

與.NET Framework不一樣,.NET Core把DI提到了很是重要的位置,其框架自己就集成了一套DI容器。
針對其自帶DI,主要理解兩個對象,IServiceCollection和 IServiceProvider。

  • IServiceCollection

用於向容器註冊服務,能夠和AutoFac的ContainerBuilder(容器構建器)類比。

  • IServiceProvider

負責從容器中向外部提供實例,能夠和AutoFac的解析的概念類比。

註冊的地方就在主程序下的startup類中。

可是其自己的註冊語法並無AutoFac那麼豐富,泛型註冊、批量註冊這些全都沒有,只有下面這種最基礎的一個一個註冊的形式:

using System.Linq;
using System.Reflection;
//
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
//
using Ray.EssayNotes.AutoFac.Infrastructure.CoreIoc.Extensions;
using Ray.EssayNotes.AutoFac.Infrastructure.CoreIoc.Helpers;


namespace Ray.EssayNotes.AutoFac.CoreApi
{
    public class Startup
    {
        public IConfiguration Configuration { get; }

        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
            //註冊
            //自定義註冊
            //註冊倉儲
            services.AddScoped<ITeacherRepository, TeacherRepository>();
            services.AddScoped<IStudentRepository, StudentRepository>();
            services.AddScoped<IBaseRepository<StudentEntity>, BaseRepository<StudentEntity>>();
            services.AddScoped<IBaseRepository<TeacherEntity>, BaseRepository<TeacherEntity>>();
            services.AddScoped<IBaseRepository<BookEntity>, BaseRepository<BookEntity>>();
            //註冊Service
            services.AddScoped<IStudentService, StudentService>();
            services.AddScoped<ITeacherService, TeacherService>();
            services.AddScoped<IBookService, BookService>();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
                app.UseHsts();
            }

            app.UseHttpsRedirection();
            app.UseMvc();
        }
    }
}

因此,你們一般都會本身去擴展這些註冊方法,以實現一些和AutoFac同樣的便捷的註冊操做,下面我根據反射寫了一個小擴展,寫的比較簡單潦草,能夠參考下:

擴展

擴展代碼:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
//
using Microsoft.Extensions.DependencyInjection;
//
using Ray.EssayNotes.AutoFac.Model;
using Ray.EssayNotes.AutoFac.Repository.IRepository;
using Ray.EssayNotes.AutoFac.Repository.Repository;
using Ray.EssayNotes.AutoFac.Service.IService;
using Ray.EssayNotes.AutoFac.Service.Service;


namespace Ray.EssayNotes.AutoFac.Infrastructure.CoreIoc.Extensions
{
    /// <summary>
    /// asp.net core註冊擴展
    /// </summary>
    public static class RegisterExtension
    {
        /// <summary>
        /// 反射批量註冊
        /// </summary>
        /// <param name="services"></param>
        /// <param name="assembly"></param>
        /// <param name="serviceLifetime"></param>
        /// <returns></returns>
        public static IServiceCollection AddAssemblyServices(this IServiceCollection services, Assembly assembly, ServiceLifetime serviceLifetime = ServiceLifetime.Scoped)
        {
            var typeList = new List<Type>();//全部符合註冊條件的類集合

            //篩選當前程序集下符合條件的類
            List<Type> types = assembly.GetTypes().
                Where(t => t.IsClass && !t.IsGenericType)//排除了泛型類
                .ToList();

            typeList.AddRange(types);
            if (!typeList.Any()) return services;

            var typeDic = new Dictionary<Type, Type[]>(); //待註冊集合<class,interface>
            foreach (var type in typeList)
            {
                var interfaces = type.GetInterfaces();   //獲取接口
                typeDic.Add(type, interfaces);
            }

            //循環實現類
            foreach (var instanceType in typeDic.Keys)
            {
                Type[] interfaceTypeList = typeDic[instanceType];
                if (interfaceTypeList == null)//若是該類沒有實現接口,則以其自己類型註冊
                {
                    services.AddServiceWithLifeScoped(null, instanceType, serviceLifetime);
                }
                else//若是該類有實現接口,則循環其實現的接口,一一配對註冊
                {
                    foreach (var interfaceType in interfaceTypeList)
                    {
                        services.AddServiceWithLifeScoped(interfaceType, instanceType, serviceLifetime);
                    }
                }
            }
            return services;
        }

        /// <summary>
        /// 暴露類型可空註冊
        /// (若是暴露類型爲null,則自動以其自己類型註冊)
        /// </summary>
        /// <param name="services"></param>
        /// <param name="interfaceType"></param>
        /// <param name="instanceType"></param>
        /// <param name="serviceLifetime"></param>
        private static void AddServiceWithLifeScoped(this IServiceCollection services, Type interfaceType, Type instanceType, ServiceLifetime serviceLifetime)
        {
            switch (serviceLifetime)
            {
                case ServiceLifetime.Scoped:
                    if (interfaceType == null) services.AddScoped(instanceType);
                    else services.AddScoped(interfaceType, instanceType);
                    break;
                case ServiceLifetime.Singleton:
                    if (interfaceType == null) services.AddSingleton(instanceType);
                    else services.AddSingleton(interfaceType, instanceType);
                    break;
                case ServiceLifetime.Transient:
                    if (interfaceType == null) services.AddTransient(instanceType);
                    else services.AddTransient(interfaceType, instanceType);
                    break;
                default:
                    throw new ArgumentOutOfRangeException(nameof(serviceLifetime), serviceLifetime, null);
            }
        }
    }
}

利用這個擴展,咱們在startup裏就能夠用相似AutoFac的語法來註冊了。

主程序

註冊代碼:

using System.Linq;
using System.Reflection;
//
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
//
using Ray.EssayNotes.AutoFac.Infrastructure.CoreIoc.Extensions;
using Ray.EssayNotes.AutoFac.Infrastructure.CoreIoc.Helpers;


namespace Ray.EssayNotes.AutoFac.CoreApi
{
    public class Startup
    {
        public IConfiguration Configuration { get; }

        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
            //註冊
            
            //自定義批量註冊
            Assembly[] assemblies = ReflectionHelper.GetAllAssembliesCoreWeb();
            //註冊repository
            Assembly repositoryAssemblies = assemblies.FirstOrDefault(x => x.FullName.Contains(".Repository"));
            services.AddAssemblyServices(repositoryAssemblies);
            //註冊service  
            Assembly serviceAssemblies = assemblies.FirstOrDefault(x => x.FullName.Contains(".Service"));
            services.AddAssemblyServices(serviceAssemblies);
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
                app.UseHsts();
            }

            app.UseHttpsRedirection();
            app.UseMvc();
        }
    }
}

其實AutoFac針對.NET Core已經幫咱們集成了一套註冊的擴展,咱們能夠經過兩種方式把AutoFac引入.NET Core:一種是將AutoFac容器做爲輔助容器,與.NET Core的DI共存,咱們能夠同時向兩個容器裏註冊組件;一種是讓AutoFac容器接管.NET Core的DI,註冊時只選擇往Autofac容器中註冊。
下面就分別實現下這兩種引入AutoFac的方式。

AutoFac做爲輔助註冊

Core容器

先按照以前寫AutoFac容器的方法,新建一個針對Core的AutoFac容器:

using System;
//
using Microsoft.Extensions.DependencyInjection;
//
using Autofac;
using Autofac.Extensions.DependencyInjection;
//
using Ray.EssayNotes.AutoFac.Repository.IRepository;
using Ray.EssayNotes.AutoFac.Repository.Repository;


namespace Ray.EssayNotes.AutoFac.Infrastructure.CoreIoc
{
    /// <summary>
    /// Core的AutoFac容器
    /// </summary>
    public static class CoreContainer
    {
        /// <summary>
        /// 容器實例
        /// </summary>
        public static IContainer Instance;

        /// <summary>
        /// 初始化容器
        /// </summary>
        /// <param name="services"></param>
        /// <param name="func"></param>
        /// <returns></returns>
        public static IServiceProvider Init(IServiceCollection services, Func<ContainerBuilder, ContainerBuilder> func = null)
        {
            //新建容器構建器,用於註冊組件和服務
            var builder = new ContainerBuilder();
            //將Core自帶DI容器內的服務遷移到AutoFac容器
            builder.Populate(services);
            //自定義註冊組件
            MyBuild(builder);
            func?.Invoke(builder);
            //利用構建器建立容器
            Instance = builder.Build();

            return new AutofacServiceProvider(Instance);
        }

        /// <summary>
        /// 自定義註冊
        /// </summary>
        /// <param name="builder"></param>
        public static void MyBuild(this ContainerBuilder builder)
        {
            var assemblies = Helpers.ReflectionHelper.GetAllAssembliesCoreWeb();

            //註冊倉儲 && Service
            builder.RegisterAssemblyTypes(assemblies)
                .Where(cc => cc.Name.EndsWith("Repository") |//篩選
                             cc.Name.EndsWith("Service"))
                .PublicOnly()//只要public訪問權限的
                .Where(cc => cc.IsClass)//只要class型(主要爲了排除值和interface類型)
                .AsImplementedInterfaces();//自動以其實現的全部接口類型暴露(包括IDisposable接口)

            //註冊泛型倉儲
            builder.RegisterGeneric(typeof(BaseRepository<>)).As(typeof(IBaseRepository<>));

            /*
            //註冊Controller
            Assembly[] controllerAssemblies = assemblies.Where(x => x.FullName.Contains(".CoreApi")).ToArray();
            builder.RegisterAssemblyTypes(controllerAssemblies)
                .Where(cc => cc.Name.EndsWith("Controller"))
                .AsSelf();
                */
        }
    }
}

主程序

在主程序中新建一個StartupWithAutoFac類,用於註冊。
StartupWithAutoFac代碼:

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
//
using Autofac;
//
using Ray.EssayNotes.AutoFac.Infrastructure.CoreIoc;


namespace Ray.EssayNotes.AutoFac.CoreApi
{
    public class StartupWithAutoFac
    {
        public IConfiguration Configuration { get; }

        public StartupWithAutoFac(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
        }

        /// <summary>
        /// 利用該方法可使用AutoFac輔助註冊,該方法在ConfigureServices()以後執行,因此當發生覆蓋註冊時,之後者爲準。
        /// 不要再利用構建器去建立AutoFac容器了,系統已經接管了。
        /// </summary>
        /// <param name="builder"></param>
        public void ConfigureContainer(ContainerBuilder builder)
        {
            builder.MyBuild();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
                app.UseHsts();
            }

            app.UseHttpsRedirection();
            app.UseMvc();
        }
    }
}

這裏其餘地方與原startup都相同,只是多了一個ConfigureContainer()方法,在該方法內能夠按照AutoFac的語法進行自由註冊。

而後修改program類,將AutoFac hook進管道,並將StartupWithAutoFac類指定爲註冊入口:

using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;


namespace Ray.EssayNotes.AutoFac.CoreApi
{
    public class Program
    {
        public static void Main(string[] args)
        {
            CreateWebHostBuilder(args).Build().Run();
        }

        public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
            WebHost.CreateDefaultBuilder(args)
                //第一種:使用自帶DI
                //.UseStartup<Startup>();

                //第二種:添加AutoFac做爲輔助容器
                .HookAutoFacIntoPipeline()
                .UseStartup<StartupWithAutoFac>();

                //第三種:添加AutoFac接管依賴注入
                //.UseStartup<StartupOnlyAutoFac>();
    }
}

AutoFac接管註冊

容器

仍是上面的CoreContainer容器。

主程序

主程序新建一個StartupOnlyAutoFac類,
代碼以下:

using System;
//
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
//
using Ray.EssayNotes.AutoFac.Infrastructure.CoreIoc;


namespace Ray.EssayNotes.AutoFac.CoreApi
{
    public class StartupOnlyAutoFac
    {
        public IConfiguration Configuration { get; }

        public StartupOnlyAutoFac(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        // This method gets called by the runtime. Use this method to add services to the container.
        public IServiceProvider ConfigureServices(IServiceCollection services)
        {
            services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);

            return CoreContainer.Init(services);
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
                app.UseHsts();
            }

            app.UseHttpsRedirection();
            app.UseMvc();
        }
    }
}

這裏直接改了ConfigureServices()方法的返回類型,而後在該方法內直接利用AutoFac註冊。

最後固然也要更改下program類,指定StartupOnlyAutoFac類爲註冊入口。
代碼:

using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;


namespace Ray.EssayNotes.AutoFac.CoreApi
{
    public class Program
    {
        public static void Main(string[] args)
        {
            CreateWebHostBuilder(args).Build().Run();
        }

        public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
            WebHost.CreateDefaultBuilder(args)
                //第一種:使用自帶DI
                //.UseStartup<Startup>();

                //第二種:添加AutoFac做爲輔助容器
                //.HookAutoFacIntoPipeline()
                //.UseStartup<StartupWithAutoFac>();

                //第三種:添加AutoFac接管依賴注入
                .UseStartup<StartupOnlyAutoFac>();
    }
}

運行調用

  • StudentController
using Microsoft.AspNetCore.Mvc;
//
using Ray.EssayNotes.AutoFac.Service.IService;


namespace Ray.EssayNotes.AutoFac.CoreApi.Controllers
{
    [ApiController]
    public class StudentController : ControllerBase
    {
        private readonly IStudentService _studentService;
        public StudentController(IStudentService studentService)
        {
            _studentService = studentService;
        }

        [Route("Student/GetStuNameById")]
        public string GetStuNameById(long id)
        {
            return _studentService.GetStuName(id);
        }
    }
}
  • 調用

相關文章
相關標籤/搜索