【C#】依赖注入及Autofac

什么是依赖?

当一个类需要另一个类协作来完成工作的时候就产生了依赖。比如我们在AccountController这个控制器需要完成和用户相关的注册、登录 等事情。其中的登录我们由EF结合Idnetity来完成,所以我们封装了一个EFLoginService。这里AccountController就有一个ILoginService的依赖。

什么是注入?

依赖注入(Dependency injection,DI)是一种实现对象及其合作者或依赖项之间松散耦合的技术。

注入体现的是一个IOC(控制反转的的思想),把依赖的创建丢给其它人,自己只负责使用,其它人丢给你依赖的这个过程理解为注入。

        private readonly IAccountAppService _accountAppService;
        /// <summary>
        /// 构造
        /// </summary>
        /// <param name="accountAppService">账户中心接口</param>
        public AccountController(
            IAccountAppService accountAppService
            )
        {
            _accountAppService = accountAppService;
        }

为什么要反转?

为了在业务变化的时候尽少改动代码可能造成的问题。
比如我们现在要把从EF中去验证登录改为从Redis去读,于是我们加了一个 RedisLoginService。这个时候我们只需要在原来注入的地方改一下就可以了。

// 用Redis来替换原来的EF登录 var controller = new AccountController(new RedisLoginService()); controller.Login(userName, password);

何为容器

上面我们在使用AccountController的时候,我们自己通过代码创建了一个IAccountServce的实例。想象一下,一个系统中如果有100个这样的地方,我们是不是要在100个地方做这样的事情? 控制是反转了,依赖的创建也移交到了外部。现在的问题是依赖太多,我们需要一个地方统一管理系统中所有的依赖,容器诞生了。
容器负责两件事情:

  • 绑定服务与实例之间的关系
  • 获取实例,并对实例进行管理(创建与销毁)

默认容器

ASP.NET Core 的底层设计支持和使用依赖注入。ASP.NET Core
应用程序可以利用内置的框架服务将它们注入到启动类Startup的方法中,并且应用程序服务ConfigureServices能够配置注入。

在内置的IoC容器中,在Startup.cs类文件ConfigureServices方法中,注入依赖方式如下:
public void ConfigureServices(IServiceCollection services)

{
    services.AddControllers();
    services.AddTransient<ITransientService, TransientService>();
    services.AddSingleton<ISingletonService, SingletonService>();
    services.AddScoped<IScopedService, ScopedService>();
    services.AddScoped<ITestService, TestService>();
}

Autofac框架

AutoFac是一个开源的轻量级的依赖注入容器,也是.net下比较流行的实现依赖注入的工具之一。

将Autofac整合到你的应用的基本流程如下:

  • 按照 控制反转 (IoC) 的思想构建你的应用.
  • 添加Autofac引用.
  • 在应用的 startup 处
  • 创建 ContainerBuilder.
  • 注册组件.
  • 创建容器,将其保存以备后续使用.
  • 应用执行阶段
  • 从容器中创建一个生命周期.
  • 在此生命周期作用域内解析组件实例.

框架使用

1、NuGet引入包

Install-Package Autofac -Version 4.8.1 Install-Package
Autofac.Extensions.DependencyInjection -Version 4.3.0

2、在ConfigureServices中创建容器对象,并返回IServiceProvider

一些需用到的方法:创建Runtime帮助获取项目程序集,排除所有的系统程序集(Microsoft.***、System.***等)、Nuget下载包

using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyModel;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.Loader;
 
namespace Dianshi.P2P.Core.Client.WebApi.Auth
{
    [ApiExplorerSettings(IgnoreApi = true)]
    public class RuntimeHelper
    {
        /// <summary>
        /// 获取项目程序集,排除所有的系统程序集(Microsoft.***、System.***等)、Nuget下载包
        /// </summary>
        /// <returns></returns>
        public static IList<Assembly> GetAllAssemblies()
        {
            List<Assembly> list = new List<Assembly>();
            var deps = DependencyContext.Default;
            //排除所有的系统程序集、Nuget下载包
            var libs = deps.CompileLibraries.Where(lib => !lib.Serviceable && lib.Type != "package");
            foreach (var lib in libs)
            {
                try
                {
                    var assembly = AssemblyLoadContext.Default.LoadFromAssemblyName(new AssemblyName(lib.Name));
                    list.Add(assembly);
                }
                catch (Exception ex)
                {
                    //
                }
            }
            return list;
        }
 
