آموزش Dependency Injection در دات نت

تاریخ: 1404/12/4 ساعت: 11:40 بازدید: 13

Dependency Injection در دات نت چیست و چرا باید یاد بگیری؟

اگه یه مدتیه داری با .NET کار می‌کنی، حتماً به اصطلاح Dependency Injection یا به اختصار DI برخوردی. شاید اول نامش یه کم ترسناک به نظر برسه، ولی وقتی مفهومش رو درست بفهمی، می‌بینی که چقدر منطقی و کاربردیه.

به زبان ساده: Dependency Injection یک الگوی طراحی (Design Pattern) است که به شما کمک می‌کند وابستگی‌های بین کلاس‌ها را به شکل صحیح و قابل نگهداری مدیریت کنید. به جای اینکه یک کلاس، اشیاء مورد نیازش رو خودش بسازه، این اشیاء از بیرون به آن تزریق می‌شوند.

این مفهوم یکی از ستون‌های اصلی اصول SOLID است و در دنیای ASP.NET Core، دات نت ۶، دات نت ۷ و دات نت ۸ به صورت Built-in وجود دارد.

مشکل چیه؟ چرا به DI نیاز داریم؟

بذار با یه مثال شروع کنیم. فرض کن یه کلاس OrderService داری که برای ارسال ایمیل، به یه کلاس EmailService نیاز داره:

// روش غلط - Tight Coupling

public class OrderService

{

private EmailService _emailService;

public OrderService()

{

_emailService = new EmailService(); // وابستگی سخت!

}

public void PlaceOrder(Order order)

{

// پردازش سفارش

_emailService.SendConfirmation(order);

}

}

این روش چند مشکل جدی داره:

  • وابستگی سخت (Tight Coupling): کلاس OrderService کاملاً به EmailService وابسته است.
  • تست‌پذیری پایین: نمی‌توانی برای تست از یک EmailService مصنوعی (Mock) استفاده کنی.
  • نقض اصل Open/Closed: تغییر سرویس ایمیل نیازمند تغییر OrderService است.
  • نگهداری سخت: با رشد پروژه، این وابستگی‌ها به یک کابوس تبدیل می‌شوند.

انواع Dependency Injection در دات نت

سه روش اصلی برای تزریق وابستگی وجود دارد:

۱. Constructor Injection (توصیه‌شده)

رایج‌ترین و پیشنهادی‌ترین روش. وابستگی از طریق سازنده کلاس تزریق می‌شود:

// تعریف Interface

public interface IEmailService

{

void SendConfirmation(Order order);

}

// پیاده‌سازی

public class EmailService : IEmailService

{

public void SendConfirmation(Order order)

{

// ارسال ایمیل واقعی

}

}

// کلاس اصلی با Constructor Injection

public class OrderService

{

private readonly IEmailService _emailService;

public OrderService(IEmailService emailService) // تزریق از بیرون

{

_emailService = emailService;

}

public void PlaceOrder(Order order)

{

_emailService.SendConfirmation(order);

}

}

۲. Property Injection

وابستگی از طریق یک Property عمومی تنظیم می‌شود. در مواردی که وابستگی اختیاری است استفاده می‌شود:

public class OrderService

{

public IEmailService EmailService { get; set; }

public void PlaceOrder(Order order)

{

EmailService?.SendConfirmation(order);

}

}

۳. Method Injection

وابستگی مستقیماً به متد پاس داده می‌شود. برای وابستگی‌هایی که فقط در یک متد خاص نیاز است مناسب است:

public class OrderService

{

public void PlaceOrder(Order order, IEmailService emailService)

{

emailService.SendConfirmation(order);

}

}

IoC Container دات نت: قلب DI در ASP.NET Core

IoC Container (Inversion of Control Container) یا همان DI Container ابزاری است که مدیریت چرخه حیات اشیاء و تزریق وابستگی‌ها را خودکار می‌کند.

دات نت یک IoC Container Built-in داره که در Microsoft.Extensions.DependencyInjection قرار داره. برای ثبت سرویس‌ها در فایل Program.cs از این روش استفاده می‌شود:

var builder = WebApplication.CreateBuilder(args);

