加载中...
.net core/.net 5中自动批量向容器中注入服务
发表于:2021-05-23 | 分类: .Net
字数统计: 1.7k | 阅读时长: 6分钟 | 阅读量:

引言


.net core 为我们提供了良好的容器机制,并可以简单的在 Configuration 方法中向容器中注入服务,但是当服务比较多的时候,就会使我们的注入过程变得很麻烦,不容易管理,所以就需要我们自己动手写一些代码,实现服务的管理以及自动注入。
关于这篇博客所写的代码,我已经上传至Github,大家可以下载源码观看,如果觉得不错,顺手给个 Star 哦。

实现思路


要实现自动注入,需要做好以下几件事:

  1. 使容器可以自动发现需要注入的服务,并注册服务
  2. 服务需要标记自己需要的生命周期,方便容器注入
  3. 异常的处理

针对以上问题,我需要首先定义一个IServiceCollection的扩展方法,可以在服务中使用我们的服务;其次,需要定义一个枚举,声明服务的生命周期;最后,我们需要定义一个属性,可以在服务类上使用,以便声明服务的声明周期等信息,使容器可以发现服务。

自动注入的实现


生命周期枚举类的定义

要定义生命周期枚举,首先需要了解一下.net 中的生命周期的机制,.net 中具有 3 种生命周期

  1. AddTransient,暂时性生存期;
  2. AddScoped,范围内生存期;
  3. AddSingleton,单例生命期。

关于生命周期方面的知识不属于这篇文章的重点,在此不多做赘述,不太熟悉的可以去微软文档查看。

根据 3 种生命周期,我们可以定义以下一个枚举类:

/// <summary>
/// 注入类型
/// </summary>
public enum InjectionType
{
    /// <summary>
    /// Transient
    /// </summary>
    Transient,

    /// <summary>
    /// Scoped
    /// </summary>
    Scoped,

    /// <summary>
    /// Singleton
    /// </summary>
    Singleton
}

自动注入属性的实现

实现自定义属性,主要是为了使用方便,这样的话我们在服务类的定义时,就可以标记这个服务是用来怎样进行服务注册的,废话不多说,上代码:

/// <summary>
/// 服务注入
/// </summary>
[AttributeUsage(AttributeTargets.Class)]
public class ServiceInjectionAttribute : Attribute
{
    /// <summary>
    ///
    /// </summary>
    public Type InterfaceType { get; set; }

    /// <summary>
    /// 注入类型
    /// </summary>
    public InjectionType InjectionType { get; }

    /// <summary>
    /// 服务注入
    /// </summary>
    public ServiceInjectionAttribute()
    {
        this.InjectionType = InjectionType.Scoped;
    }

    /// <summary>
    /// 服务注入
    /// </summary>
    /// <param name="injectionType">注入类型</param>
    public ServiceInjectionAttribute(InjectionType injectionType)
    {
        this.InjectionType = injectionType;
    }

    /// <summary>
    /// 服务注入
    /// </summary>
    /// <param name="interfaceType">服务的接口类型</param>
    /// <param name="injectionType">注入的类型</param>
    public ServiceInjectionAttribute(Type interfaceType, InjectionType injectionType)
    {
        this.InterfaceType = interfaceType;
        this.InjectionType = injectionType;
    }
}

如代码所示,我们自定义的ServiceInjectionAttribute类继承于 C#的Attribute类,并且在上面标记了这个属性类的适用范围,只能标记在类上面。可以注意到,我们这个类有三种类型的构造函数。我们可以根据自己的使用需求来决定使用哪一种构造定义。

在我们的类中,有一个属性为InterfaceType,这个属性是指服务继承的接口类,根据这个属性我们注册服务的时,会使用这个接口进行注入。

在这里面,我们定义了默认的生命周期为Scoped,如果没有定义继承的服务接口,则使用具体服务类所继承的第一个接口进行注册

服务扩展方法的实现

