MVC 依賴注入(DI)與服務全解析(附避坑實戰)

依賴注入的核心概念

依賴注入(DI)是一種設計模式,通過將對象的依賴關系從內部創建轉移到外部傳遞,實現解耦。在 MVC 框架中,DI 容器負責管理對象的生命周期和依賴關系,開發者只需聲明依賴,容器自動完成注入。

生命周期配置

不同框架的生命周期命名可能不同,但核心分為三類:

  • 瞬態(Transient):每次請求都創建新實例,適合無狀態的輕量級服務。
  • 作用域(Scoped):同一請求內共享實例,常見于 HTTP 上下文相關的服務(如數據庫連接)。
  • 單例(Singleton):全局共享一個實例,適用于耗時資源(如配置中心)。

錯誤示例:將數據庫上下文誤注冊為單例,導致多用戶數據混亂。

// 錯誤:DbContext 應使用 Scoped 而非 Singleton
services.AddSingleton<AppDbContext>();

注入方式對比

  • 構造函數注入:強類型,顯式聲明依賴,推薦作為首選。
  • 屬性注入:靈活性高,但可能隱藏依賴關系,需謹慎使用。
  • 方法注入:適用于臨時依賴,常見于工廠模式。

推薦代碼示例:

public class OrderService
{private readonly IPaymentGateway _gateway;// 構造函數注入public OrderService(IPaymentGateway gateway){_gateway = gateway;}
}

常見問題與解決方案

循環依賴:A 依賴 B,B 又依賴 A。可通過提取公共邏輯到第三類或改用方法注入解決。
過度注入:構造函數參數過多(如超過 5 個),需拆分職責或引入聚合服務。

測試中的應用

通過模擬依賴項(Mock)實現單元測試隔離。例如使用 Moq 框架:

var mockGateway = new Mock<IPaymentGateway>();
mockGateway.Setup(g => g.Process(It.IsAny<decimal>())).Returns(true);
var service = new OrderService(mockGateway.Object);

框架差異示例

  • ASP.NET Core:內置 DI 容器,通過 IServiceCollection 配置。
  • Spring Boot:使用 @Autowired 注解實現注入。
  • Laravel:通過服務容器綁定依賴,支持自動解析。

最佳實踐

  • 優先選擇構造函數注入,明確依賴關系。
  • 根據業務需求嚴格匹配生命周期,避免跨請求狀態污染。
  • 定期檢查容器配置,移除未使用的服務以減少開銷。

通過合理使用 DI,可顯著提升代碼的可維護性和可測試性,減少模塊間的耦合度。### 依賴注入在 .NET Framework 與 .NET Core 中的配置差異

基礎準備:定義服務接口與實現

定義服務接口與實現類,確保接口與實現分離,便于解耦和測試。以下是一個示例:

// 服務接口(定義契約)
public interface IProductService
{List<Product> GetHotProducts(int count);Product GetById(int id);
}// 服務實現(具體邏輯)
public class ProductService : IProductService
{private readonly AppDbContext _dbContext;// 構造函數注入依賴(DbContext 也是服務)public ProductService(AppDbContext dbContext){_dbContext = dbContext;}public List<Product> GetHotProducts(int count){return _dbContext.Products.Where(p => p.IsHot && p.IsActive).Take(count).ToList();}public Product GetById(int id){return _dbContext.Products.Find(id);}
}
.NET Core/.NET 5+ 配置

.NET Core 內置 DI 容器,配置入口在 Program.cs 文件中,通過 IServiceCollection 注冊服務:

var builder = WebApplication.CreateBuilder(args);// 添加 MVC 控制器與視圖支持
builder.Services.AddControllersWithViews();// 注冊數據庫上下文(Scoped 生命周期)
builder.Services.AddDbContext<AppDbContext>(options =>options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));// 注冊自定義服務(推薦接口+實現)
builder.Services.AddScoped<IProductService, ProductService>();// 直接注冊實現類(無接口時用)
builder.Services.AddTransient<LogService>();// 注冊配置類(從 appsettings.json 綁定)
builder.Services.Configure<AppSettings>(builder.Configuration.GetSection("AppSettings"));var app = builder.Build();
// ... 中間件配置 ...
app.Run();
.NET Framework 配置(使用 Autofac)