// ثبت سرویس‌ها

builder.Services.AddScoped();

builder.Services.AddTransient();

builder.Services.AddSingleton();

var app = builder.Build();

app.Run();

طول عمر سرویس‌ها (Service Lifetime) - مهم‌ترین بخش DI

یکی از مهم‌ترین مفاهیمی که باید خوب بفهمی، طول عمر (Lifetime) سرویس‌هاست. اشتباه در این بخش می‌تواند باگ‌های بسیار سختی ایجاد کند:

Singleton

  • فقط یک نمونه در کل طول عمر اپلیکیشن ساخته می‌شود.
  • مناسب برای: Configuration، Logging، Cache.
  • با AddSingleton() ثبت می‌شود.

Scoped

  • یک نمونه به ازای هر Request HTTP ساخته می‌شود.
  • مناسب برای: DbContext، سرویس‌های مرتبط با کاربر.
  • با AddScoped() ثبت می‌شود.

Transient

  • هر بار که سرویس درخواست می‌شود، یک نمونه جدید ساخته می‌شود.
  • مناسب برای: سرویس‌های سبک و Stateless.
  • با AddTransient() ثبت می‌شود.
⚠️ هشدار مهم - Captive Dependency:

هرگز یک سرویس Scoped یا Transient را درون یک Singleton تزریق نکن! این خطا «Captive Dependency» نام دارد و باعث می‌شود سرویس کوتاه‌عمر، عمری به اندازه Singleton پیدا کند که رفتار نامعلوم و باگ‌های جدی ایجاد می‌کند.

مثال کامل و عملی: پیاده‌سازی DI در یک پروژه واقعی

بیا یه مثال واقعی از یه سیستم مدیریت محصول ببینیم:

// ۱. تعریف اینترفیس‌ها

public interface IProductRepository

{

Task> GetAllAsync();

Task GetByIdAsync(int id);

Task AddAsync(Product product);

}

public interface IProductService

{

Task> GetProductsAsync();

Task AddProductAsync(ProductDto dto);

}

// ۲. پیاده‌سازی Repository

public class ProductRepository : IProductRepository

{

private readonly AppDbContext _context;

public ProductRepository(AppDbContext context)

{

_context = context;

}

public async Task> GetAllAsync()

=> await _context.Products.ToListAsync();

public async Task GetByIdAsync(int id)

=> await _context.Products.FindAsync(id);

public async Task AddAsync(Product product)

{

await _context.Products.AddAsync(product);

await _context.SaveChangesAsync();

}

}

// ۳. پیاده‌سازی Service

public class ProductService : IProductService

{

private readonly IProductRepository _repository;

private readonly ILogger _logger;

public ProductService(

IProductRepository repository,

ILogger logger)

{

_repository = repository;

_logger = logger;

}

public async Task> GetProductsAsync()

{

_logger.LogInformation(“دریافت لیست محصولات”);

return await _repository.GetAllAsync();

}

public async Task AddProductAsync(ProductDto dto)

{

var product = new Product { Name = dto.Name, Price = dto.Price };

await _repository.AddAsync(product);

return true;

}

}

// ۴. ثبت در Program.cs

builder.Services.AddDbContext(options =>

options.UseSqlServer(connectionString));

builder.Services.AddScoped();

builder.Services.AddScoped();

// ۵. استفاده در Controller

[ApiController]

[Route(“api/[controller]”)]

public class ProductsController : ControllerBase

{

private readonly IProductService _productService;

public ProductsController(IProductService productService)

{

_productService = productService;

}

[HttpGet]

public async Task Get()

{

var products = await _productService.GetProductsAsync();

return Ok(products);

}

}

تست‌پذیری با DI: Unit Testing آسان

یکی از بزرگترین مزایای DI، تست‌پذیری بالا است. با استفاده از Mock Objects می‌توانید هر سرویس را ایزوله تست کنید:

// Unit Test با استفاده از Moq

public class ProductServiceTests

