项目
版本

Autofac 快速入门

将 Autofac 集成到应用程序的基本模式如下:

  1. 考虑采用控制反转(IoC)进行应用架构设计
  2. 添加 Autofac 引用。
  3. 在应用程序启动时...
    • 创建一个 ContainerBuilder
    • 注册组件。
    • 构建容器并存储以备后续使用。
  4. 在应用程序执行期间...
    • 从容器创建一个生命周期作用域。
    • 使用生命周期作用域来解析组件实例。

这个入门指南会逐步引导您完成一个简单的控制台应用程序。掌握了基础知识后,您可以查看其他部分,了解更高级的用法和针对不同类型应用程序(如 WCF、ASP.NET 等)的 集成

应用程序结构

控制反转的核心思想是,而不是让应用程序中的类彼此关联并让类“new”它们的依赖项,而是反转这一过程,使得依赖项在类构造时传递进来。Martin Fowler 写了一篇很好的文章解释了依赖注入/控制反转,如果您对此感兴趣,可以阅读这篇文章。

对于我们的示例应用,我们将定义一个写当前日期的类。但不想将其绑定到 Console,因为我们希望以后能测试该类或在没有控制台的地方使用它。

我们甚至可以进一步抽象出写入操作,以便将来轻松替换一个写明天日期的版本。

我们会这样做:

using System;

namespace DemoApp
{
    // 这个接口帮助解耦“写入输出”的概念与 Console 类。我们并不关心写入操作如何实现,只关心能否写入。
    public interface IOutput
    {
        void Write(string content);
    }

    // 这个 IOutput 接口的实现实际上就是向 Console 写入。技术上讲,我们也可以实现 IOutput 来写入 Debug 或 Trace... 或任何其他地方。
    public class ConsoleOutput : IOutput
    {
        public void Write(string content)
        {
            Console.WriteLine(content);
        }
    }

    // 这个接口解耦了写入日期的概念与实际执行写入的操作。与 IOutput 类似,过程被一个接口抽象出来。
    public interface IDateWriter
    {
        void WriteDate();
    }

    // 这个 TodayWriter 就是这一切的结合点。注意它接受一个 IOutput 构造参数 - 这使得根据实现,写入器可以写入任何地方。此外,它实现了 WriteDate 方法,使得今天日期会被写出来;你可以有一个写入不同格式或日期的版本。
    public class TodayWriter : IDateWriter
    {
        private IOutput _output;
        public TodayWriter(IOutput output)
        {
            this._output = output;
        }

        public void WriteDate()
        {
            this._output.Write(DateTime.Today.ToShortDateString());
        }
    }
}

现在我们有了一个相当合理(尽管有些牵强)的依赖关系集合,让我们开始使用 Autofac!

添加 Autofac 引用

第一步是在项目中添加 Autofac 引用。在这个示例中,我们只使用核心的 Autofac。其他应用程序类型可能需要额外的 Autofac 集成库

最简单的方法是通过 NuGet。"Autofac" 包含了您所需的全部核心功能。

image

应用程序启动

在应用程序启动时,您需要创建一个 ContainerBuilder 并使用它注册您的 组件。组件是一个表达式、.NET 类型或其他代码片段,它公开一或多个 服务 并可以接收其他 依赖项

简单来说,想象一个实现了接口的 .NET 类,例如:

public class SomeType : IService
{
}

您可以使用以下两种方式之一来引用该类型:

  • 作为类型本身,SomeType
  • 作为接口,IService

在这种情况下,组件是 SomeType,它公开的服务是 SomeTypeIService

在 Autofac 中,您会像这样使用 ContainerBuilder 注册它:

// 创建您的构建器。
var builder = new ContainerBuilder();

// 通常您只对通过其接口暴露类型感兴趣:
builder.RegisterType<SomeType>().As<IService>();

// 但是,如果您想同时提供这两种服务(不那么常见)
// 您可以这样说:
builder.RegisterType<SomeType>().AsSelf().As<IService>();

对于我们的示例应用,我们需要注册所有组件(类)并暴露它们的服务(接口),以便所有东西都能很好地连接起来。

我们还需要存储容器,以便稍后使用它来解析类型。

using System;
using Autofac;

