ff16

تزریق وابستگی در ASP.NET Core

لینک کوتاه https://codecell.ir/a/ff16
به اشتراک گذاری
نویسنده:
0
0
1
آموزش های نویسنده:
تزریق وابستگی در ASP.NET Core

تزریق وابستگی چیست؟

تزریق وابستگی (DI) یک الگوی طراحی نرم افزاری است که به ما امکان ایجاد برنامه های کاربردی و ماژولار را فراهم می کند. با استفاده از تکنیک تزریق وابستگی می توانیم پیاده سازی یک شی را از وابستگی های آن جدا کنیم. به جای اینکه یک شی وابستگی های خود را ایجاد کند، آن ها از بیرون به شی ارائه می شوند. به این ترتیب، می‌توانیم وابستگی‌ ها را بدون درهم ریختن کد شی تغییر دهیم یا تعویض کنیم. تست اشیاء به طور مستقل بسیار ساده تر است زیرا می توانیم از اشیاء ساختگی به جای واقعی استفاده کنیم.

ASP.NET Core دارای یک چارچوب تزریق وابستگی داخلی برای ارائه سرویس به اجزای مختلف برنامه است. تزریق وابستگی در همه جا برای اتصال سایر بخش های برنامه با سرویس های مورد نیاز آنها استفاده می شود. این فریمورک از یک کانتینر وارونگی کنترل (IoC) برای رسیدگی به تمام وابستگی‌های بین مؤلفه‌ها استفاده می‌کند.

کانتینر  IOC

کانتینر IoC(Inversion of Control) چارچوبی است که ایجاد و طول عمر اشیاء را در یک برنامه مدیریت می کند و  از ایجاد و ردیابی اشیاء در یک برنامه مراقبت می کند. علاوه بر این، راهی برای register و resolve  وابستگی ها در صورت نیاز فراهم می کند. در ASP.NET Core، کانتینر IoC در فریمورک تعبیه شده است و می توان آن را در فایل Program.cs پیکربندی کرد.

// Program.cs
builder.Services.AddScoped<IUserService, UserService>();

در مثال بالا، ما در حال ثبت سرویسی به نام UserService هستیم که اینترفیس IUserService را پیاده سازی می کند. متد AddScoped  به کانتینر می گوید که برای هر درخواست مشتری یک نمونه جدید از کلاس UserService ایجاد کند. روش‌های دیگری مانند AddSingleton و AddTransient برای ثبت خدمات با طول عمر متفاوت در دسترس هستند.  در ادامه به سایر گزینه‌ها می‌پردازیم.

هنگامی که یک سرویس با کانتینر IoC ثبت می شود، می توان آن را با استفاده از تزریق سازنده به سایر اجزای برنامه تزریق کرد.

مزایای تزریق وابستگی

استفاده از Dependency Injection در یک برنامه ASP.NET Core مزایای متعددی دارد:

  • آزمایش پذیری بهبود یافته: با استفاده از تزریق وابستگی، اشیاء به صورت loosely coupleمی شوند و تست هر شی را به صورت مجزا آسان تر می کند. این امر پیچیدگی تست ها را کاهش می دهد و آنها را قابل نگهداری تر می کند.
  • جداسازی Concern ها: تزریق وابستگی امکان جداسازی ایجاد و استفاده از شی را فراهم می کند که به کاهش پیچیدگی برنامه و بهبود ماژولار بودن آن کمک می کند.
  • کاهش پیچیدگی کد: تزریق وابستگی به ما اجازه می دهدکدهای تمیزتر و ماژولارتری بنویسیم. این می تواند نگهداری و به روز رسانی کد را در آینده آسان تر کند.
  • قابلیت استفاده مجدد بهتر: با استفاده از Dependency Injection، اشیاء را می توان مجدداً در چندین کلاس استفاده کرد که باعث کاهش تکرار و بهبود کارایی کلی برنامه می شود.

 تزریق وابستگی از طریق سازنده یا Constructor Injection

Constructor Injection تکنیکی است که در آن وابستگی ها از طریق سازنده آن به یک کلاس ارائه می شود. این کار دیدن وابستگی های یک کلاس را آسان می کند و به کانتینر IoC اجازه می دهد تا وابستگی های مورد نیاز را به صورت خودکار ایجاد و تزریق کند.

public class UserController : ControllerBase
{
    private readonly IUserService _userService;

    public UserController(IUserService userService)
    {
        _userService = userService;
    }
}