{

[Fact]

public async Task GetProductsAsync_ShouldReturnProducts()

{

// Arrange

var mockRepo = new Mock();

var mockLogger = new Mock>();

var fakeProducts = new List

{

new Product { Id = 1, Name = “لپ‌تاپ”, Price = 25000000 },

new Product { Id = 2, Name = “موبایل”, Price = 15000000 }

};

mockRepo.Setup(r => r.GetAllAsync())

.ReturnsAsync(fakeProducts);

var service = new ProductService(mockRepo.Object, mockLogger.Object);

// Act

var result = await service.GetProductsAsync();

// Assert

Assert.Equal(2, result.Count());

mockRepo.Verify(r => r.GetAllAsync(), Times.Once);

}

}

🚀 می‌خواهید سایت شما هم در صفحه اول گوگل باشد؟

یاد گرفتن DI عالیه، ولی اگه سایت یا محصول نرم‌افزاریتون در گوگل دیده نمی‌شه، همه این تلاش‌ها نصفه‌نیمه می‌مونه. تیم متخصص ما با استراتژی‌های سئوی اثبات‌شده، سایت شما رو به صدر نتایج گوگل می‌برند و فروش و مشتریانتون رو چند برابر می‌کند.

همین حالا برای مشاوره رایگان تماس بگیرید:

📞 09190994063  |  09376846692

ثبت سرویس‌های پیشرفته: Factory و Named Services

ثبت با Factory Method

وقتی نیاز به کنترل بیشتر بر نحوه ساخت سرویس داری:

builder.Services.AddScoped(serviceProvider =>

{

var config = serviceProvider.GetRequiredService();

var gateway = config[“Payment:Gateway”];

return gateway switch

{

“Zarinpal” => new ZarinpalPaymentService(config),

“IDPay” => new IDPayPaymentService(config),

_ => throw new InvalidOperationException(“درگاه پرداخت نامعتبر”)

};

});

Keyed Services در دات نت ۸

در دات نت ۸، قابلیت جدیدی به نام Keyed Services اضافه شده که ثبت چند پیاده‌سازی از یک اینترفیس را آسان‌تر می‌کند:

// ثبت Keyed Services

builder.Services.AddKeyedScoped(“zarinpal”);

builder.Services.AddKeyedScoped(“idpay”);

// استفاده در Controller

public class PaymentController : ControllerBase

{

private readonly IPaymentService _paymentService;

public PaymentController(

[FromKeyedServices(“zarinpal”)] IPaymentService paymentService)

{

_paymentService = paymentService;

}

}

بهترین شیوه‌ها (Best Practices) در DI

  • همیشه به اینترفیس وابسته باش، نه پیاده‌سازی: به جای EmailService، به IEmailService وابسته باش.
  • Constructor Injection را به بقیه روش‌ها ترجیح بده: شفاف‌ترین و قابل تست‌ترین روش است.
  • تعداد وابستگی‌های سازنده را محدود کن: اگه بیشتر از ۵ وابستگی داری، احتمالاً کلاس باید تجزیه شود.
  • از Service Locator Pattern اجتناب کن: استفاده مستقیم از IServiceProvider در کلاس‌های Business یک Anti-Pattern است.
  • Extension Method برای ثبت سرویس‌ها بنویس: کد Program.cs را خوانا نگه دار.
// Extension Method برای سازماندهی بهتر

public static class ServiceCollectionExtensions

{

public static IServiceCollection AddApplicationServices(

this IServiceCollection services)

{

services.AddScoped();

services.AddScoped();

services.AddScoped();

return services;

}

public static IServiceCollection AddInfrastructureServices(

this IServiceCollection services)

{

services.AddScoped();

services.AddScoped();

return services;

}

}

// استفاده در Program.cs - تمیز و خوانا

builder.Services.AddApplicationServices();

builder.Services.AddInfrastructureServices();

Scrutor: پیکربندی خودکار DI با Assembly Scanning

اگه پروژه‌ات بزرگه و ثبت تک‌تک سرویس‌ها خسته‌کننده‌ست، Scrutor یه کتابخانه فوق‌العاده‌ست که به DI Container دات نت قابلیت Assembly Scanning می‌ده:

// نصب: dotnet add package Scrutor

