عنوان:

‫به چه طریق می‌توان تزریق وابستگی را بصورت خودکار انجام داد؟


نویسنده: هادی مزارعی
تاریخ: ۱۴۰۴/۱۰/۱۶ ۱۰:۵۴
آدرس: www.dntips.ir
اگر یک کلاس داشته باشم و در متد سازنده (Constructor) سرویس مورد نظر را به عنوان پارامتر ورودی تعریف کرده باشم، مطابق روال عادی و در زمان نمونه‌سازی از کلاس باید سرویس را به عنوان پارامتر ارسال کنم. آیا روشی وجود دارد که دو متد سازنده تعریف کنم (یکی بدون پارامتر) که در زمان نمونه سازی، بصورت خودکار سرویس از طریق سازنده‌‌‌ی دارای پارامتر، سرویس خود را دریافت نماید. میدونم این امکان وجود داره که در متد سازنده‌ی بدون پارامتر با استفاده از this یکی از متدهای سازنده را اجرا کنم که در داخل آن با استفاده از GetRequiredService، سرویس مورد نظر را دریافت و به متغیرهای خصوص تخصیص بدم.
public MyClass(IService service)
{
   _service = service;
}
    
public MyClass() : this(GetServiceFromContainer())
{
}
آیا بدون این مدل پیاده سازی، سازوکاری در دات‌نت برای تشخیص نیاز به سرویس (هر آنچه که قبلا در ServiceCollection ثبت شده باشد) جهت تزریق خودکار وجود دارد؟

نظرات

  • وحید نصیری در ۱۴۰۴/۱۰/۱۶ ۱۱:۱۶
    اصل سازوکار تزریق وابستگی در دات‌نت Core
    کانتینر DI داخلی دات‌نت Core (مبتنی بر Microsoft.Extensions.DependencyInjection) دارای منطقی برای انتخاب سازنده (Constructor) مناسب در زمان نمونه‌سازی یک سرویس است:
    - تشخیص سازنده: کانتینر DI تمام سازنده‌های عمومی (Public) کلاس را بررسی می‌کند.
    - اولولیت با پارامترهای قابل حل (Resolvable): کانتینر سعی می‌کند سازنده‌ای را انتخاب کند که بیشترین تعداد پارامترهای قابل حل را از طریق سرویس‌هایی که شما قبلاً در IServiceCollection (مثلاً در متد ConfigureServices یا Program.cs) ثبت کرده‌اید، داشته باشد.
    - انتخاب سازنده مناسب:
    • اگر فقط یک سازنده وجود داشته باشد (چه با پارامترهای DI و چه بدون پارامتر)، همان انتخاب می‌شود.
    • اگر چندین سازنده وجود داشته باشد، سازنده‌ای انتخاب می‌شود که بیشترین پارامترهایی را داشته باشد که کانتینر بتواند آن‌ها را تأمین کند. (این قانون معمولاً در عمل باعث می‌شود سازنده‌ای که سرویس‌ها را می‌گیرد، انتخاب شود).
    • اگر دو یا چند سازنده با تعداد مساوی و بیشترین پارامتر قابل حل وجود داشته باشند (ابهام)، استثنا (Exception) پرتاب می‌شود.

    پاسخ به پرسش شما در مورد چندین سازنده
    شما می‌خواهید دو سازنده داشته باشید: یکی با سرویس (public MyClass(IService service)) و دیگری بدون پارامتر (public MyClass()).

    1. حالت استفاده عادی (تزریق از طریق کانتینر):
    اگر کلاس MyClass را در کانتینر ثبت کرده باشید:
    services.AddTransient<MyClass>();
    و سپس آن را از طریق کانتینر درخواست کنید (serviceProvider.GetRequiredService) یا آن را به عنوان وابستگی به کلاس دیگری تزریق کنید:
    نتیجه: کانتینر DI سازنده‌ی public MyClass(IService service) را انتخاب می‌کند، چون:
    • پارامتر IService را می‌تواند حل کند (زیرا شما آن را قبلاً ثبت کرده‌اید).
    • این سازنده بیشترین تعداد پارامترهای قابل حل را دارد (یک پارامتر، در مقابل صفر پارامتر در سازنده‌ی دیگر).
    بنابراین، سرویس به صورت خودکار و بدون نیاز به تعریف GetServiceFromContainer() به آن تزریق می‌شود.

    2. حالت نمونه‌سازی دستی (خارج از کانتینر):
    اگر شما کلاس را به صورت دستی با کلمه‌ی کلیدی new نمونه‌سازی کنید (خارج از حوزه‌ی کانتینر DI):
    var myInstance = new MyClass(); // سازنده‌ی بدون پارامتر فراخوانی می‌شود
    نتیجه: در این حالت، C# (و نه کانتینر DI) مسئول نمونه‌سازی است و سازنده‌ی بدون پارامتر فراخوانی می‌شود. برای اینکه در این حالت نیز سرویس تزریق شود، چاره‌ای جز استفاده از روشی مانند this(GetServiceFromContainer()) که خودتان مثال زدید، وجود ندارد.

    جمع‌بندی و راهکار توصیه‌شده
    راهکار توصیه‌شده در دات‌نت Core پرهیز از نمونه‌سازی دستی با new و استفاده‌ی انحصاری از کانتینر DI است. اگر می‌خواهید تزریق اتوماتیک انجام شود، کلاس خود را ثبت کنید:
    services.AddScoped<MyClass>(); // یا AddTransient/AddSingleton
    services.AddScoped<IService, ServiceImplementation>();
    فقط سازنده‌ی دارای پارامتر DI را داشته باشید: این کار کلاستان را خواناتر و تمیزتر می‌کند و به وضوح وابستگی‌ها را مشخص می‌سازد.
    public class MyClass
    {
        private readonly IService _service;
        public MyClass(IService service) // فقط همین سازنده
        {
            _service = service;
        }
        // ...
    }
    اگر به هر دلیلی واقعاً نیاز دارید که امکان نمونه‌سازی بدون پارامتر (مثلاً در Unit Testها یا سناریوهای خاص) وجود داشته باشد:
    روش شما با استفاده از GetServiceFromContainer() یا الگوهای مشابه (مانند Service Locator) تنها راهکار عملی برای پر کردن وابستگی‌ها در سازنده‌ی بدون پارامتر است، اما در کل به عنوان یک ضدالگو (Anti-Pattern) توصیه نمی‌شود، زیرا کلاس شما را به کانتینر DI وابسته می‌کند.