夫天地者,万物之逆旅;光阴者,百代之过客。而浮生若梦,为欢几何?
.Net Core3.1下使用Autofac实现依赖注入

前言

Autofac是.NET领域最为流行的IOC框架之一,传说是速度最快的一个。它和C#语言的结合非常紧密,在使用过程中对你的应用的侵入性几乎为零,更容易与第三方的组件集成。主要优点如下(此段描述为转载):

  1. 它是C#语言联系很紧密,也就是说C#里的很多编程方式都可以为Autofac使用,例如可以用Lambda表达式注册组件

  2. 较低的学习曲线,学习它非常的简单,只要你理解了IoC和DI的概念以及在何时需要使用它们

  3. XML配置支持

  4. 自动装配

  5. 微软的Orchad开源程序使用的就是Autofac,从该源码可以看出它的方便和强大

准备工作

本文演示的项目时基于.Net Core3.1的,同时需要通过nuget下载安装以下几个dll文件:

基本用法

在前面的《基于Dapper的扩展方法来简单封装WebApi接口》一文中介绍过在.net core2.1中使用Autofac的方式,在ASP.NET Core 1.1 - 2.2 中, 你可以调用 WebHostBuilder 的 services.AddAutofac().。但这不适用于ASP.NET Core 3+ 或 .NET Core 3+ ,在 ASP.NET Core 3+ 需要你直接指定一个service provider factory而不是把它加入进service collection。基本用法的代码如下:

首先修改Progam中的CreateHostBuilder方法,使用autofac的容器工厂替换系统默认的容器:

public static IHostBuilder CreateHostBuilder(string[] args) =>
    Host.CreateDefaultBuilder(args)
        .UseServiceProviderFactory(new AutofacServiceProviderFactory())//使用autofac的容器工厂替换系统默认的容器
        .ConfigureWebHostDefaults(webBuilder =>
        {
            webBuilder.UseStartup<Startup>();
        });

然后在Startup中增加一个方法ConfigureContainer

public void ConfigureContainer(ContainerBuilder containerBuilder)
{
    //指定服务的注册
    containerBuilder.RegisterType<UserService>().As<IUserService>().InstancePerLifetimeScope().AsImplementedInterfaces();
    containerBuilder.RegisterType<ProductService>().As<IProductService>().InstancePerLifetimeScope().AsImplementedInterfaces();
    var container = containerBuilder.Build();
    IUserService userService = container.Resolve<IUserService>();
    IProductService productService = container.Resolve<IProductService>();
    userService.Show();
    productService.Show();
}

调试运行,我们发现ConfigureContainer的方法并没有被引用,但已经可以进到这个方法里并实现的服务的注册,这就是因为我们指定了UseServiceProviderFactory:

Autofac生命周期

简单介绍,详情请见参考资料:

//1、瞬时生命周期:注册之后,每次获取到的服务实例都不一样(默认的注册方式)
containerBuilder.RegisterType<UserService>().As<IUserService>().InstancePerDependency();
//2、单例生命周期:整个容器中获取的服务实例都是同一个
containerBuilder.RegisterType<UserService>().As<IUserService>().SingleInstance();
//3、作用域生命周期:在相同作用域下获取到的服务实例是相同的
containerBuilder.RegisterType<UserService>().As<IUserService>().InstancePerLifetimeScope();
//4、作用域生命周期:可以指定到某一个作用域,然后在相同作用域下共享服务实例
containerBuilder.RegisterType<UserService>().As<IUserService>().InstancePerMatchingLifetimeScope("My");
//5、http请求上下文的生命周期:在一次Http请求上下文中,共享一个组件实例。仅适用于asp.net mvc开发。
containerBuilder.RegisterType<UserService>().As<IUserService>().InstancePerRequest();
//6、拥有隐式关系类型的创建新的嵌套生命周期的作用域,在一个生命周期域中所拥有的实例创建的生命周期中,
//      每一个依赖组件或调用Resolve()方法创建一个单一的共享的实例,并且子生命周期域共享父生命周期域中的实例
containerBuilder.RegisterType<UserService>().InstancePerOwned<IUserService>();

实际项目中用法

上面的代码只是简单演示了下Autofac如何注册服务实例的,在实际使用时我们不会这样去写代码,每增加一个接口及其实现,都需要手动注册一下,这样的耦合度太高。因此我们需要进行代码优化,下面将使用反射的方式来实现服务的注册,改造上面的代码:

public void ConfigureContainer(ContainerBuilder containerBuilder)
{
    Assembly service = Assembly.Load("AspNetCore.Ioc.Service");
    Assembly iservice = Assembly.Load("AspNetCore.Ioc.Interface");
    containerBuilder.RegisterAssemblyTypes(service, iservice)
    .Where(t => t.FullName.EndsWith("Service") && !t.IsAbstract) //类名以service结尾,且类型不能是抽象的 
        .InstancePerLifetimeScope() //生命周期,,
        .AsImplementedInterfaces()
    .PropertiesAutowired(); //属性注入
}

运行结果如下

一个接口多个实现的服务注册

在实际应用中有这样一种场景,比如IUserService接口被多个类继承并实现,那么此时应该如何注册服务并实现调用呢?