builder.Services.Scan(scan => scan

.FromAssemblyOf()

.AddClasses(classes => classes.AssignableTo())

.AsImplementedInterfaces()

.WithScopedLifetime());

مقایسه DI Container دات نت با Autofac

DI Container داخلی دات نت برای اکثر پروژه‌ها کافیه، ولی برای پروژه‌های خیلی بزرگ، Autofac قابلیت‌های بیشتری داره:

  • Microsoft.Extensions.DI: ساده، سریع، built-in، برای اکثر پروژه‌ها کافی
  • Autofac: قدرتمندتر، Interceptor، Property Injection بهتر، Module System
  • Simple Injector: سریع‌ترین، Validation قوی‌تر

💡 آیا سایت شما در گوگل دیده می‌شود؟

رقبای شما هر روز مشتری می‌گیرند چون در گوگل هستند. سئوی حرفه‌ای یعنی سرمایه‌گذاری که ماه‌ها و سال‌ها برایتان بازدهی دارد. تیم ما با تجربه واقعی در سئوی فارسی، سایت شما را به صفحه اول گوگل می‌برد و زنگ‌خورهای فروشتان را چند برابر می‌کند.

برای مشاوره رایگان همین الان تماس بگیرید:

📞 09190994063  |  09376846692

جمع‌بندی: چرا DI یک مهارت ضروری برای توسعه‌دهندگان دات نت است؟

Dependency Injection دیگه یه “خوب‌به‌دونن” نیست؛ بلکه یک مهارت ضروری برای هر توسعه‌دهنده دات نت حرفه‌ایه. مزایاش واضحه:

  • ✅ کد تمیز‌تر و قابل نگهداری‌تر
  • تست‌پذیری بالا با Unit Testing
  • ✅ رعایت اصول SOLID
  • انعطاف‌پذیری برای تغییر پیاده‌سازی‌ها
  • ✅ مقیاس‌پذیری بهتر در پروژه‌های بزرگ

با تمرین مثال‌های این مقاله و پیاده‌سازی DI در پروژه‌های واقعی، به زودی به یکی از قوی‌ترین ابزارهای Clean Architecture مسلط خواهی شد.


سوالات متداول (FAQ)

۱. تفاوت DI و IoC چیست؟

IoC (Inversion of Control) یک اصل کلی طراحی است که می‌گوید کنترل جریان برنامه باید معکوس شود. Dependency Injection یکی از الگوهای پیاده‌سازی IoC است. به عبارت دیگر، DI یک نوع IoC است، ولی IoC الزاماً DI نیست. روش‌های دیگر IoC شامل Service Locator و Factory Method هم هستند.

۲. کِی باید از Singleton و کِی از Scoped استفاده کنم؟

قانون کلی: اگه سرویس حالت (State) اشتراکی بین Request‌ها ندارد و Thread-Safe است → Singleton. اگه سرویس به DbContext یا اطلاعات مربوط به یک Request خاص نیاز دارد → Scoped. اگه سرویس باید هر بار تازه ساخته شود و State ندارد → Transient.

۳. آیا می‌توان در Minimal API هم از DI استفاده کرد؟

بله! در Minimal API (دات نت ۶ به بعد) به راحتی می‌توان از DI استفاده کرد. سرویس‌ها مستقیماً به پارامترهای Lambda تزریق می‌شوند: app.MapGet(“/products”, async (IProductService service) => await service.GetProductsAsync());

۴. چطور یک سرویس را در یک Background Service تزریق کنم؟

از آنجا که Background Service ها به صورت Singleton هستند، نمی‌توانید مستقیماً یک Scoped Service مثل DbContext را تزریق کنید. راه‌حل: از IServiceScopeFactory استفاده کنید تا در هر اجرا یک Scope جدید بسازید و سرویس مورد نظر را از آن دریافت کنید.

۵. آیا DI عملکرد (Performance) برنامه را کند می‌کند؟

تأثیر DI بر عملکرد در دنیای واقعی کاملاً ناچیز است. DI Container دات نت بسیار بهینه است و زمان Resolve کردن سرویس‌ها در حد نانوثانیه است. مزایای طراحی صحیح (کد قابل نگهداری، تست‌پذیر و مقیاس‌پذیر) بسیار بیشتر از این هزینه ناچیز ارزش دارد.