در مثال بالا، کلاس UserController به اینترفیس IUserService وابستگی دارد. وابستگی از طریق سازنده تزریق می شود و کانتینر IoC به طور خودکار وابستگی مورد نیاز را ایجاد و تزریق می کند.

طول عمر وابستگی

در زمان ثبت سرویس، وابستگی ها نیاز به تعریف طول عمر دارند. طول عمر سرویس، شرایطی را که تحت آن یک نمونه جدید از سرویس ایجاد می شود را مشخص می کند. در زیر طول عمر تعریف شده توسط چارچوب .NET Core DI آمده است.

Singleton

متد AddSingleton یک سرویس را از طریق کانتینر تزریق وابستگی به صورت Singleton ثبت می کند. Singleton یک شی است که فقط یک بار ایجاد می شود و همان نمونه در کل برنامه به اشتراک گذاشته می شود. این به این معنی است که هر مؤلفه ای که به سرویس سینگلتون ثبت شده وابسته باشد، همان نمونه سرویس را دریافت خواهد کرد.

builder.Services.AddSingleton<IEmailService, EmailService>();

در کد بالا، ما در حال ثبت سرویسی به نام EmailService هستیم که رابط IEmailService را پیاده سازی می کند. متد AddSingleton به کانتینر می گوید که فقط یک نمونه از کلاس EmailService ایجاد کند و آن را در کل برنامه به اشتراک بگذارد.

Singleton زمانی مفید است که می خواهیم داده ها یا منابع را در اجزای مختلف یک برنامه به اشتراک بگذاریم. در مثال بالا، ما یک EmailService داریم که می‌خواهیم آن را در بخش‌های مختلف برنامه خود به اشتراک بگذاریم. با ثبت EmailService به‌عنوان Singleton ، می‌توانیم اطمینان حاصل کنیم که همه مؤلفه‌هایی که نیاز به ارسال ایمیل دارند، از همان نمونه سرویس استفاده می‌کنند.

توجه به این نکته مهم است که سرویس singleton هنگام شروع برنامه ایجاد می شود و تا زمان خاموش شدن از بین نمی رود. این به این معنی است که هر منبعی که توسط سرویس singleton استفاده می‌شود در طول عمر برنامه در حافظه نگهداری می‌شود. بنابراین، هنگام استفاده از singleton ها باید مراقب باشیم تا از memory leak یا مشکلات عملکردی جلوگیری کنیم.

ما باید از روش AddSingleton به اندازه کافی و فقط برای سرویس هایی استفاده کنیم که باید در کل برنامه به اشتراک گذاشته شوند. برای سرویس‌های دیگر، مانند سرویس‌هایی که مخصوص یک درخواست خاص هستند، باید به جای آن از روش‌های AddScoped یا AddTransient استفاده کنیم.

Scoped

متد AddScoped برای ثبت یک سرویس از طریق کانتینر تزریق وابستگی به عنوان یک سرویس محدوده استفاده می شود. یک سرویس scoped یک بار در هر درخواست کلاینت ایجاد می شود و در آن درخواست به اشتراک گذاشته می شود. این به این معنی است که هر مؤلفه ای که به سرویس scoped ثبت شده وابستگی دارد، همان نمونه سرویس را در یک درخواست کلاینت دریافت می کند.

builder.Services.AddScoped<IUserService, UserService>();

در کد بالا، سرویسی به نام UserService ثبت می کنیم که اینترفیس IUserService را پیاده سازی می کند. متد AddScoped به کانتینر می گوید که برای هر درخواست کلاینت یک نمونه از کلاس UserService ایجاد کند. این تضمین می کند که هر درخواست کلاینت نمونه ای از سرویس را دارد.

سرویس های Scoped هنگام به اشتراک گذاری داده ها یا منابع در یک درخواست کلاینت مفید هستند. به عنوان مثال، ما یک کانتکست پایگاه داده داریم که می خواهیم برای یک درخواست کلاینت استفاده کنیم. با ثبت کانتکست پایگاه داده به عنوان یک سرویس scoped ، می‌توانیم اطمینان حاصل کنیم که همه مؤلفه‌هایی که از کانتکست در یک درخواست کلاینت استفاده می‌کنند، از نمونه مشابهی از کانتکست استفاده می‌کنند.

builder.Services.AddDbContext<DataContext>(opt => opt.UseSqlServer(builder.Configuration.GetConnectionString("DataContext")));