这部份是实现自动注入的核心,它负责容器的组装,自动扫描我们需要注入的服务,代码如下:

/// <summary>
/// 服务自动注入
/// </summary>
/// <param name="serviceCollection">需要自动注入服务的服务集合</param>
/// <exception cref="ArgumentOutOfRangeException">指定的注入类型不在可注入的范围内</exception>
/// <exception cref="NoImplementationInterfaceException">指定注入的类型未实现任何服务</exception>
/// <exception cref="ArgumentException">输入的参数错误:1、注入的类型未实现指定的服务。2、指定的服务不是Interface类型</exception>
/// <returns>自动注入服务后的服务集合</returns>
public static IServiceCollection ServicesAutoInjection(this IServiceCollection serviceCollection)
{
    var directory = AppDomain.CurrentDomain.BaseDirectory;
    var types = Directory.GetFiles(directory, "*.dll", SearchOption.TopDirectoryOnly)
        .Select(Assembly.LoadFrom)
        .SelectMany(a => a.GetTypes());

    Injection(serviceCollection, types);

    return serviceCollection;
}

在这个方法中我们可以看出,程序会扫描程序运行目录下的所有 Dll,并加载具有我们自定义属性的类,并根据我们标记的生命周期等属性,注入到我们的容器中。

但是,有时候,有一些 Dll 不需要我们扫描注入,需要进行排除,所以在这个基础上,我又封装了一层,可以传入过滤条件,代码如下:

/// <summary>
/// 服务自动注入
/// </summary>
/// <param name="serviceCollection">需要自动注入服务的服务集合</param>
/// <param name="selector">应用于每个Assembly的筛选函数</param>
/// <exception cref="ArgumentOutOfRangeException">指定的注入类型不在可注入的范围内</exception>
/// <exception cref="NoImplementationInterfaceException">指定注入的类型未实现任何服务</exception>
/// <exception cref="ArgumentException">输入的参数错误:1、注入的类型未实现指定的服务。2、指定的服务不是Interface类型</exception>
/// <returns>自动注入服务后的服务集合</returns>
public static IServiceCollection ServicesAutoInjection(this IServiceCollection serviceCollection, Func<Assembly, bool> selector)
{
    var directory = AppDomain.CurrentDomain.BaseDirectory;
    var types = Directory.GetFiles(directory, "*.dll", SearchOption.TopDirectoryOnly)
        .Select(Assembly.LoadFrom)
        .Where(selector)
        .SelectMany(a => a.GetTypes());

    Injection(serviceCollection, types);

    return serviceCollection;
}

上述代码中,selector就是我们的过滤条件,它是一个 Func 函数,参数为Assembly类型,我们可以根据类型或者一些信息排除我们不需要的类型,比如根据类的名称等信息。

自动注入的使用


服务的配置

首先需要我们在Startup中的ConfigureServices函数中配置服务,如下所示:

public void ConfigureServices(IServiceCollection services)
{
    // 声明为WebAPI,也可为AddMvc()或者其他
    services.AddControllers();
    // 配置其他服务
    // 配置自动注入服务
    services.ServicesAutoInjection();
    // 配置其他服务
}

标记服务的注入

我们的服务的配置后,接下来的使用就比较简单了,找到我们的实现类,标记上我们自定义的属性标签,然后服务就可以通过我们的服务自动注入啦。

[ServiceInjection(typeof(IService),InjectionType.Scoped)]
public class ServiceImpl : IService
{
	// 业务代码
}

结语


通过我们自定义代码实现,服务就可以很简单的被容器发现并注册了,并且还做了简单的服务排除逻辑,如果代码中有什么出错的地方,欢迎大家批评指正哦。
码字不易,请顺手给个 Star 吧

上一篇:
WPF混合开发之WebView2(一) 简介及环境搭建
下一篇:
Windows 10家庭中文版中启用WSL 2
本文目录
本文目录