Skip to content

12. 依赖注入/控制反转

📝 模块更新日志 新特性*

+ 新增 `WebApplication.UseDefaultServiceProvider()` 拓展,可配置框架底层默认使用的 `IServiceProvider` 实例 4\.9\.4\.9 ⏱️2024\.07\.29 [d97467c](https://gitee.com/dotnetchina/Furion/commit/d97467c597124c081b07a316d8c17eebc533fceb) [\#IAFUNS](https://gitee.com/dotnetchina/Furion/issues/IAFUNS)
+ 新增 **`WinForm/WPF` 支持依赖注入的 `Native.CreateInstance<T>()` 静态方法** 4\.8\.7\.23 ⏱️2023\.03\.27 [53d51c3](https://gitee.com/dotnetchina/Furion/commit/53d51c3645f4066f5d68d4726d78e389fd544560)
  • 问题修复

    • 修复 依赖注入模块通过 INamedServiceProvider<> 解析服务没有应用拦截器问题 4.9.5.5 ⏱️2024.08.24 0277177
    • 修复 因 9d8cb82 代码提交导致命名服务解析异常问题 4.8.8.21 ⏱️2023.05.18 #I76JZR
    • 修复 因 9d8cb82 代码提交导致服务 AOP 异常拦截问题 4.8.8.17 ⏱️2023.05.15 #I73A8E

12.1 依赖注入

所谓依赖注入,是指程序运行过程中,如果需要调用另一个对象协助时,无须在代码中创建被调用者,而是依赖于外部的注入。

通俗来讲,就是把有依赖关系的类放到容器中,然后在我们需要这些类时,容器自动解析出这些类的实例。

依赖注入最大的好处是实现类的解耦,利于程序拓展、单元测试、自动化模拟测试等。

依赖注入的英文为:Dependency Injection,简称 DI

12.2 控制反转

控制反转只是一个概念,也就是将创建对象实例的控制权(原本是程序员)从代码控制权剥离到 IOC 容器 中控制。

控制反转的英文为:Inversion of Control,简称 IOC

12.3 IOC/DI 优缺点

传统的代码,每个对象负责管理与自己需要依赖的对象,导致如果需要切换依赖对象的实现类时,需要修改多处地方。同时,过度耦合也使得对象难以进行单元测试。

  • 优点

    • 依赖注入把对象的创建交给外部去管理,很好的解决了代码紧耦合(tight couple)的问题,是一种让代码实现松耦合(loose couple)的机制
    • 松耦合让代码更具灵活性,能更好地应对需求变动,以及方便单元测试
    • 缺点

    • 目前主流的 IOC/DI 基本采用反射的方式来实现依赖注入,在一定程度会影响性能

特别说明在本章节不打算细讲 依赖注入/控制反转 具体实现和应用场景,想了解更多知识,可查阅 【ASP.NET Core 依赖注入】 官方文档。

12.4 依赖注入的三种方式

12.4.1 构造方法注入

目前构造方法注入是依赖注入推荐使用方式。

  • 优点

    • 在构造方法中体现出对其他类的依赖,一眼就能看出这个类需要依赖哪些类才能工作
    • 脱离了 IOC 框架,这个类仍然可以工作
    • 一旦对象初始化成功了,这个对象的状态肯定是正确的
    • 缺点

    • 构造函数会有很多参数

    • 有些类是需要默认构造函数的,比如 MVC 框架的 Controller 类,一旦使用构造函数注入,就无法使用默认构造函数
    • 这个类里面的有些方法并不需要用到这些依赖

代码示例:

public class FurionService  
{  
    private readonly IRepository _repository;  
    public FurionService(IRepository repository)  
    {  
        _repository = repository;  
    }  
}  

12.4.2 属性方式注入

属性注入是在实例化对象时,同时向对象中的属性进行相应的赋值的过程。

  • 优点

    • 在对象的整个生命周期内,可以随时动态的改变依赖
    • 非常灵活
    • 缺点

    • 只能在控制器或动态 WebAPI 中使用

    • 对象在创建后,被设置依赖对象之前这段时间状态是不对的
    • 不直观,无法清晰地表示哪些属性是必须的
public class FurionService  
{  
    [FromServices]  // .NET8+ 还支持 [FromKeyedServices] 方式  
    public IRepository Repository { get; set; }  
}  

12.4.3 方法参数注入

方法参数注入的意思是在创建对象后,通过自动调用某个方法来注入依赖。

  • 优点:

    • 比较灵活
    • 缺点:

    • 只能在控制器或动态 WebAPI 中使用

    • 新加入依赖时会破坏原有的方法签名,如果这个方法已经被其他很多模块用到就很麻烦
    • 与构造方法注入一样,会有很多参数
public class FurionService  
{  
    public Person GetById([FromServices]IRepository repository, int id)  
    {  
        return repository.Find(id);  
    }  
}  

12.5 注册对象生存期

12.5.1 暂时/瞬时 生存期

暂时生存期服务是每次从服务容器进行请求时创建的。 这种生存期适合轻量级、 无状态的服务。

在处理请求的应用中,在请求结束时会释放暂时服务。

通常我们使用 ITransient 接口依赖表示该生命周期。

12.5.2 作用域 生存期

作用域生存期服务针对每个客户端请求(连接)创建一次。在处理请求的应用中,在请求结束时会释放有作用域的服务。

通常我们使用 IScoped 接口依赖表示该生命周期。

12.5.3 单例 生存期

在首次请求它们时进行创建,之后每个后续请求都使用相同的实例。

通常我们使用 ISingleton 接口依赖表示该生命周期。

了解更多想了解更多 服务生存期 知识可查阅 ASP.NET Core - 依赖注入 - 服务生存期 章节。

12.6 内置依赖接口

Furion 框架提供三个接口依赖分别对应不同的服务生存期:

  • ITransient:对应暂时/瞬时作用域服务生存期
  • IScoped:对应请求作用域服务生存期
  • ISingleton:对应单例作用域服务生存期

特别注意以上三个接口只能实例类实现,其他静态类、抽象类、及接口不能实现。

12.7 常见使用

12.7.1 第一个例子

创建 IBusinessService 接口和 BusinessService 实现类,代码如下:

using Furion.Core;  
using Furion.DatabaseAccessor;  
using Furion.DependencyInjection;  

namespace Furion.Application  
{  
    public interface IBusinessService  
    {  
        Person Get(int id);  
    }  

    public class BusinessService : IBusinessService, ITransient  
    {  
        private readonly IRepository<Person> _personRepository;  

        public BusinessService(IRepository<Person> personRepository)  
        {  
            _personRepository = personRepository;  
        }  

        public Person Get(int id)  
        {  
            return _personRepository.Find(id);  
        }  
    }  
}  

创建 PersonController 控制器,代码如下:

using Furion.Application;  
using Microsoft.AspNetCore.Mvc;  

namespace Furion.Web.Entry.Controllers  
{  
    [Route("api/[controller]")]  
    [ApiController]  
    public class PersonController : ControllerBase  
    {  
        private readonly IBusinessService _businessService;  
        public PersonController(IBusinessService businessService)  
        {  
            _businessService = businessService;  
        }  

        [HttpGet]  
        public IActionResult Get(int id)  
        {  
            var person = _businessService.Get(id);  
            return new JsonResult(person);  
        }  
    }  
}  


例子解说

Furion 框架提供了非常灵活且方便的实现依赖注入的方式,只需要实例类继承对应生存期的接口即可,这里继承了 ITransient,也就表明了这是一个 暂时/瞬时 作用域实例类。该类就可以作为被注入对象,同时也能注入其他接口对象。

上面的例子中,BusinessService 注入了 IRepository<Person> 仓储接口,同时 PersonController 控制器注入了 IBusinessService 接口。

这样 PersonControllerBusinessService 之间就实现了解耦,不再依赖于具体的 BusinessService 实例。

这就是依赖注入/控制反转最经典的例子。

12.7.2 注册泛型实例

创建 IBusinessService<T> 接口和 BusinessService<T> 实现类,代码如下:

using Furion.Core;  
using Furion.DatabaseAccessor;  
using Furion.DependencyInjection;  

namespace Furion.Application  
{  
    public interface IBusinessService<T>  
    {  
        Person Get(int id);  
    }  

    public class BusinessService<T> : IBusinessService<T>, ITransient  
    {  
        private readonly IRepository<Person> _personRepository;  

        public BusinessService(IRepository<Person> personRepository)  
        {  
            _personRepository = personRepository;  
        }  

        public Person Get(int id)  
        {  
            return _personRepository.Find(id);  
        }  
    }  
}  

创建 PersonController 控制器,代码如下:

using Furion.Application;  
using Microsoft.AspNetCore.Mvc;  

namespace Furion.Web.Entry.Controllers  
{  
    [Route("api/[controller]")]  
    [ApiController]  
    public class PersonController : ControllerBase  
    {  
        private readonly IBusinessService<int> _businessService;  
        public PersonController(IBusinessService<int> businessService)  
        {  
            _businessService = businessService;  
        }  

        [HttpGet]  
        public IActionResult Get(int id)  
        {  
            var person = _businessService.Get(id);  
            return new JsonResult(person);  
        }  
    }  
}  

12.7.3 一个接口多个实现

默认情况下,一个接口只对应一个实现类,但有些特殊情况,需要多个实现类注册同一个接口,如 DbContext 多数据库情况。

这个时候我们可以通过依赖注入 Func<string, IPrivateDependency, object> 委托来解析多个实例,其中委托的参数分别为:

  • 参数 1:string 类型,不同实现类唯一标识,默认为 nameof(实现类) 名称
  • 参数 2:Type 类型,IPrivateDependency 派生接口,也就是 ITransientIScopedISingleton
  • 返回值:object 类型,返回具体的实现类实例

创建 IBusinessService 接口和 BusinessServiceOtherBusinessService 两个实现类,代码如下:

using Furion.DependencyInjection;  

namespace Furion.Application  
{  
    public interface IBusinessService  
    {  
        string GetName();  
    }  

    public class BusinessService : IBusinessService, ITransient  
    {  
        public string GetName()  
        {  
            return "我是:" + nameof(BusinessService);  
        }  
    }  

    public class OtherBusinessService : IBusinessService, ITransient  
    {  
        public string GetName()  
        {  
            return "我是:" + nameof(OtherBusinessService);  
        }  
    }  
}  

新版本,推荐使用在 Furion 3.8.6+ 版本之后新增了 INamedServiceProvider 服务接口,可替代 Func<string, ILifetime, object> 方式:

using Furion.Application.Services;  

namespace Furion.Application;  

public class TestNamedServices : IDynamicApiController  
{  
    private readonly INamedServiceProvider<IBusinessService> _namedServiceProvider;  
    public TestNamedServices(INamedServiceProvider<IBusinessService> namedServiceProvider)  
    {  
        _namedServiceProvider = namedServiceProvider;  
    }  

    public string GetName()  
    {  
        // 第一种用法,通过反射解析服务周期,性能有损耗  
        var service1 = _namedServiceProvider.GetService(nameof(BusinessService));  
        var service2 = _namedServiceProvider.GetService(nameof(OtherBusinessService));  

        // 第二种用法,无需反射,注意下面的泛型参数传入的是生命周期依赖接口,ITransient, IScoped, ISingleton  
        var service3 = _namedServiceProvider.GetService<ITransient>(nameof(BusinessService));  
        var service4 = _namedServiceProvider.GetService<ITransient>(nameof(OtherBusinessService));  

        return service1.GetName() + "-" + service2.GetName() + "-" + service3.GetName() + "-" + service4.GetName();  
    }  
}  

不再推荐 Func<string, TLifetime, object> 方式

using Furion.Application;  
using Furion.DependencyInjection;  
using Microsoft.AspNetCore.Mvc;  
using System;  

namespace Furion.Web.Entry.Controllers  
{  
    [Route("api/[controller]")]  
    [ApiController]  
    public class ValueController : ControllerBase  
    {  
        private readonly IBusinessService _businessService;  
        private readonly IBusinessService _otherBusinessService;  

        public ValueController(Func<string, ITransient, object> resolveNamed)  
        {  
            _businessService = resolveNamed("BusinessService", default) as IBusinessService;  
            _otherBusinessService = resolveNamed("OtherBusinessService", default) as IBusinessService;  
        }  

        [HttpGet]  
        public string GetName()  
        {  
            return _businessService.GetName() + "----------" + _otherBusinessService.GetName();  
        }  
    }  
}  

小知识如果需要自定义解析名称,只需要贴 [Injection(Named = "名称")] 即可,如:

using Furion.DependencyInjection;  

namespace Furion.Application  
{  
    [Injection(Named = "BusName1")]  
    public class BusinessService : IBusinessService, ITransient  
    {  
        // ...  
    }  

    [Injection(Named = "BusName2")]  
    public class OtherBusinessService : IBusinessService, ITransient  
    {  
        // ...  
    }  
}  

解析服务:

_businessService = resolveNamed("BusName1", default) as IBusinessService;  
_otherBusinessService = resolveNamed("BusName2", default) as IBusinessService;  

12.7.4 无接口方式

有些时候,我们不想定义接口,而是想把实例类作为可依赖注入的对象,如 MVC 中的控制器。

创建 SelfService 实例类,代码如下:

using Furion.Core;  
using Furion.DatabaseAccessor;  
using Furion.DependencyInjection;  

namespace Furion.Application  
{  
    public class SelfService : ITransient  
    {  
        private readonly IRepository<Person> _personRepository;  

        public SelfService(IRepository<Person> personRepository)  
        {  
            _personRepository = personRepository;  
        }  

        public Person Get(int id)  
        {  
            return _personRepository.Find(id);  
        }  
    }  
}  

创建 ValueController 控制器,代码如下:

using Furion.Application;  
using Furion.Core;  
using Microsoft.AspNetCore.Mvc;  

namespace Furion.Web.Entry.Controllers  
{  
    [Route("api/[controller]")]  
    [ApiController]  
    public class ValueController : ControllerBase  
    {  
        private readonly SelfService _selfService;  

        public ValueController(SelfService selfService)  
        {  
            _selfService = selfService;  
        }  

        [HttpGet]  
        public Person Get(int id)  
        {  
            return _selfService.Get(id);  
        }  
    }  
}  

12.8 [Injection] 特性配置

Furion 框架提供 [Injection] 特性可以改变注册方式,同时还能配置 AOP 拦截。

[Injection] 提供以下配置支持:

  • Action:配置注册行为,InjectionActions 类型,可选值:
    • Add默认值,表示无限制添加注册服务,该方式支持一个接口多个实现
    • TryAdd:表示注册已存在则跳过注册
  • Pattern:配置注册选项,InjectionPatterns 类型,可选值:
    • Self:只注册自己
    • FirstInterface:只注册第一个接口
    • SelfWithFirstInterface:注册自己和第一个接口
    • ImplementedInterfaces:注册所有接口
    • All:注册自己包括所有接口 ,默认值
  • Named:配置实例别名,通过别名可以解析接口,如同一个接口有多个实现,那么可以通过别名解析不同的实现,默认值为实现类的类名
  • Order:注册排序,数字越大,则越在最后注册,默认 0
  • Proxy:配置代理拦截类型,也就是 AOP代理类型必须继承 AspectDispatchProxy 类和 IDispatchProxy 接口,无默认值
  • ExceptInterfaces:配置忽略注册的接口,Type[] 类型

12.9 自定义高级注册

默认情况下,Furion 提供的注册方式可以满足大多数依赖注入的需求,如有特别注册需求,只需要在 Startup 中配置即可,如:

services.AddScoped(typeof(ISpecService), provider = > {  
    // 自定义任何创建实例的方式  
    var instance = new SpecService();  // 或者可以通过 AOP插件返回代理实例  

    return instance;  
});  

补充说明Furion 框架中的 AppDbContext 数据库上下文还有 ISqlDispatchProxy 都是通过这种方式创建的。

知识导航想了解更多自定义高级中注册,可查阅 【ASP.NET Core 依赖注入】 官方文档。

12.10 appsettings.json 配置注册

除了在代码中实现依赖注入,也可以实现动态依赖注入,无需修改代码或重新编译即可实现热插拔(插件)效果。配置如下:

{  
  "DependencyInjectionSettings": {  
    "Definitions": [  
      {  
        "Interface": "Furion.Application;Furion.Application.ITestService",  
        "Service": "Furion.Application;Furion.Application.TestService",  
        "RegisterType": "Transient",  
        "Action": "Add",  
        "Pattern": "SelfWithFirstInterface",  
        "Named": "TestService",  
        "Order": 1,  
        "Proxy": "Furion.Application;Furion.Application.LogDispathProxy"  
      }  
    ]  
  }  
}  

配置说明:

  • DependencyInjectionSettings:依赖注入配置根节点
    • Definitions:动态依赖注入配置节点,ExternalService 数组类型
      • ExternalService:配置单个依赖注入信息

关于外部程序集如果动态注入的对象是外部程序集,那么首先先注册外部程序集:

{  
  "AppSettings": {  
    "ExternalAssemblies": ["外部程序集名称", "Taobao.Pay"] // 支持多个  
  }  
}  

12.11 注册顺序和优先级

Furion 框架中,默认注册顺序是按照程序集扫描顺序进行注册,如果需要改变注册顺序,可通过 [Injection(Order)] 特性指定,Order 值越大,则越在最后注册。

另外 appsettings.json 配置的优先级最大,appsettings.json 配置的注册会覆盖之前所有注册。

12.12 Aop 注册拦截

拦截说明本小节 AOP 拦截仅支持接口注入,不支持类直接注入。

关于动态 API 和服务的区别如果您的服务是动态 API,那么请使用 动态 API - AOP 拦截,原因是动态 API 本质是控制器,所以采用 Filter 方式。

AOP 是非常重要的思想和技术,也就是 面向切面 编程,可以让我们在不改动原来代码的情况下进行动态篡改业务代码。

Furion 框架中,实现 Aop 非常简单,如:

假设我们有 ITestServiceTestService 两个类型:

public interface ITestService  
{  
    string SayHello(string word);  
}  

public class TestService: ITestService, ITransient  
{  
    public string SayHello(string word)  
    {  
        return $"Hello {word}";  
    }  
}  

现在我们有一个需求,我们希望调用 SayHello 的时候可以记录日志和权限控制(之前没有考虑到的需求)。

这个时候我们只需要创建一个代理类即可,如 LogDispatchProxy

using Furion.DependencyInjection;  
using System;  
using System.Reflection;  

namespace Furion.Application  
{  
    public class LogDispatchProxy : AspectDispatchProxy, IDispatchProxy  
    {  
        /// <summary>  
        /// 当前服务实例  
        /// </summary>  
        public object Target { get; set; }  

        /// <summary>  
        /// 服务提供器,可以用来解析服务,如:Services.GetService()  
        /// </summary>  
        public IServiceProvider Services { get; set; }  

        /// <summary>  
        /// 拦截方法  
        /// </summary>  
        /// <param name="method"></param>  
        /// <param name="args"></param>  
        /// <returns></returns>  
        public override object Invoke(MethodInfo method, object[] args)  
        {  
            Console.WriteLine("SayHello 方法被调用了");  

            var result = method.Invoke(Target, args);  

            Console.WriteLine("SayHello 方法返回值:" + result);  

            return result;  
        }  

        // 异步无返回值  
        public async override Task InvokeAsync(MethodInfo method, object[] args)  
        {  
            Console.WriteLine("SayHello 方法被调用了");  

            var task = method.Invoke(Target, args) as Task;  
            await task;  

             Console.WriteLine("SayHello 方法调用完成");  
        }  

        // 异步带返回值  
        public async override Task<T> InvokeAsyncT<T>(MethodInfo method, object[] args)  
        {  
            Console.WriteLine("SayHello 方法被调用了");  

            var taskT = method.Invoke(Target, args) as Task<T>;  
            var result = await taskT;  

            Console.WriteLine("SayHello 方法返回值:" + result);  

            return result;  
        }  
    }  
}  

获取特性如果需要获取方法的特性,只需要通过 method.GetActualCustomAttribute<TArrbute>() 即可。所有获取真实的特性统一采用 method.GetActual....() 方法开头。

之后我们只需要为 TestService 增加 [Injection] 特性即可,如:

[Injection(Proxy = typeof(LogDispatchProxy))]  
public class TestService: ITestService, ITransient  
{  
    public string SayHello(string word)  
    {  
        return $"Hello {word}";  
    }  
}  

之后 SayHello 方法被调用的时候就可以实现动态拦截了,比如这里写日志。

12.12.1 全局Aop拦截

Furion 框架也提供了全局拦截的方式,只需要将 IDispatchProxy 修改为 IGlobalDispatchProxy 即可。

using Furion;  
using System.Reflection;  

namespace Furion.Application  
{  
    public class LogDispatchProxy : AspectDispatchProxy, IGlobalDispatchProxy  
    {  
        // ....  
    }  
}  

这样就会拦截所有的 Service,当然也可以通过给特定类贴 [SuppressProxy] 跳过全局拦截操作。

拦截优先级[SuppressProxy] > [Injection(Proxy = typeof(LogDispatchProxy))] > 全局拦截

12.12.2 AOP 注入解析服务

Furion 框架未提供 Proxy 构造函数注入功能,但是提供了 Services 属性,如果需要解析服务,则可以通过以下方式:

var someServices = Services.GetService<ISomeService>(); // 推荐方式  
// 或  
var someServices = App.GetService<ISomeService>();  

12.12.3 AOP 的作用

这种面向切面的能力(动态拦截/代理)可以实现很多很多功能,如:

  • 动态日志记录
  • 动态修改参数
  • 动态修改返回值
  • 动态方法重定向
  • 动态修改代码逻辑
  • 动态实现异常监听

还可以做更多更多的事情。

12.13 在非 Web 或多线程解析服务

默认情况下,在 Web 请求开始之前会自动创建范围作用域,这个作用域的生存周期是请求之前和响应之后,也就是在这个作用域内的所有服务都实现了自动管理,比如创建服务和释放服务的时机。

但在非 Web 或多线程中,框架并不会做这样的事情,也就是框架只负责了服务的创建,但是没有负责销毁,原因是框架无法得知具体的销毁时机,这样就导致了内存溢出。

解决方式是:在非 Web 或多线程中使用 非单例 服务,应该主动创建作用域,类似过去的 using,目前框架提供了几种方式。

12.13.1 IServiceProvider 方式

using var scope = serviceProvider.CreateScope();  
var services = scope.ServiceProvider;  

// 获取数据库上下文  
var dbContext = Db.GetDbContext(services);  

// 获取仓储  
var respository = Db.GetRepository<Person>(services);  

// 解析其他服务  
var otherService = services.GetService<XXX>();  
var otherService2 = App.GetService<XXX>(services);  

12.13.2 IServiceScopeFactory 方式

using var scope = serviceScopeFactory.CreateScope();  
var services = scope.ServiceProvider;  

12.13.3 Scoped 静态类

为了方法快速创建服务作用域,Furion 框架提供了 Scoped 静态类,如:

Scoped.Create((factory, scope) => {  
     var services = scope.ServiceProvider;  
});  

12.14 在单例中解析非单例服务

如果是非 单例 的服务,如 瞬时范围 服务,可通过 IServiceScopeFactory 解析服务:

public class SomeService: ISomeService, ISingleton  
{  
    private readonly ILogger<SomeService> _logger;  
    private readonly IConfiguration _configuration;  
    private readonly IServiceScopeFactory _scopeFactory;  

    public MyJob(ILogger<MyJob> logger  // 单例可以直接注入  
        , IConfiguration configuration  // 单例可以直接注入  
        , IServiceScopeFactory scopeFactory)    // 单例可以直接注入  
    {  
        _logger = logger;  
        _configuration = configuration;  
        _schedulerFactory = scopeFactory;  
    }  

    public async Task Method()  
    {  
        // 创建非单例服务  
        using var serviceScope = _scopeFactory.CreateScope();  
        var repository = serviceScope.ServiceProvider.GetService<IRepository<User>>();  

        _logger.LogInformation($"{context} {_configuration["key"]}");  
        await Task.CompletedTask;  
    }  
}  

12.15 在 WinForm/WPF 中使用

版本说明以下内容仅限 Furion 4.8.7.23 + 版本使用。

默认情况下,WinForm/WPF 不支持依赖注入功能,但 Furion 框架提供了完整的支持,只需要将所有 new Form()new Window() 替换为 Native.CreateInstance<Form>()Native.CreateInstance<Window>() 即可

12.15.1 在 WinForm 中使用

  • 修改 Progame.cs 代码

Progame.cs

namespace WinFormsApp1;  

internal static class Program  
{  
    [STAThread]  
    static void Main()  
    {  
        // 初始化 Furion 配置  
        Serve.RunNative();  

        ApplicationConfiguration.Initialize();  
        Application.Run(Native.CreateInstance<Form1>());    // 替换 new Form1() 为 Native.CreateInstance<Form1>()  
    }  
}  

  • 使用完整的依赖注入功能
using Furion.DependencyInjection;  
using Microsoft.Extensions.Configuration;  

namespace WinFormsApp1;  

public partial class Form1 : Form  
{  
    private readonly IConfiguration _configuration;  
    private readonly ITest _test;  

    public Form1(IConfiguration configuration  
        , ITest test)  
    {  
        InitializeComponent();  

        _configuration = configuration;  
        _test = test;  
    }  
}  

public class Test : ITest, ITransient  
{  
}  

public interface ITest  
{  
}  

  • 打开新窗口 Form2
using Furion.DependencyInjection;  
using Microsoft.Extensions.Configuration;  

namespace WinFormsApp1;  

public partial class Form2 : Form  
{  
    private readonly IConfiguration _configuration;  

    public Form2(IConfiguration configuration, string parameter)    // 支持其他参数 parameter  
    {  
        InitializeComponent();  

        _configuration = configuration;  
        label1.Text = parameter;  
    }  
}  

Form1 中打开 From2

private void button1_Click(object sender, EventArgs e)  
{  
    Native.CreateInstance<Form2>("我是其他参数").ShowDialog();  // new Form2() 改为  Native.CreateInstance<Form2>();  
}  

获取打开窗口实例Native.CreateInstance<> 每次都会创建新的实例,如果只是想获取打开的窗口的实例可通过以下方式:

var form1 = Application.OpenForms.OfType<Form>().FirstOrDefault(f => f is Form1);  

12.15.2 在 WPF 中使用

  • 修改 App.xaml 代码并删除 StartupUri="MainWindow.xaml" 属性
<Application x:Class="WpfApp1.App"  
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"  
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"  
             xmlns:local="clr-namespace:WpfApp1">  
             <!--StartupUri="MainWindow.xaml"-->  
    <Application.Resources>  

    </Application.Resources>  
</Application>  

  • 修改 App.xaml.cs 代码
using System.Windows;  

namespace WpfApp1;  

public partial class App : Application  
{  
    public App()  
    {  
        Serve.RunNative();  
    }  

    protected override void OnStartup(StartupEventArgs e)  
    {  
        Native.CreateInstance<MainWindow>().Show();  
        base.OnStartup(e);  
    }  
}  

  • 使用完整的依赖注入功能
using Furion.DependencyInjection;  
using Microsoft.Extensions.Configuration;  
using System.Windows;  

namespace WpfApp1;  

public partial class MainWindow : Window  
{  
    private readonly IConfiguration _configuration;  
    private readonly ITest _test;  

    public MainWindow(IConfiguration configuration  
        , ITest test)  
    {  
        InitializeComponent();  

        _configuration = configuration;  
        _test = test;  
    }  
}  

public class Test : ITest, ITransient  
{  
}  

public interface ITest  
{  
}  

  • 打开新窗口 Window1
using Furion.DependencyInjection;  
using Microsoft.Extensions.Configuration;  
using System.Windows;  

namespace WpfApp1;  

public partial class Window1 : Window  
{  
    private readonly IConfiguration _configuration;  

    public Window1(IConfiguration configuration, string parameter)  // 支持其他参数 parameter  
    {  
        InitializeComponent();  

        _configuration = configuration;  
        label1.Content = parameter;  
    }  
}  


MainWindow 中打开 Window1

private void Button_Click(object sender, RoutedEventArgs e)  
{  
    Native.CreateInstance<Window1>("我是其他参数").Show();  
}  

获取打开窗口实例Native.CreateInstance<> 每次都会创建新的实例,如果只是想获取打开的窗口的实例可通过以下方式:

var window1 = Application.Current.Windows.OfType<Window>().FirstOrDefault(w => w is MainWindow);  

12.15.3 Native.CreateInstance<T>() 静态方法

Furion 框架提供了 Native.CreateInstance<T>() 静态方法可在 WinForm/WPF 中创建窗口实例,可替代 new T() 操作,通过此方法创建实例支持构造函数注入服务操作。

12.16 自定义扫描/筛选注册服务

Furion 框架中并未提供完全自定义依赖注入扫描的机制,但推荐一个非常优秀的 .NET Core 依赖注入拓展库:Scrutor,使用非常简单,主要通过 FromAssemblyOf<> 扫描程序集和 AddClasses(o) 进行筛选注册。

使用如下:

services.Scan(scan => scan  
    // 扫描特定类型所在的程序集,这里是 ITransientService 所在的程序集  
    .FromAssemblyOf<ITransientService>()  
        // .AddClasses 在上面获取到的程序集中扫描所有公开、非抽象类型  
        // 之后可以通过委托进行类型筛选,例如下面只扫描实现 ITransientService 的类型  
        .AddClasses(classes => classes.AssignableTo<ITransientService>())  
            // 将上面的类型作为它实现的所有接口进行注册  
            // 如果类型实现了 N 个接口,那么就会有三个独立的注册  
            .AsImplementedInterfaces()  
            // 最后指定注册的生存期,如瞬时,作用域,还是单例  
            .WithTransientLifetime()  
        // 重复上面操作,比如这里扫描 IScopedService 所在的程序集  
        .AddClasses(classes => classes.AssignableTo<IScopedService>())  
            // 这里和上面不一样的是,这里指定只实现特定的几口,也就是只注册一次  
            .As<IScopedService>()  
            // 指定注册的生存期  
            .WithScopedLifetime()  
        // 也支持泛型注册,单个泛型参数  
        .AddClasses(classes => classes.AssignableTo(typeof(IOpenGeneric<>)))  
            .AsImplementedInterfaces()  
        // 多个泛型参数  
        .AddClasses(classes => classes.AssignableTo(typeof(IQueryHandler<,>)))  
            .AsImplementedInterfaces());  

详细文档请查阅 https://github.com/khellang/Scrutor

12.17 反馈与建议

与我们交流给 Furion 提 Issue