aspnet-core-系列14-net-core-中的IOC

45次阅读

共计 5062 个字符,预计需要花费 13 分钟才能阅读完成。

0. 前言

通过前面几篇,我们了解到了如何实现项目的基本架构:数据源、路由设置、加密以及身份验证。那么在实现的时候,我们还会遇到这样的一个问题:当我们业务类和数据源越来越多的时候,我们无法通过普通的构造对象的方法为每个实例进行赋值。同时,传统意义上的赋值遇到底层切换或者其他修改的时候,就需要修改大量的代码,对改变不友好。为了改变这种现状,我们基于面向接口编程,然后使用一些 DI 功能和 IOC 框架。

1. IOC 和 DI

先来给大家解释几个概念,IOC 全称 Inversion of Control,翻译过来就是控制反转,是面向对象编程的一种设计原则,用来降低代码之间的耦合度。所谓的控制反转简单来讲就是将类中属性或者其他参数的初始化交给其他方处理,而不是直接使用构造函数。

public class Demo1
{
}

public class Demo2
{public Demo1 demo;}

对于以上简单示例代码中,在 Demo2 类中持有了一个 Demo1 的实例。如果按照之前的情况来讲,我们会通过以下方法为 demo 赋值:

// 方法一
public Demo1 demo = new Demo1();
// 方法二
public Demo2()
{demo = new Demo1();
}

这时候,如果 Demo1 变成下面的样子:

public class Demo1
{public Demo1(Demo3 demo3)
    {// 隐藏}
}
public class Demo3
{}

那么,如果 Demo2 没有持有一个 Demo3 的实例对象,这时候创建 Demo1 的时候就需要额外构造一个 Demo3。如果 Demo3 需要持有另外一个类的对象,那么 Demo2 中就需要多创建一个对象。最后就会发现这样就陷入了一个构造“地狱”(我发明的词,指这种为了一个对象却得构造一大堆其他类型的对象)。

实际上,对于 Demo2 并不关心 Demo1 的实例对象是如何获取的,甚至都不关心它是不是 Demo1 的子类或者接口实现类。我在示例中使用了类,但这里可以同步替换成 Interface,替换之后,Demo2 在调用 Demo1 的时候,还需要知道 Demo1 有实现类,以及实现类的信息。

为了解决这个问题,一些高明的程序员们提出了将对象的创建这一过程交给第三方去操作,而不是调用类来创建。于是乎,上述代码就变成了:

public class Demo2
{public Demo1 Demo {get;set;}
    public Demo2(Demo1 demo)
    {Demo = demo;}
}

似乎并没有什么变化?对于 Demo2 来说,Demo2 从此不再负责 Demo1 的创建,这个步骤交由 Demo2 的调用方去创建,Demo2 从此从负责维护 Demo1 这个对象的大麻烦中解脱了。

但实际上构造地狱的问题还是没有解决,只不过是通过 IOC 的设计将这一步后移了。这时候,那些大神们想了想,不如开发一个框架这些实体对象吧。所以就出现了很多 IOC 框架:AutoFac、Sping.net、Unity 等。

说到 IOC 就不得不提一下 DI(Dependency Injection) 依赖注入。所谓的依赖注入就是属性对应实例通过构造函数或者使用属性由第三方进行赋值。也就是最后 Demo2 的示例代码中的写法。

早期 IOC 和 DI 是指一种技术,后来开始确定这是不同的描述。IOC 描述的是一种设计模式,而 DI 是一种行为。

2. 使用 asp.net core 的默认 IOC

在之前的 ASP.NET 框架中,微软并没有提供默认的 IOC 支持。在最新的 asp.net core 中微软提供了一套 IOC 支持,该支持在命名空间:

Microsoft.Extensions.DependencyInjection

里,在代码中引用即可。

主要通过以下几组方法实现:

public static IServiceCollection AddScoped<TService>(this IServiceCollection services) where TService : class;
public static IServiceCollection AddSingleton<TService>(this IServiceCollection services) where TService : class;
public static IServiceCollection AddTransient<TService>(this IServiceCollection services) where TService : class;

这里只列出了这三组方法的一种重载版本。

这三组方法分别代表三种生命周期:

