当前位置:首页 > 技术 > 正文内容

ASP.NET Core 依赖注入机制详解

访客 技术 2026年7月8日 1

在 ASP.NET Core 架构中,依赖注入(DI)被视为核心设计理念,内置框架天然支持这一模式。应用程序不仅能使用框架提供的服务,也能灵活注册自定义服务。内置的 IoC 容器虽然功能精简,但足以满足多数场景,并非设计用来取代 Autofac 等第三方容器。

1. 理解依赖注入核心概念

依赖注入是一项实现对象与其依赖项之间松耦合的技术。核心思想是:类不应在内部直接实例化依赖对象或使用静态引用,而应由外部将所需依赖"注入"进来。最常见的实现方式是通过构造函数声明依赖,这被称为"构造函数注入"。

采用 DI 设计的类,由于没有硬编码依赖的具体实现,耦合度显著降低。这符合"依赖倒置原则"(DIP):高层模块不应依赖低层模块,两者都应依赖于抽象。类在构造时接受抽象(如接口)作为参数,而非具体实现,这正是"策略设计模式"的体现。

负责管理和提供这些依赖的组件称为"容器"(Container),也称为"控制反转容器"(IoC Container)或"DI容器"。容器本质上是一个工厂,负责实例化被请求的类型。如果某类型声明了依赖,且容器已配置好这些依赖的映射关系,容器会在创建该类型实例时,自动解决其依赖链。此外,容器通常还管理着对象实例的生命周期。

ASP.NET Core 提供了一个内建的轻量级容器,默认支持构造函数注入。容器管理的类型被称为"服务"(Services),这些服务的配置通常在 Startup 类的 ConfigureServices 方法中完成。

2. 使用框架内置服务

ConfigureServices 方法的核心任务是注册应用程序运行所需的服务,包括框架自身功能(如 Entity Framework Core、ASP.NET Core MVC)和用户自定义的业务服务。除了 IServiceCollection 提供的基本方法,框架提供了丰富的扩展方法(如 AddDbContextAddMvcAddTransient 等)来批量注册服务:

public void ConfigureServices(IServiceCollection services)
{
    services.Configure<CookiePolicyOptions>(options =>
    {
        options.CheckConsentNeeded = context => true;
        options.MinimumSameSitePolicy = SameSiteMode.None;
    });

    services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
    services.AddDbContext<AccessManagementContext>(options =>
        options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"),
        providerOptions => providerOptions.EnableRetryOnFailure()));

    services.AddTransient<ICompanyServices, CompanyServices>();
}

框架功能遵循约定,通常通过单一的 Add[ServiceName] 扩展方法完成所需服务的全部注册。

3. 注册和管理自定义服务

注册自定义服务的语法为 services.AddTransient<IInterface, Implementation>();。第一个泛型参数是服务请求类型(通常是接口),第二个泛型参数是实现类型,由容器负责实例化。

AddTransient 方法将抽象类型映射到具体实现,每次请求该服务时都会创建一个新的实例。正确选择服务的生命周期至关重要,下文将详述。

以下是一个完整的服务注册与使用示例:

1. 接口定义

public interface IAccountServices
{
    Task<List<AccountViewModel>> GetList();
}

2. 实现类

public class AccountServices : IAccountServices
{
    private readonly AccessManagementContext _context;
    public AccountServices(AccessManagementContext context)
    {
        _context = context; // 构造函数注入 DbContext
    }
    public async Task<List<Account>> GetList()
    {
        return await _context.Account.ToListAsync();
    }
}

3. 在 Startup 中注册

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
    services.AddDbContext<AccessManagementContext>(options =>
        options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"),
        providerOptions => providerOptions.EnableRetryOnFailure()));
    services.AddTransient<IAccountServices, AccountServices>();
}

4. 在 Controller 中注入

public class AccountController : Controller
{
    private readonly IAccountServices _accountServices;
    public AccountController(IAccountServices accountServices)
    {
        _accountServices = accountServices;
    }
    public async Task<ActionResult> Index()
    {
        var viewModels = await _accountServices.GetList();
        return View(viewModels);
    }
}

4. 服务生命周期与注册选项详解

ASP.NET Core 容器支持三种主要生命周期模式:

  • Transient(瞬态):每次请求服务时都会创建一个新的实例。适用于轻量级、无状态的服务。
  • Scoped(作用域):在同一个作用域(通常是一个 HTTP 请求)内,所有对服务的请求共享同一个实例。
  • Singleton(单例):服务在首次被请求时创建,后续所有请求都使用同一个实例。

除了标准的注册方法,还可以通过工厂模式等方式注册服务。

以下示例演示了三种生命周期的区别:

1. 定义接口

namespace MvcDemo.Interfaces
{
    public interface IOperation
    {
        Guid Id { get; }
    }
    public interface IOperationTransient : IOperation { }
    public interface IOperationScoped : IOperation { }
    public interface IOperationSingleton : IOperation { }
}

2. 实现类

public class Operation : IOperationTransient, IOperationScoped, IOperationSingleton
{
    public Guid Id { get; } = Guid.NewGuid();
}

3. 注册服务

public void ConfigureServices(IServiceCollection services)
{
    services.AddTransient<IOperationTransient, Operation>();
    services.AddScoped<IOperationScoped, Operation>();
    services.AddSingleton<IOperationSingleton, Operation>();
    services.AddTransient<OperationService>();
    services.AddMvc();
}

4. 定义测试服务类

