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

شنبه 6 خرداد 1396

در این مقاله نشان می دهیم چگونه می توان همبستگی های داخلی برنامه را کاهش داد و برنامه هایی با قابلیت تست پذیری نوشت. پیش از آنکه به آموزش تست واحد بپردازیم، در این مقاله به آموزش تزریق وابستگی می پردازیم و نقش آن در طراحی برنامه ها را بررسی می کنیم.

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

 هدف اصلی این مقالات این است که نشان دهیم چگونه می توان همبستگی را کاهش داد و برنامه هایی با قابلیت تست پذیری نوشت. این مقاله بیشتر مناسب برنامه نویسان تازه کار در زمینه IoC, DI و تست واحد است. پیش از آنکه به آموزش تست واحد بپردازیم، در این مقاله به آموزش تزریق وابستگی می پردازیم و نقش آن در طراحی برنامه ها را بررسی می کنیم.

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

تزریق وابستگی (DI) یک نوع از IoC (Inversion of Control) یا همان وارونگی کنترل است.

عبارت وارونگی کنترل یک عبارت ترسناک است که برای آن دسته از طراحی هایی که وابستگی ها را از داخل کد ها حذف می کنند به کار می رود و با فرستادن نمونه های وابستگی به ماژول دیگری که با نام container شناخته می شود کار می کند.

DI در واقع یک نوع از IoC است که اجازه تزریق وابستگی ها به صورت مستقیم از container به سازنده کلاس یا یک خصوصیت کلاس را می دهد.

با یک مثال ساده نحوه کارکرد آن را نمایش می دهیم.

public class CodeEditor
{
  private SyntaxChecker syntaxChecker;

  public CodeEditor()
  {
  this.syntaxChecker = new SyntaxChecker();
  }
}

در تعریف SyntaxChecker به کمک روش new کردن که وابسته به سازنده تابع است، یک وابستگی شدید بین CodeEditor و SyntaxChecker ایجاد می شود چرا که در واقع ما برای ساختن یک نمونه از hard-code استفاده کرده ایم.

و این اتفاق اصلا قابل قبول نیست.

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

بیایید یک رویکرد مناسبتر را بررسی کنیم.

public class CodeEditor
{
  private ISyntaxChecker syntaxChecker;

  public CodeEditor(ISyntaxChecker syntaxChecker)
  {
  this.syntaxChecker = syntaxChecker;
  }
}

در اینجا ما دو تغییر کوچک ولی بسیار قدرتمند را انجام داده ایم. اول اینکه ما به کمک اینترفیس ها یک abstract برای متد SyntaxChecker ساخته ایم و این یعنی ما به پیاده سازی این متد ارجاع نخواهیم داشت و ما فقط به مجموعه رفتارهای آن متد ارجاع خواهیم داشت.

پس ما می توانیم هر نوعی از SyntaxChecker را که براساس IsyntaxChecker پیاده سازی شده باشد را بپذیریم(این پیاده سازی می تواند یک JavaScriptSyntaxChecker یا CsharpSyntaxChecker یا PythonSyntaxChecker و ... باشد ). تا زمانی که این کلاس طراحی شده بر اساس IsyntaxChecker باشد، بسیار خوب است و برای استفاده مناسب است.

// a contract to define the behavior of a syntax checker
public interface ISyntaxChecker
{
  bool IsValid();
  bool GetLineCount();
  bool GetErrorCount();
  ...
}

// a concrete SyntaxChecker implementation focused on JavaScript
public class JavaScriptSyntaxChecker : ISyntaxChecker
{
  
  public JavaScriptSyntaxChecker()
  {

  }

  public bool IsValid()
  {
  // implement JavaScript IsValid() method here...
  }
  
  ... other methods defined in our interface
}

دومین تغییر اساسی در کلاس CodeEditor این است که وابستگی شدید ما به SyntaxChecker برطرف شده است. ما به جای استفاده از new برای ساختن SyntaxChecker در سازنده کلاس از یک پارامتر ورودی از نوع IsyntaxChecker  در سازنده کلاس استفاده کرده ایم.

بنابراین کاربرانی که قصد ساخت نمونه از CodeEditor را دارند خودشان باید مشخص کنند که از کدام پیاده سازی  SyntaxChecker می خواهند استفاده کنند.

JavaScriptSyntaxChecker jsc = new JavaScriptSyntaxChecker(); // dependency
 CodeEditor codeEditor = new CodeEditor(jsc);

 CSharpSyntaxChecker cssc = new CSharpSyntaxChecker(); // dependency
 CodeEditor codeEditor = new CodeEditor(cssc);

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

1-قابلیت کنترل ایجاد وابستگی در خارج از کلاس هایی که از آنها استفاده می کنند. معمولا این کار در یک بخش مرکزی مثل IoC container انجام می شود به جای اینکه بارها و بارها در طول برنامه انجام شود.

