همانطور که در
قسمت قبل نیز بررسی کردیم، ASP.NET Core امکان تزریق وابستگیهای متداول را در اکثر قسمتهای آن مانند کنترلرها، میانافزارها و غیره، میسر و پیش بینی کردهاست؛ اما همیشه و در تمام مکانهای یک برنامهی وب، امکان تزریق وابستگیها در سازندهی کلاسها وجود ندارد و مجبور به استفادهی از الگوی Service Locator میباشیم. در این قسمت این مکانهای ویژه را بررسی خواهیم کرد.
HttpContext و امکان دسترسی به Service Locatorها
در ASP.NET Core هر جائیکه دسترسی به HttpContext وجود داشته باشد، میتوان از الگوی Service Locator نیز توسط خاصیت HttpContext.RequestServices آن استفاده کرد. این خاصیت از نوع IServiceProvider قرار گرفتهی در فضای نام System است که در
قسمت دوم آنرا بررسی کردیم. توسط این اینترفیس به متد object GetService(Type serviceType) دسترسی خواهیم یافت و برای کار با نگارشهای جنریک آن نیاز است فضای نام Microsoft.Extensions.DependencyInjection را مورد استفاده قرار داد:
using Microsoft.Extensions.DependencyInjection;
namespace CoreIocSample02.Controllers
{
public class HomeController : Controller
{
public IActionResult Privacy()
{
var myDisposableService = this.HttpContext.RequestServices.GetRequiredService<IMyDisposableService>();
myDisposableService.Run();
return View();
}
}
}
در اینجا یک نمونه مثال را از کار با HttpContext.RequestServices، در یک اکشن متد ملاحظه میکنید.
استفاده از Service Locatorها در فیلترها
هرچند استفادهی از this.HttpContext.RequestServices در یک اکشن متد که کنترلر آن تزریق وابستگیهای در سازندهی کلاس را به صورت توکار پشتیبانی میکند، مزیت خاصی را به همراه ندارد و توصیه نمیشود، اما در انتهای
قسمت قبل، امکان تزریق وابستگیهای متداول در فیلترها را نیز بررسی کردیم. زمانیکه کار تزریق وابستگیها در سازندهی یک فیلتر صورت میگیرد، دیگر نمیتوان ApiExceptionFilter را به نحو متداول [ApiExceptionFilter] فراخوانی کرد؛ چون پارامترهای سازندهی آن جزو ثوابت قابل کامپایل نیستند و کامپایلر سیشارپ چنین اجازهای را نمیدهد. به همین جهت مجبور به استفادهی از [ServiceFilter(typeof(ApiExceptionFilter))] برای معرفی یک چنین فیلترهایی هستیم. اما میتوان این وضعیت را با استفاده از الگوی Service Locator بهبود بخشید. اینبار بجای تعریف وابستگیها در سازندهی یک فیلتر:
public class ApiExceptionFilter : ExceptionFilterAttribute
{
private ILogger<ApiExceptionFilter> _logger;
private IHostingEnvironment _environment;
private IConfiguration _configuration;
public ApiExceptionFilter(IHostingEnvironment environment, IConfiguration configuration, ILogger<ApiExceptionFilter> logger)
{
_environment = environment;
_configuration = configuration;
_logger = logger;
}
میتوان آنها را به صورت زیر نیز دریافت کرد:
using Microsoft.Extensions.DependencyInjection;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.Logging;
namespace Filters
{
public class ApiExceptionFilter : ExceptionFilterAttribute
{
public override void OnException(ExceptionContext context)
{
var logger = context.HttpContext.RequestServices.GetRequiredService<ILogger<ApiExceptionFilter>>();
logger.LogError(context.Exception, context.Exception.Message);
base.OnException(context);
}
}
}
در اینجا برای مثال سرویس ILogger توسط context.HttpContext.RequestServices قابل دسترسی شدهاست. به این ترتیب با حذف پارامترهای سازندهی این کلاس فیلتر که به صورت ثوابت زمان کامپایل قابل تعریف نیستند، امکان استفادهی از آن به صورت متداول [ApiExceptionFilter] میسر میشود.
استفاده از Service Locatorها در ValidationAttributes
روش تزریق وابستگیها در سازندهی کلاسهای ValidationAttribute مهیا نیست و امکانی مانند ServiceFilterها در اینجا کار نمیکند. به همین جهت تنها روشی که برای دسترسی به سرویسها باقی میماند استفاده از الگوی Service Locator است که مثالی از آنرا در کدهای زیر از طریق ValidationContext مشاهده میکنید:
using Microsoft.Extensions.DependencyInjection;
using System.ComponentModel.DataAnnotations;
using CoreIocServices;
namespace Test
{
public class CustomValidationAttribute : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var service = validationContext.GetRequiredService<IMyDisposableService>();
// use service
// ... validation logic
}
}
}
استفاده از Service Locatorها در متد Main کلاس Program
فرض کنید سرویسی را در متد ConfigureServices کلاس Startup یک برنامهی وب ثبت کردهاید:
namespace Test
{
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<ITokenGenerator, TokenGenerator>();
}
برای استفادهی از این سرویس در متد Main کلاس Program میتوان به صورت زیر عمل کرد:
namespace Test
{
public class Program
{
public static void Main(string[] args)
{
IWebHost webHost = CreateWebHostBuilder(args).Build();
var tokenGenerator = webHost.Services.GetRequiredService<ITokenGenerator>();
string token = tokenGenerator.GetToken();
System.Console.WriteLine(token);
webHost.Run();
}
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>();
}
}
متد Build در اینجا، یک وهلهی از نوع IWebHost را بازگشت میدهد. یکی از خواص این اینترفیس نیز Services از نوع IServiceProvider است:
namespace Microsoft.AspNetCore.Hosting
{
public interface IWebHost : IDisposable
{
IServiceProvider Services { get; }
}
}
زمانیکه به IServiceProvider دسترسی داشته باشیم، میتوان از متدهای GetRequiredService و یا GetService آن که در
قسمت دوم، تفاوتهای آنها را بررسی کردیم، استفاده کرد و به وهلههای سرویسهای مدنظر دسترسی یافت.
استفاده از Service Locatorها در متد ConfigureServices کلاس Startup
برای دسترسی به سرویسهای برنامه در متد ConfigureServices میتوان متد BuildServiceProvider را بر روی پارامتر services فراخوانی کرد. خروجی آن از نوع کلاس ServiceProvider است که امکان دسترسی به متدهایی مانند GetRequiredService را میسر میکند:
namespace CoreIocSample02
{
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
var scopeFactory = services.BuildServiceProvider().GetRequiredService<IServiceScopeFactory>();
using (var scope = scopeFactory.CreateScope())
{
var provider = scope.ServiceProvider;
using (var dbContext = provider.GetRequiredService<ApplicationDbContext>())
{
// ...
}
}
}
در بسیاری از موارد، کار با GetRequiredService کافی است و مرحلهی بعدی هم ندارد. اما اگر سرویس شما دارای طول عمر از نوع Scoped و همچنین IDispoable نیز بود، همانطور که در نکتهی «روش صحیح Dispose اشیایی با طول عمر Scoped، در خارج از طول عمر یک درخواست ASP.NET Core»
قسمت سوم عنوان شد، نیاز است یک Scope صریح را برای آن ایجاد و سپس آنرا به نحو صحیحی Dispose کرد که روش آنرا در مثال فوق ملاحظه میکنید.
استفاده از Service Locatorها در متد Configure کلاس Startup
در
قسمت قبل عنوان شد که میتوان سرویسهای مدنظر خود را به صورت پارامترهایی جدید به متد Configure اضافه کرد و کار وهله سازی آنها توسط Service Provider برنامه به صورت خودکار صورت میگیرد:
public class Startup
{
public void ConfigureServices(IServiceCollection services) { }
public void Configure(IApplicationBuilder app, IAmACustomService customService)
{
// ....
}
}
در اینجا روش دومی نیز وجود دارد. میتوان از پارامتر app نیز به صورت Service Locator استفاده کرد:
namespace CoreIocSample02
{
public class Startup
{
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
var scopeFactory = app.ApplicationServices.GetRequiredService<IServiceScopeFactory>();
using (var scope = scopeFactory.CreateScope())
{
var provider = scope.ServiceProvider;
using (var dbContext = provider.GetRequiredService<ApplicationDbContext>())
{
//...
}
}
خاصیت app.ApplicationServices از نوع IServiceProvider است و مابقی نکات آن با توضیحات «استفاده از Service Locatorها در متد ConfigureServices کلاس Startup» مطلب جاری دقیقا یکی است.