public class OperationService
{
    public IOperationTransient TransientOp { get; }
    public IOperationScoped ScopedOp { get; }
    public IOperationSingleton SingletonOp { get; }
    public OperationService(
        IOperationTransient transientOp,
        IOperationScoped scopedOp,
        IOperationSingleton singletonOp)
    {
        TransientOp = transientOp;
        ScopedOp = scopedOp;
        SingletonOp = singletonOp;
    }
}

5. 在 Controller 中注入

public class HomeController : Controller
{
    private readonly IOperationTransient _transient;
    private readonly IOperationScoped _scoped;
    private readonly IOperationSingleton _singleton;
    private readonly OperationService _service;
    public HomeController(
        IOperationTransient transient,
        IOperationScoped scoped,
        IOperationSingleton singleton,
        OperationService service)
    {
        _transient = transient;
        _scoped = scoped;
        _singleton = singleton;
        _service = service;
    }
    public IActionResult Index()
    {
        ViewBag.Transient = _transient.Id;
        ViewBag.Scoped = _scoped.Id;
        ViewBag.Singleton = _singleton.Id;
        ViewBag.ServiceTransient = _service.TransientOp.Id;
        ViewBag.ServiceScoped = _service.ScopedOp.Id;
        ViewBag.ServiceSingleton = _service.SingletonOp.Id;
        return View();
    }
}

6. 视图展示

<h2>Controller 中的服务实例 ID</h2>
<p>Transient: @ViewBag.Transient</p>
<p>Scoped: @ViewBag.Scoped</p>
<p>Singleton: @ViewBag.Singleton</p>
<h2>OperationService 中的服务实例 ID</h2>
<p>Transient: @ViewBag.ServiceTransient</p>
<p>Scoped: @ViewBag.ServiceScoped</p>
<p>Singleton: @ViewBag.ServiceSingleton</p>

7. 运行结果分析

刷新页面时,Singleton ID 始终不变;Scoped ID 在同一请求内的 Controller 和 Service 中一致,但不同请求间变化;Transient ID 则在每次注入时都不同。

5. 请求服务与上下文

在 ASP.NET Core 请求管道中,通过 HttpContext.RequestServices 可以访问当前请求范围内的服务集合。这使得框架能够在处理请求时,正确地从配置的服务容器中解析所需依赖。

6. 设计符合 DI 原则的服务

在开发自定义服务时,应避免静态方法和直接 new 出依赖的行为。依赖应通过构造函数注入方式请求。如果一个构造函数需要注入过多的依赖,通常意味着该类承担了过多的职责(违反单一职责原则),应考虑拆分或重构。

Controller 应专注于 UI 逻辑,而业务逻辑、数据访问等应委托给独立的服务类处理。

7. 替换为 Autofac 容器

当内置容器无法满足高级需求时,可以将其替换为 Autofac 等第三方 IoC 容器,以获得更丰富的功能支持。

相关文章

Linux crontab 详解

1) crontab 是什么cron 是 Linux 的定时任务守护进程;crontab 是用来编辑/查看“按时间周期执行命令”的表(cron table)。常见两类:用户 crontab:每个用户一份(crontab -e 编辑)系统级 crontab / cron.d:可指定执行用户(/etc/crontab、/etc/cron.d/*)2) crontab 时间...

富文本里可以允许的 HTML 属性

一、所有标签默认允许的安全属性(极少)class        (可选)id           (通常建议禁用)title️ 注意:id 容易被滥用做锚点注入,很多系统直接禁用class 允许的话最好只允许固定前缀(如 editor-*)二、a 标签允许属性<a href="" t...

Mac 安装 Node.js 指南

方法一:通过官网安装包(最简单,适合初学者)如果你只是想快速安装并开始使用,这是最直接的方法。访问 Node.js 官网。页面会显示两个版本:LTS (Recommended For Most Users):长期支持版,最稳定。建议选这个。Current:最新特性版,包含最新功能但可能不够稳定。下载 .pkg 安装包并运行。按照安装向导点击“下一步”即可完成。方法二:使用 Homebrew 安装(...

Dom\HTML_NO_DEFAULT_NS 的副作用:自动加闭合标签

在使用Dom\HTMLDocument时,Dom\HTML_NO_DEFAULT_NS 将禁止在解析过程中设置元素的命名空间, 此设置是为了与DOMDocument向后兼容而存在的。当使用它时,已知的一个副作用就是:自动加闭合标签例如 </img> 为什么会这样?当你使用:Dom\HTML_NO_DEFAULT_NS文档会变成 无命名空间模式,此时内部更接近 XML...

Laravel 事件和监听器创建

在 Laravel 中,使用 Artisan 命令创建 Events(事件) 和 Listeners(监听器) 是非常高效的。你可以通过以下几种方式来实现:1. 手动创建单个 Event如果你只想创建一个事件类,可以使用 make:event 命令:Bashphp artisan make:event UserRegistered执行后,文件将生成在 app/Even...

自定义域名解析神器 dnsmasq

什么是 dnsmasq?dnsmasq 是一个轻量级、功能强大的网络服务工具,专为小型和中等规模网络设计。它是一个综合的网络基础设施解决方案[1]。dnsmasq 能做什么?功能说明应用场景DNS 转发与缓存将 DNS 查询转发到上游服务器(ISP、Google DNS 等),并在本地缓存结果加快 DNS 查询速度,减少外部 DNS 流量本地 DNS解析本地网络设备的主机名,无需编辑&n...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。