در مثال بالا، Entity Framework DbContext به طور پیش فرض به عنوان یک سرویس scoped ثبت شده است.

توجه به این نکته مهم است که  سرویس های scoped در پایان هر درخواست کلاینت حذف می شوند. این بدان معنی است که هر منبعی که توسط سرویس scoped استفاده می شود در پایان هر درخواست کلاینت آزاد می شود که  این موضوع از memory leak  جلوگیری و باعث بهبود عملکرد می شود.

ما باید از متد AddScoped برای سرویس هایی استفاده کنیم که باید در یک درخواست کلاینت به اشتراک گذاشته شوند.

Transient

متد AddTransient یک سرویس را  از طریق کانتینر تزریق وابستگی به عنوان یک سرویس Transientثبت می کند. یک سرویس Transienهر زمان که درخواست شود ایجاد می شود و بین اجزا یا درخواست های مختلف به اشتراک گذاشته نمی شود. این بدان معنی است که هر مؤلفه ای که به سرویس Transient ثبت شده وابسته است، هر بار که درخواست شود، نمونه جدیدی از سرویس را دریافت می کند.

builder.Services.AddTransient<IPasswordGeneratorService, PasswordGeneratorService>();

در کد بالا، سرویسی به نام PasswordGeneratorService ثبت می کنیم که اینترفیس IPasswordGeneratorService را پیاده سازی می کند. متد AddTransient به کانتینر می گوید هر زمان که درخواست شد نمونه جدیدی از کلاس PasswordGeneratorService را ایجاد کند.

سرویس‌های Transient زمانی که می‌خواهیم در هر لحظه یک نمونه جدید از یک سرویس را ایجاد کنیم مفید هستند. در مثال بالا، ما سرویسی داریم که رمزهای عبور تصادفی تولید می کند. با ثبت PasswordGeneratorService به عنوان یک سرویس Transient ، می توانیم اطمینان حاصل کنیم که هر بار که سرویس درخواست می شود، یک نمونه جدید از آن ایجاد می شود.

ما باید از متد AddTransient برای سرویس هایی استفاده کنیم که هر زمان که درخواست می شود باید ایجاد شوند. برای سرویس هایی که باید در یک درخواست کلاینت به اشتراک گذاشته شوند، باید از متد AddScoped و برای سرویس هایی که باید در کل برنامه به اشتراک گذاشته شوند، باید از روش AddSingleton استفاده کنیم.

تصمیم گیری برای انتخاب طول عمر وابستگی

تصمیم به استفاده از AddSingleton، AddTransient یا AddScoped برای ثبت سرویس با کانتینر تزریق وابستگی به طول عمر سرویس و نحوه استفاده از آن در برنامه ما بستگی دارد. در اینجا چند دستورالعمل وجود دارد که به ما کمک می کند تصمیم بگیریم از کدام روش استفاده کنیم:

  • زمانی که می خواهیم یک نمونه از یک سرویس را ایجاد کنیم و آن را در کل برنامه به اشتراک بگذاریم، از AddSingleton استفاده کنیم. این برای سرویس هایی مفید است که در طول عمر برنامه تغییر نمی کنند و می توانند توسط اجزای مختلف دوباره استفاده شوند.
  • زمانی که می‌خواهیم نمونه‌ای از سرویسی ایجاد کنیم که در یک درخواست کلاینت به اشتراک گذاشته شده است، از AddScoped استفاده کنید. این برای سرویس هایی که نیاز به حفظ وضعیت یا داده در حین رسیدگی به یک درخواست HTTP دارند مفید است. به طور معمول، AddScoped بیشترین طول عمر سرویس در برنامه های ASP .NET Core است. DbContext، ریپازیتوری ها و سرویس های مرتبط با پایگاه داده باید با استفاده از طول عمر scoped  ثبت شوند.
  • زمانی که می‌خواهیم نمونه جدیدی از یک سرویس را درهر زمان درخواست کنیم، از AddTransient استفاده می کنیم. این برای سرویس هایی که عملیات سبک و بدون حالت را انجام می دهند مفید است.

نباید ها در تزریق وابستگی:

چندین کار وجود دارد که برای جلوگیری از خطاهای مربوط به تزریق وابستگی نباید انجام دهیم:

❌ تزریق سرویس scoped به سرویس singleton

تزریق یک سرویس scoped به یک کلاس singleton می‌تواند منجر به خطاهای runtime شود، مانند یک InvalidOperationException با پیامی که می‌گوید: « Cannot consume scoped service from singleton».

