本文主要来自Andrew的博客:

Using Scrutor to automatically register your services with the ASP.NET Core DI container

有兴趣的可以阅读原文。顺便Scrutor是开源的,github地址:Scrutor

目录

1.关于依赖注入

2.Scrutor的优缺点

3.使用方法

4.举例说明

 4.1指定具体的类型

4.2扫描类型所在的程序集

4.4处理多个多个服务

4.5将类注册为服务

 4.6 指定生命周期

4.7 链式注册 



1.关于依赖注入

依赖注入是Asp.dotnet core框架的核心功能之一,因此官方自带的DI容器并不是很强大,只提供了基本的功能。具体可以在Microsoft.Extensions.DependencyInjection NuGet 包中找到。.Net上还有很多好用的第三方依赖注入库:

 他们有个共同的特性:可以通过扫描类型所在的程序集并找到约定的类型提供自动注册服务,这样可以大大减少你在Startup.ConfigureServices 函数的工作量

比如你有三个接口,每隔接口都有一个实现:那么你要添加三次,

services.AddScoped<IFoo, Foo>();
services.AddScoped<IBar, Bar>();
services.AddScoped<IBaz, Baz>();

但是你可以通过扫描应用程序集来简化你的注册操作:

services.Scan(scan => 
    scan.FromCallingAssembly()                    
        .AddClasses()
        .AsMatchingInterface());

2.Scrutor的优缺点

Scrutor不是一个依赖注入容器,它使用官方默认的依赖注入容器。

优点

  • 因为使用的是默认的依赖注入容器,因此易于添加到已有的项目中,不用担心会出什么意外情况
  • 因为很多第三方容器适配了.net core,因此,Scrutor也支持配合第三方依赖注入容器使用
  • 不大可能随着.net升级而无法使用,至少相对第三方依赖注入容器,除非.net版本发生巨大更改。

缺点

  •         因为使用默认容器,首先于默认容器,无法提供更强大的功能

要安装Scrutor,直接在Nugtet包里面搜索关键字 Scrutor即可。
 


3.使用方法

Scrutor扩展了IServiceCollection方法Scan.Scan配置注册主要分下面四部:

  1. 选择器:指定要注册那些实现
  2. 注册策略:怎么处理重复注册的服务或者实现
  3. 服务: 这些注册的实现类应该被设为什么服务
  4. 生命周期:将服务注册为什么生命周期

举个例子:

services.Scan(scan => scan     
  .FromCallingAssembly() // 1. Find the concrete classes
    .AddClasses()        //    to register
      .UsingRegistrationStrategy(RegistrationStrategy.Skip) // 2. Define how to handle duplicates
      .AsSelf()    // 2. Specify which services they are registered as
      .WithTransientLifetime()); // 3. Set the lifetime for the services

所以,当你在Startup中使用了上面的语句:那么

public interface IService { }
public class Service1 : IService { }
public class Service2 : IService { }
public class Service : IService { }

public interface IFoo {}
public interface IBar {}
public class Foo: IFoo, IBar {}

就会等价于:

services.AddTransient<Service1>();
services.AddTransient<Service2>();
services.AddTransient<Service>();
services.AddTransient<Foo>();

4.举例说明

假设你的项目有这么几个类:

    public interface IService
    {
    }
    public class Service : IService
    {
    }
    public class Service1:IService
    {
    }
    public class Service2:IService
    { }
    public interface IBag { }
    public class HandleBag : IBag { }
    public class BackBag:IBag { }

 4.1指定具体的类型

使用AddType<T>(),或者AddTypes<T1,T2>() 

services.Scan(scan => scan
  .AddTypes<Service1, Service2>()
    .AsSelf()
    .WithTransientLifetime());

等价于:

services.AddTransient<Service1>();
services.AddTransient<Service2>();

最多可以一次性指定三个类型:

namespace Scrutor
{
    public static class TypeSelectorExtensions
    {
        public static IServiceTypeSelector AddType<T>(this ITypeSelector selector)
        {
            Preconditions.NotNull(selector, "selector");
            return selector.AddTypes(typeof(T));
        }

        public static IServiceTypeSelector AddTypes<T1, T2>(this ITypeSelector selector)
        {
            Preconditions.NotNull(selector, "selector");
            return selector.AddTypes(typeof(T1), typeof(T2));
        }

        public static IServiceTypeSelector AddTypes<T1, T2, T3>(this ITypeSelector selector)
        {
            Preconditions.NotNull(selector, "selector");
            return selector.AddTypes(typeof(T1), typeof(T2), typeof(T3));
        }
    }
}

这个办法并不是那么实用,当要注册的类很多时,还是很繁琐,我们希望能够自动扫描程序集。

4.2扫描类型所在的程序集

直接看例子:

services.Scan(scan => scan
  .FromAssemblyOf<IService>()
    .AddClasses()
      .AsSelf()
      .WithTransientLifetime());