۶. آیا می‌توان در پروژه‌های Console Application هم از DI استفاده کرد؟

بله! کافیه پکیج Microsoft.Extensions.DependencyInjection را نصب کنید و یک ServiceCollection بسازید. یا از Generic Host استفاده کنید که همه زیرساخت‌های لازم از جمله DI، Logging و Configuration را فراهم می‌کند.

نظرات کاربران


حامد یوسفی
تاریخ 1404/12/4 ساعت 11:43

یک توضیح کامل و جامع. ممنون از زحماتتون. قسمت Captive Dependency واقعاً یه نکته طلایی بود که کمتر جایی به این وضوح بهش اشاره شده. آیا راهی برای تشخیص این نوع خطا در زمان کامپایل یا Early Runtime وجود داره؟

سایت اینجا:

خواهش می‌کنم حامد جان. Captive Dependency یک خطای رایج و مهم است. متأسفانه، DI Container داخلی دات نت به صورت پیش‌فرض مکانیزمی برای تشخیص این خطا در زمان کامپایل نداره. اما، بعضی از DI Containerهای Third-Party مثل Simple Injector ابزارهای Validation قوی‌تری دارن که میتونن این نوع مشکلات رو زودتر شناسایی کنن. همچنین ابزارهای Static Code Analyzer میتونن کمک کننده باشن. برای راهنمایی بیشتر میتونید با ما تماس بگیرید: 09190994063 و 09376846692

مریم بابایی
تاریخ 1404/12/4 ساعت 11:42

واقعاً این مقاله رو به همه توسعه‌دهندگان دات نت توصیه می‌کنم. از اهمیت DI و اصول SOLID تا نکات کاربردی و Best Practices، همه چیز پوشش داده شده. الان دیگه DI رو بهتر از همیشه درک می‌کنم.

سایت اینجا:

ممنون از لطف شما مریم جان. هدف ما همین بود که یک منبع جامع و کاربردی برای درک DI ارائه کنیم. درک و استفاده صحیح از DI واقعاً مهارت حیاتی برای توسعه‌دهندگان حرفه‌ایه. اگه در پروژه‌هاتون نیاز به مشاوره یا خدمات سئو داشتید، میتونید با ما تماس بگیرید: 09190994063 و 09376846692

رضا کمالی
تاریخ 1404/12/4 ساعت 11:42

یک مرجع کامل برای DI در دات نت. دست مریزاد. قسمت Factory Method برای ثبت سرویس‌ها و چگونگی انتخاب درگاه پرداخت مثال کاربردی و خوبی بود. آیا میشه در Factory Method از Keyed Services هم استفاده کرد؟

سایت اینجا:

ممنون رضا جان. بله، کاملاً میشه! شما میتونید Keyed Services رو هم از serviceProvider داخل Factory Method دریافت کنید و بر اساس منطق خودتون، سرویس مناسب رو برگردونید. این به شما انعطاف‌پذیری زیادی میده. برای مشاوره تخصصی‌تر در این زمینه میتونید با ما تماس بگیرید: 09190994063 و 09376846692

نازنین کریمی
تاریخ 1404/12/4 ساعت 11:42

بهترین مقاله‌ای بود که تا به حال در مورد Dependency Injection خوندم. مثال Unit Test با Moq هم عالی بود. همیشه در تست کردن مشکل داشتم، الان می‌تونم با DI و Mocking خیلی راحت‌تر تست بنویسم.

سایت اینجا:

خوشحالیم که این مقاله تونسته بهتون در زمینه تست‌پذیری کمک کنه. DI و Mocking واقعاً دو ابزار قدرتمند برای نوشتن Unit Test‌های مؤثر و جداگانه هستند. اگه در این زمینه نیاز به کمک یا آموزش بیشتری داشتید، میتونید با ما تماس بگیرید: 09190994063 و 09376846692

سارا محمدی
تاریخ 1404/12/4 ساعت 11:41