        public static Assembly GetAssembly(string assemblyName)
        {
            return GetAllAssemblies().FirstOrDefault(f => f.FullName.Contains(assemblyName));
        }
 
        public static IList<Type> GetAllTypes()
        {
            List<Type> list = new List<Type>();
            foreach (var assembly in GetAllAssemblies())
            {
                var typeinfos = assembly.DefinedTypes;
                foreach (var typeinfo in typeinfos)
                {
                    list.Add(typeinfo.AsType());
                }
            }
            return list;
        }
 
        /// <summary>
        /// 根据AssemblyName获取所有的类
        /// </summary>
        /// <param name="assemblyName"></param>
        /// <returns></returns>
        public static IList<Type> GetTypesByAssembly(string assemblyName)
        {
            List<Type> list = new List<Type>();
            var assembly = AssemblyLoadContext.Default.LoadFromAssemblyName(new AssemblyName(assemblyName));
            var typeinfos = assembly.DefinedTypes;
            foreach (var typeinfo in typeinfos)
            {
                list.Add(typeinfo.AsType());
            }
            return list;
        }
 
        public static Type GetImplementType(string typeName, Type baseInterfaceType)
        {
            return GetAllTypes().FirstOrDefault(t =>
            {
                if (t.Name == typeName && t.GetTypeInfo().GetInterfaces().Any(b => b.Name == baseInterfaceType.Name))
                {
                    var typeinfo = t.GetTypeInfo();
                    return typeinfo.IsClass && !typeinfo.IsAbstract && !typeinfo.IsGenericType;
                }
                return false;
            });
        }
    }
}

然后

 #region 依赖注入
            var builder = new ContainerBuilder();//实例化容器
            //注册所有模块module
            builder.RegisterAssemblyModules(Assembly.GetExecutingAssembly());
            //获取所有的程序集
            //var assemblys = BuildManager.GetReferencedAssemblies().Cast<Assembly>().ToArray();
            var assemblys = RuntimeHelper.GetAllAssemblies().ToArray();
 
            //注册所有继承IDependency接口的类
            //builder.RegisterAssemblyTypes().Where(type => typeof(IDependency).IsAssignableFrom(type) && !type.IsAbstract);
            //注册仓储,所有IRepository接口到Repository的映射
            builder.RegisterAssemblyTypes(assemblys).Where(t => t.Name.EndsWith("Repository") && !t.Name.StartsWith("I")).AsImplementedInterfaces();
            //注册服务,所有IApplicationService到ApplicationService的映射
            builder.RegisterAssemblyTypes(assemblys).Where(t => t.Name.EndsWith("AppService") && !t.Name.StartsWith("I")).AsImplementedInterfaces();
            builder.Populate(services);
            this.ApplicationContainer = builder.Build();
 
            return new AutofacServiceProvider(this.ApplicationContainer); //第三方IOC接管 core内置DI容器 
            //return services.BuilderInterceptableServiceProvider(builder => builder.SetDynamicProxyFactory());
            #endregion

ConfigureServices的返回值(默认为void)为IServiceProvider

测试

 public ActionResult<string> Ioc()
 {
 	//_accountAppService定义在《什么是注入?》章节下
     return _accountAppService.Login(new Application.Members.Accounts.Dto.LoginInput() { Password = "1", Phone = "2" });
 }

Autofac注册组件

通过创建 ContainerBuilder 来注册组件,并且告诉容器哪些组件,暴露了哪些服务。

使用 Register() 方法来注册实现:

ContainerBuilder 包含一组 Register() 注册方法,而组件暴露服务,可用使用 ContainerBuilder 上的 As() 方法。

即在容器初始化时候,向容器组件添加对象的操作过程。

通过梳理Autofac所有可用的注册组件方法,显示如下图展示的流程图。
在这里插入图片描述

反射注册

直接注册的组件必须是具体的类型,并可用暴露抽象和接口作为服务,但不能注册一个抽象和接口组件。