2- قابلیت تست هر کلاس به صورت جداگانه از بقیه کلاس ها چراکه می توانیم یک شی جعلی یا تقلیدی را به سازنده کلاس بفرستیم به جای اینکه مجبور باشیم از یک متد پیاده سازی شده مشخص استفاده کنیم.(در مورد این قابلیت در مقاله دوم بیشتر توضیح داده شده است.)

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

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

یکی از ویژگی های جدید در ASP.Net Core این است که به کمک یک IoC container ساده و سبک، تزریق وابستگی را به صورت بسیار خوب و با کیفیتی انجام می دهد.

این یک گام بزرگ رو به جلو است همانطور که محصولات گذشته ما مثل وب فرم، MVC، SignalR و Web API هر کدام مکانیزم مخصوص خود را برای استفاده از container ها داشته اند مثل Ninject، Autofac، SturctureMap و Castle Windsor.

توجه کنید که ASP.NET Core به صورت کامل از تعویض بین تامین کننده های IoC پشتیبانی می کند و این یکی دیگر از قابلیت ها و مزایای آن است. این ماژول طوری طراحی شده که ساده و سریع باشد که برای پروژه های کوچک بسیار  ایده آل هستند.

برای شروع، یک برنامه ASP.Net Core می سازیم.

نمونه پروژه های موجود در بخش افزودن پروژه، به صورت پیشفرض IoC container را دارند ولی اگر نیاز شد که این پکیج را به صورت جداگانه اضافه کنید از بخش مدیریت پکیج NuGet پکیج Microsoft.Extensions.DependencyInjection را به برنامه اضافه کنید.

مفاهیم container

built-in container در ASP.NET Core به وسیله اینترفیس IserviceProvider نشان داده می شود که این اینترفیس به صورت پیشفرض از تزریق به وسیله سازنده کلاس پشتیبانی می کند.

انواع container  هایی که نشان داده شده اند به عنوان service شناخته می شوند. شما می توانید نوع container مورد نظرتان را در متد ConfigureServices که در کلاس Startup است اضافه کنید.

// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
  // Add framework services.
  services.AddMvc();

  // registering our custom player repository
  services.AddScoped<IPlayerRepository, PlayerRepository>();

  // registering our custom game repository
  services.AddScoped<IGameRepository, GameRepository>();
}

پارامتر IserviceCollection مجموعه ایی است که نوع های ثبت شده را نگه داری می کند. مانند دیگر container ها از تطابق اینترفیس ها پشتیبانی می کند یا به ایجاد نمونه به وسیله پیاده سازی کارخانه ایی می پردازد.

همچنین ما باید دوره حیات این سرویس ها را در نظر بگیریم. دوره حیات یک سرویس در زمینه برنامه های وب به این معناست که چه مدت پس از ارسال درخواست به سرور یک نمونه باید وجود داشته باشد.

Container پیشفر ض از سه دوره حیات پشتیبانی می کند.

1- ServiceLifetime.Transient- به ازای هر شی یک نمونه ساخته می شود.

2- ServiceLifetime.Scoped – در برنامه های ASP.NET Core به ازای هر درخواست سرور یک محدوده ساخته می شود. نمونه در این محدوده قرار می گیرد. این نوع از دوره حیات برای شی های Entity Framework DbContext مناسب است ( الگوی Unit of Work). برای به اشتراک گذاری بین محدوده ایی باید از چندین شی استفاده شود.

3- ServiceLifetime.Singleton – یک نمونه فقط ساخته می شود و در تمام طول عمر نرم افزار استفاده می شود.

با داشتن repository های player و game ، استفاده از این خدمات در داخل برنامه از این آسانتر نمی تواند باشد.

در داخل کنترلر Home در متد Index هم از تزریق از طریق سازنده کلاس و هم تزریق از طریق پارامتر استفاده شده است. نیازی نیست که به صورت دستی یک نمونه از آنها ساخته شود و آنها خیلی  ساده در کنترلر موجود هستند.

public class HomeController : Controller
{
  private readonly IPlayerRepository _playerRepository;

  public HomeController(IPlayerRepository playerRepository)
  {
  _playerRepository = playerRepository;
  }

  public IActionResult Index([FromServices] IGameRepository gameRepository)
  {
  var players = _playerRepository.GetAll(); // constructor injected
  var games = gameRepository.GetTodaysGames(); // parameter injected
  return View();
  }

معمولا، اکثر وابستگی ها از طریق سازنده کلاس ارسال می شود اما در ASP.Net Core قابلیت تزریق پارامتر خیلی ساده تر است و ارزش آن را دارد که در ذهن داشته باشید تا در شرایطی که قصد استفاده از تزریق از طریق سازنده کلاس را ندارید از این روش استفاده کنید.

آموزش asp.net mvc

برنامه نویسان

نویسنده 3355 مقاله در برنامه نویسان

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

در صورتی که در رابطه با این مقاله سوالی دارید، در تاپیک های انجمن مطرح کنید