تزریق خودکار وابستگیها در برنامههای ASP.NET Web forms
آشنایی با مدل برنامه نویسی TAP
دات نت فریم ورک، از زمان ارائه نگارش یک آن، از اعمال غیرهمزمان و API خاص آن پشتیبانی میکردهاست. همچنین این مورد یکی از ویژگیهای Win32 نیز میباشد. نوشتن کدهای همزمان متداول بسیار ساده است. در این نوع کدها هر عملیات خاص، پس از پایان عملیات قبلی انجام میشود.
public string TestNoneAsync() { var webClient = new WebClient(); return webClient.DownloadString("http://www.google.com"); }
این مورد همچنین در برنامههای سمت سرور نیز حائز اهمیت است. با قفل شدن تعداد زیادی ترد در حال اجرا، عملا قدرت پاسخدهی سرور نیز کاهش مییابد. بنابراین در این نوع موارد، برنامههای چند ریسمانی هرچند در سمت کلاینت ممکن است مفید واقع شوند و برای مثال ترد UI را آزاد کنند، اما اثر آنچنانی بر روی برنامههای سمت سرور ندارند. زیرا در آنها میتوان هزاران ترد را ایجاد کرد که همگی دارای کدهای اصطلاحا blocking باشند. برای حل این مساله استفاده از API غیرهمزمان توصیه میشود.
برای نمونه کلاس WebClient توکار دات نت، دارای متدی به نام DownloadStringAsync نیز میباشد. این متد به محض فراخوانی، ترد جاری را آزاد میکند. به این معنا که فراخوانی آن سبب توقف ترد جاری برای دریافت نتیجهی دریافت اطلاعات از وب نمیشود. به این نوع API، یک Asynchronous API گفته میشود؛ زیرا با سایر کدهای نوشته شده، هماهنگ و همزمان اجرا نمیشود.
هر چند این کد جدید مشکل عدم پاسخ دهی برنامه را برطرف میکند، اما مشکل دیگری را به همراه دارد؛ چگونه باید حاصل عملیات آنرا پس از پایان کار دریافت کرد؟ چگونه باید خطاها و مشکلات احتمالی را مدیریت کرد؟
برای مدیریت این مساله، رخدادی به نام DownloadStringCompleted تعریف شدهاست. روال رویدادگردان آن پس از پایان کار دریافت اطلاعات از وب، فراخوانی میگردد.
public void TestAsync() { var webClient = new WebClient(); webClient.DownloadStringAsync(new Uri("http://www.google.com")); webClient.DownloadStringCompleted += webClientDownloadStringCompleted; } void webClientDownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e) { // use e.Result }
مشکل! ما سادگی یک عملیات همزمان را از دست دادیم. متد TestNoneAsync از لحاظ پیاده سازی و همچنین خواندن و نگهداری آن در طول زمان، بسیار سادهتر است از نمونهی TestAsync نوشته شده. در کدهای غیرهمزمان فوق، یک متد ساده، به دو متد مجزا خرد شدهاست و نتیجهی نهایی، درون یک روال رخدادگردان بدست میآید.
به این مدل، EAP یا Event based asynchronous pattern نیز گفته میشود. EAP در دات نت 2 معرفی شد. روالهای رخدادگردان در این حالت، در ترد اصلی برنامه اجرا میشوند. اما اگر به حالت اصلی اعمال غیرهمزمان موجود از دات نت یک کوچ کنیم، اینطور نیست. در WinForms و WPF برای به روز رسانی رابط کاربری نیاز است اطلاعات دریافت شده در همان تردی که رابط کاربری ایجاد شده است، تحویل گرفته شده و استفاده شوند. در غیراینصورت استثنایی صادر شده و برنامه خاتمه مییابد.
آشنایی با Synchronization Context
ابتدا یک برنامهی WinForms ساده را آغاز کرده و یک دکمهی جدید را به نام btnGetInfo و یک تکست باکس را به نام txtResults، به آن اضافه کنید. سپس کدهای فرم اصلی آنرا به نحو ذیل تغییر دهید:
using System; using System.Linq; using System.Net; using System.Windows.Forms; namespace Async02 { public partial class Form1 : Form { public Form1() { InitializeComponent(); } private void btnGetInfo_Click(object sender, EventArgs e) { var req = (HttpWebRequest)WebRequest.Create("http://www.google.com"); req.Method = "HEAD"; req.BeginGetResponse( asyncResult => { var resp = (HttpWebResponse)req.EndGetResponse(asyncResult); var headersText = formatHeaders(resp.Headers); txtResults.Text = headersText; }, null); } private string formatHeaders(WebHeaderCollection headers) { var headerString = headers.Keys.Cast<string>() .Select(header => string.Format("{0}:{1}", header, headers[header])); return string.Join(Environment.NewLine, headerString.ToArray()); } } }
همچنین در این مثال از متد غیرهمزمان BeginGetResponse نیز استفاده شدهاست. در این نوع API خاص، کار با BeginGetResponse آغاز شده و سپس در callback نهایی توسط EndGetResponse، نتیجهی عملیات به دست میآید.
اگر برنامه را اجرا کنید، با استثنای زیر مواجه خواهید شد:
An exception of type 'System.InvalidOperationException' occurred in System.Windows.Forms.dll but was not handled in user code Additional information: Cross-thread operation not valid: Control 'txtResults' accessed from a thread other than the thread it was created on.
private void btnGetInfo_Click(object sender, EventArgs e) { var sync = SynchronizationContext.Current; var req = (HttpWebRequest)WebRequest.Create("http://www.google.com"); req.Method = "HEAD"; req.BeginGetResponse( asyncResult => { var resp = (HttpWebResponse)req.EndGetResponse(asyncResult); var headersText = formatHeaders(resp.Headers); sync.Post(delegate { txtResults.Text = headersText; }, null); }, null); }
برای درک بهتر آن، سه break point را پیش از متد BeginGetResponse، داخل Async calback و داخل delegate متد Post قرار دهید. پس از اجرای برنامه، از منوی دیباگ در VS.NET گزینهی Windows و سپس Threads را انتخاب کنید.
در اینجا همانطور که مشخص است، کد داخل delegate تعریف شده، در ترد اصلی برنامه اجرا میشود و نه یکی از Worker threadهای ثانویه.
هر چند استفاده از متدهای تو در تو و lambda syntax، نیاز به تعریف چندین متد جداگانه را برطرف کردهاست، اما باز هم کد سادهای به نظر نمیرسد. در سی شارپ 5، برای مدیریت بهتر تمام مشکلات یاد شده، پشتیبانی توکاری از اعمال غیرهمزمان، به هستهی زبان اضافه شدهاست.
Syntax ابتدایی یک متد Async
در ابتدا کلاس و متد Async زیر را در نظر بگیرید:
using System; using System.Threading.Tasks; namespace Async01 { public class AsyncExample { public async Task DoWorkAsync(int parameter) { await Task.Delay(parameter); Console.WriteLine(parameter); } } }
- در مدل برنامه نویسی TAP، متدهای غیرهمزمان باید یک Task را بازگشت دهند؛ یا نمونهی جنریک آنرا. البته کامپایلر، async void را نیز پشتیبانی میکند ولی در قسمتهای بعدی بررسی خواهیم کرد که چرا استفاده از آن مشکلزا است و باید از آن پرهیز شود.
- همچنین مطابق TAP، اینگونه متدها باید به پسوند Async ختم شوند تا استفاده کننده در حین کار با Intellisense، بتواند آنها را از متدهای معمولی سریعتر تشخیص دهد.
- از واژهی کلیدی async نیز استفاده میگردد تا کامپایلر از وجود اعمال غیر همزمان مطلع گردد.
- await به کامپایلر میگوید، عبارت پس از من، یک وظیفهی غیرهمزمان است و ادامهی کدهای نوشته شده، تنها زمانی باید اجرا شوند که عملیات غیرهمزمان معرفی شده، تکمیل گردد.
در متد DoWorkAsync، ابتدا به اندازهای مشخص توقف حاصل شده و سپس سطر بعدی یعنی Console.WriteLine اجرا میشود.
یک اشتباه عمومی! استفاده از واژههای کلیدی async و await متد شما را async نمیکنند.
برخلاف تصور ابتدایی از بکارگیری واژههای کلیدی async و await، این کلمات نحوهی اجرای متد شما را async نمیکنند. این کلمات صرفا برای تشکیل متدهایی که هم اکنون غیرهمزمان هستند، مفید میباشند. برای توضیح بیشتر آن به مثال ذیل دقت کنید:
public async Task<double> GetNumberAsync() { var generator = new Random(); await Task.Delay(generator.Next(1000)); return generator.NextDouble(); }
در ادامه برای استفاده از آن خواهیم داشت:
public async Task<double> GetSumAsync() { var leftOperand = await GetNumberAsync(); var rightOperand = await GetNumberAsync(); return leftOperand + rightOperand; }
در کدهای همزمان متداول، سطر اول ابتدا انجام میشود و بعد سطر دوم و الی آخر. با استفاده از واژهی کلیدی await یک چنین عملکردی را با اعمال غیرهمزمان خواهیم داشت. پیش از این برای مدیریت اینگونه اعمال از یک سری callback و یا رخداد استفاده میشد. برای مثال ابتدا عملیات همزمانی شروع شده و سپس نتیجهی آن در یک روال رخداد گردان جایی در کدهای برنامه دریافت میشد (مانند مثال ابتدای بحث). اکنون تصور کنید که قصد داشتید جمع نهایی حاصل دو عملیات غیرهمزمان را از دو روال رخدادگردان جدا از هم، جمع آوری کرده و بازگشت دهید. هرچند اینکار غیرممکن نیست، اما حاصل کار به طور قطع آنچنان زیبا نبوده و قابلیت نگهداری پایینی دارد. واژهی کلیدی await، انجام اینگونه امور غیرهمزمان را طبیعی و همزمان جلوه میدهد. به این ترتیب بهتر میتوان بر روی منطق و الگوریتمهای مورد استفاده تمرکز داشت، تا اینکه مدام درگیر مکانیک اعمال غیرهمزمان بود.
امکان استفاده از واژهی کلیدی await در هر جایی از کدها وجود دارد. برای نمونه در مثال زیر، برای ترکیب دو عملیات غیرهمزمان، از await در حین تشکیل عملیات ضرب نهایی، دقیقا در جایی که مقدار متد باید بازگشت داده شود، استفاده شدهاست:
public async Task<double> GetProductOfSumAsync() { var leftOperand = GetSumAsync(); var rightOperand = GetSumAsync(); return await leftOperand * await rightOperand; }
Operator '*' cannot be applied to operands of type 'System.Threading.Tasks.Task<double>' and 'System.Threading.Tasks.Task<double>'
اگر متد DownloadString همزمان ابتدای بحث را نیز بخواهیم تبدیل به نمونهی async سیشارپ 5 کنیم، میتوان از متد الحاقی جدید آن به نام DownloadStringTaskAsync کمک گرفت:
public async Task<string> DownloadAsync() { var webClient = new WebClient(); return await webClient.DownloadStringTaskAsync("http://www.google.com"); }
سؤال: آیا استفاده از await نیز ترد جاری را قفل میکند؟
اگر به کدها دقت کنید، استفاده از await به معنای صبر کردن تا پایان عملیات async است. پس اینطور به نظر میرسد که در اینجا نیز ترد اصلی، همانند قبل قفل شدهاست.
public void TestDownloadAsync() { Debug.WriteLine("Before DownloadAsync"); DownloadAsync(); Debug.WriteLine("After DownloadAsync"); }
Before DownloadAsync After DownloadAsync
برنامههای Async و نگارشهای مختلف دات نت
شاید در ابتدا به نظر برسد که قابلیتهای جدید async و await صرفا متعلق هستند به دات نت 4.5 به بعد؛ اما خیر. اگر کامپایلری را داشته باشید که از این واژههای کلیدی را پشتیبانی کند، امکان استفاده از آنها را با دات نت 4 نیز خواهید داشت. برای این منظور تنها کافی است از VS 2012 به بعد استفاده نمائید. سپس در کنسول پاورشل نیوگت دستور ذیل را اجرا نمائید (فقط برای برنامههای دات نت 4 البته):
PM> Install-Package Microsoft.Bcl.Async
EF Code First #12
سناریویی را در نظر بگیرید که یک برنامه وب نوشته شده، قرار است به چندین مستاجر (مشتری یا tenant) خدماتی را ارائه کند. در این حالت اطلاعات هر مشتری به صورت کاملا جدا شده از دیگر مشتریان در سیستم قرار دارد و فقط به همان قسمتها دسترسی دارد.
مثلا یک برنامه مدیریت رستوران را در نظر بگیرید که برای هر مشتری، در دامین
مخصوص به خود قرار دارد و همه آنها به یک سیستم متمرکز متصل شده و اطلاعات
خود را از آنجا دریافت میکنند.
در معماری Multi-Tenancy، چندین کاربر میتوانند از یک نمونه (Single
Instance) از اپلیکیشن نرمافزاری استفاده کنند. یعنی این نمونه روی سرور
اجرا میشود و به چندین کاربر سرویس میدهد. هر کاربر را یک Tenant
مینامیم. میتوان به Tenantها امکان تغییر و شخصیسازی بخشی از اپلیکیشن
را داد؛ مثلا امکان تغییر رنگ رابط کاربری و یا قوانین کسبوکار، اما آنها نمیتوانند
کدهای اپلیکیشن را شخصیسازی کنند.
خوشبختانه اوضاع با وجود OWIN بهتر شده و ما در این مطلب قصد استفاده از یک تولکیت را به نام SaasKit، برای پیاده سازی این معماری در ASP.NET Core داریم. هدف از این toolkit، سادهتر کردن هر چه بیشتر ساخت برنامههای SaaS (Software as a Service) هست. با استفاده از OWIN ما قادریم که بدون در نظر گرفتن فریم ورک مورد استفاده، رفتار مورد نظر خودمان را مستقیما در یک چرخه درخواست HTTP پیاده سازی کنیم و البته به لطف طراحی خاص ASP.NET Core 1.0 و استفاده از میان افزارهایی مشابه OWIN در برنامه، کار ما با SaasKit باز هم راحتتر خواهد بود.
شروع کار
یک پروژه ASP.NET Core جدید را ایجاد کنید و سپس ارجاعی را به فضای نام SaasKit.Multitenancy (موجود در Nuget) بدهید.PM> Install-Package SaasKit.Multitenancy
شناسایی مستاجر (tenant)
اولین جنبه در معماری multi-tenant، شناسایی مستاجر بر اساس اطلاعات درخواست جاری میباشد که میتواند از hostname ، کاربر جاری یا یک HTTP header باشد.ابتدا به تعریف کلاس مستاجر میپردازیم:
public class AppTenant { public string Name { get; set; } public string[] Hostnames { get; set; } }
public class AppTenantResolver : ITenantResolver<AppTenant> { IEnumerable<AppTenant> tenants = new List<AppTenant>(new[] { new AppTenant { Name = "Tenant 1", Hostnames = new[] { "localhost:6000", "localhost:6001" } }, new AppTenant { Name = "Tenant 2", Hostnames = new[] { "localhost:6002" } } }); public async Task<TenantContext<AppTenant>> ResolveAsync(HttpContext context) { TenantContext<AppTenant> tenantContext = null; var tenant = tenants.FirstOrDefault(t => t.Hostnames.Any(h => h.Equals(context.Request.Host.Value.ToLower()))); if (tenant != null) { tenantContext = new TenantContext<AppTenant>(tenant); } return tenantContext; } }
سیم کشی کردن
بعد از پیاده سازی این اینترفیس نوبت به سیم کشیهای SaasKit میرسد. من در اینجا سعی کردم که مثل الگوی برنامههای ASP.NET Core عمل کنم. ابتدا نیاز داریم که وابستگیهای SaasKit را ثبت کنیم. فایل startups.cs را باز کنید و کدهای زیر را در متد ConfigureServices اضافه نمایید:public void ConfigureServices(IServiceCollection services) { services.AddMultitenancy<AppTenant, AppTenantResolver>(); }
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) { // after .UseStaticFiles() app.UseMultitenancy<AppTenant>(); // before .UseMvc() }
دریافت مستاجر جاری
حالا هر جا که نیاز به وهلهای از شیء مستاجر جاری داشتید، میتوانید به روش زیر عمل کنید:public class HomeController : Controller { private AppTenant tenant; public HomeController(AppTenant tenant) { this.tenant = tenant; } }
@inject AppTenant Tenant;
<a asp-controller="Home" asp-action="Index">@Tenant.Name</a>
اجرای نمونه مثال
فایل project.json را باز کنید و مقدار web را به شکل زیر مقدار دهی کنید: (در اینجا برای سایت خود 3 آدرس را نگاشت کردیم)"commands": { "web": "Microsoft.AspNet.Server.Kestrel --server.urls=http://localhost:6000;http://localhost:6001;http://localhost:6002", },
dotnet run
و اگر آدرس http://localhost:6002 را وارد کنیم، مستاجر 2 را مشاهده میکنیم:
قابل پیکربندی کردن مستاجر ها
از آنجائیکه نوشتن مشخصات مستاجرها در کد زیاد جالب نیست، برای همین
تصمیم داریم که این مشخصات را با استفاده از قابلیتهای ASP.NET Core از
فایل appsettings.json دریافت کنیم. تنظیمات مستاجرها را مطابق اطلاعات زیر به این فایل اضافه کنید:
"Multitenancy": { "Tenants": [ { "Name": "Tenant 1", "Hostnames": [ "localhost:6000", "localhost:6001" ] }, { "Name": "Tenant 2", "Hostnames": [ "localhost:6002" ] } ] }
public class MultitenancyOptions { public Collection<AppTenant> Tenants { get; set; } }
services.Configure<MultitenancyOptions>(Configuration.GetSection("Multitenancy"));
public class AppTenantResolver : ITenantResolver<AppTenant> { private readonly IEnumerable<AppTenant> tenants; public AppTenantResolver(IOptions<MultitenancyOptions> options) { this.tenants = options.Value.Tenants; } public async Task<TenantContext<AppTenant>> ResolveAsync(HttpContext context) { TenantContext<AppTenant> tenantContext = null; var tenant = tenants.FirstOrDefault(t => t.Hostnames.Any(h => h.Equals(context.Request.Host.Value.ToLower()))); if (tenant != null) { tenantContext = new TenantContext<AppTenant>(tenant); } return Task.FromResult(tenantContext); } }
در آخر
اولین قدم در پیاده سازی یک معماری multi-tenant، تصمیم گیری درباره این
موضوع است که شما چطور مستاجر خود را شناسایی کنید. به محض این شناسایی شما میتوانید عملیاتهای بعدی خود را مثل تفکیک بخشی از برنامه، فیلتر کردن دادهای، نمایش یک view خاص برای هر مستاجر و یا بازنویسی قسمتهای مختلف
برنامه بر اساس هر مستاجر، انجام دهید.
_ سورس مثال بالا در گیت هاب قابل دریافت میباشد.
_ منبع: اینجا
نکته: آشنایی با مفاهیم MEF برای درک بهتر مطالب الزامی است.
*در صورتی که با مفاهیم MEF آشنایی ندارید میتوانید از اینجا شروع کنید.
در MEF سه نوع PartCreationPolicy وجود دارد:
#1 Shared : آبجکت مورد نظر فقط یک بار در کل طول عمر Composition Container ساخته میشود.(Singleton)
#2 NonShared : آبجکت مورد نظر به ازای هر درخواست دوباره نمونه سازی میشود.
#3 Any : از حالت پیش فرض CompositionContainar برای نمونه سازی استفاده میشود که همان مورد اول است(Shared)
در اکثر پروژهها ساخت نمونه اشیا به صورت Singleton میسر نیست و باعث اشکال در پروژه میشود. برای حل این مشکل باید PartCreationPolicy رو برای هر شی مجزا تعریف کنیم. برای مثال
[Export] [PartCreationPolicy( CreationPolicy.NonShared )] internal class ShellViewModel : ViewModel<IShellView> { private readonly DelegateCommand exitCommand; [ImportingConstructor] public ShellViewModel( IShellView view ) : base( view ) { exitCommand = new DelegateCommand( Close ); } }
برای حل این مسئله بهتر است که رفتار Composition Container رو در هنگام نمونه سازی تغییر دهیم. یعنی آبجکتها به صورت پیش فرض به صورت NonShared تولید شوند و در صورت نیاز به نمونه Shared این Attribute رو در کلاس مورد نظر استفاده کنیم. کافیست از کلاس Composition Container که قلب MEF محسوب میشود ارث برده و رفتار مورد نظر را Override کنیم. برای نمونه :
public class CustomCompositionContainer : CompositionContainer { public CustomCompositionContainer(ComposablePartCatalog catalog) : base(catalog) { } protected override IEnumerable<Export> GetExportsCore(ImportDefinition definition) { definition = AdaptDefinition(definition); return base.GetExportsCore(definition); } private ImportDefinition AdaptDefinition(ImportDefinition definition) { ContractBasedImportDefinition namedDefinition = definition as ContractBasedImportDefinition; if (namedDefinition != null && namedDefinition.RequiredCreationPolicy == CreationPolicy.Any) { definition = new ContractBasedImportDefinition(namedDefinition.ContractName, namedDefinition.RequiredMetadata, namedDefinition.Cardinality, namedDefinition.IsRecomposable, namedDefinition.IsPrerequisite, CreationPolicy.NonShared); } return definition; } }
حال فقط کافیست در پروژه به جای استفاده از CompositionContainer از CustomCompositionContainer استفاده کنیم.
«بررسی مفاهیم معکوس سازی وابستگیها و ابزارهای مرتبط با آن»
«اصول طراحی SOLID»
«مطالعهی بیشتر»
تزریق وابستگیها (یا Dependency injection = DI) به معنای ارسال نمونهای/وهلهای از وابستگی (یک سرویس) به شیء وابستهی به آن (یک کلاینت) است. در فرآیند تزریق وابستگیها، یک کلاس، وهلههای کلاسهای دیگر مورد نیاز خودش را بجای وهله سازی مستقیم، از یک تزریق کننده دریافت میکند. بنابراین بجای نوشتن newها در کلاس جاری، آنها را به صورت وابستگیهایی در سازندهی کلاس تعریف میکنیم تا توسط یک IoC Container تامین شوند. در اینجا به فریم ورکهایی که کار وهله سازی این وابستگیها را انجام میدهند، IoC Container و یا DI container میگوییم (IoC = inversion of control ).
چندین نوع تزریق وابستگیها وجود دارند که دو حالت زیر، عمومیترین آنها است:
الف) تزریق در سازندهی کلاس: لیست وابستگیهای یک کلاس، به عنوان پارامترهای سازندهی آن ذکر میشوند.
ب) تزریق در خواص یا Setter injection: کلاینت خواصی get و set را به صورت public معرفی میکند و سپس IoC Container با وهله سازی آنها، وابستگیهای مورد نیاز را تامین خواهد کرد.
تزریق وابستگیها در ASP.NET Core
برخلاف نگارشهای قبلی ASP.NET، این نگارش جدید از ابتدا با دید پشتیبانی کامل از DI طراحی شدهاست و این مفهوم، در سراسر اجزای آن به صورت یکپارچهای پشتیبانی میشود. همچنین به همراه یک minimalistic DI container توکار نیز هست .
این IoC Container توکار از 4 حالت طول عمر ذیل پشتیبانی میکند:
- instance: در هربار نیاز به یک وابستگی خاص، تنها یک وهله از آن در اختیار مصرف کننده قرار میگیرد و در اینجا شما هستید که مسئول تعریف نحوهی وهله سازی این شیء خواهید بود (برای بار اول).
- transient: هربار که نیاز به وابستگی خاصی بود، یک وهلهی جدید از آن توسط IoC Container تولید و ارائه میشود.
- singleton: در این حالت تنها یک وهله از وابستگی درخواست شده در طول عمر برنامه تامین میشود.
- scoped: در طول عمر یک scope خاص، تنها یک وهله از وابستگی درخواست شده، در اختیار مصرف کنندهها قرار میگیرد. برای مثال مرسوم است که به ازای یک درخواست وب، تنها یک وهله از شیءایی خاص در اختیار تمام مصرف کنندههای آن قرار گیرد (single instance per web request).
طول عمر singleton، برای سرویسها و کلاسهای config مناسب هستند. به این ترتیب به کارآیی بالاتری خواهیم رسید و دیگر نیازی نخواهد بود تا هر بار این اطلاعات خوانده شوند. حالت scoped برای وهله سازی الگوی واحد کار و پیاده سازی تراکنشها مناسب است. برای مثال در طی یک درخواست وب، یک تراکنش باید صورت گیرد.
حالت scoped در حقیقت نوع خاصی از حالت transient است. در حالت transient صرفنظر از هر حالتی، هربار که وابستگی ویژهای درخواست شود، یک وهلهی جدید از آن تولید خواهد شد. اما در حالت scoped فقط یک وهلهی از وابستگی مورد نظر، در بین تمام اشیاء وابستهی به آن، در طول عمر آن scope تولید میشود.
بنابراین در برنامههای وب دو نوع singleton برای معرفی کلاسهای config و نوع scoped برای پیاده سازی تراکنشها و همچنین بالابردن کارآیی برنامه در طی یک درخواست وب (با عدم وهله سازی بیش از اندازهی از کلاسهای مختلف مورد نیاز)، بیشتر از همه به کار برده میشوند.
یک مثال کاربردی: بررسی نحوهی تزریق یک سرویس سفارشی به کمک IoC Container توکار ASP.NET Core
مثال جاری که بر اساس ASP.NET Core Web Application و با قالب خالی آن ایجاد شدهاست، دارای نام فرضی Core1RtmEmptyTest است. در همین پروژه بر روی پوشهی src، کلیک راست کرده و گزینهی Add new project را انتخاب کنید و سپس یک پروژهی جدید از نوع NET Core -> Class library. را به آن، با نام Core1RtmEmptyTest.Services اضافه کنید (تصویر فوق).
در ادامه کلاس نمونهی سرویس پیامها را به همراه اینترفیس آن، با محتوای زیر به آن اضافه کنید:
namespace Core1RtmEmptyTest.Services { public interface IMessagesService { string GetSiteName(); } public class MessagesService : IMessagesService { public string GetSiteName() { return "DNT"; } } }
انجام اینکار معادل است با افزودن یک سطر ذیل به فایل project.json پروژه:
{ "dependencies": { // same as before "Core1RtmEmptyTest.Services": "1.0.0-*" },
public void Configure( IApplicationBuilder app, IHostingEnvironment env, IMessagesService messagesService)
public void Configure( IApplicationBuilder app, IHostingEnvironment env, IMessagesService messagesService) { app.Run(async context => { var siteName = messagesService.GetSiteName(); await context.Response.WriteAsync($"Hello {siteName}"); }); }
System.InvalidOperationException No service for type 'Core1RtmEmptyTest.Services.IMessagesService' has been registered. at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(IServiceProvider provider, Type serviceType) at Microsoft.AspNetCore.Hosting.Internal.ConfigureBuilder.Invoke(object instance, IApplicationBuilder builder) System.Exception Could not resolve a service of type 'Core1RtmEmptyTest.Services.IMessagesService' for the parameter 'messagesService' of method 'Configure' on type 'Core1RtmEmptyTest.Startup'. at Microsoft.AspNetCore.Hosting.Internal.ConfigureBuilder.Invoke(object instance, IApplicationBuilder builder) at Microsoft.AspNetCore.Hosting.Internal.WebHost.BuildApplication()
public class Startup { public void ConfigureServices(IServiceCollection services) { services.AddTransient<IMessagesService, MessagesService>(); }
پس از اینکار، مجددا برنامه را اجرا کنید. اکنون این خروجی باید مشاهده شود:
و به این معنا است که اکنون IoC Cotanier توکار ASP.NET Core، میداند زمانیکه به IMessagesService رسید، چگونه باید آنرا وهله سازی کند.
چه سرویسهایی به صورت پیش فرض در IoC Container توکار ASP.NET Core ثبت شدهاند؟
در ابتدای متد ConfigureServices یک break point را قرار داده و برنامه را در حالت دیباگ اجرا کنید:
همانطور که ملاحظه میکنید، به صورت پیش فرض 16 سرویس در اینجا ثبت شدهاند که تاکنون با دو مورد از آنها کار کردهایم.
امکان تزریق وابستگیها در همه جا!
در مثال فوق، سرویس سفارشی خود را در متد Configure کلاس آغازین برنامه تزریق کردیم. نکتهی مهم اینجا است که برخلاف نگارشهای قبلی ASP.NET MVC (یعنی بدون نیاز به تنظیمات خاصی برای قسمتهای مختلف برنامه)، میتوان این تزریقها را در کنترلرها، در میان افزارها، در فیلترها در ... همه جا و تمام اجزای ASP.NET Core 1.0 انجام داد و دیگر اینبار نیازی نیست تا نکتهی ویژهی نحوهی تزریق وابستگیها در فیلترها یا کنترلرهای ASP.NET MVC را یافته و سپس اعمال کنید. تمام اینها از روز اول کار میکنند. همینقدر که کار ثبت سرویس خود را در متد ConfigureServices انجام دادید، این سرویس در سراسر اکوسیستم ASP.NET Core، قابل دسترسی است.
نیاز به تعویض IoC Container توکار ASP.NET Core
قابلیت تزریق وابستگیهای توکار ASP.NET Core صرفا جهت برآورده کردن نیازمندیهای اصلی آن طراحی شدهاست و نه بیشتر. بنابراین توسط آن قابلیتهای پیشرفتهای را که سایر IoC Containers ارائه میدهند، نخواهید یافت. برای مثال تعویض امکانات تزریق وابستگیهای توکار ASP.NET Core با StructureMap این مزایا را به همراه خواهد داشت:
• امکان ایجاد child/nested containers (پشتیبانی از سناریوهای چند مستاجری)
• پشتیبانی از Setter Injection
• امکان انتخاب سازندهای خاص (اگر چندین سازنده تعریف شده باشند)
• سیم کشی خودکار یا Conventional "Auto" Registration (برای مثال اتصال اینترفیس IName به کلاس Name به صورت خودکار و کاهش تعداد تعاریف ابتدای برنامه)
• پشتیبانی توکار از Lazy و Func
• امکان وهله سازی از نوعهای concrete (یا همان کلاسهای معمولی)
• پشتیبانی از مفاهیمی مانند Interception و AOP
• امکان اسکن اسمبلیهای مختلف جهت یافتن اینترفیسها و اتصال خودکار آنها (طراحیهای افزونه پذیر)
روش تعویض IoC Container توکار ASP.NET Core با StructureMap
جزئیات این جایگزین کردن را در مطلب «جایگزین کردن StructureMap با سیستم توکار تزریق وابستگیها در ASP.NET Core 1.0» میتوانید مطالعه کنید.
یا میتوانید از روش فوق استفاده کنید و یا اکنون قسمتی از پروژهی رسمی استراکچرمپ در آدرس https://github.com/structuremap/structuremap.dnx جهت کار با NET Core. طراحی شدهاست. برای کار با آن نیاز است این مراحل طی شوند:
الف) دریافت بستهی نیوگت StructureMap.Dnx
برای این منظور بر روی گره references کلیک راست کرده و گزینهی manage nuget packages را انتخاب کنید. سپس در برگهی browse آن، StructureMap.Dnx را جستجو کرده و نصب نمائید (تیک مربوط به انتخاب pre releases هم باید انتخاب شده باشد):
انجام این مراحل معادل هستند با افزودن یک سطر ذیل به فایل project.json برنامه:
{ "dependencies": { // same as before "StructureMap.Dnx": "0.5.1-rc2-final" },
پس از نصب بستهی StructureMap.Dnx، به کلاس آغازین برنامه مراجعه کرده و این تغییرات را اعمال کنید:
public class Startup { public IServiceProvider ConfigureServices(IServiceCollection services) { services.AddDirectoryBrowser(); var container = new Container(); container.Configure(config => { config.Scan(_ => { _.AssemblyContainingType<IMessagesService>(); _.WithDefaultConventions(); }); //config.For<IMessagesService>().Use<MessagesService>(); config.Populate(services); }); container.Populate(services); return container.GetInstance<IServiceProvider>(); }
سپس وهلهای از IServiceProvider، توسط استراکچرمپ تامین شده و بازگشت داده میشود تا بجای IoC Container توکار ASP.NET Core استفاده شود.
در این مثال چون در متد Scan، کار بررسی اسمبلی لایه سرویس برنامه با قراردادهای پیش فرض استراکچرمپ انجام شدهاست، دیگر نیازی به سطر تعریف config.For نیست. در اینجا هرگاه IName ایی یافت شد، به کلاس Name متصل میشود (name هر نامی میتواند باشد).
آشنایی با الگوی M-V-VM - قسمت دوم
و اما در مثالی که آوردین اگر رکوردی select نشه برنامه error میده .پس باید متد canexecute درکلاس DoDecreaseCommand بار اول false برگردونه ولی canexecute فقط یکبار توسط دکمه مورد نظر فراخونی میشه.
این مشکل رو چطور باید رفع کرد که در ضمن موقعی که age>60 شد هم کلید غیر فعال شه
«این کار احتمالا در ASP.NET WebForm کار سختی نیست»
اتفاقا کار سادهای نیست و همین مراحل باید طی شود. ضمن اینکه کار با jquery ajax در آنجا به این یک دستی نیست. نیاز است در code behind فرم، متد وب سرویس مانندی به صورت استاتیک تعریف شود (که خودش سبب میشود تا دسترسی به اعتبار سنجی توکار مبتنی بر فرمها محدود شود) یا اینکه از یک هندلر مجزا بجای یک اکشن متد کمک گرفته شود ... خلاصه خیلی داستان دارد.