بررسی علت CPU Usage بالای برنامه در حال اجرا
<!-- https://github.com/dotnet/sdk/issues/1458#issuecomment-420456386 --> <Target Name="_ResolveCopyLocalNuGetPackagePdbsAndXml" Condition="$(CopyLocalLockFileAssemblies) == true" AfterTargets="ResolveReferences"> <ItemGroup> <ReferenceCopyLocalPaths Include="@(ReferenceCopyLocalPaths->'%(RootDir)%(Directory)%(Filename).pdb')" Condition="'%(ReferenceCopyLocalPaths.NuGetPackageId)' != '' and Exists('%(RootDir)%(Directory)%(Filename).pdb')" /> <ReferenceCopyLocalPaths Include="@(ReferenceCopyLocalPaths->'%(RootDir)%(Directory)%(Filename).xml')" Condition="'%(ReferenceCopyLocalPaths.NuGetPackageId)' != '' and Exists('%(RootDir)%(Directory)%(Filename).xml')" /> </ItemGroup> </Target>
آشنایی با نحوه ایجاد یک IoC Container
IoC Container چیست؟
IoC Container، فریم ورکی است برای انجام تزریق وابستگیها. در این فریم ورک امکان تنظیم اولیه وابستگیهای سیستم وجود دارد. برای مثال زمانیکه برنامه از یک IoC Container، نوع اینترفیس خاصی را درخواست میکند، این فریم ورک با توجه به تنظیمات اولیهاش، کلاسی مشخص را بازگشت خواهد داد.
IoC Containerهای قدیمیتر، برای انجام تنظیمات اولیه خود از فایلهای کانفیگ استفاده میکردند. نمونههای جدیدتر آنها از روشهای Fluent interfaces برای مشخص سازی تنظیمات خود بهره میبرند.
زمانیکه از یک IOC Container در کدهای خود استفاده میکنید، مراحلی چند رخ خواهند داد:
الف) کد فراخوان، از IOC Container، یک شیء مشخص را درخواست میکند. عموما اینکار با درخواست یک اینترفیس صورت میگیرد؛ هرچند محدودیتی نیز نداشته و امکان درخواست یک کلاس از نوعی مشخص نیز وجود دارد.
ب) در ادامه IOC Container به لیست اشیاء قابل ارائه توسط خود نگاه کرده و در صورت وجود، وهله سازی شیء درخواست شده را انجام و نهایتا شیء مطلوب را بازگشت خواهد داد.
در این بین زنجیرهی وابستگیهای مورد نیاز نیز وهله سازی خواهند شد. برای مثال اگر وابستگی اول به وابستگی دوم برای وهله سازی نیاز دارد، کار وهله سازی وابستگیهای وابستگی دوم نیز به صورت خودکار انجام خواهند شد. (این موردی است که بسیاری از تازه واردان به این بحث تا یکبار آنرا امتحان نکنند باور نخواهند کرد!)
ج) سپس کد فراخوان وهله دریافتی را مورد پردازش قرار داده و سپس شروع به استفاده از متدها و خواص آن خواهد نمود.
در تصویر فوق محل قرارگیری یک IOC Container را مشاهده میکنید. یک IOC Container در مورد تمام وابستگیهای مورد نیاز، اطلاعات لازم را دارد. همچنین این فریم ورک در مورد کلاسی که قرار است از وابستگیهای سیستم استفاده نماید نیز مطلع است؛ به این ترتیب میتواند به صورت خودکار در زمان وهله سازی آن، نوعهای وابستگیهای مورد نیاز آنرا در اختیارش قرار دهد.
برای مثال در اینجا MyClass، وابستگی مشخص شده در سازنده خود را به نام IDependency از IOC Container درخواست میکند. سپس این IOC Container بر اساس تنظیمات اولیه خود، یکی از وابستگیهای A یا B را بازگشت خواهد داد.
آغاز به کار ساخت یک IOC Container نمونه
در ابتدا کدهای آغازین مثال بحث جاری را در نظر بگیرید:
using System; namespace DI01 { public interface ICreditCard { string Charge(); } public class Visa : ICreditCard { public string Charge() { return "Charging with the Visa!"; } } public class MasterCard : ICreditCard { public string Charge() { return "Swiping the MasterCard!"; } } public class Shopper { private readonly ICreditCard creditCard; public Shopper(ICreditCard creditCard) { this.creditCard = creditCard; } public void Charge() { var chargeMessage = creditCard.Charge(); Console.WriteLine(chargeMessage); } } }
var shopper = new Shopper(new Visa()); shopper.Charge();
using System; using System.Collections.Generic; using System.Linq; namespace DI01 { public class Resolver { //کار ذخیره سازی و نگاشت از یک نوع به نوعی دیگر در اینجا توسط این دیکشنری انجام خواهد شد private Dictionary<Type, Type> dependencyMap = new Dictionary<Type, Type>(); /// <summary> /// یک نوع خاص از آن درخواست شده و سپس بر اساس تنظیمات برنامه، کار وهله سازی /// نمونه معادل آن صورت خواهد گرفت /// </summary> public T Resolve<T>() { return (T)Resolve(typeof(T)); } private object Resolve(Type typeToResolve) { Type resolvedType; // ابتدا بررسی میشود که آیا در تنظیمات برنامه نگاشت متناظری برای نوع درخواستی وجود دارد؟ if (!dependencyMap.TryGetValue(typeToResolve, out resolvedType)) { //اگر خیر، کار متوقف خواهد شد throw new Exception(string.Format("Could not resolve type {0}", typeToResolve.FullName)); } var firstConstructor = resolvedType.GetConstructors().First(); var constructorParameters = firstConstructor.GetParameters(); // در ادامه اگر این نوع، دارای سازندهی بدون پارامتری است // بلافاصله وهله سازی خواهد شد if (!constructorParameters.Any()) return Activator.CreateInstance(resolvedType); var parameters = new List<object>(); foreach (var parameterToResolve in constructorParameters) { // در اینجا یک فراخوانی بازگشتی صورت گرفته است برای وهله سازی // خودکار پارامترهای مختلف سازنده یک کلاس parameters.Add(Resolve(parameterToResolve.ParameterType)); } return firstConstructor.Invoke(parameters.ToArray()); } public void Register<TFrom, TTo>() { dependencyMap.Add(typeof(TFrom), typeof(TTo)); } } }
var resolver = new Resolver(); //تنظیمات اولیه resolver.Register<Shopper, Shopper>(); resolver.Register<ICreditCard, Visa>(); //تزریق وابستگیها و وهله سازی var shopper = resolver.Resolve<Shopper>(); shopper.Charge();
ابتدا کار تعاریف نگاشتهای اولیه انجام میشود. در این صورت زمانیکه متد Resolve فراخوانی میگردد، نوع درخواستی آن به همراه سازنده دارای آرگومانی از نوع ICreditCard وهله سازی شده و بازگشت داده خواهد شد. سپس با در دست داشتن یک وهله آماده، متد Charge آنرا فراخوانی خواهیم کرد.
بررسی نحوه استفاده از Microsoft Unity به عنوان یک IoC Container
Unity چیست؟
Unity یک فریم ورک IoC Container تهیه شده توسط مایکروسافت میباشد که آنرا به عنوان جزئی از Enterprise Library خود قرار داده است. بنابراین برای دریافت آن یا میتوان کل مجموعه Enterprise Library را دریافت کرد و یا به صورت مجزا به عنوان یک بسته نیوگت نیز قابل تهیه است.
برای این منظور در خط فرمان پاورشل نیوگت در VS.NET دستور ذیل را اجرا کنید:
PM> Install-Package Unity
پیاده سازی مثال خریدار توسط Unity
همان مثال قسمت قبل را درنظر بگیرید. قصد داریم اینبار بجای IoC Container دست سازی که تهیه شد، پیاده سازی آنرا به کمک MS Unity انجام دهیم.
using Microsoft.Practices.Unity; namespace DI02 { class Program { static void Main(string[] args) { var container = new UnityContainer(); container.RegisterType<ICreditCard, MasterCard>(); var shopper = container.Resolve<Shopper>(); shopper.Charge(); } } }
مطابق کدهای فوق، ابتدا تنظیمات IoC Container انجام شده است. به آن اعلام کردهایم که در صورت نیاز به ICreditCard، نوع MasterCard را یافته و وهله سازی کن. با این تفاوت که Unity هوشمندتر بوده و سطر مربوط به ثبت کلاس Shoper ایی را که در قسمت قبل انجام دادیم، در اینجا حذف شده است.
سپس به این IoC Container اعلام کردهایم که نیاز به یک وهله از کلاس خریدار داریم. در اینجا Unity کار وهله سازیهای خودکار وابستگیها و تزریق آنها را در سازنده کلاس خریدار انجام داده و نهایتا یک وهله قابل استفاده را در اختیار ادامه برنامه قرار خواهد داد.
یک نکته:
به صورت پیش فرض کار تزریق وابستگیها در سازنده کلاسها به صورت خودکار انجام میشود. اگر نیاز به Setter injection و مقدار دهی خواص کلاس وجود داشت میتوان به نحو ذیل عمل کرد:
container.RegisterType<ICreditCard, MasterCard>(new InjectionProperty("propertyName", 5));
مدیریت طول عمر اشیاء در Unity
توسط یک IoC Container میتوان یک وهله معمولی از شیءایی را درخواست کرد و یا حتی طول عمر این وهله را به صورت Singleton معرفی نمود (یک وهله در طول عمر کل برنامه). در Unity اگر تنظیم خاصی اعمال نشود، هربار که متد Resolve فراخوانی میگردد، یک وهله جدید را در اختیار ما قرار خواهد داد. اما اگر پارامتر متد RegisterType را با وهلهای از ContainerControlledLifetimeManager مقدار دهی کنیم:
container.RegisterType<ICreditCard, MasterCard>(new ContainerControlledLifetimeManager());
حالت پیش فرض مورد استفاده، بدون ذکر پارامتر متد RegisterType، مقدار TransientLifetimeManager میباشد.
استفاده از IIS در VS.NET و پروژههای ASP.NET داستان خودش را دارد. در نگارشهای 2002 و 2003 آن، تنها وب سرور قابل استفاده جهت کار با VS.NET همان IIS اصلی بود. مهمترین مشکل این روش، نیاز به داشتن دسترسی مدیریتی بر روی سیستم بود (که در بعضی از شرکتها، این مورد برای عموم کاربران ممنوع است) به همراه نصب جداگانه و تنظیمات مخصوص IIS ، صرفا جهت آزمایش یک برنامهی ساده؛ همچنین با توجه به اینکه IIS جزو کامپوننتها ویندوز بوده و هر نگارشی، IIS خاص خودش را دار است، این مورد هم مشکلات ویژهای را به همراه دارد (برای مثال IIS5 ویندوز XP را با IIS7 ویندوز سرور 2008 در نظر بگیرید؛ یکی برای توسعه یکی جهت محیط کاری). این روش در VS.Net 2005 کنار گذاشته شد و از وب سرور توکاری به نام Cassini یا ASP.NET Development Server استفاده گردید. به این صورت دیگر نیازی به نصب مجزای IIS کامل جهت آزمایش برنامههای ASP.NET نبود و همچنین نیاز به داشتن دسترسی مدیریتی الزامی نیز منتفی گردید. این روش هنوز هم تا نگارش 2010 ویژوال استودیو مرسوم است؛ اما ... اما کسانی که با Cassini کار کرده باشند میدانند که یک سری از رفتارهای آن با IIS واقعی تطابق ندارد و اگر برنامهی ASP.NET شما با Cassini خوب نمایش داده میشود الزامی ندارد که با IIS واقعی هم به همان نحو رفتار کند، برای نمونه رفتار مسیریابی آدرسهای نسبی در IIS واقعی و Cassini یکی نیست. علاوه بر آن IIS های 7 و 7.5 هم امکانات و ویژگیهای خاص خود را دارند که Cassini آنها را پوشش نمیدهد؛ به علاوه این دو فقط در ویندوزهای جدید مانند ویندوز سرور 2008 یا ویندوز 7 قابل دسترسی هستند. به همین جهت اخیرا یک نسخهی سبک و express از IIS 7.5 به صورت جداگانه برای برنامه نویسها فقط جهت آزمودن برنامههای خود تهیه شده است و البته هدفگیری اصلی آن پروژهی WebMatrix است؛ به همراه ویژگیهای جدید IIS7 مانند امکان آزمودن تنظیمات ویژه IIS7 در وب کانفیگ برنامه، پشتیبانی کامل از SSL ، Url Rewrite و سایر ماژولهای IIS7، عدم نیاز به دسترسی مدیریتی برای اجرای آن، امکان اجرای آن بر روی پورتهای مختلف بدون تداخل با وب سرور(های) موجود بر روی سیستم و همچنین برخلاف IIS7 اصلی، بر روی ویندوز XP نیز قابل اجرا است. حجم نگارش IIS Express 7.5 تنها 3.9 مگابایت است:
سرویس پک یک ویژوال استودیوی 2010 (که در زمان نگارش این مطلب نسخهی بتای آن ارائه شده)، یک گزینهی جدید را به منوی کلیک راست بر روی نام پروژه در VS.NET به نام Use IIS Express ، اضافه کرده است تا به سادگی بتوان از این امکان جدید استفاده کرد (یا به عبارتی با IIS Express یکپارچه است و نیاز به تنظیم خاصی ندارد).
در سایر حالات (و نسخههایی که این یکپارچگی وجود ندارد و نخواهد داشت) به صورت زیر میتوان عمل کرد:
روش اول:
دستور زیر را در خط فرمان وارد نمائید:
"C:\Program Files\IIS Express\iisexpress.exe" /path:D:\Prog\1389\MySite\ /port:4326 /clr:v4.0
روش دوم (که در حقیقت همان روش اول با ارائهی پشت صحنهی موقت آن است):
الف) ابتدا به مسیر My Documents\IISExpress\config مراجعه کرده و فایل applicationhost.config را باز کنید. سپس گره مربوط به site را یافته (حدود سطر 153) و گزینهی serverAutoStart را حذف کنید:
<site name="WebSite1" id="1">
<application path="/">
<virtualDirectory path="/" physicalPath="%IIS_SITES_HOME%\WebSite1" />
</application>
<bindings>
<binding protocol="http" bindingInformation=":8080:localhost" />
</bindings>
</site>
<site name="WebSite2" id="2">
<application path="/" applicationPool="Clr4IntegratedAppPool">
<virtualDirectory path="/" physicalPath="D:\Prog\1389\MyTestSite\" />
</application>
<bindings>
<binding protocol="http" bindingInformation=":1389:localhost" />
</bindings>
</site>
Name در اینجا نامی دلخواه است که وارد خواهید نمود.
Id شماره سایتی است که ثبت خواهد شد.
applicationPool در اینجا بسیار مهم است. اگر سایت شما مبتنی بر دات نت 4 است، Clr4IntegratedAppPool را وارد نمائید و اگر غیر از این است، Clr2IntegratedAppPool باید تنظیم شود.
physicalPath همان مسیر پروژه شما است.
در قسمت bindingInformation هم میتوان شماره پورت مورد نظر را وارد کرد.
اکنون فایل applicationhost.config را ذخیره کرده و ببندید.
سپس دستور زیر را در خط فرمان ویندوز وارد نمائید:
"C:\Program Files\IIS Express\iisexpress.exe" /site:WebSite2
تنظیمات دیباگر VS.NET :
تا اینجا تنها موفق شدهایم که این وب سرور آزمایشی را راه اندازی کنیم. اما نکتهی مهم امکان دیباگ کردن برنامه توسط آنرا از دست دادهایم. برای این منظور در VS.NET به خواص پروژه، برگهی Web آن مراجعه کنید. در قسمت Servers گزینهی use custom web server را انتخاب کرده و آدرسی را که در یکی از دو روش فوق ساختهاید وارد نمایید. برای مثال http://localhost:4326/
همچنین باید دقت داشت که در همین قسمت هیچکدام از debuggers ذیل گزینهی use custom web server نباید تیک خورده باشند (چون VS.NET دقیقا نمیداند که باید به کدام پروسه در ویندوز attach شود).
اکنون برنامه را در حالت دیباگ در VS.NET آغاز کنید (بدیهی است فرض بر این است که iisexpress.exe با تنظیمات ذکر شده باید در حال اجرا باشد).
و ... حداقل مزیت آن بسیار سریعتر بودن این روش نسبت به Cassini یا ASP.NET Development Server است.
تا اینجا فقط VS.NET به صورت خودکار مرورگر را باز کرده و سایت نمایش داده میشود؛ اما اگر در قسمتی از کدهای خود breakpoint قرار دهیم کار نمیکند. برای این منظور باید در حین اجرای برنامه، از منوی debug ، گزینهی attach to process را انتخاب کرده و به iisexpress متصل شوید.
بررسی اجمالی Redis
Redis | RDBMS |
Redis همه چیز را در حافظه اصلی ذخیره میکند. | RDBMS همه چیز را در حافظه ثانویه ذخیره میکند. |
در Redis بخاطر ذخیره سازی دادهها در حافظه اصلی، خواندن و نوشتن عملیات به شدت سریع میباشد. | در RDBMS بخاطر ذخیره سازی دادهها در حافظه ثانویه، خواندن و نوشتن عملیات کند است. |
حافظه اصلی از نظر size کوچکتر و از لحاظ قیمت نسبت به حافظه ثانویه گرانتر میباشد. Redis نمیتواند دادههای بزرگ یا binary data را ذخیره کند. | حافظه ثانویه از نظر size بزرگتر و از لحاظ قیمت نسبت به حافظه اصلی ارزانتر میباشد. RDBMS به آسانی میتواند با انواع فایلها کار کند. |
- Redis : Exceptionally fast خیلی سریع است و میتواند حدود 110000 ، SET و 81000 ، GET را به ازای هر ثانیه انجام دهد.
- Redis : Supports rich data type بیشتر دیتا تایپها را که توسعه دهندگان قبلا آنها را شناختهاند، پشتیبانی میکند؛ از قبیل string ، list ، set ، sorted set یا hash .
- Operations are atomic : تمام عملیات Redis اتمیک میباشند که این اطمینان خاطر را میدهد اگر دو کلاینت به صورت همزمان به آن دسترسی داشته باشند، Redis server مقدار update شده را دریافت خواهد کرد.
- Redis : Multi-utility tool یک ابزار چند منظوره است که میتواند در برخی از سناریوها استفاده شود از قبیل: Redis ) messaging-queues , caching به صورت بومی از Publish/Subscribe پشتیبانی میکند ) , هر داده ای با طول عمر کوتاه در Application مانند web application sessions , ... .
Blazer یک فریمورک جدید تحت وب هست که این امکان را به برنامه نویسان دات نت میدهد تا از طریق Open Web Standards بتوانند کدهای خود را در مرورگر اجرا و تجربه جدیدی از ساخت برنامههای تک صفحهای را داشته باشند. در این نوشتار قصد داریم ساختار و نحوه کارکرد این فناوری را بررسی نماییم. قبل از هر چیزی به دوران قبل از ایجاد Web Assembly برمیگردیم :
همانطور که در شکل زیر میبینید، زمانی تنها جاوااسکریپت فرمانروای یک مرورگر محسوب میشد. در این حالت کدهای جاوااسکریپت به هر شکلی که نوشته شده باشند در اختیار parser قرار میگیرند و یک درخت از کدهای نوشته شده ایجاد شده و از طریق یک کامپایلر، کدها به سطح پایینتری مشابه بایت کدها تبدیل میگردند و سپس از طریق یک مفسر دسترسی به بخشهای مختلف api یک مرورگر در اختیار این کدها قرار میگیرند تا کار مورد نظر انجام شود.
در تصویر بعدی Web Assembly به بخش مفسر تزریق میشود و از طریق آن زبانهای مختلف باید بر اساس Web Standard، به کدهای سطحهای پایینتری کامپایل شوند. در اینجا این نکته مدنظر باشد که کدهایی که به سطح پایینتری کامپایل میشوند، تنها در داخل مرورگر شناخته شده میباشند و در خارج از دنیای وب قابل استفاده نیستند و نمیتوانند در سطح سیستم عامل قابل اجرا باشند. به همین جهت به شکل یک sandbox مورد استفاده قرار میگیرند و از این لحاظ، مشکلات امنیتی را در خارج از مرورگر ایجاد نمیکنند.
در شکل سوم Blazor که ترکیبی از
نام Browser + Razor
میباشد اضافه میشود. Blazor در
اینجا وظیفه دارد محتوای فایل دریافتی را که شامل کدهای HTML و CSS و
جاوااسکریپت است، به کدهای قابل فهمی برای مرورگر تبدیل کند. سپس mono وارد کار میشود. همانطور که میدانید mono جهت پشتیبانی از اجرای چندسکویی پروژههای دات نت اضافه شده که در اینجا هم همان وظیفه را منتها برای مرورگرهای مختلف، دارد. بدین جهت مونوی کامپایل شده بر روی Web Assembly قرار میگیرد تا کدهای دریافتی را تفسیر نماید. Blazor در اینجا dllهای لازم را در mono بارگذاری میکند و سپس mono کدها را برای Web Assembly تفسیر میکند.
اگر به گوگل ریدر دقت کرده باشید، دو گزینهی به اشتراک گذاری دارد: share و share with note .
اگر گزینهی share with note را انتخاب کرده و توضیحی را ارسال یا اضافه کنیم، این توضیحات، به فید از نوع Atom اشتراکها هم اضافه میشود. مثلا:
<?xml version="1.0"?>
<feed xmlns:media="http://search.yahoo.com/mrss/"
xmlns:gr="http://www.google.com/schemas/reader/atom/"
xmlns:idx="urn:atom-extension:indexing"
xmlns="http://www.w3.org/2005/Atom"
idx:index="no"
gr:dir="ltr">
...
<entry gr:crawl-timestamp-msec="1316627782108">
...
<gr:annotation>
<content type="html">text-text-text</content>
<author>
<name>Vahid</name>
</author>
</gr:annotation>
...
</entry>
...
</feed>
این افزونه استاندارد نیست و همانطور که در قسمت xmlns:gr اطلاعات فوق مشخص است، در فضای نام http://www.google.com/schemas/reader/atom/ معنا پیدا میکند. از دات نت سه و نیم به بعد هم کلاسی جهت خواندن فیدهای استاندارد وجود دارد (تعریف شده در فضای نام System.ServiceModel.Syndication). اما چگونه میتوان این افزونهی غیر استاندارد را با کمک امکانات توکار دات نت خواند؟
روش کار با استفاده از ElementExtensions هر آیتم یک فید است؛ به صورت زیر :
using System;
using System.Collections.Generic;
using System.Linq;
using System.ServiceModel.Syndication;
using System.Xml;
using System.Xml.Linq;
namespace Linq2Rss
{
public class RssEntry
{
public string Title { set; get; }
public string Description { set; get; }
public string Link { set; get; }
public DateTime PublicationDate { set; get; }
public string Author { set; get; }
public string BlogName { set; get; }
public string BlogAddress { set; get; }
public string Annotation { set; get; }
}
public static class AtomReader
{
private static string getAtomAnnotation(this SyndicationElementExtensionCollection items)
{
if (!items.Any()) return string.Empty;
var item = items.Where(x => x.OuterName.ToLowerInvariant() == "annotation").FirstOrDefault();
if (item == null) return string.Empty;
var element = item.GetObject<XElement>();
var content = element.Element("{http://www.w3.org/2005/Atom}content");
return content == null ? string.Empty : content.Value;
}
public static IList<RssEntry> GetEntries(string feedUrl)
{
using (var reader = XmlReader.Create(feedUrl))
{
var feed = SyndicationFeed.Load(reader);
if (feed == null) return null;
return feed.Items.Select(x =>
new RssEntry
{
Title = x.Title.Text,
Author = x.Authors.Any() ? x.Authors.First().Name : string.Empty,
Description = x.Content == null ? string.Empty : ((TextSyndicationContent)x.Content).Text,
Link = x.Links.Any() ? x.Links.First().Uri.AbsoluteUri : string.Empty,
PublicationDate = x.PublishDate.UtcDateTime,
BlogName = x.SourceFeed.Title.Text,
BlogAddress = x.SourceFeed.Links.Any() ? x.SourceFeed.Links.First().Uri.AbsoluteUri : string.Empty,
Annotation = x.ElementExtensions.getAtomAnnotation()
}).ToList();
}
}
}
}
در این مثال به کمک متد الحاقی getAtomAnnotation، مجموعهی SyndicationElementExtensionCollection هر آیتم یک فید بررسی شده، در بین اینها، موردی که از نوع annotation باشد انتخاب و سپس content آن استخراج میگردد.
نکتهای دیگر:
اکثر کلاسهای موجود در فضاهای نام مرتبط با XML در دات نت امکان خواندن اطلاعات را از یک Uri هم دارند؛ مانند مثال فوق و متد XmlReader.Create بکارگرفته شده در آن. اما اگر بخواهیم حین خواندن اطلاعات، یک پروکسی را نیز به پروسه جاری اضافه کنیم، به نظر خاصیت یا متدی جهت انجام اینکار وجود ندارد. برای رفع این مشکل میتوان یک پروکسی سراسری را تعریف کرد. تنها کافی است خاصیت System.Net.WebRequest.DefaultWebProxy مقدار دهی شود. پس از آن به صورت خودکار بر روی کل برنامه تاثیر خواهد گذاشت.
MVC Scaffolding #2
دو نوع پارامتر حین کار با MVC Scaffolding مهیا هستند:
الف) سوئیچها
مانند پارامترهای boolean عمل کرده و شامل موارد ذیل میباشند. تمام این پارامترها به صورت پیش فرض دارای مقدار false بوده و ذکر هرکدام در دستور نهایی سبب true شدن مقدار آنها میگردد:
Repository: برای تولید کدها بر اساس الگوی مخزن
Force: برای بازنویسی فایلهای موجود.
ReferenceScriptLibraries: ارجاعاتی را به اسکریپتهای موجود در پوشه Scripts، اضافه میکند.
NoChildItems: در این حالت فقط کلاس کنترلر تولید میشود و از سایر ملحقات مانند تولید Viewها، DbContext و غیره صرفنظر خواهد شد.
ب) رشتهها
این نوع پارامترها، رشتهای را به عنوان ورودی خود دریافت میکنند و شامل موارد ذیل هستند:
ControllerName: جهت مشخص سازی نام کنترلر مورد نظر
ModelType: برای ذکر صریح کلاس مورد استفاده در تشکیل کنترلر بکار میرود. اگر ذکر نشود، از نام کنترلر حدس زده خواهد شد.
DbContext: نام کلاس DbContext تولیدی را مشخص میکند. اگر ذکر نشود از نامی مانند ProjectNameContex استفاده خواهد کرد.
Project: پیش فرض آن پروژه جاری است یا اینکه میتوان پروژه دیگری را برای قرار دادن فایلهای تولیدی مشخص کرد. (برای مثال هربار یک سری کد مقدماتی را در یک پروژه جانبی تولید کرد و سپس موارد مورد نیاز را از آن به پروژه اصلی افزود)
CodeLanguage: میتواند cs یا vb باشد. پیش فرض آن زبان جاری پروژه است.
Area: اگر میخواهید کدهای تولیدی در یک ASP.NET MVC area مشخص قرار گیرند، نام Area مشخصی را در اینجا ذکر کنید.
Layout: در حالت پیش فرض از فایل layout اصلی استفاده خواهد شد. اما اگر نیاز است از layout دیگری استفاده شود، مسیر نسبی کامل آنرا در اینجا قید نمائید.
یک نکته:
نیازی به حفظ کردن هیچکدام از موارد فوق نیست. برای مثال در خط فرمان پاورشل، دستور Scaffold را نوشته و پس از یک فاصله، دکمه Tab را فشار دهید. لیست پارامترهای قابل اجرای در این حالت ظاهر خواهند شد. اگر در اینجا برای نمونه Controller انتخاب شود، مجددا با ورود یک فاصله و خط تیره و سپس فشردن دکمه Tab، لیست پارامترهای مجاز و همراه با سوئیچ کنترلر ظاهر میگردند.
MVC Scaffolding و مدیریت روابط بین کلاسها
مثال قسمت قبلی بسیار ساده و شامل یک کلاس بود. اگر آنرا کمی پیچیدهتر کرده و برای مثال روابط one-to-many و many-to-many را اضافه کنیم چطور؟
using System; using System.Collections.Generic; using System.ComponentModel; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; namespace MvcApplication1.Models { public class Task { public int Id { set; get; } [Required] public string Name { set; get; } [DisplayName("Due Date")] public DateTime? DueDate { set; get; } [ForeignKey("StatusId")] public virtual Status Status { set; get; } // one-to-many public int StatusId { set; get; } [StringLength(450)] public string Description { set; get; } public virtual ICollection<Tag> Tags { set; get; } // many-to-many } public class Tag { public int Id { set; get; } [Required] public string Name { set; get; } public virtual ICollection<Task> Tasks { set; get; } // many-to-many } public class Status { public int Id { set; get; } [Required] public string Name { set; get; } } }
در ادامه دستور تولید کنترلرهای Task، Tag و Status ساخته شده با الگوی مخزن را در خط فرمان پاورشل ویژوال استودیو صادر میکنیم:
PM> Scaffold Controller -ModelType Task -ControllerName TasksController -DbContextType TasksDbContext -Repository -Force PM> Scaffold Controller -ModelType Tag -ControllerName TagsController -DbContextType TasksDbContext -Repository -Force PM> Scaffold Controller -ModelType Status -ControllerName StatusController -DbContextType TasksDbContext -Repository -Force
چند نکته:
- با توجه به اینکه مدلها تغییر کردهاند، نیاز است بانک اطلاعاتی متناظر نیز به روز گردد. مطالب مرتبط با آنرا در مباحث Migrations میتوانید مطالعه نمائید.
- View تولیدی رابطه many-to-many را پشتیبانی نمیکند. این مورد را باید دستی اضافه و طراحی کنید: (^ و ^)
- رابطه one-to-many به خوبی با View متناظری دارای یک drop down list تولید خواهد شد. در اینجا لیست تولیدی به صورت خودکار با مقادیر خاصیت Name کلاس Status پر میشود. اگر این نام دقیقا Name نباشد نیاز است توسط ویژگی به نام DisplayColumn که بر روی نام کلاس قرار میگیرد، مشخص کنید از کدام خاصیت باید استفاده شود.
@Html.DropDownListFor(model => model.StatusId, ((IEnumerable<Status>)ViewBag.PossibleStatus).Select(option => new SelectListItem { Text = (option == null ? "None" : option.Name), Value = option.Id.ToString(), Selected = (Model != null) && (option.Id == Model.StatusId) }), "Choose...") @Html.ValidationMessageFor(model => model.StatusId)
تولید آزمونهای واحد به کمک MVC Scaffolding
MVC Scaffolding امکان تولید خودکار کلاسها و متدهای آزمون واحد را نیز دارد. برای این منظور دستور زیر را در خط فرمان پاورشل وارد نمائید:
PM> Scaffold MvcScaffolding.ActionWithUnitTest -Controller TasksController -Action ArchiveTask -ViewModel Task
نکته مهم آن، عدم حذف یا بازنویسی کامل کنترلر یاد شده است. کاری هم که در تولید متد آزمون واحد متناظر انجام میشود، تولید بدنه متد آزمون واحد به همراه تولید کدهای اولیه الگوی Arrange/Act/Assert است. پر کردن جزئیات بیشتر آن با برنامه نویس است.
و یا به صورت خلاصهتر:
PM> Scaffold UnitTest Tasks Delete
کار مقدماتی با MVC Scaffolding و امکانات مهیای در آن همینجا به پایان میرسد. در قسمتهای بعد به سفارشی سازی این مجموعه خواهیم پرداخت.