  • AddScored 表示对象的生命周期为整个 Request 请求
  • AddTransient 表示每次从服务容器进行请求时创建的,适合轻量级、无状态的服务
  • AddSingleton 表示该对象在第一次从服务容器请求后获取,之后就不会再次初始化了

这里每组方法只介绍了一个版本,但实际上每个方法都有以下几个版本:

public static IServiceCollection AddXXX<TService>(this IServiceCollection services) where TService : class;
public static IServiceCollection AddXXX(this IServiceCollection services, Type serviceType, Type implementationType);
public static IServiceCollection AddXXX(this IServiceCollection services, Type serviceType, Func<IServiceProvider, object> implementationFactory);
public static IServiceCollection AddXXX<TService, TImplementation>(this IServiceCollection services)
            where TService : class
            where TImplementation : class, TService;
public static IServiceCollection AddXXX(this IServiceCollection services, Type serviceType);
public static IServiceCollection AddXXX<TService>(this IServiceCollection services, Func<IServiceProvider, TService> implementationFactory) where TService : class;
public static IServiceCollection AddXXX<TService, TImplementation>(this IServiceCollection services, Func<IServiceProvider, TImplementation> implementationFactory)
            where TService : class
            where TImplementation : class, TService;

其中:implementationFactory 表示通过一个 Provider 实现 TService/TImplementation 的工厂方法。当方法指定了泛型的时候,会自动依据泛型参数获取要注入的类型信息,如果没有使用泛型则必须手动传入参数类型。

asp.net core 如果使用依赖注入的话,需要在 Startup 方法中设置,具体内容可以参照以下:

public void ConfigureServices(IServiceCollection services)
{
    // 省略其他代码
    services.AddScoped<ISysUserAuthRepository,SysUserAuthRepository>();}

asp.net core 为 DbContext 提供了不同的 IOC 支持,AddDbContext:

public static IServiceCollection AddDbContext<TContext>(
      this IServiceCollection serviceCollection,
      Action<DbContextOptionsBuilder> optionsAction = null,
      ServiceLifetime contextLifetime = ServiceLifetime.Scoped,
      ServiceLifetime optionsLifetime = ServiceLifetime.Scoped)
      where TContext : DbContext;

使用方法如下:

services.AddDbContext<DefaultContext>();

3. AutoFac 使用

理论上,asp.net core 的 IOC 已经足够好了,但是依旧原谅我的贪婪。如果有二三百个业务类需要我来设置的话,我宁愿不使用 IOC。因为那配置起来就是一场极其痛苦的过程。不过,可喜可贺的是 AutoFac 可以让我免收这部分的困扰。

这里简单介绍一下如何使用 AutoFac 作为 IOC 管理:

cd Web  # 切换目录到 Web 项目
dotnet package add Autofac.Extensions.DependencyInjection # 添加 AutoFac 的引用 

因为 asp.net core 版本 3 更改了一些逻辑,AutoFac 的引用方式发生了改变,现在不介绍之前版本的内容,以 3 为主。使用 AutoFac 需要先在 Program 类里设置以下代码:

public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
            .UseServiceProviderFactory(new AutofacServiceProviderFactory()) // 添加这行代码
            .ConfigureWebHostDefaults(webBuilder =>
            {webBuilder.UseStartup<Startup>();
            });

在 Program 类里启用 AutoFac 的一个 Service 提供工厂类。然后在 Startup 类里添加如下方法:

public void ConfigureContainer(ContainerBuilder builder)
{builder.RegisterType<DefaultContext>().As<DbContext>()
                .WithParameter("connectStr","Data Source=./demo.db")
                .InstancePerLifetimeScope();
            

    builder.RegisterAssemblyTypes(Assembly.Load("Web"))
        .Where(t => t.BaseType.FullName.Contains("Filter"))
        .AsSelf();

    builder.RegisterAssemblyTypes(Assembly.Load("Domain"),
                    Assembly.Load("Domain.Implements"), Assembly.Load("Service"), Assembly.Load("Service.Implements"))
                .AsSelf()
                .AsImplementedInterfaces()
                .InstancePerLifetimeScope()
                .PropertiesAutowired();}

修改:

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllersWithViews(options =>
            {options.Filters.Add<UnitOfWorkFilterAttribute>();
            }).AddControllersAsServices();// 这行新增
    // 省略其他
}

4. 总结

这一篇简单介绍了如何在 Asp.net Core 中启用 IOC 支持,并提供了两种方式,可以说是各有优劣。小伙伴们根据自己需要选择。后续会为大家详细深入 AutoFac 之类 IOC 框架的核心秘密。

更多内容烦请关注我的博客《高先生小屋》

正文完
 0