zoukankan      html  css  js  c++  java
  • 【半小时大话.net依赖注入】(下)详解AutoFac+实战Mvc、Api以及.NET Core的依赖注入

    系列目录

    1. 上|理论基础+实战控制台程序实现AutoFac注入

    2. 下|详解AutoFac+实战Mvc、Api以及.NET Core的依赖注入

    前言

    本来计划是五篇文章的,每章发个半小时随便翻翻就能懂,但是第一篇发了之后,我发现.NET环境下很多人对IoC和DI都很排斥,搞得评论区异常热闹。

    同一个东西,在Java下和在.NET下能有这么大的差异,也是挺有意思的一件事情。

    所以我就把剩下四篇内容精简再精简,合成一篇了,权当是写给自己的一个备忘记录了。

    GitHub源码地址:https://github.com/WangRui321/Ray.EssayNotes.AutoFac

    源码是一个虚构的项目框架,类似于样例性质的代码或者测试程序,里面很多注释,对理解DI,或怎么在MVC、WebApi和Core Api分别实现依赖注入有很好的帮助效果。
    所以,以下内容,配合源码食用效果更佳~

    第一部分:详解AutoFac用法

    名词解释

    老规矩,理论先行。

    组件(Components)

    一串声明了它所提供服务和它所消费依赖的代码。

    可以理解为容器内的基本单元,一个容器内会被注册很多个组件,每个组件都有自己的信息:比如暴露的服务类型、生命周期域、绑定的具象对象等。

    服务(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}");
    }
    

    上面的stu1、stu2、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);
            }
        }
    }
    
    • 调用

  • 相关阅读:
    mybatis 批量插入时候的一个注意点
    centos7 kubernetes单机安装
    debug 模式缓慢
    那些年,我们误解的 JavaScript 闭包
    maven仓库的配置
    闭包
    docker 搭建自己的github
    docker 搭建小型的node开发环境。
    jquery validate
    使用ueditor中的setContent() 时经常报innerHtml错误(笔记)
  • 原文地址:https://www.cnblogs.com/RayWang/p/11165509.html
Copyright © 2011-2022 走看看