ASP.NET Core با ذهنیت پشتیبانی و استفاده از تزریق وابستگیها ایجاد شدهاست. اپلیکیشنهای ASP.NET Core از سرویسهای ذاتی فریم ورک که داخل متدهای کلاس Startup پروژه تزریق شدهاند و همچنین سرویسهای اپلیکیشن که تنظیمات خاص آنها در پروژه انجام گرفته است، استفاده میکنند. سرویس کانتینر پیش فرض ارائه شده توسط ASP.NET Core، مجموعهای حداقلی از ویژگیها را ارائه میکند و هدف آن جایگزینی با دیگر فریم ورکهای تزریق وابستگی نمیباشد.
مشاهده یا دانلود کدهای مقاله
تزریق وابستگی چیست؟
تزریق وابستگی (DI) تکنیکی برای دستیابی به اتصال شل بین اشیاء و همکاران اشیاء و وابستگیهای بین آنها میباشد. یک شیء برای انجام وظایف خود، بجای اینکه اشیاء همکار خود را به صورت مستقیم نمونه سازی کند، یا از ارجاعات استاتیک استفاده نماید، میتواند از اشیائی که برایش تامین شدهاست، استفاده کند. در اغلب موارد کلاسها، وابستگیهای خود را از طریق سازندهی خود درخواست میکنند، که به آنها اجازه میدهد اصل وابستگی صریح را رعایت کنند (
Explicit Dependencies Principle). این روش را «تزریق در سازنده» مینامند.
از آنجا که در طراحی کلاسها با استفاده از DI، نمونه سازی مستقیم، توسط کلاسها و به صورت Hard-coded انجام نمیگیرد، وابستگی بین اشیاء کم شده و پروژهای با اتصالات شل به دست میآید. با این کار اصل وابستگی معکوس (
Dependency Inversion Principle) رعایت میشود. بر اساس این اصل، ماژولهای سطح بالا نباید به ماژولهای سطح پایین خود وابسته باشند؛ بلکه هر دو باید به کلاسهایی انتزاعی وابسته باشند. اشیاء بجای ارجاع به پیاده سازیهای خاص کلاسهای همکار خود، کلاسهای انتزاعی، معمولاٌ اینترفیس آنها را درخواست میکنند و هنگام نمونه سازی از آنها (داخل متد سازنده) کلاس پیاده سازی شده برایشان تامین میشود. خارج کردن وابستگیهای مستقیم از کلاسها و تامین پیاده سازیهای این اینترفیسها به صورت پارامترهایی برای کلاسها، یک مثال از الگوی طراحی استراتژی (
Strategy design pattern) میباشد.
در حالتیکه کلاسها به تعداد زیادی کلاس وابستگی داشته باشند و برای اجرا شدن، نیاز به تامین وابستگیهایشان داشته باشند، بهتر است یک کلاس اختصاصی، برای نمونه سازی این کلاسها با وابستگیهای مورد نیاز آنها، در سیستم وجود داشته باشد. این کلاس نمونه ساز را کانتینرIoC، یا کانتینر DI یا به طور خلاصه کانتینر مینامند (
Inversion of Control (IoC) ). کانتینر در اصل یک کارخانه میباشد که وظیفهی تامین نمونههایی از کلاسهایی را که از آن درخواست میشود، انجام میدهد. اگر یک کلاس تعریف شده، وابستگی به کلاسهای دیگر داشته باشد و کانتینر برای ارائه وابستگیهای کلاس تعریف شده تنظیم شده باشد، هر موقع نیاز به یک نمونه از این کلاس وجود داشته باشد، به عنوان بخشی از کار نمونه سازی از کلاس مورد نظر، کلاسهای وابستهی آن نیز ایجاد میشوند (همهی کارهای مربوط به نمونه سازی کلاس خاص و کلاسهای وابسته به آن توسط کانتینر انجام میگیرد). به این ترتیب، میتوان وابستگیهای بسیار پیچیده و تو در توی موجود در سیستم را بدون نیاز به هیچگونه نمونه سازی hard-code شده، برای کلاسها فراهم کرد. کانتینرها علاوه بر ایجاد اشیاء و وابستگیهای موجود در آنها، معمولا طول عمر اشیاء در اپلیکیشن را نیز مدیریت میکنند.
ASP.NET Core یک کانتینر بسیار ساده را به نام اینترفیس IServiceProvider ارائه داده است که به صورت پیش فرض از تزریق وابستگی در سازندهی کلاسها پشتیبانی میکند و همچنین ASP.NET برخی از سرویسهای خود را از طریق DI در دسترس قرار داده است. کانتینرASP.NET، یک اشارهگر به کلاسهایی است که به عنوان سرویس عمل میکنند. در ادامهی این مقاله، سرویسها به کلاسهایی گفته میشود که به وسیلهی کانتینر ASP.NET Core مدیریت میشوند. شما میتوانید سرویس ConfigureServices کانتینر را در داخل کلاس Startup پروژه خود پیکربندی کنید.
تزریق وابستگی از طریق متد سازندهی کلاس
تزریق وابستگی از طریق متد سازنده، مستلزم آن است که سازندهی کلاس مورد نظر عمومی باشد. در غیر این صورت، اپلیکیشن شما استثنای InvalidOperationException را با پیام زیر نشان میدهد:
A suitable constructor for type 'YourType' could not be located. Ensure the type is concrete and services are registered for all parameters of a public constructor.
تزریق از طریق متد سازنده مستلزم آن است که تنها یک سازندهی مناسب وجود داشته باشد. البته Overload سازنده امکان پذیر است؛ ولی باید تنها یک متد سازنده وجود داشته باشد که آرگومانهای آن توسط DI قابل ارائه باشند. اگر بیش از یکی وجود داشته باشد، سیستم استثنای InvalidOperationException را با پیام زیر نشان میدهد:
Multiple constructors accepting all given argument types have been found in type 'YourType'. There should only be one applicable constructor.
سازندگان میتوانند آرگومانهایی را از طریق DI دریافت کنند. برای این منظور آرگومانهای این سازندهها باید مقدار پیش فرضی را داشته باشند. به مثال زیر توجه نمایید:
// throws InvalidOperationException: Unable to resolve service for type 'System.String'...
public CharactersController(ICharacterRepository characterRepository, string title)
{
_characterRepository = characterRepository;
_title = title;
}
// runs without error
public CharactersController(ICharacterRepository characterRepository, string title = "Characters")
{
_characterRepository = characterRepository;
_title = title;
}
استفاده از سرویس ارائه شده توسط فریم ورک
متد ConfigureServices در کلاس Startup، مسئول تعریف سرویسهایی است که سیستم از آن استفاده میکند. از جملهی این سرویسها میتوان به ویژگیهای پلتفرم مانند EF Core و ASP.NET Core MVC اشاره کرد. IServiceCollection که به ConfigureServices ارائه میشود، سرویسهای زیر را تعریف میکند (که البته بستگی به نوع پیکربندی هاست دارد):
نوع سرویس |
طول زندگی |
Microsoft.AspNetCore.Hosting.IHostingEnvironment | Singleton |
Microsoft.AspNetCore.Hosting.IApplicationLifetime | Singleton |
Microsoft.AspNetCore.Hosting.IStartup | Singleton |
Microsoft.AspNetCore.Hosting.Server.IServer | Singleton |
Microsoft.Extensions.Options.IConfigureOptions | Transient |
Microsoft.Extensions.ObjectPool.ObjectPoolProvider | Singleton |
Microsoft.AspNetCore.Hosting.IStartupFilter | Transient |
System.Diagnostics.DiagnosticListener | Singleton |
System.Diagnostics.DiagnosticSource | Singleton |
Microsoft.Extensions.Options.IOptions | Singleton |
Microsoft.AspNetCore.Http.IHttpContextFactory | Transient |
Microsoft.AspNetCore.Hosting.Builder.IApplicationBuilderFactory | Transient |
Microsoft.Extensions.Logging.ILogger | Singleton |
Microsoft.Extensions.Logging.ILoggerFactory | Singleton |
در زیر نمونه ای از نحوهی اضافه کردن سرویسهای مختلف را به کانتینر، با استفاده از متدهای الحاقی مانند AddDbContext، AddIdentity و AddMvc، مشاهده میکنید:
// 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.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
services.AddMvc();
// Add application services.
services.AddTransient<IEmailSender, AuthMessageSender>();
services.AddTransient<ISmsSender, AuthMessageSender>();
}
ویژگیها و میان افزارهای ارائه شده توسط ASP.NET، مانند MVC، از یک قرارداد، با استفاده از متد الحاقی AddServiceName برای ثبت تمام سرویسهای مورد نیاز این ویژگی پیروی میکنند.
ثبت سرویسهای اختصاصی
شما میتوانید سرویسهای اپلیکیشن خودتان را به ترتیبی که در تکه کد زیر مشاهده میکنید، ثبت نمایید. اولین نوع جنریک، نوعی است که از کانتینر درخواست خواهد شد و معمولا به شکل اینترفیس میباشد. نوع دوم، نوع پیاده سازی شدهای است که به وسیلهی کانتینر، نمونه سازی خواهد شد و کانتینر برای درخواستهای از نوع اول، این نمونه از تایپ را ارائه خواهد کرد:
services.AddTransient<IEmailSender, AuthMessageSender>();
services.AddTransient<ISmsSender, AuthMessageSender>();
نکته:
هر متد الحاقی <services.Add<ServiceName، سرویسهایی را اضافه و پیکربندی میکند. به عنوان مثال services.AddMvc نیازمندیهای سرویس MVC را اضافه میکند. توصیه میشود شما هم با افزودن متدهای الحاقی در فضای نام Microsoft.Extensions.DependencyInjection این قرارداد را رعایت نمائید. این کار باعث کپسوله شدن ثبت گروهی سرویسها میشود.
متد AddTransient، برای نگاشت نوعهای انتزاعی به سرویسهای واقعی که نیاز به نمونه سازی به ازای هر درخواست دارند، استفاده میشود. در اصطلاح، طول عمر سرویسها در اینجا مشخص میشوند. در ادامه گزینههای دیگری هم برای طول عمر سرویسها تعریف خواهند شد. خیلی مهم است که برای هر یک از سرویسهای ثبت شده، طول عمر مناسبی را انتخاب نمایید. آیا برای هر کلاس که سرویسی را درخواست میکند، باید یک نمونهی جدید ساخته شود؟ آیا فقط یک نمونه در طول یک درخواست وب مورد استفاده قرار میگیرد؟ یا باید از یک نمونهی واحد برای طول عمر کل اپلیکیشن استفاده شود؟
در مثال ارائه شدهی در این مقاله، یک کنترلر ساده به نام CharactersController وجود دارد که نام کاراکتری را نشان میدهد. متد Index، لیست کنونی کاراکترهایی را که در اپلیکیشن ذخیره شدهاند، نشان میدهد. در صورتیکه این لیست خالی باشد، تعدادی به آن اضافه میکند. توجه داشته باشید، اگرچه این اپلیکیشن از Entity Framework Core و ClassDataContext برای دادههای مانا استفاده میکند، هیچیکدام از آنها در کنترلر ظاهر نمیشوند. در عوض، مکانیزم دسترسی به دادههای خاص، در پشت یک اینترفیس (ICharacterRepository) مخفی شده است (طبق الگوی طراحی ریپازیتوری). یک نمونه از ICharacterRepository از طریق سازنده درخواست میشود و به یک فیلد خصوصی اختصاص داده میشود، سپس برای دسترسی به کاراکترها در صورت لزوم استفاده میشود:
public class CharactersController : Controller
{
private readonly ICharacterRepository _characterRepository;
public CharactersController(ICharacterRepository characterRepository)
{
_characterRepository = characterRepository;
}
// GET: /characters/
public IActionResult Index()
{
PopulateCharactersIfNoneExist();
var characters = _characterRepository.ListAll();
return View(characters);
}
private void PopulateCharactersIfNoneExist()
{
if (!_characterRepository.ListAll().Any())
{
_characterRepository.Add(new Character("Darth Maul"));
_characterRepository.Add(new Character("Darth Vader"));
_characterRepository.Add(new Character("Yoda"));
_characterRepository.Add(new Character("Mace Windu"));
}
}
}
ICharacterRepository دو متد مورد نیاز کنترلر برای کار با نمونههای Character را تعریف میکند:
using System.Collections.Generic;
using DependencyInjectionSample.Models;
namespace DependencyInjectionSample.Interfaces
{
public interface ICharacterRepository
{
IEnumerable<Character> ListAll();
void Add(Character character);
}
}
این اینترفیس با نوع واقعی CharacterRepository پیاده سازی شده است که در زمان اجرا استفاده میشود:
using System.Collections.Generic;
using System.Linq;
using DependencyInjectionSample.Interfaces;
namespace DependencyInjectionSample.Models
{
public class CharacterRepository : ICharacterRepository
{
private readonly ApplicationDbContext _dbContext;
public CharacterRepository(ApplicationDbContext dbContext)
{
_dbContext = dbContext;
}
public IEnumerable<Character> ListAll()
{
return _dbContext.Characters.AsEnumerable();
}
public void Add(Character character)
{
_dbContext.Characters.Add(character);
_dbContext.SaveChanges();
}
}
}
توجه داشته باشید که CharacterRepository یک ApplicationDbContext را در سازندهی خود درخواست میکند. همانطور که مشاهده میشود هر وابستگی درخواست شده، به نوبه خود وابستگیهای دیگری را درخواست میکند. تزریق وابستگیهایی به شکل زنجیرهای، همانند این مثال غیر معمول نیست. کانتینر مسئول resolve (نمونه سازی) همهی وابستگیهای موجود در گراف وابستگی و بازگرداندن سرویس کاملا resolve شده میباشد.
نکته
ایجاد شیء درخواست شده و تمامی اشیاء مورد نیاز شیء درخواست شده را گراف شیء مینامند. به همین ترتیب مجموعهای از وابستگیهایی را که باید resolve شوند، به طور معمول، درخت وابستگی یا گراف وابستگی مینامند.
در مورد مثال مطرح شده، ICharacterRepository و به نوبه خود ApplicationDbContext باید با سرویسهای خود در کانتینر ConfigureServices و کلاس Startup ثبت شوند. ApplicationDbContext با فراخوانی متد <AddDbContext<T پیکربندی میشود. کد زیر ثبت کردن نوع CharacterRepository را نشان میدهد:
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>(options =>
options.UseInMemoryDatabase()
);
// Add framework services.
services.AddMvc();
// Register application services.
services.AddScoped<ICharacterRepository, CharacterRepository>();
services.AddTransient<IOperationTransient, Operation>();
services.AddScoped<IOperationScoped, Operation>();
services.AddSingleton<IOperationSingleton, Operation>();
services.AddSingleton<IOperationSingletonInstance>(new Operation(Guid.Empty));
services.AddTransient<OperationService, OperationService>();
}
کانتکست انتیتی فریم ورک، با استفاده از متدهای کمکی که در تکه کد بالا نشان داده شده است، باید با طول عمر Scoped به کانتینر سرویسها افزوده شود. این کار میتواند به صورت اتوماتیک انجام گیرد. همهی ریپازیتوریهایی که از Entity Framework استفاده میکنند، باید از یک طول عمر مشابه استفاده کنند.
هشدار
خطر بزرگی را که باید در نظر گرفت، resolve کردن سرویس Scoped از طول عمر singleton میباشد. در صورت انجام این کار، احتمال دارد که سرویسها وارد حالت نادرستی شوند.
سرویسهایی که وابستگیهای دیگری هم دارند، باید آنها را در کانتینر ثبت کنند. اگر سازندهی سرویس نیاز به یک primitive به عنوان ورودی داشته باشد، میتوان با استفاده از الگوی گزینهها و پیکربندی (
options pattern and configuration)، ورودیهای مناسبی را به سازندهها منتقل کرد.
طول عمر سرویسها و گزینههای ثبت
سرویسهای ASP.NET را میتوان با طول عمرهای زیر پیکربندی کرد:
Transient: سرویسهایی با طول عمر Transient، در هر زمان که درخواست میشوند، مجددا ایجاد میشوند. این طول عمر برای سرویسهای سبک و بدون حالت مناسب میباشند.
Scoped: سرویسهایی با طول عمر Scoped، تنها یکبار در طی هر درخواست ایجاد میشوند.
Singleton: سرویسهایی با طول عمر Singleton، برای اولین باری که درخواست میشوند (یا اگر در ConfigureServices نمونهای را مشخص کرده باشید) ایجاد میشوند و درخواستهای آتی برای این سرویسها از همان نمونهی ایجاد شده استفاده میکنند. اگر اپلیکیشن شما درخواست رفتار singleton را داشته باشد، پیشنهاد میشود که سرویس کانتینر را برای مدیریت طول عمر سرویس مورد نیاز پیکربندی کنید و خودتان الگوی طراحی singleton را پیاده سازی نکنید.
سرویسها به چندین روش میتوانند در کانتینر ثبت شوند. چگونگی ثبت کردن یک سرویس پیاده سازی شده برای یک نوع، در بخشهای پیشین توضیح داده شده است. علاوه بر این، یک کارخانه را میتوان مشخص کرد، که برای ایجاد نمونه بر اساس تقاضا استفاده شود. رویکرد سوم، ایجاد مستقیم نمونهای از نوع مورد نظر است که در این حالت کانتینر اقدام به ایجاد یا نابود کردن نمونه نمیکند.
به منظور مشخص کردن تفاوت بین این طول عمرها و گزینههای ثبت کردن، یک اینترفیس ساده را در نظر بگیرید که نشان دهندهی یک یا چند operation است و یک شناسهی منحصر به فرد operation را از طریق OperationId نشان میدهد. برای مشخص شدن انواع طول عمرهای درخواست شده، بسته به نحوهی پیکربندی طول عمر سرویس مثال زده شده، کانتینر، نمونهی یکسان یا متفاوتی را از سرویس، به کلاس درخواست کننده ارائه میدهد. ما برای هر طول عمر، یک نوع را ایجاد میکنیم:
using System;
namespace DependencyInjectionSample.Interfaces
{
public interface IOperation
{
Guid OperationId { get; }
}
public interface IOperationTransient : IOperation
{
}
public interface IOperationScoped : IOperation
{
}
public interface IOperationSingleton : IOperation
{
}
public interface IOperationSingletonInstance : IOperation
{
}
}
ما این اینترفیسها را با استفاده از یک کلاس واحد به نام Operation پیاده سازی کردهایم. سازندهی این کلاس، یک Guid به عنوان ورودی میگیرد؛ یا اگر Guid برایش تامین نشد، خودش یک Guid جدید را میسازد.
سپس در ConfigureServices، هر نوع با توجه به طول عمر مورد نظر، به کانتینر افزوده میشود:
services.AddScoped<ICharacterRepository, CharacterRepository>();
services.AddTransient<IOperationTransient, Operation>();
services.AddScoped<IOperationScoped, Operation>();
services.AddSingleton<IOperationSingleton, Operation>();
services.AddSingleton<IOperationSingletonInstance>(new Operation(Guid.Empty));
services.AddTransient<OperationService, OperationService>();
توجه داشته باشید که سرویس IOperationSingletonInstance، از یک نمونهی خاص، با شناسهی شناخته شدهی Guid.Empty استفاده میکند (این Guid فقط شامل اعداد صفر میباشد). بنابراین زمانیکه این تایپ مورد استفاده قرار میگیرد، کاملا واضح است. تمام این سرویسها وابستگیهای خود را به صورت پراپرتی نمایش میدهند. بنابراین میتوان آنها را در View نمایش داد.
using DependencyInjectionSample.Interfaces;
namespace DependencyInjectionSample.Services
{
public class OperationService
{
public IOperationTransient TransientOperation { get; }
public IOperationScoped ScopedOperation { get; }
public IOperationSingleton SingletonOperation { get; }
public IOperationSingletonInstance SingletonInstanceOperation { get; }
public OperationService(IOperationTransient transientOperation,
IOperationScoped scopedOperation,
IOperationSingleton singletonOperation,
IOperationSingletonInstance instanceOperation)
{
TransientOperation = transientOperation;
ScopedOperation = scopedOperation;
SingletonOperation = singletonOperation;
SingletonInstanceOperation = instanceOperation;
}
}
}
برای نشان دادن طول عمر اشیاء، در بین درخواستهای جداگانهی یک اپلیکیشن، مثال ذکر شده شامل کنترلر OperationsController میباشد که هر کدام از انواع IOperation و همچنین OperationService را درخواست میکند. سپس اکشن Index تمام مقادیر OperationId کنترل کننده و سرویسها را نمایش میدهد:
using DependencyInjectionSample.Interfaces;
using DependencyInjectionSample.Services;
using Microsoft.AspNetCore.Mvc;
namespace DependencyInjectionSample.Controllers
{
public class OperationsController : Controller
{
private readonly OperationService _operationService;
private readonly IOperationTransient _transientOperation;
private readonly IOperationScoped _scopedOperation;
private readonly IOperationSingleton _singletonOperation;
private readonly IOperationSingletonInstance _singletonInstanceOperation;
public OperationsController(OperationService operationService,
IOperationTransient transientOperation,
IOperationScoped scopedOperation,
IOperationSingleton singletonOperation,
IOperationSingletonInstance singletonInstanceOperation)
{
_operationService = operationService;
_transientOperation = transientOperation;
_scopedOperation = scopedOperation;
_singletonOperation = singletonOperation;
_singletonInstanceOperation = singletonInstanceOperation;
}
public IActionResult Index()
{
// viewbag contains controller-requested services
ViewBag.Transient = _transientOperation;
ViewBag.Scoped = _scopedOperation;
ViewBag.Singleton = _singletonOperation;
ViewBag.SingletonInstance = _singletonInstanceOperation;
// operation service has its own requested services
ViewBag.Service = _operationService;
return View();
}
}
}
حالا دو درخواست جداگانه برای این کنترلر ساخته شده است:
به تفاوتهای موجود در مقادیر OperationId در یک درخواست و بین درخواستها توجه کنید:
- OperationId اشیاء Transient همیشه متفاوت میباشند. چون یک نمونه جدید برای هر کنترلر و هر سرویس ایجاد شدهاست.
- اشیاء Scoped در یک درخواست، یکسان هستند؛ اما در درخواستهای مختلف متفاوت میباشند.
- اشیاء Singleton برای هر شیء و هر درخواست (صرف نظر از اینکه یک نمونه در ConfigureServices ارائه شده است) یکسان میباشند.
درخواست سرویس
در ASP.NET سرویسهای موجود در یک درخواست HttpContext از طریق مجموعه RequestServices قابل مشاهده میباشد.
RequestServices نشان دهندهی سرویسهایی است که شما به عنوان بخشی از اپلیکیشن خود، آنها را پیکربندی و درخواست میکنید. هنگامیکه اشیاء اپلیکیشن شما وابستگیهای خود را مشخص میکنند، این وابستگیها با استفاده از نوعهای موجود در RequestServices برآورده میشوند و نوعهای موجود در ApplicationServices در این مرحله مورد استفاده قرار نمیگیرد.
به طور کلی، شما نباید مستقیما از این خواص استفاده کنید و بجای آن، نوعهای کلاس خود را توسط سازندهی کلاس، درخواست کنید و اجازه دهید فریم ورک این وابستگیها را تزریق کند. این کار باعث بهوجود آمدن کلاسهایی با قابلیت آزمونپذیری بالاتر و اتصالات شلتر بین آنها میشود.
نکته
درخواست وابستگیها با استفاده از پارامترهای کلاس سازنده، بر روش کار با مجموعهی RequestServices ارجحیت دارد.
طراحی سرویسها برای تزریق وابستگیها
شما باید سرویسهای خود را طوری طراحی کنید که از تزریق وابستگیها برای ارتباطات خود استفاده نمایند. این کار باعث کاهش استفاده از فراخوانیهای متدهای استاتیک (متدهای استاتیک، حالت دار میباشند و استفادهی زیاد از آنها باعث به وجود آمدن بوی بد کدی به نام
static cling، میشود) و همچنین از بین رفتن نیاز به نمونه سازی مستقیم کلاسهای وابسته داخل سرویسها، میشود. هر موقع بخواهید بین new کردن یک کلاس، یا درخواست دادن آن از طریق تزریق وابستگی، یکی را انتخاب کنید، این اصطلاح را به یاد بیاورید،
New is Glue. با پیروی از اصول SOLID طراحی شیء گرا، به طور طبیعی کلاسهای شما تمایل به کوچک بودن، کارا و قابل تست بودن را دارند.
اگر متوجه شدید که کلاسهای شما تمایل دارند تا تعداد وابستگیهای زیادی به آنها تزریق شود، چه باید بکنید؟ به طور کلی این مشکل نشانهای است از نقض
Single Responsibility Principle یا SRP است و احتمالا کلاسهای شما وظایف بیش از اندازهای را دارند. در این گونه موارد تلاش کنید مقداری از وظایف کلاس را به یک کلاس جدید منتقل کنید. در نظر داشته باشید که کلاسهای کنترلر باید به مسائل UI تمرکز کنند و قوانین کسب و کار و جزئیات دسترسی به دادهها باید در کلاسهایی جداگانه و مرتبط با خود قرار داشته باشند.
به طور خاص برای دسترسی به داده ، شما میتوانید DbContext را به کنترلرهای خود تزریق کنید (با فرض اینکه شما EF را به کانتینر سرویس ConfigureServices اضافه کردهاید). بعضی از توسعه دهندگان به جای تزریق مستقیم DbContext از یک اینترفیس ریپازیتوری استفاده مینمایند. میتوانید با استفاده از یک اینترفیس برای کپسوله کردن منطق دسترسی به دادهها در یک مکان، تعداد تغییرات مورد نیاز را در صورت تغییر دیتابیس، به حداقل برسانید.
تخریب سرویس ها
سرویس کانتینر برای نوعهای IDisposable که خودش ایجاد کردهاست، متد Dispose را فراخوانی خواهد کرد. با این حال، اگر شما خودتان نمونهای را به صورت دستی نمونه سازی و به کانتینر اضافه کرده باشید، سرویس کانتینر آنرا dispose نخواهد کرد.
مثال: // Services implement IDisposable:
public class Service1 : IDisposable {}
public class Service2 : IDisposable {}
public class Service3 : IDisposable {}
public void ConfigureServices(IServiceCollection services)
{
// container will create the instance(s) of these types and will dispose them
services.AddScoped<Service1>();
services.AddSingleton<Service2>();
// container did not create instance so it will NOT dispose it
services.AddSingleton<Service3>(new Service3());
services.AddSingleton(new Service3());
}
نکته:
در نسخه 1.0، کانتینر برای تمام اشیاء از نوع IDisposable از جمله اشیائی که خودش ایجاد نکرده بود، متد dispose را فراخوانی میکرد.
سرویسهای کانتینر جانشین
کانتینر موجود در net core. به منظور تامین نیازهای اساسی فریم ورک ایجاد شدهاست و تعداد زیادی از اپلیکیشنها از آن استفاده میکنند. با این حال، توسعه دهندگان میتوانند کانتینرهای مورد نظر خود را جایگزین آن کنند. متد ConfigureServices به طور معمول مقدار void را بر میگرداند. اما با تغییر امضای آن به نوع بازگشتیIServiceProvider، میتوان سرویس کانتینر متفاوتی را در اپلیکیشن پیکربندی کرد. سرویسهای کانتینر IOC مختلفی برای NET. وجود دارند؛ در مثال زیر، Autofac استفاده شده است.
در ابتدا بستههای زیر را نصب کنید:
Autofac
Autofac.Extensions.DependencyInjection
سپس کانتینر را در ConfigureServices پیکربندی کنید و IServiceProvider را به عنوان خروجی بازگردانید:
public IServiceProvider ConfigureServices(IServiceCollection services)
{
services.AddMvc();
// Add other framework services
// Add Autofac
var containerBuilder = new ContainerBuilder();
containerBuilder.RegisterModule<DefaultModule>();
containerBuilder.Populate(services);
var container = containerBuilder.Build();
return new AutofacServiceProvider(container);
}
توصیه ها
هنگام کار با تزریق وابستگیها، توصیههای ذیر را در نظر داشته باشید:
- DI برای اشیایی که دارای وابستگی پیچیده هستند، مناسب میباشد. کنترلرها، سرویسها، آداپتورها و ریپازیتوریها، نمونههایی از این اشیاء هستند که میتوانند به DI اضافه شوند.
- از ذخیرهی دادهها و پیکربندی مستقیم در DI اجتناب کنید. به عنوان مثال، معمولا سبد خرید کاربر نباید به سرویس کانتینر اضافه شود. پیکربندی باید از
مدل گزینهها استفاده کند. همچنین از اشیاء "data holder"، که فقط برای دسترسی دادن به اشیاء دیگر ایجاد شدهاند، نیز اجتناب کنید. در صورت امکان بهتر است شیء واقعی مورد نیاز DI درخواست شود.
- از دسترسی استاتیک به سرویسها اجتناب شود.
- از نمونه سازی مستقیم سرویسها در کد برنامه خود اجتناب کنید.
- از دسترسی استاتیک به HttpContext اجتناب کنید.
توجه
مانند هر توصیهی دیگری، ممکن است شما با شرایطی مواجه شوید که مجبور به نقض هر یک از این توصیهها شوید. اما این موارد استثناء بسیار نادر میباشند و رعایت این نکات یک عادت برنامه نویسی خوب محسوب میشود.
مرجع: Introduction to Dependency Injection in ASP.NET Core