zoukankan      html  css  js  c++  java
  • 浅谈.Net Core中使用Autofac替换自带的DI容器

    为什么叫 浅谈 呢?就是字面上的意思,讲得比较浅,又不是不能用(这样是不对的)!!!

    Aufofac大家都不陌生了,说是.Net生态下最优秀的IOC框架那是一点都过分。用的人多了,使用教程也十分丰富,官网教程也比较详细(如果英文功底还不错的话)。

    那我为什么还要写这样一篇博客呢,一是用作学习笔记,二就是闲的。

    废话不多说,开始正文


    项目创建

    云创建一个.Net Core Api项目,然后再添加一个类库,大概就是下面这样的结构:

    新建一个类库项目,分别添加一个接口文件与类文件:

    就这样,我们的演示方案就搭建完成了,下面就到了演示阶段。

    方案演示

    原始方案

    俗话说的好,没有对象 new 一个就对了:

    1 [HttpGet]
    2 public string Original()
    3 {
    4     IUserService userService = new UserService();
    5     return userService.GetName("Original");
    6 }

    结果当然是没问题的:

     .Net Core自带DI

    微软给我们提供的 DI 解决方案。如果是小项目,需要注入的服务不多,简直无敌好用,缺点就是不能批量注入,下面我们来复习一下:

    先在 Startup 里面的 ConfigureServices 方法内注入(默认且只能构造函数注入

    services.AddScoped<IUserService, UserService>();

    然后在控制器中拿到刚才注入的服务:

     1 public class DefaultController : ControllerBase
     2 {
     3     private readonly IUserService userService;
     4     public DefaultController(IUserService _userService)
     5     {
     6         this.userService = _userService;
     7     }16 
    17     [HttpGet]
    18     public string CoreDI()
    19     {
    20         return userService.GetName("CoreDI");
    21     }
    22 }

    很显然,一点问题都没有:

     Autofac

    注意事项说在前面:

    在 .Net Core2 中一般是把 Startup 的 ConfigureServices 方法返回值类型改为IServiceProvider,然后通过构建Autofac容器并注入服务后返回。

    在 .Net Core3.0之后,集成方式做了部分调整

    下面演示的版本是.Net Core 3.1,也就是调整后的版本。

    1、先引用 Autofac 的包,看看这下载次数

    2、在 Program 中改用 Autofac 来实现依赖注入

    1 public static IHostBuilder CreateHostBuilder(string[] args) =>
    2     Host.CreateDefaultBuilder(args)
    3         // 就是这句
    4         .UseServiceProviderFactory(new AutofacServiceProviderFactory())
    5         .ConfigureWebHostDefaults(webBuilder =>
    6         {
    7             webBuilder.UseStartup<Startup>();
    8         });

    3、添加我们自定义的 Autofac 注册类,并注册我们需要的服务(默认构造函数注入,支持属性注入)

    1 public class AutofacModuleRegister : Autofac.Module
    2 {
    3     //重写Autofac管道Load方法,在这里注册注入
    4     protected override void Load(ContainerBuilder builder)
    5     {
    6         builder.RegisterType<UserService>().As<IUserService>();
    7    }
    8 }

    4、在 Startup 类中添加方法:ConfigureContainer,

    public void ConfigureContainer(ContainerBuilder builder)
    {
        // 直接用Autofac注册我们自定义的 
        builder.RegisterModule(new AutofacModuleRegister());
    }

    5、大功告成,控制器内的方法甚至不用去改

     1  public class DefaultController : ControllerBase
     2  {
     3      private readonly IUserService userService;
     4      public DefaultController(IUserService _userService)
     5      {
     6          this.userService = _userService;
     7      }
     8  
     9      [HttpGet]
    10      public string Autofac()
    11      {
    12          return userService.GetName("Autofac");
    13      }
    14  }

     演示到这里就结束了,是不是感觉 Autofac 比自带的 DI 还要麻烦。其实不然,下面我们就来看看 Autofac 对比自带 DI 的一些特有特性。

    不同的特性

    批量注入

    之前的项目我们有了用户 UserService,需求更新,加入了商品(ProductService),有了商品那又怎么能少得了订单(OrderService),那后面是不是还得有售后、物流、仓库、营销......

    如果是.Net Core 自带的注入框架,那就只能不停的:

    1 services.AddScoped<IProductService, ProductService>();
    2 services.AddScoped<IOrderService, OrderService>();
    3 ......

    这时候,Autofac 的好处就体现出来了:批量注入。

    我们先回到上面的:AutofacModuleRegister 类,加入下面这段代码:

    1 // 服务项目程序集
    2 Assembly service = Assembly.Load("XXX.Service");
    3 // 服务接口项目程序集
    4 Assembly iservice = Assembly.Load("XXX.IService");
    5 builder.RegisterAssemblyTypes(service, iservice)
    6     .Where(t => t.FullName.EndsWith("Service") && !t.IsAbstract)
    7     .InstancePerLifetimeScope() 
    8     .AsImplementedInterfaces();

    上面的代码就是批量注入 XXX.Service 与 XXX.IService 项目下的服务与接口。

    注意:如果需要注入的服务没有 interfac ,那么 builder.RegisterAssemblyTypes 就只需要传一个程序集就OK了。如果服务与接口同在一个项目,那也是要传两个程序集的哦。

    然后我们在控制器去通过构造函数获取注入的实例:

    1 private readonly IUserService userService;
    2 private readonly IProductService productService;
    3 
    4 public DefaultController(IUserService _userService, IProductService _productService)
    5 {
    6     this.userService = _userService;
    7     this.productService = _productService;
    8 }

    再对之前的 Autofac 接口添油加醋:

    1 [HttpGet]
    2 public string Autofac()
    3 {
    4     var name = userService.GetName("Autofac");
    5     return productService.Buy(name, "批量注入");
    6 }

    结果自然是没有问题的,如果后续需要加入其它服务都不用再单独注入了,是不是优点就体现出来了。批量注入还有一些其它的玩法,比如筛选类名,筛选父类等。

    属性注入

    .Net Core 自带的 DI 框架与 Autofac 默认都是构造函数注入,官方建议也是构造函数注入。

    但是有些同学可能就不喜欢构造函数注入,再加上有些场景确实不适合构造函数注入(比如基类实体),所以 Autofac 也支持属性注入,下面我们来看看使用方法,在之前批量注入的基础上,我们简单改造一下:

    1 Assembly service = Assembly.Load("Autofac.Service");
    2 Assembly iservice = Assembly.Load("Autofac.Service");
    3 builder.RegisterAssemblyTypes(service, iservice)
    4     .Where(t => t.FullName.EndsWith("Service") && !t.IsAbstract) 
    5     .InstancePerLifetimeScope() 
    6     .AsImplementedInterfaces()
    7     .PropertiesAutowired(); // 属性注入

    对比构造函数注入,属性注入就多追加了 PropertiesAutowired() 函数,控制器内修改:

    1 public IUserService userService { get; set; }
    2 public IProductService productService { get; set; }

    注意:属性注入记得将属性的访问修饰符改为注册类可访问的修饰符,否则会注入失败。

    下面我们来看看使用效果:

    咦,怎么会空引用呢?原因大概就是 Controller 是由 Mvc 模块管理的,不在 IOC 容器内,所以在 Controller 中无法使用 Autofac 注入的实例。

    那为什么构造函数注入的时候又可以呢?大概或许可能他们都是构造函数注入吧...

    为什么是大概呢?因为我暂时也没有具体去深入研究到底是什么原因导致的,如果有一天,我想起来去研究了并且有结果了,我会在这里补上。

    我们先解决上面的问题先,在 Startup 的 ConfigureServices 方法底部加入如下代码:

    // 使用 ServiceBasedControllerActivator 替换 DefaultControllerActivator;
    // Controller 默认是由 Mvc 模块管理的,不在 Ioc 容器中。替换之后,将放在 Ioc 容器中。
    services.Replace(ServiceDescriptor.Transient<IControllerActivator, ServiceBasedControllerActivator>());

    然后回到我们的 AutofacModuleRegister 注入 Controller:

    builder.RegisterTypes(GetAssemblyTypes<Startup>(type => typeof(ControllerBase).IsAssignableFrom(type)))
                    .PropertiesAutowired();

    这样处理完后,属性注入就Ok了。

    存储并提取容器实例 

    我们在之前项目的基础上添加两个项目 Common 与 Entities,存放公共类与实体类。

    我们需要在实体类里面使用到 Common 项目中的某个类,结构如下:

        
       // 基类实体
       public class BaseEntity { public Class1 common_Class1 { get; set; } public string CreateId { get; set; } public void Create() { this.CreateId = common_Class1.getCurrentUserId(); } } // 公共类 public class Class1 { public string getCurrentUserId() { return Guid.NewGuid().ToString(); } }

    从上面的接口中我们可以看到,我需要将 Class1 通过属性注入到容器中:

    builder.RegisterType<Class1>().PropertiesAutowired().InstancePerLifetimeScope();

    我们先在 Controller 中看看效果:

    public Class1 class1 { get; set; }
    
    [HttpGet]
    public string Autofac()
    {
        return class1.getCurrentUserId();
    }

    很显然结果是没问题的:

     那我们再到 BaseEntity 中去试试看:

     咦,又出现空引用,注入失败了。其实这个问题很明显,我们使用的是 new 来实例化的 BaseEntity对象,没有遵循容器实例使用规则,自然就无法使用容器中的实例了。

    大家可以自己试一下,将 new 改为属性注入就没问题了,但是这种方案并不友好,下面要说的是另一种方案。

    我们再新添加一个公共类:ContainerHelper,并声明一个属性用来存储容器的实例:

    public static class ContainerHelper
    {
        public static ILifetimeScope ContainerBuilder { get; set; }
    }

    然后回到 Startup 中,在 Configure 方法的底部加入如下代码:

    ContainerHelper.ContainerBuilder = app.ApplicationServices.CreateScope().ServiceProvider.GetAutofacRoot();

    再回到实体类中去使用:

    public void Create()
    {
        if (common_Class1 == null)
        {
            using (var scope = ContainerHelper.ContainerBuilder.BeginLifetimeScope())
            {
                common_Class1 = scope.Resolve<Class1>();
            }
        }
        this.CreateId = common_Class1.getCurrentUserId();
    }

     


    Autofac 的替换方案暂时就写到这里了,后续如果有新的理解或心得会再做修改,浅谈嘛就真的是浅谈,有错误或补充的地方请大家不吝赐教。

    源码这里就不提供了,大家有耐心的可以跟着手敲一遍,虽然对理解没啥作用,但能使记忆更深刻一点。

  • 相关阅读:
    【转】shell脚本中echo显示内容带颜色的实现方法
    升级到windows10之后的骚操作,安装debian,centos7,支持linux、docker、kubectl命令
    【转】Gradle的使用教程
    【转】.net core开发windows服务
    是时候抛弃Postman了,试试直接在 VS Code上调试并共享你的REST API调用
    Qt-开发环境中的代码中文显示正常,但是运行后是乱码?
    Qt-QMenu的自定义内容-在右击列表中增加一个EDIT框
    2020年十一自驾出行大攻略
    OSG-使用VS2019编译整个源代码方法
    VS2019注册码
  • 原文地址:https://www.cnblogs.com/cool-net/p/14903129.html
Copyright © 2011-2022 走看看