Autofac
是.NET领域最为流行的IOC框架之一,传说是速度最快的一个。它和C#语言的结合非常紧密,在使用过程中对你的应用的侵入性几乎为零,更容易与第三方的组件集成。主要优点如下(此段描述为转载):
Autofac
使用,例如可以用Lambda
表达式注册组件IoC
和DI
的概念以及在何时需要使用它们Orchad
开源程序使用的就是Autofac
,从该源码可以看出它的方便和强大本文演示的项目时基于.Net Core 3.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
:
简单介绍,详情请见参考资料:
//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
{
///
/// IUserService服务实现的集合
///
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
等,本文暂时就到这里了。