تزریق وابستگیها در ASP.NET Core - بخش 4 - طول حیات سرویس ها یا Service Lifetime
نویسنده: رضا پویا
تاریخ: ۱۳۹۹/۰۳/۰۴ ۳:۲۰
آدرس: www.dntips.ir
| مطالب | ۳۶۹۴ |
| نویسندگان | ۲۷۶ |
| گروههای مطالب | ۱۰۲۴ |
| نقشههای راه | ۱۱۹ |
| دورهها | ۱۴ |
| اشتراکها | ۱۷۹۱۴ |
services.AddSingleton(services => new GuidProvider());
public HomeController(ILogger<HomeController> logger,
IMessageServiceA messageService,
LiteDbConfig liteDbConfig,
GuidProvider guidHelper)
{
_logger = logger;
_messageService = messageService;
_messageService = new MessageServiceAA();
_guidHelper = guidHelper;
} حالا اگر برنامه
را اجرا کنیم، میبینید که با تازه
سازی صفحهی Home/Index ، همچنان Id، برابر با یک رشتهی یکسان
است. حتی اگر تب دیگری را در مرورگر باز کنیم و دوباره به این صفحه برویم، میبینیم
که Id برابر همان
رشتهی قبلی است و دلیل این موضوع، ثبت
سرویس Guid Service به صورت Singleton است.
Scoped ( محدود شده )
به ازای هر درخواست (در اینجا معمولا درخواستهای Http مد نظر است) یک نمونه از این سرویس ساخته میشود و در طول حیات این درخواست، DI Container به هر کلاسی که به این سرویس نیاز دارد، همان یک نمونه را برگشت میدهد و این نمونه در کل طول اجرای این درخواست، بین تمامی سرویس گیرندگان، یکسان است. هر زمانی، درخواست به پایان برسد، نمونهی ساخته شده از سرویس، Disposed میگردد و GC میتواند آن را از بین ببرد.
معمولا سرویسهای اتصال به پایگاه دادهها و کار بر روی آنها که شامل خواندن، نوشتن، ویرایش، حذف میشوند را با طول حیات Scoped ، درون DI Container ثبت میکنند . EF Core به صورت پیش فرض ، Db Context را به صورت Scoped ثبت میکند.
سرویسهای Scoped در محدودهی درخواست، مانند Singleton عمل میکنند و شیء ساخته شده و وضعیت آن در بین تمامی سرویسهایی که به آن نیاز دارند، مشترک است. بنابراین باید به این نکته در هنگام تعریف سرویس به صورت Scoped ، توجه داشته باشید.
تمام Middleware های ASP.NET Core هم فقط همان نمونهی ایجاد شده از سرویس Scoped را در طی اجرای یک درخواست خاص، میگیرند.
هر سرویسی که به سرویسهای Scoped نیاز دارد، یا باید به صورت Transient و یا باید به صورت Scoped ثبت شود، تا مانع از این شویم که شیء ساخته شده، فراتر از طول حیات موردنظرمان، در حافظه بماند و از آن استفاده شود .
برای ثبت یک سرویس
به صورت Scoped میتوانیم از متدهای توسعهای با نام AddScoped() با سربارهای
مختلف بر روی IServiceCollection استفاده کنیم. در اینجا از نسخهای که دو
پارامتر جنریک را میگیرد، برای ثبت یک سرویس به صورت Scoped استفاده میکنیم:
services.AddScoped<IMessageServiceB, MessageServiceBA>();
می توانیم سرویس GuidProvider را به جای Signleton ، به صورت Scoped ثبت کنیم:
services.AddScoped(services => new GuidProvider());
Transient (گذرا)
به ازای هر درخواست دهندهی جدید، یک نمونهی جدید از سرویس، توسط DI Container ساخته میشود و در اختیار آن قرار میگیرد.
سرویسهایی را به این صورت ثبت کنید که:
سرویسهای Transient ، کارآیی پائینتری دارند و سربار عملکردی زیادی بر روی Garbage Collector می گذارند؛ ولی به علت اینکه به ازای هر واکشی، یک نمونهی جدید از آنها ساخته میشود و State بین این اشیاء به اشتراک گذاشته نمیشود، امنیت بیشتری دارند و درک و خطایابی آنها سادهتر است.
برای ثبت سرویسهای Transient از متد توسعهای AddTransient() استفاده میکنیم. سربارهای این متد مانند سربارهای متدهای AddSingleton() و AddScoped() است:
services.AddTransient<IMessageServiceC, MessageServiceCA>();
وابستگیهای محصور شده
یک سرویس نباید وابستهی به سرویسی باشد که طول حیاتی کمتر از طول حیات خودش را دارد.
برای مثال اگر درون سرویسی با طول حیات Singleton، از یک سرویس با طول حیات Transient استفاده کنیم، اینکار باعث میشود که یک نمونه از سرویس Transient در طول حیات برنامه، همیشه درون حافظه بماند و این ممکن است باعث خطاهای عجیبی در هنگام اجرا شود که معمولا خطایابی و رفع آنها مشکل است.
اثرات جانبی وابستگیهای محصور شده:
سرویسهای Transient میتوانند به سرویسهایی با طول حیات زیر وابستگی داشته باشند:
سرویسهای Scoped میتوانند به سرویسهایی با طول حیات زیر وابستگی داشته باشند:
سرویسهای Singleton میتوانند به سرویس هایی با طول حیات زیر وابستگی داشته باشند:
Singleton
میتوانید از جدول زیر به عنوان راهنمای خلاصه شدهی برای استفادهی امن از سرویسها درون یکدیگر بهره ببرید:
Scope Validation
این قابلیت که به صورت پیش
فرض در حالت توسعهی برنامههای ASP.NET Core فعال است، در زمان شروع برنامه و در Startup ، بررسی میکند که سرویسها، وابستگی
به سرویسهایی با طول حیاتی مناسب، داشته باشند.
در متن آمده که EF Core به صورت پیش فرض ، Db Context را به صورت Scoped ثبت میکند. و نحوه تزریق از طریق سازنده کلاسها است. از طرفی اگر درست متوجه شده باشم در مستندات ماکروسافت هشدار داده شده که این مدل طول حیات را نباید از طریق سازنده تزریق کرد.
When using a scoped service in a middleware, inject the service into the Invoke or InvokeAsync method. Don't inject via constructor injection because it forces the service to behave like a singleton. For more information, see Write custom ASP.NET Core middleware.
همچنین شما گفتید که "سرویسهای Scoped در محدودهی درخواست، مانند Singleton عمل میکنند و شیء ساخته شده و وضعیت آن در بین تمامی سرویسهایی که به آن نیاز دارند، مشترک است. بنابراین باید به این نکته در هنگام تعریف سرویس به صورت Scoped ، توجه داشته باشید."
در پروژهای که روی آن کار میکنم (ASP.NET Core Razor Pages) یک کلاس validation هست که میخوام به عنوان یک سرویس به کلاس هر صفحه تزریق بشه. اگر امکانش هست توضیح بدید که بر اساس مواردی که در بالا آوردم و هدفی که دارم آیا بین حالات singleton و scoped فرقی وجود دارد؟
ممنون.
public class UserAccountManager : IUserAccountManager
{
private I,serRepository _userRepository ;
// تزریق درون سازنده
}
public class UserFinancialManager : IUserFinancialManager
{
private IUserRepository _userRepository ;
// تزریق درون سازنده
}
public class UserController
{
IUserFinancialManager _userFinancialManager ;
IUserAccountManager _userAccountManager;
// تزریق درون سازنده
} using System;
namespace AspNetCoreDependencyInjection.Services
{
public interface IUserRepository
{
public string GID { get; }
}
public class UserRepository : IUserRepository
{
public string GID { get; private set; }
public UserRepository()
{
GID = Guid.NewGuid().GetHashCode().ToString("x");
}
}
//---------------------------------------------
public interface IUserAccountManager
{
public string DisplayGuid();
}
public class UserAccountManager : IUserAccountManager
{
private IUserRepository _userRepository;
public UserAccountManager(IUserRepository userRepository)
{
_userRepository = userRepository;
}
public string DisplayGuid()
{
return "UserFinancialManager Guid : " + _userRepository.GID;
}
}
//-------------------------------------------------
public interface IUserFinancialManager
{
public string DisplayGuid();
}
public class UserFinancialManager : IUserFinancialManager
{
private IUserRepository _userRepository;
public UserFinancialManager(IUserRepository userRepository)
{
_userRepository = userRepository;
}
public string DisplayGuid()
{
return "UserFinancialManager Guid : " + _userRepository.GID;
}
}
} public class UserController : Controller
{
private readonly IUserFinancialManager _userFinancialManager;
private readonly IUserAccountManager _userAccountManager;
public UserController(IUserFinancialManager userFinancialManager,
IUserAccountManager userAccountManager)
{
_userFinancialManager = userFinancialManager;
_userAccountManager = userAccountManager;
}
public IActionResult Index()
{
ViewBag.FinancialManager = _userFinancialManager.DisplayGuid();
ViewBag.AccountManager = _userAccountManager.DisplayGuid();
return View();
}
} @{
ViewData["Title"] = "User";
}
<div class="text-center">
<div class="row">
<div class="col-12">
<p class="alert alert-info text-left">
<b>User Finanacial Manager / UserRepository Guid : </b><span>@ViewBag.FinancialManager </span> <br />
<b>User Account Manager / UserRepository Guid : </b><span>@ViewBag.AccountManager</span> <br />
</p>
</div>
</div>
</div> services.AddTransient<IUserRepository, UserRepository>(); services.AddScoped<IUserAccountManager, UserAccountManager>(); services.AddScoped<IUserFinancialManager, UserFinancialManager>(); // اجرای دوم // services.AddScoped<IUserRepository, UserRepository>(); //services.AddScoped<IUserAccountManager, UserAccountManager>(); //services.AddScoped<IUserFinancialManager, UserFinancialManager>();
سرویسهای Scoped در محدودهی درخواست، مانند Singleton عمل میکنند و شیء ساخته شده و وضعیت آن در بین تمامی سرویسهایی که به آن نیاز دارند، مشترک است. بنابراین باید به این نکته در هنگام تعریف سرویس به صورت Scoped ، توجه داشته باشید.
سرویسهای Scoped میتوانند به سرویسهایی با طول حیات زیر وابستگی داشته باشند:
Transient
Scoped