使用RegisterType()或者RegisterType(typeof(T))方法:

builder.RegisterType<TestService>().As<ITestService>();
// 或者
builder.RegisterType(typeof(TestService)).As(typeof(ITestService))

在多个构造函数时,如果需要,也可手动指定一个构造函数。

使用 UsingConstructor 方法和构造方法中代表参数类型的类型。

builder.RegisterType<TestService>()
       .UsingConstructor(typeof(TransientService), typeof(SingletonService));

实例注册

提前生成对象的实例并加入容器,以供注册组件时使用。

使用RegisterInstance()方法

// new出一个对象注册:
var output = new StringWriter();
builder.RegisterInstance(output).As<TestService>();

如果单例中存在实例且需要在容器中被组件使用时,

builder.RegisterInstance(MySingleton.Instance).ExternallyOwned();

Lambda表达式注册

当组件创建不再是简单调用构造方法时,可用利用lambda表达式来实现一些常规反射无法实现的操作。

比如一些复杂参数注册,参数注入,以及选择参数值实现等。

builder.Register(x => new TransientService()).As<ITransientService>();
// 或者指定参数
  builder.Register(x => new TestService(x.Resolve<ITransientService>(), x.Resolve<IScopedService>(), x.Resolve<ISingletonService>()))
  .As<ITestService>().InstancePerLifetimeScope();

泛型注册

支持泛型注册操作,使用 RegisterGeneric() 方法:

builder.RegisterGeneric(typeof(NHibernateRepository<>))
       .As(typeof(IRepository<>))
       .InstancePerLifetimeScope();

条件注册

在一些特殊场景,可能需要通过加上判断条件,来决定是否执行该条注册语句。

两种方法:

1、OnlyIf() - 提供一个表达式, 表示只有满足条件,才会执行语句。

builder.RegisterType<Manager>()
       .As<IManager>()
       .OnlyIf(reg =>
         reg.IsRegistered(new TypedService(typeof(IService))) &&
         reg.IsRegistered(new TypedService(typeof(HandlerB))));

2、IfNotRegistered() - 表示没有其他服务注册的情况下,就执行语句。
方法在 ContainerBuilder.Build() 时执行并且以实际组件注册的顺序执行。

builder.RegisterType<ServiceA>()
       .As<IService>();
builder.RegisterType<ServiceB>()
       .As<IService>()
       .IfNotRegistered(typeof(IService));

属性注入

构造方法参数注入是一种传值给组件的首选的方法。

在构造函数中是直接使用服务类型作为参数,然后AutoFac解析该类时,就会去容器内部已存在的组件中查找,然后将匹配的对象注入到构造函数中去。

但你同样也可以使用属性方法注入来传值。

是将容器内对应的组件直接注入到类内的属性中去,在注册该属性所属类的时候,需要使用PropertiesAutowired()方法额外标注。

这里不讨论属性注入的好坏,也不做说明服务层属性怎么注入,只讨论说明控制器中属性如何实现注入

1、注册组件方法,并使用属性注入PropertiesAutowired()标注。

builder.RegisterType<TransientService>().As<ITransientService>().PropertiesAutowired();

2、在控制器中使用属性来接收, 其中注入属性必须标注为public

[ApiController]
[Route("[controller]")]
public class TestController : ControllerBase
{

    public ITransientService _transientService { get; set; }

    [HttpGet]
    public JsonResult Get()
    {
        var data1 =   _transientService.GetGuid();
        return new JsonResult(new {
            data1
        });
    }
}

程序集注册

当我们需要实现批量注册的时候,也可以使用程序集的方式来注册,这也是常用的方法。

可通过指定过滤类型,服务,扫描模块等方式来找到需要注册的组件。

var assemblies = Assembly.GetExecutingAssembly();

builder.RegisterAssemblyTypes(assemblies)//程序集内所有具象类
.Where(c => c.Name.EndsWith("Service"))
.PublicOnly()//只要public访问权限的
.Where(cc => cc.IsClass)//只要class型(主要为了排除值和interface类型)
.AsImplementedInterfaces();//自动以其实现的所有接口类型暴露(包括IDisposable接口)