ممنون از مقاله خوبتون. همیشه مفهوم DI برام گنگ بود، اما با این توضیحات و مثال‌ها خیلی بهتر متوجه شدم. مخصوصاً بخش طول عمر سرویس‌ها و Captive Dependency خیلی مفید بود.

سایت اینجا:

خوشحالیم که مقاله براتون مفید بوده. درک صحیح طول عمر سرویس‌ها و اجتناب از Captive Dependency برای جلوگیری از باگ‌های پیچیده بسیار حیاتیه. اگه سوالی داشتید، میتونید با ما تماس بگیرید: 09190994063 و 09376846692

علی احمدی
تاریخ 1404/12/4 ساعت 11:41

سلام، مقاله جامع و کاملی بود. Constructor Injection رو همیشه استفاده می‌کردم ولی نمی‌دونستم چرا بهترینه. الان دلیلش رو فهمیدم. راستی، در مورد Keyed Services در دات نت 8، چطور میشه داینامیک کلید رو انتخاب کرد؟ مثلاً بر اساس یه پارامتر در URL؟

سایت اینجا:

سلام علی جان، خوشحالیم که مقاله براتون روشنگر بوده. برای انتخاب داینامیک Keyed Services بر اساس پارامتر در URL یا هر شرط دیگه، باید Service Locator رو در داخل متد کنترلتون استفاده کنید و با استفاده از GetKeyedService<TService>(key) سرویس مورد نظر رو دریافت کنید. این یه مورد خاصه که استفاده از Service Locator رو توجیه می‌کنه. برای جزئیات بیشتر و راهنمایی تخصصی میتونید با ما تماس بگیرید: 09190994063 و 09376846692

محسن حسینی
تاریخ 1404/12/4 ساعت 11:41

مقاله فوق‌العاده‌ای بود! همیشه به دنبال یه منبع کامل فارسی در مورد DI بودم. قسمت Extension Method برای ثبت سرویس‌ها و همچنین مقایسه با Autofac بسیار ارزشمند بود. آیا استفاده از Autofac برای پروژه‌های متوسط هم توصیه میشه؟

سایت اینجا:

ممنون محسن جان. برای پروژه‌های متوسط، DI Container داخلی دات نت معمولاً کافیه و استفاده از Autofac تنها در صورتی توصیه میشه که به قابلیت‌های خاصی مثل Interceptor ها یا سیستم ماژولار پیشرفته‌تر نیاز داشته باشید. در غیر این صورت، پیچیدگی اضافه رو به پروژه تحمیل می‌کنه. برای مشاوره بیشتر میتونید با ما تماس بگیرید: 09190994063 و 09376846692

پریسا ناصری
تاریخ 1404/12/4 ساعت 11:41

مقاله بی‌نظیری بود! سوالات متداول هم خیلی کاربردی بودن. خصوصاً سوال در مورد استفاده از DI در Background Service. این یک مشکل رایج برای من بوده. ممنون بابت راه‌حل IServiceScopeFactory.

سایت اینجا:

خوشحالیم که بخش FAQ و راه‌حل Background Service براتون مفید بوده. مدیریت Scope در Background Service ها یک چالش رایجه و IServiceScopeFactory راه حل استاندارد برای اون هست. اگه سوالات بیشتری داشتید، میتونید با ما تماس بگیرید: 09190994063 و 09376846692

فاطمه رضایی
تاریخ 1404/12/4 ساعت 11:40

تشکر فراوان بابت این آموزش عالی. من تازه با ASP.NET Core شروع کردم و DI واقعاً برام یک چالش بود. مثال‌های عملی خیلی به درکم کمک کرد. الان می‌خوام Scrutor رو هم امتحان کنم، به نظرم خیلی کاربردی میاد.

سایت اینجا:

خواهش می‌کنم فاطمه جان، خوشحالیم که براتون مفید بوده. Scrutor واقعاً برای پروژه‌های بزرگ‌تر و مدیریت تعداد زیاد سرویس‌ها بسیار کاربردیه و باعث میشه کدتون تمیزتر و قابل نگهداری‌تر بشه. اگه در پیاده‌سازی نیاز به راهنمایی داشتید، میتونید با ما تماس بگیرید: 09190994063 و 09376846692