Skip to content

Autofac 依赖注入与三层架构实践 ​

这是一个在 ASP.NET Core 3.0 环境下,配合 EF Core 和 Autofac 搭建经典三层项目架构的实践总结。

1. 为什么要使用依赖注入框架 ​

  • 解耦:实现表现层(Web)、业务层(Service)和数据访问层(Data/Repository)的物理与逻辑解耦。
  • 对象生命周期管理:由容器统一管理对象的创建与销毁,避免手动 new 对象导致的依赖混乱。
  • 提高测试性:方便通过接口进行 Mock 测试。

2. 核心项目结构 ​

建议的经典分层结构如下:

  • TinyErp.Web: 表现层(控制器、视图/API)。
  • TinyErp.Service: 业务逻辑层。
  • TinyErp.Data: 数据访问层(EF Core DbContext、Repository 接口实现)。
  • TinyErp.Model: 实体模型层。

3. Autofac 核心配置流程 ​

3.1 启用 Autofac 服务工厂 ​

在 Program.cs 中,通过 .UseServiceProviderFactory(new AutofacServiceProviderFactory()) 替换默认的容器工厂。

csharp
public static IHostBuilder CreateHostBuilder(string[] args) =>
    Host.CreateDefaultBuilder(args)
        .ConfigureLogging((context, loggingBuilder) =>
        {
            // 日志配置...
        })
        .ConfigureWebHostDefaults(webBuilder =>
        {
            webBuilder.UseStartup<Startup>();
        })
        // 关键:启用 Autofac
        .UseServiceProviderFactory(new AutofacServiceProviderFactory());

3.2 批量注册逻辑 ​

在 Startup.cs 中添加 ConfigureContainer 方法。这是 Autofac 独有的配置入口,支持程序集扫描(Assembly Scanning)。

csharp
public void ConfigureContainer(ContainerBuilder builder)
{
    // 1. 加载程序集
    Assembly service = Assembly.Load("TinyErp.Service");
    Assembly data = Assembly.Load("TinyErp.Data");

    // 2. 程序集扫描注册(按命名约定)
    builder.RegisterAssemblyTypes(service, data)
           .Where(t => (t.FullName.EndsWith("Service") || t.Name.EndsWith("Repository")) && !t.IsAbstract)
           .InstancePerLifetimeScope() // 生命周期:作用域内单例
           .AsImplementedInterfaces()  // 自动映射到接口
           .PropertiesAutowired();     // 开启属性注入

    // 3. 泛型仓储注册
    builder.RegisterGeneric(typeof(GenericRepository<>))
           .As(typeof(IGenericRepository<>))
           .InstancePerLifetimeScope();

    // 4. 工作单元与基础上下文注册
    builder.RegisterType(typeof(TinyErpDbContext)).As(typeof(DbContext)).InstancePerLifetimeScope();
    builder.RegisterType(typeof(UnitOfWork)).As(typeof(IUnitOfWork)).InstancePerLifetimeScope();

    // 5. 如果需要在 Controller 中使用属性注入,需显式注册控制器
    var controllerBaseType = typeof(ControllerBase);
    builder.RegisterAssemblyTypes(typeof(Program).Assembly)
           .Where(t => controllerBaseType.IsAssignableFrom(t) && t != controllerBaseType)
           .PropertiesAutowired();
}

4. EF Core 集成要点 ​

4.1 数据库初始化 ​

在 Program.cs 启动时进行数据库检查与初始化。

csharp
private static void CreateDbIfNotExists(IHost host)
{
    using (var scope = host.Services.CreateScope())
    {
        var services = scope.ServiceProvider;
        try
        {
            var context = services.GetRequiredService<TinyErpDbContext>();
            DbInitializer.Initialize(context);
        }
        catch (Exception ex)
        {
            var logger = services.GetRequiredService<ILogger<Program>>();
            logger.LogError(ex, "数据库初始化失败");
        }
    }
}

4.2 DbContext 定义 ​

csharp
public class TinyErpDbContext : DbContext
{
    public TinyErpDbContext(DbContextOptions<TinyErpDbContext> options) : base(options) { }

    public DbSet<Student> Students { get; set; }
    // ... 其他 DbSet

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Student>().ToTable("Student");
    }
}

5. 常见坑点与踩坑笔记 ​

5.1 子类继承父类的构造函数问题 ​

在实现 Service 层基类继承时,子类必须显式调用父类的构造函数并传递相应参数。

csharp
public class StudentService : EntityService<Student>, IStudentService
{
    public IUnitOfWork unitOfWork;
    public IGenericRepository<Student> studentRepository;

    public StudentService(IUnitOfWork unitOfWork, IGenericRepository<Student> studentRepository)
        : base(unitOfWork, studentRepository) // 必须调用 base
    {
        this.unitOfWork = unitOfWork;
        this.studentRepository = studentRepository;
    }
}

5.2 属性注入失效 ​

如果发现属性注入(PropertiesAutowired)不生效,通常是因为:

  1. 对象不是通过容器创建的(使用了 new)。
  2. 控制器没有显式在 ConfigureContainer 中注册。

6. 参考资料 ​

方舟 · Ark