builder.Services.AddSingleton<ISingletonService, SingletonService>();
builder.Services.AddScoped<IScopedService, ScopedService>();

public class SingletonService : ISingletonService
{
    public SingletonService(IScopedService scopedService)
    {
    }
}

کد مثال بالا منجر به خطای runtime می شود. این خطا به این دلیل رخ می دهد که یک سرویس scoped یک بار در هر درخواست ایجاد می شود در حالی که یک سرویس singleton یک بار در طول عمر برنامه ایجاد می شود. بنابراین، اگر ما یک سرویس scoped را به یک سرویس singleton تزریق کنیم، سرویس Scoped از سرویس singleton بیشتر خواهد ماند و ممکن است قبل از اتمام سرویس singleton باطل شود یا از بین برود.

❌ ایجاد وابستگی های circular

وابستگی های circular زمانی رخ می دهد که دو یا چند کلاس به طور مستقیم یا غیرمستقیم به یکدیگر وابسته باشند. به مثال زیر توجه کنید:

public class ClassA
{
    public ClassA(ClassB b)
    {
    }
}

public class ClassB
{
    public ClassB(ClassC c)
    {
    }
}

public class ClassC
{
    public ClassC(ClassA a)
    {
    }
}

در کد بالا، ClassA به ClassB بستگی دارد که به ClassC بستگی دارد و ClassC به ClassA بستگی دارد. این یک وابستگی circular  یا دایره ای بین کلاس ها ایجاد می کند. وابستگی circular می‌تواند باعث ایجاد خطای سرریز پشته(stack overflow) یا یک حلقه بی‌نهایت همانطور که کلاس‌ها یکدیگر را به صورت بازگشتی فراخوانی می‌کنند، شود. برای جلوگیری از این خطا، مهم است که کلاس های خود را به گونه ای طراحی کنیم که از وابستگی های دایره ای جلوگیری شود.

نتیجه گیری:

تزریق وابستگی یک تکنیک قدرتمند است که به مدیریت وابستگی بین اجزای مختلف یک برنامه کمک می کند. در ASP.NET Core، تزریق وابستگی در فریمورک تعبیه شده است و به طور گسترده برای ارائه خدمات به اجزای مختلف برنامه استفاده می شود. کانتینر IoC وابستگی های بین اجزا را مدیریت می کند و از تزریق سازنده برای تزریق وابستگی ها به کلاس ها استفاده می شود. با استفاده از تزریق وابستگی، می‌توانیم برنامه‌های قابل تست و نگهداری بیشتری ایجاد کنیم که اصلاح و گسترش آن آسان‌ تر است.

برای درک تفاوت بین طول عمرهای وابستگی و تفاوت های آن به صورت عملی، می توانیدآموزش بررسی طول عمر سرویس ها در تزریق وابستگی را به صورت رایگان از کدسل دانلود کنید.

دیگر مقالات آموزش برنامه نویسی مدرس

Response Caching در ASP.NET Core

Response Caching یک مکانیزم ذخیره سازی پاسخ سرور توسط مرورگر یا سایر کلاینت ها است. با استفاده از Response Caching در Asp.net Core می توانیم عملکرد و کارایی سیستم را بالا ببریم.

669 0 1402/10/22

Output Caching در ASP.NET Core 8.0

Output Caching یک ویژگی جدید است که از زمان .NET 7 معرفی شده است. این ویژگی به ما این امکان را می دهد که به راحتی یک کش پاسخ برای Web API خود بدون پیاده سازی IMemoryCache راه اندازی کنیم.

723 0 1402/10/14

الگوی طراحی زنجیره مسئولیت در سی شارپ

الگوی زنجیره مسئولیت یا Chain of Responsibility Design Pattern یک الگوی طراحی رفتاری(behavioral design pattern) است که اجازه می‌دهد یک درخواست در امتداد زنجیره‌ای از کنترل‌کننده‌های بالقوه ارسال شود تا زمانی که درخواست مناسب برای پردازش آن را پیدا کند.

685 2 1402/10/01

ViewComponent در ASP.NET Core

در ASP.NET Core با استفاده از ViewComponent ها می توان اجزای ماژولار، قابل استفاده مجدد، قابل نگهداری و مقیاس پذیر برای ساخت برنامه های کاربردی وب ایجاد کرد. در این مقاله به نحوه ایجاد و فراخوانی ViewComponent در Asp.net core پرداخته ایم.