其实上面Startup中的注册方式就已经满足服务的注册,只是需要在相应的Controller调用的地方修改即可,如:

public class UserController : Controller
{
    /// <summary>
    /// IUserService服务实现的集合
    /// </summary>
    private readonly IEnumerable<IUserService> _userServices = null;
    public UserController(IEnumerable<IUserService> userServices)
    {
        _userServices = userServices;
    }
    public IActionResult Index()
    {
        foreach (var item in _userServices)
        {
            item.Show();
        }
        return View();
    }
}

调试运行的结果如下

值得说明的是,一个接口有多个实现的情况,在注册服务的时候,可以选择一些策略来实现只注册其中某几个实例,示例代码如下:

//一个接口有多个实现:注册所有实现的服务实例
builder.RegisterAssemblyTypes(Assembly.Load("AspNetCore.Ioc.Service")).As<IUserService>();
//一个接口有多个实现:只注册以A结尾的服务实例
builder.RegisterAssemblyTypes(Assembly.Load("AspNetCore.Ioc.Service")).Where(c=>c.Name.EndsWith("A")).As<IUserService>();
//一个接口有多个实现:注册所有实现的服务实例,并排除UserServiceA服务实例
builder.RegisterAssemblyTypes(Assembly.Load("AspNetCore.Ioc.Service")).Except<UserServiceA>().As<IUserService>();

隔离服务注册的逻辑代码

为了简化Startup中的代码,还可以自定义一个MyAutofacModule的方式,将服务注册的代码抽离出来,放到单独的文件中。这时我们就需要新建一个MyAutofacModule类,并继承Autofac.Module,同时重写其中的Load方法,具体代码如下:

using System.Linq;
using System.Reflection;
using Autofac;
using Autofac.Configuration;
using Microsoft.Extensions.Configuration;
namespace AspNetCore.Ioc.Web.Utility
{
    public class MyAutofacModule : Autofac.Module
    {
        protected override void Load(ContainerBuilder builder)
        {
            //反射程序集方式服务注册
            Assembly service = Assembly.Load("AspNetCore.Ioc.Service");
            Assembly iservice = Assembly.Load("AspNetCore.Ioc.Interface");
            builder.RegisterAssemblyTypes(service, iservice)
            .Where(t => t.FullName.EndsWith("Service") && !t.IsAbstract) //类名以service结尾,且类型不能是抽象的 
                .InstancePerLifetimeScope() //作用域生命周期
                .AsImplementedInterfaces()
                .PropertiesAutowired(); //属性注入
        }
    }
}

重写Load方法中的逻辑其实就是将原本 写在Startup中的注册代码迁移到MyAutofacModule中,然后将Startup中的ConfigureContainer方法修改成如下:

public void ConfigureContainer(ContainerBuilder containerBuilder)
{
    containerBuilder.RegisterModule<MyAutofacModule>();
}

具体运行结果这里就不展示,和前面的一样。

配置文件的方式服务注册

为了让注册服务的方式更灵活,我们还可以通过配置文件的方式来实现,将所有的程序集信息放到配置文件中,这样便于后期的程序扩展。那么首先来看下配置文件应该如何写:

需要注意的是要将autofac.json文件的属性改成始终复制

autofac.json文件:

{
  "defaultAssembly": "AspNetCore.Ioc.Interface", //接口所在的程序集名称
  "components": [
    {
      "type": "AspNetCore.Ioc.Service.UserService,AspNetCore.Ioc.Service", //接口的实现 全名称
      "services": [
        {
          "type": "AspNetCore.Ioc.Interface.IUserService" // 接口的全名称
        }
      ],
      "instanceScope": "single-instance", //单例生命周期
      "injectProperties": true //是否支持属性注入
    },
    {
      "type": "AspNetCore.Ioc.Service.ProductService,AspNetCore.Ioc.Service", //接口的实现 全名称
      "services": [
        {
          "type": "AspNetCore.Ioc.Interface.IProductService" // 接口的全名称
        }
      ],
      "instanceScope": "single-instance", //单例生命周期
      "injectProperties": true //是否支持属性注入
    }
  ]
}

修改MyAutofacModule中的调用方法:

protected override void Load(ContainerBuilder builder)
{
    //Autofac 基于配置文件的服务注册
    IConfigurationBuilder configurationBuilder = new ConfigurationBuilder();
    configurationBuilder.AddJsonFile("Config/autofac.json");
    IConfigurationRoot root = configurationBuilder.Build();
    //开始读取配置文件中的内容
    ConfigurationModule module = new ConfigurationModule(root);
    //根据配置文件的内容注册服务
    builder.RegisterModule(module);
}

运行结果如下

总结

到这里基本就完成了.net core3.1下使用Autofac的基本用法,当然还有其他的一些用法,比如Autofac中实现AOP等,本文暂时就到这里了。参考资料:

Autofac中文文档

多种IOC容器的比较及选择

作者:一蓑烟雨

本文版权归作者所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

0

支持

0

反对

posted @2020-7-19  拜读(733)

评论列表

评论内容:



喜欢请打赏

支付宝 微信

请放心支付