namespace DemoApp
{
    public class Program
    {
        private static IContainer Container { get; set; }

        static void Main(string[] args)
        {
            var builder = new ContainerBuilder();
            builder.RegisterType<ConsoleOutput>().As<IOutput>();
            builder.RegisterType<TodayWriter>().As<IDateWriter>();
            Container = builder.Build();

            // WriteDate 方法是我们将利用依赖注入的地方。稍后我们会定义这个方法。
            WriteDate();
        }
    }
}

现在我们有了一个包含所有组件的 容器,并且它们已经正确地公开了 服务。让我们使用它吧。

应用程序执行

在应用程序执行期间,您需要使用已注册的组件。您通过从一个 生命周期作用域解析 它们来做到这一点。容器本身就是一个生命周期作用域,您可以直接从容器解析东西。然而,不建议直接从容器解析

当您解析一个组件时,根据定义的 实例作用域,会为该对象创建一个新的实例。(解析组件大致相当于调用 new 来实例化一个类。这非常简化了,但从类比的角度来看是可以的。)一些组件可能需要释放(例如,它们实现了 IDisposable)- Autofac 可以在作用域释放时为您处理这些组件的释放

但是,容器会存在整个应用程序的生命周期。如果您直接从容器解析很多东西,可能会有很多东西等待释放,这可能不是好事(而且您可能会看到 “内存泄漏”)。相反,从容器创建一个 子生命周期作用域,然后从中解析。当您完成解析组件时,释放子作用域,所有东西都会为您清理干净。

(当您使用 Autofac 集成库 时,子作用域的创建在很大程度上为您处理,您无需考虑。)

对于我们的示例应用,我们将实现 WriteDate 方法,从作用域中获取写入器,并在完成后释放作用域。

namespace DemoApp
{
    public class Program
    {
        private static IContainer Container { get; set; }

        static void Main(string[] args)
        {
            // ...前面看到的内容...
        }

        public static void WriteDate()
        {
            // 创建作用域,解析 IDateWriter,使用它,然后释放作用域。
            using (var scope = Container.BeginLifetimeScope())
            {
                var writer = scope.Resolve<IDateWriter>();
                writer.WriteDate();
            }
        }
    }
}

现在当你运行程序时...

  • WriteDate 方法从作用域中创建一个生命周期作用域,以便避免任何内存泄漏 - 如果 IDateWriter 或其依赖项是可释放的,它们会在作用域释放时自动释放。
  • WriteDate 方法手动从生命周期作用域中解析一个 IDateWriter。(这是 “服务定位” 。)内部...
    • Autofac 观察到 IDateWriter 映射到 TodayWriter,所以开始创建一个 TodayWriter
    • Autofac 观察到 TodayWriter 的构造函数需要一个 IOutput。 ( 这是 “构造注入” 。 )
    • Autofac 观察到 IOutput 映射到 ConsoleOutput,所以创建了一个新的 ConsoleOutput 实例。
    • Autofac 使用新创建的 ConsoleOutput 实例完成 TodayWriter 的构造。
    • Autofac 返回完全构造的 TodayWriterWriteDate 使用。
  • 对于 writer.WriteDate() 的调用,会去调用 TodayWriter.WriteDate(),因为这就是解析的结果。
  • Autofac 生命周期作用域被释放。从该生命周期作用域解析的所有可释放项也会被释放。

稍后,如果您想让应用程序写不同的日期,您可以实现不同的 IDateWriter,然后在启动时更改注册。您不必更改其他任何类。太棒了,控制反转!

注意:一般来说,服务定位通常被认为是反模式 (参见文章)。也就是说,在代码中到处创建作用域并在每个地方散播对容器的使用并不是最佳做法。使用 Autofac 集成库 通常你不必像上面示例应用中那样做。相反,你通常会在应用的中央、顶层 位置进行依赖注入,手动解析的情况非常少见。当然,如何设计你的应用取决于你自己。

更进一步

示例应用演示了如何使用 Autofac,但你可以做的更多:

需要帮助?

从源代码构建

源代码和 Visual Studio 项目文件可以在 GitHub 上找到。构建说明和贡献指南请参阅 贡献者指南

在本文档中