1٬431 2 1402/05/15

تطبیق الگو در NET 7: ساده سازی تجزیه و تحلیل داده ها

تطبیق الگو یا Pattern Matching در net7 یک تکنیک قدرتمند برای تجزیه و تحلیل و دستکاری داده ها بر اساس ساختار آن ها است. تطبیق الگو یا Pattern Matching به توسعه دهندگان اجازه می دهد تا مقادیر را با یک الگوی خاص مطابقت دهند و اقدامات مربوطه را انجام دهند.

880 1 1402/04/05

مقایسه List و Array در سی شارپ

آرایه و لیست در سی شارپ هر دو به عنوان مجموعه ای از مقادیر عمل می کنند، اما در نحوه ذخیره محتوای خود در حافظه و نحوه دسترسی به آنها متفاوت هستند. در این مقاله Benchmark و کارایی List<T> با سایز داینامیک، List<T> با سایز ثابت و Array ها را بررسی می کنیم.

2٬221 2 1401/09/01

LinkedList در سی شارپ

LinkedList یا لیست پیوندی در سی شارپ یک ساختار داده خطی است که عنصر را در مکان غیر پیوسته ذخیره می کند. LinkedList شامل گره هایی است که هر گره حاوی یک فیلد داده و یک مرجع (پیوند) به گره بعدی در لیست است. در سی شارپ، LinkedList یا لیست پیوندی یک نوع مجموعه جنریک است.

2٬046 0 1401/08/13

Garbage Collection در دات نت

به طور کلی، Garbage Collection (GC) یا جمع آوری زباله در .Net چیزی نیست جز به دست آوردن مجدد حافظه اختصاص داده شده به اشیایی که در حال حاضر در هیچ بخشی از برنامه ما استفاده نمی شوند. ما در این مقاله Garbage Collection را در سی شارپ بررسی خواهیم کرد.

2٬838 5 1401/03/30

پشته(Stack) در سی شارپ

پشته یا Stack در سی شارپ یک نوع خطی از ساختار داده است که قادر به ذخیره اشیاء است. پشته یک ساختار داده LIFO یا Last-In-First-Out است، به این معنی که آخرین موردی که در پشته قرار می گیرد اولین موردی است که از پشته خارج می شود.

2٬187 0 1401/03/09

صف (Queue) در سی شارپ

صف (Queue) در سی شارپ یک نوع خطی از ساختار داده است که قادر به ذخیره اشیاء است. Queue یک ساختار داده first-in-first-out یا FIFO است که به این معنی است که اولین مورد اضافه شده به صف اولین موردی است که حذف می شود.

2٬162 0 1401/02/30

تفاوت Hashtable و Dictionary در سی شارپ

در سی شارپ از Hashtable و Dictionary می توان برای ذخیره مجموعه‌ای از داده‌ها، شبیه به List معمولی استفاده کرد. با این تفاوت که Hashtable و Dictionary عناصر را به عنوان جفت Key/Value ذخیره می کنند.در این مقاله به تفاوت های Hashtable و Dictionary پرداخته شده است.

1٬843 1 1401/02/23

Dictionary در سی شارپ

در سی شارپ از دیکشنری ها (Dictionary) می‌توان برای ذخیره مجموعه‌ای از داده‌ها، شبیه به List معمولی استفاده کرد. تفاوت اصلی این است که یک Dictionary می تواند عناصر را به عنوان جفت Key/Value ذخیره کند.Key ها باید منحصر به فرد و نمی توانند null باشند. Value ها می توانند تکراری یا null باشند.

2٬249 3 1401/02/17

Hashtable در سی شارپ

از Hashtable می‌توان برای ذخیره مجموعه‌ای از داده‌ها، شبیه به List معمولی استفاده کرد. تفاوت اصلی این است که یک Hashtable می تواند عناصر را به عنوان جفت Key/Value به عنوان جایگزینی برای استفاده از Index ذخیره کند.

1٬721 5 1401/02/14

تبدیل enum به لیست انتخابی در asp.net core

در این آموزش ما بوسیله Reflection ها در سی شارپ و asp.net core یک enum را به لیست انتخابی یا SelectListItem تبدیل خواهیم کرد و بوسیله تگ select در بوت استرپ 5 نمایش می دهیم.

1٬753 3 1401/02/03
نظرات

برای ثبت نظر باید در سایت ثبت نام یا ورود نمایید