معرفی طول عمر ها (Lifetimes) در Asp.Net Core

در این مقاله استفاده مناسبت از Scope ، Transient و Singleton در Asp.net Core را یاد میگیرم .

معرفی Transient Lifetime

افزودن سرویس Transient به این معنی است که هر بار سرویس درخواست شود و نمونه جدیدی ایجاد می شود .

در مثال زیر من یک سرویس و اینترفیس ساده با نام MyServicve و IMyService ایجاد کردم من این سرویس را به صورت Transient ثبت میکنم و دوبار از آن نمونه سازی (instanse) ایجاد میکنم در حالت من به صورت دستی آن را درخواست میدهم ولی در اکثر موافق ما به صورت سازنده کنترلر سرویس را درخواست میدهیم .

public void ConfigureServices(IServiceCollection services)
{
     services.AddTransient<IMyService, MyService>();

     var serviceProvider = services.BuildServiceProvider();

     var instanceOne = serviceProvider.GetService<IMyService>();
     var instanceTwo = serviceProvider.GetService<IMyService>();

     Debug.Assert(instanceOne != instanceTwo);
}

در این صورت نمونه ها یکسان نیستند و هر بار یک نمونه جدیدی ایجاد می شود اگر نمونه ای سرویس را به صورت دستی و بدون استفاده از DI ایجاد می کنید طول عمر (Lifetime) Transient به یک افت خواهد رسید .

درباره استفاده از GetService در مقاله مربوط به تفاوت بین <GetService<T و <GetRequiredService<T صحبت کردیم .

معرفی Singleton Lifetime

نمونه های Singleton در تمام طول برنامه دوام خواهند داشت و به این معنی است که پس از درخواست اولیه در درخواست های بعدی از همان نمونه استفاده می شود (بنابراین اگر دو کاربر به سایت شما درخواست دهند از همان نمونه استفاده می شود)

public void ConfigureServices(IServiceCollection services)
{
     services.AddSingleton<IMyService, MyService>();

     var serviceProvider = services.BuildServiceProvider();

     var instanceOne = serviceProvider.GetService<IMyService>();
     var instanceTwo = serviceProvider.GetService<IMyService>();

     Debug.Assert(instanceOne != instanceTwo);
}

ما اکنون سرویس خود را به صورت Singleton اضافه میکنیم و در خط آخر با Debug کد ما تخریب می شود زیرا این دو نمونه یکسان هستند .

حالا چرا این را میخواهید ؟

در اکثر مواقع استفاده از داده ها در یک کلاس در چندین درخواست استفاده می شود زیرا Singleton وضعیت (State) را در طول برنامه نگه می دارد . و با استفاده از آن می توانیم در هر درخواست از همان نمونه استفاده کنیم .

استفاده از Transient داخل Singleton

public interface IMyTransientService { }
public class MyTransientService : IMyTransientService
{
}
public interface IMySingletonService
{
	IMyTransientService _myTransientService { get; }
}
public class MySingletonService : IMySingletonService
{
	//Only public in this example. Would normally be private, readonly etc. 
	public IMyTransientService _myTransientService { get; }

	public MySingletonService(IMyTransientService myTransientService)
	{
		_myTransientService = myTransientService;
	}
}

بنابراین ما دو سرویس داریم ، یکی به نام MySingletonService و دیگری به نام MyTransientService در داخل آن .

رجیستر کردن سرویس را به صورت زیر انجام میدهیم :

public void ConfigureServices(IServiceCollection services)
{
    services.AddSingleton<IMySingletonService, MySingletonService>();
    services.AddTransient<IMyTransientService, MyTransientService>();
    var serviceProvider = services.BuildServiceProvider();
    var singletonOne = serviceProvider.GetService<IMySingletonService>();
    var singletonTwo = serviceProvider.GetService<IMySingletonService>();

    Debug.Assert(singletonOne == singletonTwo);
    Debug.Assert(singletonTwo._myTransientService != singletonTwo._myTransientService);
}

حالا میخواهیم بفهمیم DI چگونه عمل می کند :

 Transient در هر بار خواست نمونه جدید را از سرویس ایجاد می کند وقتی برای اولین بار نمونه ای از کلاس والد (Parent) را به عنوان Singleton درخواست میکنیم نمونه ای (instance) از آن و تمام وابستگی های (Dependencies) خود را ایجاد میکند (منظور کلاس Transient است) .

بار دوم که ما از کلاس Singleton درخواست میکنیم . این قبلا برای ما ایجاد شده است و نمی تواند وابستگی به وجود بیاد بلکه به نمونه (Instance) اول برمیگردد .

این موضوع در مورد نوع دیگر طول عمر (Lifetime) مانند Scoped درون Singleton نیز صادق است .

این بدین معناست که تمام کلاس هایی که باید ماهیت Transient داشته باشند بعدا به یک نمونه واحد (Singleton Instance) تبدیل و بین درخواست ها انتقال پیدا میکنند .

معرفی Scoped Lifetime

در Scoped اشیاء غالبا به یک نمونه در هر درخواست وب منجر می شود بنابراین میتوانید در هر درخواست از شی از Scoped استفاده کنید

یکی دیگر از کاربرد های بسیار متدوال برای اشیاء مربوط به طول عمر (Lifetime) Scoped زمانی است که میخواهید یک Per request cache ایجاد کنید .

طول عمر Scoped به این معنی است که در یک Scoped ایجاد شده ، اشیاء همان نمونه خواهند بود . و شما میتوانید Scoped را به صورت دستی ایجاد کنید :

public void ConfigureServices(IServiceCollection services)
{
	services.AddScoped<IMyScopedService, MyScopedService>();

	var serviceProvider = services.BuildServiceProvider();

	var serviceScopeFactory = serviceProvider.GetRequiredService<IServiceScopeFactory>();

	IMyScopedService scopedOne;
	IMyScopedService scopedTwo;

	using (var scope = serviceScopeFactory.CreateScope())
	{
		scopedOne = scope.ServiceProvider.GetService<IMyScopedService>();
	}

	using (var scope = serviceScopeFactory.CreateScope())
	{
		scopedTwo = scope.ServiceProvider.GetService<IMyScopedService>();
	}


	Debug.Assert(scopedOne != scopedTwo);
}

در این مثال دو شی ایجاد شده در هر Scope یکسان نیستند زیرا هر شی را در Scope خود ایجاده کرده ایم .