.NET Framework 原生 DI 功能較弱,需借助第三方容器(如 Autofac):

  1. 安裝 Autofac 包:

    Install-Package Autofac.Mvc5
    
  2. Global.asax 中配置:

    public class MvcApplication : System.Web.HttpApplication
    {protected void Application_Start(){// 初始化 Autofac 容器var builder = new ContainerBuilder();// 注冊控制器(Autofac 需顯式注冊控制器)builder.RegisterControllers(typeof(MvcApplication).Assembly);// 注冊數據庫上下文(InstancePerRequest 對應 .NET Core 的 Scoped)builder.RegisterType<AppDbContext>().InstancePerRequest();// 注冊自定義服務builder.RegisterType<ProductService>().As<IProductService>().InstancePerRequest();// 設置 MVC 的依賴解析器var container = builder.Build();DependencyResolver.SetResolver(new AutofacDependencyResolver(container));// 其他 MVC 初始化AreaRegistration.RegisterAllAreas();FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);}
    }
    
生命周期對比
  • .NET Core:提供 Transient(每次請求新建)、Scoped(每次請求單例)、Singleton(全局單例)三種生命周期。
  • Autofac:提供 InstancePerRequest(類似 Scoped)、InstancePerDependency(類似 Transient)、SingleInstance(類似 Singleton)。
關鍵區別
  1. 配置入口:.NET Core 在 Program.cs,.NET Framework 在 Global.asax
  2. 容器依賴:.NET Core 內置 DI 容器,.NET Framework 需引入第三方庫。
  3. 控制器注冊:.NET Core 自動注冊控制器,Autofac 需顯式注冊。

注入服務到控制器

在控制器中使用構造函數注入是最推薦的方式。通過私有只讀字段存儲服務實例,確保依賴項通過構造函數傳入,避免手動實例化服務。

public class ProductsController : Controller
{private readonly IProductService _productService;private readonly IOptions<AppSettings> _appSettings;public ProductsController(IProductService productService,IOptions<AppSettings> appSettings){_productService = productService;_appSettings = appSettings;}public ActionResult HotProducts(){int hotCount = _appSettings.Value.HotProductCount;var hotProducts = _productService.GetHotProducts(hotCount);return View(hotProducts);}
}

注入服務到視圖

視圖中的服務注入適用于簡單場景,避免使視圖邏輯過于復雜。使用@inject指令聲明服務,直接在視圖中使用。

@model List<Product>
@inject IProductService ProductService
@inject IOptions<AppSettings> AppSettings<h3>熱門商品(共 @AppSettings.Value.HotProductCount 個)</h3>
<ul>@foreach (var product in Model){<li>@product.Name - ¥@product.Price</li>}
</ul><p>本月熱銷:@ProductService.GetHotProducts(1).FirstOrDefault()?.Name</p>

注入服務到過濾器

過濾器默認不支持構造函數注入,需通過TypeFilterServiceFilter實現依賴注入。

public class LogFilter : IActionFilter
{private readonly LogService _logService;public LogFilter(LogService logService){_logService = logService;}public void OnActionExecuting(ActionExecutingContext filterContext){var controller = filterContext.Controller.ToString();var action = filterContext.ActionDescriptor.ActionName;_logService.WriteLog($"請求:{controller}/{action}");}public void OnActionExecuted(ActionExecutedContext filterContext) { }
}

在控制器或方法上使用TypeFilter

[TypeFilter(typeof(LogFilter))]
public class ProductsController : Controller
{// 控制器邏輯
}

全局注冊過濾器:

builder.Services.AddControllersWithViews(options =>
{options.Filters.Add<TypeFilter<LogFilter>>();
});

Singleton、Scoped、Transient 核心區別

Singleton
實例在第一次請求時創建,整個應用生命周期內保持唯一。適用于無狀態服務,如全局配置、工具類。

builder.Services.AddSingleton<IMailService, MailService>();

Scoped
每個請求范圍內創建一個實例,同一請求內多次注入共享同一實例。適用于有狀態服務,如數據庫上下文(DbContext)、用戶會話。

builder.Services.AddScoped<IProductService, ProductService>();

Transient
每次注入或獲取服務時都創建新實例。適用于輕量級、無狀態服務,如日志記錄器、驗證器。

builder.Services.AddTransient<ILoginValidator, LoginValidator>();

常見錯誤案例與解決方案

錯誤1:Singleton 依賴 Scoped 服務
問題:Singleton 長期持有 Scoped 服務(如 DbContext),導致內存泄漏和數據不一致。
錯誤代碼示例:

public class SingletonService : ISingletonService
{private readonly AppDbContext _dbContext;public SingletonService(AppDbContext dbContext) => _dbContext = dbContext;public void DoWork() => var data = _dbContext.Products.ToList();
}
// ? 錯誤注冊
builder.Services.AddSingleton<ISingletonService, SingletonService>();
builder.Services.AddScoped<AppDbContext>();

解決方案
通過 IServiceScopeFactory 創建臨時作用域:

public class SingletonService : ISingletonService
{private readonly IServiceScopeFactory _scopeFactory;public SingletonService(IServiceScopeFactory scopeFactory) => _scopeFactory = scopeFactory;public void DoWork(){using (var scope = _scopeFactory.CreateScope()){var dbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();var data = dbContext.Products.ToList(); // 正確釋放}}
}

錯誤2:Transient 用于有狀態服務
問題:每次注入生成新實例,導致狀態丟失(如購物車數據)。
錯誤代碼示例:

public class CartService : ICartService
{public List<CartItem> Items { get; set; } = new();public void AddItem(CartItem item) => Items.Add(item);
}
// ? 錯誤注冊
builder.Services.AddTransient<ICartService, CartService>();// 控制器中狀態丟失
_cartService1.AddItem(new CartItem { Id = 1 });
var count = _cartService2.Items.Count; // 結果為 0

解決方案
改用 Scoped 生命周期:

builder.Services.AddScoped<ICartService, CartService>();

生命周期選擇原則

  • 無狀態且全局共享:Singleton
  • 請求內有狀態或需隔離:Scoped
  • 短暫、無狀態且輕量:Transient

避免跨生命周期依賴(如 Singleton 直接依賴 Scoped),優先通過工廠模式或作用域隔離解決。

自定義緩存過濾器的實現步驟

定義緩存服務接口與實現
public interface ICacheService
{T Get<T>(string key);void Set<T>(string key, T value, TimeSpan expiration);void Remove(string key);
}public class MemoryCacheService : ICacheService
{private readonly IMemoryCache _memoryCache;public MemoryCacheService(IMemoryCache memoryCache){_memoryCache = memoryCache;}public T Get<T>(string key) => _memoryCache.TryGetValue(key, out T value) ? value : default;public void Set<T>(string key, T value, TimeSpan expiration) => _memoryCache.Set(key, value, expiration);public void Remove(string key) => _memoryCache.Remove(key);
}
創建自定義緩存過濾器
public class CustomCacheFilter : IActionFilter
{private readonly ICacheService _cacheService;private readonly string _cacheKey;private readonly int _expirationMinutes;public CustomCacheFilter(ICacheService cacheService, string cacheKey, int expirationMinutes){_cacheService = cacheService;_cacheKey = cacheKey;_expirationMinutes = expirationMinutes;}public void OnActionExecuting(ActionExecutingContext filterContext){var cacheData = _cacheService.Get<object>(_cacheKey);if (cacheData != null){filterContext.Result = new ViewResult { ViewData = (ViewDataDictionary)cacheData };}}public void OnActionExecuted(ActionExecutedContext filterContext){if (filterContext.Result is ViewResult viewResult && _cacheService.Get<object>(_cacheKey) == null){_cacheService.Set(_cacheKey, viewResult.ViewData, TimeSpan.FromMinutes(_expirationMinutes));}}
}
服務注冊與配置
builder.Services.AddMemoryCache();
builder.Services.AddScoped<ICacheService, MemoryCacheService>();
在控制器中使用過濾器
[TypeFilter(typeof(CustomCacheFilter), Arguments = new object[] { "HotProductsCache", 10 })]
public ActionResult HotProducts()
{var hotProducts = _productService.GetHotProducts(8);return View(hotProducts);
}

關鍵注意事項

  • 過濾器構造函數注入的服務需通過DI容器注冊
  • TypeFilter用于傳遞運行時參數(如cacheKey
  • OnActionExecuting中若命中緩存會直接短路請求
  • OnActionExecuted僅在首次未命中緩存時執行存儲

坑 1:手動 new 服務實例(繞過 DI 容器,依賴無法注入)

在 ASP.NET Core 中,依賴注入(DI)是核心機制,手動通過 new 創建服務實例會導致依賴鏈斷裂。例如 ProductService 需要 DbContext,但手動實例化時無法自動注入依賴,導致編譯錯誤或運行時異常。

正確做法:始終通過構造函數注入服務,禁止手動 new

public class ProductsController : Controller
{private readonly IProductService _productService;public ProductsController(IProductService productService){_productService = productService;}public ActionResult Index(){var products = _productService.GetHotProducts(8);return View(products);}
}

坑 2:控制器構造函數參數過多(“構造函數爆炸”)

當控制器依賴過多服務時,構造函數會變得冗長且難以維護。例如 OrderController 依賴 5 個服務,導致代碼臃腫。

解決方案:使用聚合服務封裝相關依賴

// 定義聚合服務
public class OrderAggregateService
{public IOrderService OrderService { get; }public IProductService ProductService { get; }public ICartService CartService { get; }public OrderAggregateService(IOrderService orderService,IProductService productService,ICartService cartService){OrderService = orderService;ProductService = productService;CartService = cartService;}
}// 注冊聚合服務
services.AddScoped<OrderAggregateService>();// 簡化后的控制器
public class OrderController : Controller
{private readonly OrderAggregateService _aggregateService;private readonly IUserService _userService;public OrderController(OrderAggregateService aggregateService,IUserService userService){_aggregateService = aggregateService;_userService = userService;}public ActionResult Create(){var products = _aggregateService.ProductService.GetAll();// 其他邏輯}
}

坑 3:循環依賴問題

當服務 A 依賴服務 B,同時服務 B 又依賴服務 A 時,會導致 DI 容器無法解析。

解決方案

  • 重構設計,通過引入第三個服務(如中介者模式)解耦循環依賴。
  • 必要時使用 IServiceProvider.GetRequiredService 延遲解析(需謹慎)。

坑 4:未正確管理服務生命周期

誤用 Singleton 生命周期注冊需要請求作用域的服務(如 DbContext),會導致內存泄漏或數據污染。

生命周期選擇指南

  • Transient:每次請求創建新實例(輕量級無狀態服務)。
  • Scoped:同一請求內共享實例(如 DbContext)。
  • Singleton:全局單例(配置類服務)。

坑 5:過度依賴 DI 容器

在非 DI 管理的類(如靜態類或實體類)中強行使用 DI,會導致設計混亂。

解決方案

  • 遵循“構造函數注入”原則,避免在非 DI 上下文中解析服務。
  • 對于需要服務的實體類,可采用“領域事件”模式解耦。

坑 6:忽略 IDisposable 服務的釋放

未正確處理實現了 IDisposable 的服務(如文件流、數據庫連接),可能導致資源泄漏。

正確做法

  • DI 容器會自動釋放 Scoped/Transient 服務的 IDisposable 實例。
  • 手動創建的 IDisposable 對象需使用 using 語句包裹。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/96609.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/96609.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/96609.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

【實證分析】上市公司經營風險數據集-含代碼(2000-2022年)

數據簡介&#xff1a;上市公司經營風險涉及多維度、多層次的復雜因素&#xff0c;本文章參考王竹泉-經營風險與營運資金融資決策對上市公司經驗風險進行測算&#xff0c;經營風險是該公司息稅折舊攤銷前利潤率的標準差&#xff0c;經營風險是該公司息稅折舊攤銷前利潤率的標準差…

領碼方案|Windows 下 PLT → PDF 轉換服務超級完整版:異步、權限、進度

摘要 面向 Windows 平臺&#xff0c;使用 ASP.NET Core Web API 結合 Ghostscript.NET 庫&#xff0c;實現 PLT&#xff08;HPGL&#xff09;→PDF 的純庫調用轉換&#xff0c;無需外部進程。支持同步與異步模式&#xff0c;采用 JWTRBAC 進行權限治理&#xff0c;任務狀態存儲…

瀏覽器兼容性問題全解:CSS 前綴、Grid/Flex 布局兼容方案與跨瀏覽器調試技巧

1. 瀏覽器兼容性與前綴問題 不同瀏覽器&#xff08;尤其是老版本 IE、Edge、Safari&#xff09;對新特性&#xff08;比如 CSS 變量、Grid、Flex 等&#xff09;的支持程度不一&#xff0c;需要使用廠商前綴&#xff08;-webkit-、-moz- 等&#xff09;或降級方案。新手往往忽…

【Android View】事件分發機制

參考文獻 https://juejin.cn/post/6844904041487532045https://juejin.cn/post/6844903894103883789#heading-12https://www.jianshu.com/p/dea72779a6b7 文章目錄

【大數據相關】ClickHouse命令行與SQL語法詳解

ClickHouse命令行與SQL語法詳解一、ClickHouse命令行與SQL語法詳解第一部分&#xff1a;ClickHouse SQL 命令行客戶端 (clickhouse-client)1. 基礎連接2. 核心命令行參數3. 數據導入與導出實戰第二部分&#xff1a;ClickHouse SQL 語法詳解1. DDL (數據定義語言)2. DML (數據操…

學習日記-CSS-day53-9.11

1.CSS介紹知識點核心內容重點CSS定義層疊樣式表&#xff0c;用于內容修飾和樣式展現英文全稱cascading style sheetsCSS作用實現HTML內容與樣式分離&#xff0c;提高開發效率對比傳統HTML元素單獨設置樣式的低效方式學習建議掌握常用功能即可&#xff0c;重點在打通前后端數據通…

Maven中optional的作用

目的&#xff1a; 控制依賴傳遞 &#xff1a;將依賴標記為可選&#xff0c;這樣當其他模塊依賴common-component時&#xff0c;不會自動繼承Elasticsearch依賴。這遵循了"依賴最小化"原則&#xff0c;避免不必要的庫被引入到不需要它們的模塊中。模塊化設計 &#xf…

藍橋杯算法之基礎知識(7)---排序題的快排和歸并排序

一、快排》快排方法&#xff0c;就三步1.隨便選一個值作為基準值x2.拿選中的這個x值劃分隊列為左右兩個區間&#xff08;左邊的都小于x&#xff0c;右邊的都大于x&#xff09;3.然后遞歸左區間和右區間就行》代碼舉例&#xff1a;#qs排序#1 6 7 8 6 5 4 #先找比較點&#xff0c…

緩存未命中

緩存未命中&#xff08;Cache Miss&#xff09; 發生在 CPU 訪問某塊內存時&#xff0c;該地址不在當前緩存&#xff08;L1/L2/L3&#xff09;中&#xff0c;導致程序被迫從更慢的內存&#xff08;RAM&#xff09;讀取數據&#xff0c;嚴重拖慢程序執行速度。 &#x1f4cd; 一…

AR眼鏡:化工安全生產的技術革命

在石化企業的壓縮機組巡檢中&#xff0c;佩戴AR眼鏡的巡檢員眼前實時顯示著設備溫度場分布和振動頻譜曲線&#xff0c;單臺設備巡檢時間從45分鐘縮短至18分鐘。這不僅是效率的提升&#xff0c;更是化工安全生產的一場智能革命。一、行業痛點&#xff1a;傳統化工巡檢的困境與挑…

消息中間件RabbitMQ(從入門到精通)

RabbitMQ概念_MQ 消息隊列 MQ全稱Message Queue(消息隊列),是在消息的傳輸過程中保存消息的容器。多用于系統之間的異步通信。 同步通信相當于兩個人當面對話,你一言我一語。必須及時回復 異步通信相當于通過第三方轉述對話,可能有消息的延遲,但不需要二人時刻保持聯系。…

前端學習之后端java小白(五)之多表查詢/事務

一、多表查詢概念二、概述 1. 內連接隱式內連接 SELECT 字段列表 FROM 表1&#xff0c;表2... WHERE 條件顯示內連接SELECT 字段列表 FROM 表1 [INNER] JOIN 表2 ON 條件2. 外連接 左外連接SELECT 列名 FROM 左表 LEFT [OUTER] JOIN 右表 ON 連接條件;右外連接SELECT 列名…

Java全棧學習筆記34

# JDBCjava database connection Java 數據庫連接技術## JDBC 驅動程序如果需要通過jdbc技術連接關系型數據庫&#xff0c;就需要為jdbc提供一個該數據庫的驅動。驅動程序由對應的數據庫廠商提供。mysql提供了針對于各種語言的驅動程序。去官網下載和java相關的驅動即可## JDB…

如何為MySQL中的JSON字段設置索引

背景 MySQL在2015年中發布的5.7.8版本中首次引入了JSON數據類型。自此&#xff0c;它成了一種逃離嚴格列定義的方式&#xff0c;可以存儲各種形狀和大小的JSON文檔&#xff0c;例如審計日志、配置信息、第三方數據包、用戶自定義字段等。 雖然MySQL提供了讀寫JSON數據的函數&am…

【學習日記】

1.上午看了會面經&#xff0c;八股&#xff0c;很多看不懂1.5排查本地mysql服務啟動問題2.刷了兩道題翻轉二叉樹的Dfs和bfs遞歸方法&#xff0c;看了幾分鐘看懂了&#xff0c;一開始刷題&#xff0c;沒有這種感覺&#xff0c;可能思維上升了3.下午做了會ppt4.看了ssm的一個gith…

本地大模型部署指南-Ollama與HuggingFace對比

在本地部署大模型時&#xff0c;用 Ollama 和 Hugging Face (HF) 確實有很大區別&#xff0c;涉及系統、硬件、訓練、推理方式&#xff0c;以及能否查看模型源代碼。下面我分幾個維度說明&#xff1a; 系統和安裝 Ollama 定位是「開箱即用」的本地大模型運行環境。 自帶運行時&…

河北周邊有哪些比較靠譜的智算中心?

河北省通過算力普惠、綠色能源、數據開放、金融支持四大支柱政策&#xff0c;推動智算中心高質量發展。河北及周邊地區的智算中心已形成高可靠性、先進技術和戰略協同的布局。那么&#xff0c;河北周邊有哪些比較靠譜的智算中心&#xff1f;一、河北周邊智算中心盤點?1、尚航懷…

電動汽車充電標準之 — 國標 GB/T 18487《電動汽車傳導充電系統》 簡介

GB/T 18487 的全稱是 《電動汽車傳導充電系統》 &#xff0c;它是中國電動汽車充電領域最基礎、最核心的國家標準之一。該標準規定了電動汽車傳導充電系統的通用要求、通信協議、安全要求等&#xff0c;是整個中國充電基礎設施建設的基石。 與您之前了解的IEC 61851類似&#x…

溫濕度傳感器如何守護工業制造?

在工業制造、農業養殖、倉儲物流乃至文物保護等領域&#xff0c;環境溫濕度的精確監測是保障品質與安全的關鍵。溫濕度傳感器作為無聲的守護者&#xff0c;如何通過穩定可靠的數據采集&#xff0c;為現代工業生產的精細化與智能化管理提供堅實基礎&#xff1f;本文將深入探討其…

破壁·融合·共贏:杭州大成慧谷基金與涉海科技混改項目公司正式啟航!

2025 年 7 月 15 日,一家融合國企基金實力與民企創新活力的混合所有制項目公司正式誕生——由杭州大成慧谷股權投資基金管理有限公司與山東涉海海洋生物科技有限公司共同出資設立的武創慧聚創芯科學技術(上海)有限公司,當日完成法律合規手續。此前,上海武創大智高新技術集團副總…