در این قسمت میخواهیم در رابطه با قابلیت dependency Injection و یا تزریق وابستگی که به صورت درونی در فریم ورک Asp.Net Core MVC پشتیبانی میشود صحبت کنیم. امیدواریم که با dependency Injection آشنایی ابتدایی را داشته باشید. در این رابطه توصیه میکنیم که از بسته ی آموزش ویدئویی Inversion of Control و IoC Container ها دیدن کنید.
کار کردن با dependency Injection در Asp.Net Core
احتمالا می دانید که فریم ورک Asp.Net Core به صورت درونی از dependency Injection استفاده میکند. تکنیک تزریق وابستگی و یا dependency Injection باعث می شود که بین اجزای مختلف تشکیل دهنده یک برنامه loose coupling نداشته باشیم. loose coupling یک ویژگی مطلوب است چرا که باعث میشود شما بتوانید قسمتهای مختلف برنامه را از یکدیگر تفکیک کرده و قابلیت تست پذیری برنامه و امکان جایگزین کردن قسمتهای مختلف آن با یکدیگر را تسهیل کنید. علاوه بر این موضوع با استفاده از dependency Injection ایجاد تغییر در یک قسمت برنامه باعث ایجاد تغییر در سرتاسر برنامه نخواهد شد. dependency Injection بر اساس اصل dependency inversion عمل می کند و اغلب روشی برای رسیدن به اصل open/closed نیز میباشد. در رابطه با این دو اصل بسیار مهم در توسعه نرم افزار ها از بسته ی آموزش ویدئویی اصول SOLID در طراحی شی گرا دیدن کنید. زمانی که می خواهید در اپلیکیشن خود با وابستگیها و یا dependency ها کار کنید می بایست از دو code smell با نامهای static cling روتینگ و همچنین new is glue اطلاع داشته باشید. در رابطه با این موضوع می توانید از بسته ی آموزش ویدئویی ریفکتورینگ در سی شارپ و بسته ی آموزش ویدئویی Design Pattern ها در سی شارپ دیدن نمایید.
پدیده static cling زمانی رخ میدهد که کلاسهای شما فراخوان هایی را به متدهای استاتیکی میزنند و یا به پروپرتی های استاتیکی دسترسی پیدا میکنند که آن متدها و یا پروپرتی ها side effect و یا Dependency های را به infrastructure دارند. برای درک هرچه بهتر این قضیه بیاید مثالی را بررسی کنیم. فرض کنید که شما متدی را دارید که به یک static method فراخوانی را میزند و آن static method به نوبه خود بر روی یک دیتابیس دادههایی را مینویسد. در این سناریو متد اولی شما به صورت tightly coupled به دیتابیس و یا همان infrastructure وابستگی دارد. هر چیزی که باعث ایجاد تغییر در دیتابیس بشود باعث میشود متد شما نیز تغییر کند. علاوه بر این موضوع تست کردن چنین متدهای بسیار دشوار است. چرا که این گونه از متد ها نیاز به کتابخانه های پیاده سازی mocking تا بتوانند Static call ها را اصطلاحاً mock کنند و اغلب احتیاج به یک تست دیتابیس نیز احساس میشود. Static call هایی که به infrastructure وابستگی ندارند و یا حتی آنهایی که به صورت stateless پیادهسازی شدهاند مشکلی را بر روی coupling و یا testability برنامه ایجاد نخواهد کرد. تنها مشکلی که در این نوع از متدها پیش میآید ایجاد coupling بین متد اول و Static call ایجاد شده به متد دوم می باشد.
بسیاری از توسعه دهندگان نرمافزار خطرات مربوط به پدیده static cling را درک میکند و از وابسته شدن به global state برنامه جلوگیری می نمایند. البته بسیاری از این برنامه نویسان هنوز هم بین کد خود و پیادهسازی های خاصی از برنامه با استفاده از کلمه کلیدی New وابستگی ایجاد میکنند. این موضوع باعث ایجاد پدیده New is glue که به معنی استفاده از کلمه کلیدی New به معنی چسب می باشد می شود. در واقع پدیده New is glue باعث میشود که Coupling ایجاد شود. دقیقا شبیه Static call هایی که در قسمت قبلی بررسی کردهایم، ایجاد کردن instance ها و یا اشیا جدید از Type های که هیچ external dependency خاصی را ندارند باعث نمی شود که کد شما به implementation detail ها و یا جزئیات پیادهسازی وابستگی پیدا کند و نهایتاً نوشتن تست ها دشوار نخواهد شد. اما هر زمانی که یک کلاس نمونهسازی میشود باید به این نکته فکر کرد که آیا نوشتن کلمه کلیدی New روشی صحیح است و یا می بایست روال نمونه سازی به عهده یک ماهیت سوم قرار بگیرد. در رابطه با این موضوع به طور مفصل در بسته ی آموزش ویدئویی Inversion of Control و IoC Container ها صحبت کرده ایم.
تعریف کردن Dependency های برنامه
فریم ورک Asp.Net Core باعث میشود که متدها و کلاسها بتواند به سادگی Dependency های خود را تعریف کرده و آنها را در قالب arguments هایی درخواست کنند. اپلیکیشنهای Asp.Net اغلب در کلاس Startup پیاده سازی شده و پیکربندی های مربوط به dependency injection نیز در چندین بخش لحاظ می گردند. اگر کلاس Startup یک تابع سازنده داشته باشد میتواند dependency ها را در تابع سازنده شبیه به کد زیر درخواست دهد.
کلاس Startup از این جهت کلاس جالبی است که در آن هیچ گونه نیازمندیهایی برای تایپ آن لحاظ نگردیده است. به عبارت دیگر این کلاس از هیچ Base class خاصی ارث بری نکرده و هیچ اینترفیسی را پیاده سازی نمی کند. این کلاس می تواند یک و یا چندین تابع سازنده داشته باشد و هر کدام از این توابع سازنده میتوانند به هر تعدادی که مورد نیاز است پارامتر ورودی تعریف کنند. زمانی که Web Host مورد نظر شما پیکربندی شده است تا در زمان Start شدن برنامه یک کلاس Startup را انتخاب کند مکانیزم dependency injection وارد عمل میشود و تمامی dependency های تعریف شده در کلاس Startup را مقدار دهی می کند. البته اگر شما پارامترهایی را درخواست دهید که در services container مورد استفاده توسط Asp.Net Core تعریف نشده باشند یک Exception را دریافت خواهید کرد. در رابطه با مدیریت کردن خطا می توانید از بسته ی آموزش ویدئویی مدیریت خطاها در سی شارپ با Exception Handling استفاده کنید. اما تا زمانی که dependency هایی را مورد استفاده قرار بدهید که Container مورد نظر از آنها اطلاع دارند هیچ گونه از درخواستهای شما دچار خطا نخواهند شد.
همانطور که گفته شد مکانیزم dependency injection بهصورت درونی در Asp.Net Core و اپلیکیشنهای آن پیاده سازی شده است. این موضوع در کلاس Startup لحاظ شده و در قسمت های مختلف برنامه مورد استفاده قرار میگیرند. بنابراین شما می توانید dependencyهای برنامه را در متد Configure نیز شبیه آنچه که در کد زیر مشاهده می کنید درخواست بدهید.
متد ConfigureServices فقط یک پارامتر ورودی از نوع اینترفیس IServiceCollection. را دریافت خواهد کرد. در واقع این متد نیازی به پشتیبانی کردن از dependency injection ندارد. چراکه از یک طرف مسئول اضافه کردن آبجکت ها به services container است و از طرف دیگر دسترسی به تمامی سرویس های پیکربندی شده از طریق پارامتر IServiceCollection را دارد. بنابراین می توانید با dependency های تعریف شده در services collection مربوط به Asp.Net Core در هر بخشی از کلاس Startup کار کنید. این موضوع هم در قالب درخواست کردن پارامتر مورد نظر در قسمت پارامترهای یک متد و یا با استفاده از IService Collection موجود در متد ConfigureServices لحاظ میشود.
موضوع مهم دیگر اینکه اگر میخواهید سرویس های خاصی در کلاس Startup شما همواره وجود داشته باشند میتوانند آنها را با استفاده از اینترفیس IWebHostBuilder و متد ConfigureServices آن در یک فراخوان به متد CreateDefaultBuilder پیکر بندی کنید. کلاس Startup می تواند یک الگوی بسیار مناسب برای ساختار دادن به تمامی قسمت های دیگر یک اپلیکیشن Asp.Net Core باشد. در واقع از ساختار استفاده شده در کلاس Startup میتوان در تعریف Controller ها و Middleware ها و Filter ها و حتی سرویس های خاص خودمان نیز استفاده کرد. با استفاده از روش استفاده شده در کلاس Startup شما می بایست اصل Explicit Dependency را لحاظ کنید. بر اساس این اصل شما Dependency های مورد نظر یک کلاس را به عنوان پارامترهای ورودی تابع سازنده دریافت کرده و از تعریف کردن آنها به صورت دستی در کلاس مورد نظر جلوگیری میکنید. علاوه بر این با استفاده از dependency injection مکانیسم ایجاد کردن اشیا از این dependency ها را به عهده خود خواهید گذاشت. سرویسها و dependency هایی که با infrastructure و یا زیرساخت از قبیل دیتابیس درگیر میشوند و یا حتی عوارض جانبی و یا side effect دارند می بایست در این روش شرکت داده شوند. ضمنا به جای استفاده کردن از پیاده سازی های خاص مربوط به تایپ های مختلف بهتر است که با Abstraction های تعریف شده در Application Core کرده و آنها را به عنوان Argument در پارامتر ورودی دریافت کنید. در این رابطه ما توصیه میکنیم از بسته ی آموزش ویدئویی اصول طراحی نرم افزار Domain Driven Design دیدن نماپید