说明:

  • RegisterAssemblyTypes() :接收包含一个或多个程序集的数组作为参数
  • RegisterAssemblyModules() : 接收模块作为参数,进行模块扫描注册
  • PublicOnly() :指定公有方法被注册
  • Where() :要过滤注册的类型
  • Except() :要排除的类型
  • As() :反射出其实现的接口
  • AsImplementedInterfaces() : 自动以其实现的所有接口类型暴露(包括IDisposable接口)

Autofac暴露服务

上面提到了注册组件时, 我们得告诉Autofac, 组件暴露了哪些服务。

在上面注册实现中,大部分使用到了As() 方法。

当然,Autofac也提供了其他标注来暴露服务的方法。

默认暴露自身类型服务

常用的几种方法如下:

builder.RegisterType<CallLogger>();//不标注,默认以自身类型暴露服务
builder.RegisterType<CallLogger>().AsSelf();
builder.RegisterType<CallLogger>().As<CallLogger>();
builder.RegisterType<CallLogger>().As(typeof(CallLogger));

多个暴露服务类型

以其实现的接口(interface)暴露服务,暴露的类型可以是多个,比如CallLogger类实现了ILogger接口和ICallInterceptor接口。

暴露服务后, 可以解析基于该服务的组件了. 但请注意, 一旦将组件暴露为一个特定的服务, 默认的服务 (组件类型) 将被覆盖。

所以,为了防止被其他服务覆盖,可以使用 AsSelf() 方法。

Autofac解析服务

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

这样你既可以实现组件暴露一系列特定的服务, 又可以让它暴露默认的服务。

程序集注册指定暴露类型

1、可通过指定接口类型暴露服务,使用As() 方法

publi void ConfigureContainer(ContainerBuilder builder)
{
    builder.RegisterAssemblyTypes(assemblies)//程序集内所有具象类
        .Where(cc =>cc.Name.EndsWith("Repository")|//筛选
               cc.Name.EndsWith("Service"))
        .As(x=>x.GetInterfaces()[0])//反射出其实现的接口,并指定以其实现的第一个接口类型暴露
}

2、指定所有实现的接口类型进行暴露

使用AsImplementedInterfaces()函数实现,相当于一个类实现了几个接口(interface)就会暴露出几个服务,等价于上面连写多个As()的作用。

publi void ConfigureContainer(ContainerBuilder builder)
{
builder.RegisterAssemblyTypes(asm)
       .Where(t => t.Name.EndsWith("Repository"))
       .AsImplementedInterfaces();//自动以其实现的所有接口类型暴露(包括IDisposable接口)
}

解析服务

在注册完组件并暴露相应的服务后, 可以从创建的容器或其生命周期中解析服务。

使用 Resolve() 方法来解析实现:

通过梳理Autofac所有可用的解析服务方法,显示如下图展示的流程图。
在这里插入图片描述
在 注册完组件并暴露相应的服务后, 你可以从创建的容器或其子 生命周期 中解析服务. 让我们使用 Resolve() 方法来实现:

var builder = new ContainerBuilder();
builder.RegisterType<MyComponent>().As<IService>();
var container = builder.Build();

using(var scope = container.BeginLifetimeScope())
{
  var service = scope.Resolve<IService>();
}

解析时传参

当解析服务时, 需要传参,可以使用Resolve() 方法来接受可变长度的参数。

  • 可用参数类型

NamedParameter - 通过名称匹配目标参数

TypedParameter - 通过类型匹配目标参数 (需要匹配具体类型)

ResolvedParameter - 灵活的参数匹配

  • 反射组件的参数
var reader = scope.Resolve<ConfigReader>(new NamedParameter("configSectionName", "sectionName"));
  • Lambda表达式组件的参数
  • 不显式调用Resolve传参

Autofac生命周期

在这里插入图片描述
下面讲下AutoFac定义的几种生命周期作用域,并与.NET Core默认的生命周期作了简要的对比。

暂时性

每次在向服务容器进行请求时都会创建新的实例,相当于每次都new出一个。

注册方式:

使用InstancePerDependency()方法标注,如果不标注,这也是默认的选项。以下两种注册方法是等效的:

//不指定,默认就是瞬时的
builder.RegisterType<TransientService>().As<ITransientService>();