上面的例子会将IService所在的程序集中所有的类都注册进去。(注意:是IService所在的程序集,而不是仅仅实现了IService的类

var s1=app.Services.GetService<Service1>();
//var s=app.Services.GetService<Dog>();
var b=app.Services.GetService<BackBag>();
//var Is=app.Services.GetService<IBag>();
Console.WriteLine("Get   + (Ibag)  "+s1?.GetType().Name);
//Console.WriteLine("Get:  +  "+s?.GetType().FullName);
Console.WriteLine("Get: -------- " +b?.GetType().Name);

运行结果是s1和b都能拿到实例。但是有个问题,这样都是直接注册类,所以在传统的Controller,才用IService接口定义,就无法拿到服务,要直接指定具体类。

此外常用的还有:  FromAssembliesOf,意思是多个程序集

FromCallingAssemblyFromExecutingAssemblyFromEntryAssembly ,这三个的意义可以查看具体资料。主要使用FromCallingAssembly拿到当前项目的程序集,FromExecutingAssembly拿到的是

Scrutor自身的程序集,所以基本上用不着。

scan.FromCallingAssembly === scan.FromAssemblies(Assembly.GetExecutingAssembly())
scan.FromExecutingAssembly === scan.FromAssemblies(typeof(Scrutor).Assembly)

4.3过滤类型

无论你使用那种程序集,都需要使用AddClass——将具体的类型添加到容器。AddClass函数有好些重载类型:

  • AddClasses() - 添加所有公共的非抽象的类
  • AddClasses(publicOnly) -添加所有公共的非抽象的类,当publicOnly为false,可以添加Internel和private类。
  • AddClass(predicate)  -添加自定义过滤器,灵活度高很实用
  • AddClasses(predicate, publicOnly)  -前面两者的结合

比如下面的例子,只注册能转化为IService的类:

services.Scan(scan => scan
  .FromAssemblyOf<IService>()
    .AddClasses(classes => classes.AssignableTo<IService>())
        .AsImplementedInterfaces()
        .WithTransientLifetime());

或者注册指定名称的类型

services.Scan(scan => scan
  .FromAssemblyOf<IService>()
    .AddClasses(classes => classes.Where(type => type.Name.EndsWith("Repository"))
        .AsImplementedInterfaces()
        .WithTransientLifetime());

 或者指定特定命名空间

services.Scan(scan => scan
  .FromAssemblyOf<IService>()
    .AddClasses(classes => classes.InNamespaces("MyApp"))
        .AsImplementedInterfaces()
        .WithTransientLifetime());

4.4处理多次注册策略

在使用选择器后使用,例子如下:

services.Scan(scan => scan
  .FromAssemblyOf<IService>()
    .AddClasses()
      .UsingRegistrationStrategy(RegistrationStrategy.Skip)
      .AsSelf()
      .WithTransientLifetime());

·如果使用Skip:

services.AddTransient<ITransientService, TransientService>(); // From previous registrations
services.AddScoped<IScopedService, ScopedService>(); // From previous registrations
services.AddTransient<IFooSerice, TransientService>();
services.AddScoped<IScopedService, AnotherService>();

那后面注册的IScopedService会被忽略,如果使用append,则所有注册都有效。

还有其它策略类型,可以见具体博客

4.5将类注册为服务(接口类型)

Scrutor有多种注册方式:

使用方法如下:

services.Scan(scan => scan
  .FromAssemblyOf<IService>()
    .AddClasses()
      .AsSelf() // Specify how the to register the implementations/services
      .WithSingletonLifetime());

AsSelf将类注册为自己,等价于:

services.AddSingleton<TestService>();

AsMatchingInterface() ,注册为标准的接口实现类, 等价于:

services.AddSingleton<ITestService, TestService>();

如果一个类派生自多个接口,那么可以使用AsImplementedInterfaces():

services.AddSingleton<ITestService, TestService>();
services.AddSingleton<IService, TestService>();

那么这个类可以从任何一个接口中获得实例。

如果要将一个实现类注册为多个服务,在默认容器你得这么做:

services.AddSingleton<TestService>();
services.AddSingleton<ITestService>(x => x.GetRequiredService<TestService>());
services.AddSingleton<IService>(x => x.GetRequiredService<TestService>());

但是使用AsSelfWithInterfaces(),可以实现上述效果。

最后是使用As<T>()将实现类注册为任意的服务(接口),但是T必须能转化为该接口类型,并不是随意的。

 4.6 指定生命周期

这个就不用赘述了.三种生命周期:

  • WithTransientLifetime() - Transient is the default lifetime if you don't specify one.
  • WithScopedLifetime() - Use the same service scoped to the lifetime of a request.
  • WithSingletonLifetime() - Use a single instance of the service for the lifetime of the app.

4.7 链式注册 

可以在一个语句注册多种服务。给个例子:

services.Scan(scan => scan
  .FromAssemblyOf<CombinedService>()
    .AddClasses(classes => classes.AssignableTo<ICombinedService>()) // Filter classes
      .AsSelfWithInterfaces()
      .WithSingletonLifetime()

    .AddClasses(x=> x.AssignableTo(typeof(IOpenGeneric<>))) // Can close generic types
      .AsMatchingInterface()

    .AddClasses(x=> x.InNamespaceOf<MyClass>())
      .UsingRegistrationStrategy(RegistrationStrategy.Replace()) // Defaults to ReplacementBehavior.ServiceType
      .AsMatchingInterface()
      .WithScopedLifetime()

  .FromAssemblyOf<DatabaseContext>()   // Can load from multiple assemblies within one Scan()
    .AddClasses() 
      .AsImplementedInterfaces()
);

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