//指定其生命周期域为瞬时
builder.RegisterType<TransientService>().As<ITransientService>().InstancePerDependency();

对比:

与默认的容器中自带的生命周期AddTransient相同,也是每次都是全新的实例。
使用AddTransient()注册:

services.AddTransient<ITransientService, TransientService>()

作用域内

在每次Web请求时被创建一次实例,生命周期横贯整次请求。即在每个生命周期作用域内是单例的。

注册方式:

使用InstancePerLifetimeScope()方法标识:

builder.RegisterType<ScopedService>().As<IScopedService>().InstancePerLifetimeScope();

对比:

与默认的容器中自带的生命周期AddScoped相同,.NET Core框架自带的容器全权接管了请求和生命周期作用域的创建,使用Scoped()可以实现相同的效果。
使用AddScoped()注册:

services.AddScoped<IScopedService, ScopedService>();

匹配作用域内

即每个匹配的生命周期作用域一个实例。
该类型其实是上面的“作用域内”的其中一种,可以对实例的共享有更加精准的控制.。我们通过允许给域“打标签”,只要在这个特定的标签域内就是单例的。

  • 注册

使用InstancePerMatchingLifetimeScope(string tagName)方法注册:

var builder = new ContainerBuilder();
builder.RegisterType<Worker>().InstancePerMatchingLifetimeScope("myrequest");

当你开始一个生命周期时, 提供的标签值和它就关联起来了。

  • 解析
// myrequest标签子域一
using(var scope1 = container.BeginLifetimeScope("myrequest"))
{
  for(var i = 0; i < 100; i++)
  {
    var w1 = scope1.Resolve<Worker>();
    using(var scope2 = scope1.BeginLifetimeScope())
    {
      var w2 = scope2.Resolve<Worker>();

      //解析了2次,但2次都是同一个实例(w1和w2指向同一个内存块Ⅰ)
    }
  }
}

// //myrequest标签子域二
using(var scope3 = container.BeginLifetimeScope("myrequest"))
{
  for(var i = 0; i < 100; i++)
  {
   //因为标签域内已注册过,所以可以解析成功
    var w3 = scope3.Resolve<Worker>();
    using(var scope4 = scope3.BeginLifetimeScope())
    {
      var w4 = scope4.Resolve<Worker>();
	}
  }
}

对比:

services.AddSingleton<IProductService, ProductService>();

来源

.NET Core API框架实战(五) 依赖注入 服务的注册与提供

Autofac 框架初识与应用

C#中,依赖注入是一种设计模式,用于将一个对象的依赖关系从它的客户端解耦。通过依赖注入,创建对象时,它所依赖的其他对象会被注入到它的构造函数、属性或方法中,从而实现对象之间的松耦合。 在C#中,可以使用构造方法注入和属性注入两种方式来实现依赖注入。 构造方法注入是在对象的构造方法中将依赖对象作为参数传入,例如: ```csharp internal class ClientClass { private IServiceClass _serviceImpl; public ClientClass(IServiceClass serviceImpl) { this._serviceImpl = serviceImpl; } // ... } ``` 属性注入是通过对象的属性来注入依赖对象,例如: ```csharp internal class ClientClass { public IServiceClass ServiceImpl { get; set; } // ... } ``` 需要注意的是,构造方法注入只能在实例化客户类时注入一次,而属性注入可以在程序运行期间改变客户类对象内的服务类实例。 为了实现依赖注入,通常需要使用一个依赖注入容器(例如:Microsoft.Extensions.DependencyInjection),通过容器来管理依赖对象的创建和注入。 除了依赖注入容器,还可以使用配置文件来管理依赖对象的创建和注入。可以使用ConfigurationBuilder类来导入配置文件,并使用IOptions<T>来获取配置文件中的内容。 总结起来,C#中的依赖注入是通过构造方法注入或属性注入来创建对象并将依赖对象注入其中。使用依赖注入容器可以更方便地管理依赖对象的创建和注入,而配置文件可以用于配置和管理依赖对象的属性值。 <span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* [C#依赖注入](https://blog.csdn.net/qq_18145031/article/details/83181937)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *3* [c#依赖注入](https://blog.csdn.net/wanxiweilai/article/details/127878160)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

软泡芙

给爷鞠躬!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值