اشتراکها
الگوی Facade در داتنت
اشتراکها
نگاهی به انواع دیکشنریها در داتنت
اشتراکها
NuoDB 1.1 برای توسعهدهندگان داتنت
اشتراکها
الگوی Decorator در داتنت
اشتراکها
TypeInfo در داتنت فریمورک 4.5
اگر مطلب «تفاوت بین Interface و کلاس Abstract در چیست؟» را مطالعه کرده باشید، به این نتیجه میرسید که طراحی یک کتابخانهی عمومی با اینترفیسها، بسیار شکنندهاست. اگر عضو جدیدی را به یک اینترفیس عمومی اضافه کنیم، تمام پیاده سازی کنندههای آنرا از درجهی اعتبار ساقط میکند و آنها نیز باید این عضو را حتما پیاده سازی کنند تا برنامهای که پیش از این به خوبی کار میکرده، باز هم بدون مشکل کامپایل شده و کار کند. هدف از ویژگی جدید «پیاده سازیهای پیشفرض در اینترفیسها» در C# 8.0، پایان دادن به این مشکل مهم است. با استفاده از این ویژگی جدید، میتوان یک عضو جدید را با پیاده سازی پیشفرضی داخل خود اینترفیس قرار داد. به این ترتیب تمام برنامههایی که از کتابخانههای عمومی شما استفاده میکنند، با به روز رسانی آن، به یکباره از کار نخواهند افتاد.
همچنین مزیت دیگر آن، انتقال سادهتر کدهای جاوا به سیشارپ است؛ از این لحاظ که ویژگی مشابهی در زبان جاوا تحت عنوان «Default Methods» سالها است که وجود دارد.
یک مثال از ویژگی «پیاده سازیهای پیشفرض در اینترفیسها»
فرض کنید کتابخانهی شما، اینترفیس ILogger را ارائه دادهاست و در برنامهای دیگر، استفاده کننده، کلاس ConsoleLogger را بر مبنای آن پیاده سازی و استفاده کردهاست.
مدتی بعد بر اساس نیازمندیهای مشخصی به این نتیجه خواهید رسید که بهتر است overload دیگری را برای متد Log در اینترفیس ILogger، درنظر بگیریم. مشکلی که این تغییر به همراه دارد، کامپایل نشدن کلاس ConsoleLogger در یک برنامهی ثالث است و این کلاس باید الزاما این overload جدید را پیاده سازی کند؛ در غیراینصورت قادر به کامپایل برنامهی خود نخواهد شد. اکنون در C# 8.0 میتوان برای این نوع تغییرات، در همان اینترفیس اصلی، یک پیاده سازی پیشفرض را نیز قرار داد:
به این ترتیب استفاده کنندگان از این اینترفیس، برای کامپایل برنامهی خود به مشکلی برنخواهند خورد و اگر از این overload جدید استفاده کنند، از همان پیاده سازی پیشفرض آن بهره خواهند برد. بدیهی است هنوز هم پیاده سازی کنندههای اینترفیس ILogger میتوانند پیاده سازیهای سفارشی خودشان را در مورد این overload جدید ارائه دهند. در این حالت از پیاده سازی پیشفرض صرفنظر خواهد شد.
ویژگی «پیاده سازیهای پیشفرض در اینترفیسها» چگونه پیاده سازی شدهاست؟
واقعیت این است که امکان پیاده سازی این ویژگی، سالها است که در سطح کدهای IL دات نت وجود داشته (از زمان دات نت 2) و اکنون از طریق کدهای برنامه با بهبود کامپایلر آن، قابل دسترسی شدهاست.
تاثیر زمینهی کاری بر روی دسترسی به پیاده سازیهای پیشفرض
مثال زیر را درنظر بگیرید:
در اینجا اینترفیس IDeveloper، به همراه یک پیاده سازی پیشفرض است و بر این اساس، کلاس BackendDev پیاده سازی کنندهی آن، دیگر نیازی به پیاده سازی اجباری متد LearnNewLanguage ای که تنها یک رشته را میپذیرد، ندارد.
سؤال: به نظر شما اکنون کدامیک از کاربردهای زیر از کلاس BackendDev، کامپایل میشود و کدامیک خیر؟
پاسخ: فقط مورد اول. مورد دوم با خطای کامپایلر زیر مواجه خواهد شد:
به این معنا که اگر کلاس BackendDev را به خودی خود (دقیقا از نوع BackendDev) و بدون معرفی آن از نوع اینترفیس IDeveloper، بکار بگیریم، فقط همان متدهایی که داخل این کلاس تعریف شدهاند، قابل دسترسی میباشند و نه متدهای پیشفرض تعریف شدهی در اینترفیس مشتق شدهی از آن.
ارثبری چندگانه چطور؟
احتمالا حدس زدهاید که این قابلیت ممکن است ارثبری چندگانه را که در سیشارپ ممنوع است، میسر کند. تا C# 8.0، یک کلاس تنها از یک کلاس دیگر میتواند مشتق شود؛ اما این محدودیت در مورد اینترفیسها وجود ندارد. به علاوه تاکنون اینترفیسها مانند کلاسها، امکان تعریف پیاده سازی خاصی را نداشتند و صرفا یک قرارداد بیشتر نبودند. بنابراین اکنون این سؤال مطرح میشود که آیا میتوان با ارائهی پیاده سازی پیشفرض متدها در اینترفیسها، ارثبری چندگانه را در سیشارپ پیاده سازی کرد؛ مانند مثال زیر؟!
سؤال: کد فوق بدون مشکل کامپایل میشود. اما در فراخوانی زیر، دقیقا از کدام متد LearnNewLanguage استفاده خواهد شد؟ آیا پیاده سازی آن از IBackendDev فراهم میشود و یا از IFrontendDev؟
پاسخ: هیچکدام! برنامه با خطای زیر کامپایل نخواهد شد:
کامپایلر سیشارپ در این مورد خاص از قانونی به نام «the most specific override rule» استفاده میکند. یعنی اگر برای مثال در IFullStackDev متد LearnNewLanguage به صورت صریحی بازنویسی و تامین شد، آنگاه امکان استفادهی از آن وجود خواهد داشت. یا حتی میتوان این پیاده سازی را در کلاس Dev نیز ارائه داد و از نوع آن (بجای نوع اینترفیس) استفاده کرد.
تفاوت امکانات کلاسهای Abstract با متدهای پیشفرض اینترفیسها چیست؟
اینترفیسها هنوز نمیتوانند مانند کلاسها، سازندهای را تعریف کنند. نمیتوانند متغیرها/فیلدهایی را در سطح اینترفیس داشته باشند. همچنین در اینترفیسها همهچیز public است و امکان تعریف سطح دسترسی دیگری وجود ندارد.
بنابراین باید بخاطر داشت که هدف از تعریف اینترفیسها، ارائهی «یک رفتار» است و هدف از تعریف کلاسها، ارائه «یک حالت».
یک نکته: در نگارشهای پیش از C# 8.0 هم میتوان ویژگی «متدهای پیشفرض» را شبیه سازی کرد
واقعیت این است که توسط ویژگی «متدهای الحاقی»، سالها است که امکان افزودن «متدهای پیشفرضی» به اینترفیسها در زبان سیشارپ وجود دارد:
و در این حالت هرچند به نظر اینترفیس IMyInterface دارای متدی نیست، اما فراخوانی زیر مجاز است:
همچنین مزیت دیگر آن، انتقال سادهتر کدهای جاوا به سیشارپ است؛ از این لحاظ که ویژگی مشابهی در زبان جاوا تحت عنوان «Default Methods» سالها است که وجود دارد.
یک مثال از ویژگی «پیاده سازیهای پیشفرض در اینترفیسها»
interface ILogger { void Log(string message); } class ConsoleLogger : ILogger { public void Log(string message) { Console.WriteLine(message); } }
مدتی بعد بر اساس نیازمندیهای مشخصی به این نتیجه خواهید رسید که بهتر است overload دیگری را برای متد Log در اینترفیس ILogger، درنظر بگیریم. مشکلی که این تغییر به همراه دارد، کامپایل نشدن کلاس ConsoleLogger در یک برنامهی ثالث است و این کلاس باید الزاما این overload جدید را پیاده سازی کند؛ در غیراینصورت قادر به کامپایل برنامهی خود نخواهد شد. اکنون در C# 8.0 میتوان برای این نوع تغییرات، در همان اینترفیس اصلی، یک پیاده سازی پیشفرض را نیز قرار داد:
interface ILogger { void Log(string message); void Log(Exception exception) => Console.WriteLine(exception); }
ویژگی «پیاده سازیهای پیشفرض در اینترفیسها» چگونه پیاده سازی شدهاست؟
واقعیت این است که امکان پیاده سازی این ویژگی، سالها است که در سطح کدهای IL دات نت وجود داشته (از زمان دات نت 2) و اکنون از طریق کدهای برنامه با بهبود کامپایلر آن، قابل دسترسی شدهاست.
تاثیر زمینهی کاری بر روی دسترسی به پیاده سازیهای پیشفرض
مثال زیر را درنظر بگیرید:
interface IDeveloper { void LearnNewLanguage(string language, DateTime dueDate); void LearnNewLanguage(string language) { // default implementation LearnNewLanguage(language, DateTime.Now.AddMonths(6)); } } class BackendDev : IDeveloper // compiles OK { public void LearnNewLanguage(string language, DateTime dueDate) { // Learning new language... } }
سؤال: به نظر شما اکنون کدامیک از کاربردهای زیر از کلاس BackendDev، کامپایل میشود و کدامیک خیر؟
IDeveloper dev1 = new BackendDev(); dev1.LearnNewLanguage("Rust"); var dev2 = new BackendDev(); dev2.LearnNewLanguage("Rust");
There is no argument given that corresponds to the required formal parameter 'dueDate' of 'BackendDev.LearnNewLanguage(string, DateTime)' (CS7036) [ConsoleApp]
ارثبری چندگانه چطور؟
احتمالا حدس زدهاید که این قابلیت ممکن است ارثبری چندگانه را که در سیشارپ ممنوع است، میسر کند. تا C# 8.0، یک کلاس تنها از یک کلاس دیگر میتواند مشتق شود؛ اما این محدودیت در مورد اینترفیسها وجود ندارد. به علاوه تاکنون اینترفیسها مانند کلاسها، امکان تعریف پیاده سازی خاصی را نداشتند و صرفا یک قرارداد بیشتر نبودند. بنابراین اکنون این سؤال مطرح میشود که آیا میتوان با ارائهی پیاده سازی پیشفرض متدها در اینترفیسها، ارثبری چندگانه را در سیشارپ پیاده سازی کرد؛ مانند مثال زیر؟!
using System; namespace ConsoleApp { public interface IDev { void LearnNewLanguage(string language) => Console.Write($"Learning {language} in a default way."); } public interface IBackendDev : IDev { void LearnNewLanguage(string language) => Console.Write($"Learning {language} in a backend way."); } public interface IFrontendDev : IDev { void LearnNewLanguage(string language) => Console.Write($"Learning {language} in a frontend way."); } public interface IFullStackDev : IBackendDev, IFrontendDev { } public class Dev : IFullStackDev { } }
IFullStackDev dev = new Dev(); dev.LearnNewLanguage("TypeScript");
The call is ambiguous between the following methods or properties: 'IBackendDev.LearnNewLanguage(string)' and 'IFrontendDev.LearnNewLanguage(string)' (CS0121)
تفاوت امکانات کلاسهای Abstract با متدهای پیشفرض اینترفیسها چیست؟
اینترفیسها هنوز نمیتوانند مانند کلاسها، سازندهای را تعریف کنند. نمیتوانند متغیرها/فیلدهایی را در سطح اینترفیس داشته باشند. همچنین در اینترفیسها همهچیز public است و امکان تعریف سطح دسترسی دیگری وجود ندارد.
بنابراین باید بخاطر داشت که هدف از تعریف اینترفیسها، ارائهی «یک رفتار» است و هدف از تعریف کلاسها، ارائه «یک حالت».
یک نکته: در نگارشهای پیش از C# 8.0 هم میتوان ویژگی «متدهای پیشفرض» را شبیه سازی کرد
واقعیت این است که توسط ویژگی «متدهای الحاقی»، سالها است که امکان افزودن «متدهای پیشفرضی» به اینترفیسها در زبان سیشارپ وجود دارد:
namespace MyNamespace { public interface IMyInterface { IList<int> Values { get; set; } } public static class MyInterfaceExtensions { public static int CountGreaterThan(this IMyInterface myInterface, int threshold) { return myInterface.Values?.Where(p => p > threshold).Count() ?? 0; } } }
var myImplementation = new MyInterfaceImplementation(); // Note that there's no typecast to IMyInterface required var countGreaterThanFive = myImplementation.CountGreaterThan(5);
پیش نویس: این مقاله ترجمه شده فصل 5 کتاب Pro Asp.Net Core MVC2 میباشد.
ایجاد یک پروژه با استفاده Razor
در ادامه با هم یک مثال را با استفاده از Razor ایجاد میکنیم. یک پروژه جدید را با قالب Empty و با نام Razor ایجاد میکنیم.
مراحل:
1- ابتدا در کلاس startup قابلیت MVC را فعال میکنیم؛ با قرار دادن کد زیر در متد ConfigureServices:
و بعد کد زیر را که مربوط به اجرای پروژهی hello Word است ، از متد Configure حذف میکنیم:
در نهایت محتویات فایل StartUp به صورت زیر میباشد:
ایجاد یک Model
یک پوشه جدید را به نام Models ایجاد و بعد در این پوشه یک کلاس را به نام Product ایجاد میکنیم و کدهای زیر را در آن قرار میدهیم:
ایجاد Controller
تنظیمات پیشفرض را در فایل Startup انجام دادهایم. درخواستهایی را که توسط کاربر ارسال میشوند، به controller پیشفرضی که نامش در اینجا Home است، ارسال میکند. حالا ما یک پوشه جدید را به نام Controllers ایجاد میکنیم و در آن یک کنترلر جدید را به نام HomeController ایجاد میکنیم و کدهای زیر را در آن قرار میدهیم:
در این کلاس یک Action Method را به نام index ایجاد میکنیم. سپس در آن یک شیء را از مدل ایجاد و مقدار دهی و آنرا به View ارسال میکنیم تا در زمان بارگذاری View از این شیء استفاده نماییم. نیاز نیست نام View را مشخص کنید. به صورت پیشفرض نام View با نام اکشن متد یکسان میباشد.
ایجاد View
برای ایجاد یک View پیشفرض برای Action Method فوق در پوشه Views/Home یک MVC View Page (Razor View Page) را به نام Index.schtml ایجاد میکنیم.
- نکته1: پوشه View و داخل آن Home را ایجاد کنید.
- نکته2: معادل MVC View Page در نسخه جدید، Razor View میباشد. اگر در لیست این آیتم را انتخاب کنید، در توضیحات پنل سمت راست میتوانید این مطلب را مشاهده کنید.
- نکته3: دقت نمایید برای اینکه پروژه net Core2. باشد و تمام مشخصات موردنظر را داشته باشد، باید نگارش ویژوال استودیو VS 2017.15.6.6 و یا بیشتر باشد.
تا اینجا ما یک پروژه ساده را ایجاد نمودهایم که قابلیت استفادهی از Razor را هم دارد. در ادامه نحوهی استفاده از امکانات Razor شرح داده میشوند.
استفاده از Model در یک View
برای استفاده از شیء مدل در View، باید در View به آن شیء و مشخصات آن دسترسی داشته باشیم که این دسترسی را Razor با استفاده از کاراکتر @ برای ما ایجاد میکند. برای اتصال به Model از عبارت model@ (حتما باید حروف کوچک باشد) استفاده میکنیم و برای دسترسی به مشخصات مدل از عبارت Model@ (حتما باید حرف اول آن بزرگ باشد) استفاده میکنیم. به کد زیر دقت کنید:
خط اولی که در View تعریف شده است، با استفاده از عبارت model@ مانند تعریف نوع مدل میباشد و کار اتصال مدل به View را انجام میدهد و همین خط باعث میشود زمانی که شما در تگ body عبارت Model@ وبعد دات (.) را میزنید، لیست خصوصیات آن مدل ظاهر میشوند. لیست شدن خصوصیات بعد از دات(.) یکی از کارهای پیشفرض ویژوال استودیو میباشد؛ برای اینکه از خطاهای احتمالی کاربر جلوگیری کند.
نتیجه خروجی بالا مانند زیر میباشد:
معرفی View Imports
زمانیکه بخواهیم به یک کلاس در View دسترسی داشته باشیم، باید فضای نام آن کلاس را مانند کد زیر در بالای View اضافه کنیم. حالا اگر بخواهیم به چند کلاس دسترسی داشته باشیم، باید این کار را به ازای هر کلاس در هر View انجام دهیم که سبب ایجاد کدهای اضافی در Viewها میشود. برای بهبود این وضعیت میتوانید یک کلاس View Import را در پوشهی Views ایجاد کنید و تمام فضاهای نام را در آن قرار دهید. با اینکار تمام فضاهای نامی که در این کلاس View Import قرار گرفتهاند، در تمام Viewهای موجود در پوشه Views قابل دسترسی خواهند بود.
در پوشه View راست کلیک کرده و گزینه Add و بعد New Item را انتخاب میکنیم و در کادر باز شده، آیتم MVC View Import Page (در نسخه جدید نام آن Razor View Imports است) انتخاب میکنیم. ویژوال استودیو به صورت پیش فرض نام ViewImports.cshtml_ را برای آن قرار میدهد.
نکته: استاندارد نام گذاری این View این میباشد که ابتدای آن کاراکتر (_) حتما وجود داشته باشد.
در کلاس تعریف شده با استفاده از عبارت using@ فضای نامهای خود را قرار میدهیم؛ مانند زیر:
در این کلاس شما فقط میتوانید فضاهای نام را مانند بالا قرار دهید. پس از آم قسمت فضاهای نام اضافی در Viewها قابل حذف میشوند و در این حالت فقط نام کلاس مدل را در بالای فرم قرار میدهیم مانند زیر:
Layout ها
یکی دیگر از عبارتهای مهم Razor که در فایل Index وجود دارد، عبارت زیر است:
شما میتوانید در بین {} کدهای سی شارپ را قرار دهید. حالا مقدار Layout را مساوی نال قرار دادهایم که بگوییم View مستقلی است و از قالب مشخصی استفاده نمیکند.
از Layout برای طراحی الگوی Viewها استفاده میکنیم. اگر بخواهیم برای View ها یک قالب طراحی کنیم و این الگو بین تمام یا چندتای از آنها مشترک باشد، کدهای مربوط به الگو را با استفاده از Layout ایجاد میکنیم و از آن در View ها استفاده میکنیم. اینکار برای جلوگیری از درج کدهای تکراری قالب در برنامه انجام میشود. با اینکار اگر بخواهیم در الگو تغییری را انجام دهیم، این تغییر را در یک قسمت انجام میدهم و سپس به تمام Viewها اعمال میشود.
Layout
طرحبندی Viewهای برنامه بطور معمول بین چند View مشترک است و طبق استاندارد ویژوال استودیو در پوشهی Views/Shared قرار میگیرد. برای ایجاد Layout، روی پوشه Views/shared راست کلیک کرده و بعد گزینه Add وبعد NewItem و سپس گزینه MVC View Layout Page (نام آن در نسخه جدید Razor Layout است) را انتخاب میکنیم و ابتدای نام آن را به صورت پیشفرض کاراکتر (_) قرار میدهیم.
هنگام ایجاد این فایل توسط ویژوال استودیو، کدهای زیر به صورت پیش فرض در فایل ایجاد شده وجود دارند:
طرحبندیها فرم خاصی از View هستند و دو عبارت @ در کدهای آن وجود دارد. در اینجا فراخوانی RenderBody@ سبب درج محتویات View مشخص شده توسط Action Method در این مکان میشود. عبارت دیگری که در اینجا وجود دارد، ViewBag است که برای مشخص کردن عنوان در اینجا استفاده شدهاست.
ViewBag ویژگی مفیدی است که اجازه میدهد تا مقادیر و دادهها در برنامه گردش داشته باشند و در این مورد بین یک View و Layout منتقل شوند. در ادامه خواهید دید وقتی Layout را به یک نمایه اعمال میکنیم، این مورد چگونه کار میکند.
عناصر HTML در یک Layout به هر View که از آن استفاده میکند، اعمال و توسط آن یک الگو برای تعریف محتوای معمولی ارائه میشود؛ مانند کدهای زیر. من برخی از نشانه گذاریهای ساده را به Layout اضافه کردم تا اثر قالب آن آشکارتر شود:
در اینجا یک عنصر عنوان و همچنین بعضی از CSSها را به عنصر div که حاوی عبارت RenderBody@ است، اضافه کردهام؛ فقط برای اینکه مشخص شود، چه محتوایی از طرحبندی سایت میآید و چه چیزی از View.
اعمال Layout
برای اعمال کردن Layout به یک View، نیاز است مشخصه Layout آنرا مقدار دهی و سپس Htmlهای اضافی موجود در آنرا مانند المنتهای head و Body حذف کنید؛ همانند کدهای زیر:
در خاصیت Layout، مقدار را برابر نام فایل Layout، بدون پسوند cshtml آن قرار میدهیم. Razor در مسیر پوشه Views/shared و پوشه Views/Home فایل Layout را جستجو میکند.
در اینجا عبارت ViewBag.Title را نیز مقدار دهی میکنیم. زمانیکه فایل فراخوانی میشود، عنوان آن صفحه با این مقدار، جایگزین خواهد شد.
تغییرات این View بسیار چشمگیر است؛ حتی برای چنین برنامه سادهای. طرحبندی شامل تمام ساختار مورد نیاز برای هر پاسخ HTML است که View را به صورت یک محتوای پویا ارائه میدهد و دادهها را به کاربر منتقل میکند. هنگامیکه MVC فایل Index.cshtmal را پردازش میکند، این طرحبندی برای ایجاد پاسخ HTML نهایی یکپارچه میشود؛ مانند عکس زیر:
View Start
بعضی موارد هنوز در برنامه وجود دارند که میتوان کنترل بیشتری بر روی آنها داشته باشید. مثلا اگر بخواهیم نام یک فایل layout را تغییر دهیم، مجبور هستیم تمام Viewهایی را که از آن Layout استفاده میکنند، پیدا کنید و نام Layout استفاده شده در آنها را تغییر دهیم. اینکار احتمال خطای بالایی دارد و امکان دارد بعضی View ها از قلم بیفتند و برنامه دچار خطا شود. بنابراین با استفاده از View Start میتوانیم این مشکل را برطرف کنیم. وقتی نام Layout تغییر کرد، تنها کافی است نام آنرا در View Start تغییر دهیم. اکنون زمانیکه برنامه را اجرا میکنیم، MVC به دنبال فایل View Start میگردد و اگر اطلاعاتی داشته باشد، آن را اجرا میکند و الویت این فایل از تمام فایلهای دیگر بیشتر است و ابتدا تمام آنها اجرا میشوند.
برای ایجاد یک فایل شروع مشاهده، روی پوشهی Views کلیک راست کرده و گزینه add->New Items را انتخاب میکنیم و از پنجره باز شده گزینه ( Razor View Start ) Mvc View Start Page را انتخاب میکنیم؛ مانند تصویر زیر:
ویژوال استودیو به صورت پیش فرض نام ViewStart.cshtml_ را به عنوان نام آن قرار میدهد؛ شما گزینهی Create را در این حالت انتخاب کنید. محتویات فایل ایجاد شده به صورت زیر میباشد:
برای اعمال Layout جدید به تمام Viewها، مقدار Layout را معادل طرحبندی خود تغییر میدهیم؛ مانند کد زیر:
از آنجا که فایل View Start دارای مقداری برای Layout میباشد، میتوانیم عبارتهای مربوطه را در Index.cshtmlها حذف کنیم:
در اینجا لازم نیست مشخص کنیم که من میخواهم از فایل View Start استفاده کنم. MVC این فایل را پیدا خواهد کرد و از محتویات آن به طور خودکار استفاده میکند. البته باید دقت داشت که مقادیر تعریف شدهی در فایل View اولویت دارند و باعث میشوند با معادلهای فایل View Start جایگزین شوند.
شما همچنین میتوانید چندین فایل View Start را برای تنظیم مقادیر پیش فرض قسمتهای مختلف برنامه، استفاده کنید. یک فایل Razor همواره توسط نزدیکترین فایل View start، پردازش میشود. به این معنا که شما میتوانید تنظیمات پیش فرض را با افزودن یک فایل View Start به پوشه Views / Home و یا Views / Shared لغو کنید.
نکته: درک تفاوت میان حذف محتویات فایل View Start یا مساوی Null قرار دادن آن مهم است. اگر View شما مستقل است و شما نمیخواهید از آن استفاده کنید، بنابراین مقدار Layout آنرا صریحا برابر Null قرار دهید. اگر مقدار دهی صریح شما مشخصه Layout را نادیده بگیرید، Mvc فرض میکند که میخواهید layout را داشته باشید و مقدار آن را از فایل View Start تامین میکند.
استفاده از عبارتهای شرطی در Razor
حالا که من اصول و مبانی View و Layout را به شما نشان دادم، قصد دارم به انواع مختلفی از اصطلاحات که Razor آنها را پشتیبانی میکند و نحوه استفادهی از آنها را برای ایجاد محتوای نمایشی، ارائه دهم. در یک برنامه MVC، بین نقشهایی که توسط View و Action متدها انجام میشود، جدایی روشنی وجود دارد. در اینجا قوانین سادهای وجود دارند که در جدول زیر مشخص شدهاند:
برای به دست آوردن بهترین نتیجه از MVC، نیاز به تفکیک و جداسازی بین قسمتهای مختلف برنامه را دارید. همانطور که میبینید، میتوانید کاملا با Razor کار کنید و این نوع فایلها شامل دستورالعملهای سی شارپ نیز هستند. اما شما نباید از Razor برای انجام منطق کسب و کار استفاده کنید و یا هر گونه اشیاء Domain Model خود را دستکاری کنید. کد زیر نشان میدهد که یک عبارت جدید به View اضافه میشود:
میتوان برای خصوصیت price، در اکشن متد فرمتی را تعریف و بعد آن را به View ارسال کنیم. این روش کار میکند، اما استفاده از این رویکرد منافع الگوی MVC را تضعیف میکند و توانایی من برای پاسخ دادن به تغییرات در آینده را کاهش میدهد. باید به یاد داشته باشید که در ASP NET Core MVC، استفاده مناسب از الگوی MVC اجتناب ناپذیر است و شما باید از تاثیر تصمیمات طراحی و کدگذاری که انجام میدهید مطلع باشید.
پردازش دادهها در مقابل فرمت
تفاوت بین پردازش داده و قالب بندی داده مهم است.
- نمایش فرمت دادهها: به همین دلیل در آموزش قبل من یک نمونه از شیء کلاس Product را برای View ارسال کردهام و نه فرمت خاص یک شیء را به صورت یک رشته نمایشی.
- پردازش داده: انتخاب اشیاء دادهای برای نمایش، مسئولیت کنترلر است و در این حالت مدلی را برای دریافت و تغییر داده مورد نیاز، فراخوانی میکند.
گاهی سخت است که متوجه شویم کدی جهت پردازش داده است و یا فرمت آن.
اضافه نمودن مقدار داده ای
سادهترین کاری را که میتوانید با یک عبارت Razor انجام دهید این است که یک مقدار داده را در نمایش دهید. رایجترین کار برای انجام آن، استفاده از عبارت Model@ است. ویوو Index یک مثال از این مورد است؛ شبیه به این مورد:
شما همچنین میتوانید یک مقدار را با استفاده قابلیت ViewBag نیز به View ارسال نمایید که از این قابلیت در Layout برای تنظیم کردن محتوای عنوان استفاده کردیم. اما در حالت زیر یک مدل نوع دار را به سمت View ارسال کردهایم:
خصوصیت ViewBag یک شیء پویا را باز میگرداند که میتواند برای تعیین خواص دلخواهی مورد استفاده قرار گیرد. از آنجا که ویژگی ViewBag پویا است، لازم نیست که نام خصوصیات را پیش از آن اعلام کنم. اما این بدان معنا است که ویژوال استودیو قادر به ارائه پیشنهادهای تکمیل کننده برای ViewBag نیست.
در مثال زیر از یک مدل نوع دار و مزایای به همراه آن استفاده شدهاست:
نتیجه آنرا در زیر میتوانید مشاهده کنید:
تنظیم مقادیر مشخص
شما همچنین میتوانید از عبارات Razor برای تعیین مقدار عناصر، استفاده کنید:
در اینجا از عبارات Razor، برای تعیین مقدار برای برخی از ویژگیهای داده در عنصر div استفاده کردهام.
نکته: ویژگیهای دادهها که نام آنها *-data است، روشی برای ایجاد ویژگیهای سفارشی برای سالها بوده است و بعنوان بخشی از استاندارد HTML5 است. عموما کدهای جاوا اسکریپت از آنها برای یافتن اطلاعات استفاده میکنند.
اگر برنامه را اجرا کنید و به منبع HTML که به مرورگر فرستاده شده نگاهی بیندازید، خواهید دید که Razor مقادیر صفات را تعیین کرده است؛ مانند این:
استفاده از عبارتهای شرطی
Razor قادر به پردازش عبارات شرطی است. در ادامه کدهای Index View را که در آن دستورات شرطی اضافه شدهاند میبینید:
برای شروع یک عبارت شرطی، یک علامت @ را در مقابل کلمه کلیدی if یا swicth سی شارپ قرار دهید. سپس بخش کد را داخل } قرار میدهیم. درون قطعه کد Razor، میتوانید عناصر HTML و مقادیر داده را در خروجی نمایش دهید؛ مانند:
در اینجا لازم نیست عناصر یا عبارات را در نقل قول قرار دهیم و یا آنها را به روش خاصی تعریف کنیم. موتور Razor این را به عنوان خروجی برای پردازش تفسیر خواهد کرد.
با این حال، اگر میخواهید متن واقعی را در نظر بگیرید و دستورات Razor را لغو کنید،میتوانید از :@ استفاده کنید تا عین آن عبارت درج شود.
ایجاد یک پروژه با استفاده Razor
در ادامه با هم یک مثال را با استفاده از Razor ایجاد میکنیم. یک پروژه جدید را با قالب Empty و با نام Razor ایجاد میکنیم.
مراحل:
1- ابتدا در کلاس startup قابلیت MVC را فعال میکنیم؛ با قرار دادن کد زیر در متد ConfigureServices:
services.AddMvc();
app.Run(async (context) => { await context.Response.WriteAsync("Hello World!"); });
namespace Razor { public class Startup { // This method gets called by the runtime. Use this method to add services to the container. // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940 public void ConfigureServices(IServiceCollection services) { services.AddMvc(); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IHostingEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } //app.Run(async (context) => //{ // await context.Response.WriteAsync("Hello World!"); //}); } } }
ایجاد یک Model
یک پوشه جدید را به نام Models ایجاد و بعد در این پوشه یک کلاس را به نام Product ایجاد میکنیم و کدهای زیر را در آن قرار میدهیم:
namespace Razor.Models { public class Product { public int ProductID { get; set; } public string Name { get; set; } public string Description { get; set; } public decimal Price { get; set; } public string Category { set; get; } } }
ایجاد Controller
تنظیمات پیشفرض را در فایل Startup انجام دادهایم. درخواستهایی را که توسط کاربر ارسال میشوند، به controller پیشفرضی که نامش در اینجا Home است، ارسال میکند. حالا ما یک پوشه جدید را به نام Controllers ایجاد میکنیم و در آن یک کنترلر جدید را به نام HomeController ایجاد میکنیم و کدهای زیر را در آن قرار میدهیم:
namespace Razor.Controllers { public class HomeController : Controller { // GET: /<controller>/ public ViewResult Index() { Product myProduct = new Product { ProductID = 1, Name = "Kayak", Description = "A boat for one person", Category = "Watersports", Price = 275M }; return View(myProduct); } } }
ایجاد View
برای ایجاد یک View پیشفرض برای Action Method فوق در پوشه Views/Home یک MVC View Page (Razor View Page) را به نام Index.schtml ایجاد میکنیم.
- نکته1: پوشه View و داخل آن Home را ایجاد کنید.
- نکته2: معادل MVC View Page در نسخه جدید، Razor View میباشد. اگر در لیست این آیتم را انتخاب کنید، در توضیحات پنل سمت راست میتوانید این مطلب را مشاهده کنید.
- نکته3: دقت نمایید برای اینکه پروژه net Core2. باشد و تمام مشخصات موردنظر را داشته باشد، باید نگارش ویژوال استودیو VS 2017.15.6.6 و یا بیشتر باشد.
@model Razor.Models.Product @{ Layout = null; } <!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width"/> <title>Index</title> </head> <body> Content will go here </body> </html>
تا اینجا ما یک پروژه ساده را ایجاد نمودهایم که قابلیت استفادهی از Razor را هم دارد. در ادامه نحوهی استفاده از امکانات Razor شرح داده میشوند.
استفاده از Model در یک View
برای استفاده از شیء مدل در View، باید در View به آن شیء و مشخصات آن دسترسی داشته باشیم که این دسترسی را Razor با استفاده از کاراکتر @ برای ما ایجاد میکند. برای اتصال به Model از عبارت model@ (حتما باید حروف کوچک باشد) استفاده میکنیم و برای دسترسی به مشخصات مدل از عبارت Model@ (حتما باید حرف اول آن بزرگ باشد) استفاده میکنیم. به کد زیر دقت کنید:
@model Razor.Models.Product @{ Layout = null; } <!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width"/> <title>Index</title> </head> <body> @Model.Name </body> </html>
نتیجه خروجی بالا مانند زیر میباشد:
معرفی View Imports
زمانیکه بخواهیم به یک کلاس در View دسترسی داشته باشیم، باید فضای نام آن کلاس را مانند کد زیر در بالای View اضافه کنیم. حالا اگر بخواهیم به چند کلاس دسترسی داشته باشیم، باید این کار را به ازای هر کلاس در هر View انجام دهیم که سبب ایجاد کدهای اضافی در Viewها میشود. برای بهبود این وضعیت میتوانید یک کلاس View Import را در پوشهی Views ایجاد کنید و تمام فضاهای نام را در آن قرار دهید. با اینکار تمام فضاهای نامی که در این کلاس View Import قرار گرفتهاند، در تمام Viewهای موجود در پوشه Views قابل دسترسی خواهند بود.
در پوشه View راست کلیک کرده و گزینه Add و بعد New Item را انتخاب میکنیم و در کادر باز شده، آیتم MVC View Import Page (در نسخه جدید نام آن Razor View Imports است) انتخاب میکنیم. ویژوال استودیو به صورت پیش فرض نام ViewImports.cshtml_ را برای آن قرار میدهد.
نکته: استاندارد نام گذاری این View این میباشد که ابتدای آن کاراکتر (_) حتما وجود داشته باشد.
در کلاس تعریف شده با استفاده از عبارت using@ فضای نامهای خود را قرار میدهیم؛ مانند زیر:
@using Razor.Models
@model Product @{ Layout = null; } <!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width"/> <title>Index</title> </head> <body> @Model.Name </body> </html>
Layout ها
یکی دیگر از عبارتهای مهم Razor که در فایل Index وجود دارد، عبارت زیر است:
@{ Layout = null; }
از Layout برای طراحی الگوی Viewها استفاده میکنیم. اگر بخواهیم برای View ها یک قالب طراحی کنیم و این الگو بین تمام یا چندتای از آنها مشترک باشد، کدهای مربوط به الگو را با استفاده از Layout ایجاد میکنیم و از آن در View ها استفاده میکنیم. اینکار برای جلوگیری از درج کدهای تکراری قالب در برنامه انجام میشود. با اینکار اگر بخواهیم در الگو تغییری را انجام دهیم، این تغییر را در یک قسمت انجام میدهم و سپس به تمام Viewها اعمال میشود.
Layout
طرحبندی Viewهای برنامه بطور معمول بین چند View مشترک است و طبق استاندارد ویژوال استودیو در پوشهی Views/Shared قرار میگیرد. برای ایجاد Layout، روی پوشه Views/shared راست کلیک کرده و بعد گزینه Add وبعد NewItem و سپس گزینه MVC View Layout Page (نام آن در نسخه جدید Razor Layout است) را انتخاب میکنیم و ابتدای نام آن را به صورت پیشفرض کاراکتر (_) قرار میدهیم.
هنگام ایجاد این فایل توسط ویژوال استودیو، کدهای زیر به صورت پیش فرض در فایل ایجاد شده وجود دارند:
<!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width" /> <title>@ViewBag.Title</title> </head> <body> <div> @RenderBody() </div> </body> </html>
ViewBag ویژگی مفیدی است که اجازه میدهد تا مقادیر و دادهها در برنامه گردش داشته باشند و در این مورد بین یک View و Layout منتقل شوند. در ادامه خواهید دید وقتی Layout را به یک نمایه اعمال میکنیم، این مورد چگونه کار میکند.
عناصر HTML در یک Layout به هر View که از آن استفاده میکند، اعمال و توسط آن یک الگو برای تعریف محتوای معمولی ارائه میشود؛ مانند کدهای زیر. من برخی از نشانه گذاریهای ساده را به Layout اضافه کردم تا اثر قالب آن آشکارتر شود:
<!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width" /> <title>@ViewBag.Title</title> <style> #mainDiv { padding: 20px; border: solid medium black; font-size: 20pt } </style> </head> <body> <h1>Product Information</h1> <div id="mainDiv"> @RenderBody() </div> </body> </html>
اعمال Layout
برای اعمال کردن Layout به یک View، نیاز است مشخصه Layout آنرا مقدار دهی و سپس Htmlهای اضافی موجود در آنرا مانند المنتهای head و Body حذف کنید؛ همانند کدهای زیر:
@model Product @{ Layout = "_BasicLayout"; ViewBag.Title = "Product"; }
در اینجا عبارت ViewBag.Title را نیز مقدار دهی میکنیم. زمانیکه فایل فراخوانی میشود، عنوان آن صفحه با این مقدار، جایگزین خواهد شد.
تغییرات این View بسیار چشمگیر است؛ حتی برای چنین برنامه سادهای. طرحبندی شامل تمام ساختار مورد نیاز برای هر پاسخ HTML است که View را به صورت یک محتوای پویا ارائه میدهد و دادهها را به کاربر منتقل میکند. هنگامیکه MVC فایل Index.cshtmal را پردازش میکند، این طرحبندی برای ایجاد پاسخ HTML نهایی یکپارچه میشود؛ مانند عکس زیر:
View Start
بعضی موارد هنوز در برنامه وجود دارند که میتوان کنترل بیشتری بر روی آنها داشته باشید. مثلا اگر بخواهیم نام یک فایل layout را تغییر دهیم، مجبور هستیم تمام Viewهایی را که از آن Layout استفاده میکنند، پیدا کنید و نام Layout استفاده شده در آنها را تغییر دهیم. اینکار احتمال خطای بالایی دارد و امکان دارد بعضی View ها از قلم بیفتند و برنامه دچار خطا شود. بنابراین با استفاده از View Start میتوانیم این مشکل را برطرف کنیم. وقتی نام Layout تغییر کرد، تنها کافی است نام آنرا در View Start تغییر دهیم. اکنون زمانیکه برنامه را اجرا میکنیم، MVC به دنبال فایل View Start میگردد و اگر اطلاعاتی داشته باشد، آن را اجرا میکند و الویت این فایل از تمام فایلهای دیگر بیشتر است و ابتدا تمام آنها اجرا میشوند.
برای ایجاد یک فایل شروع مشاهده، روی پوشهی Views کلیک راست کرده و گزینه add->New Items را انتخاب میکنیم و از پنجره باز شده گزینه ( Razor View Start ) Mvc View Start Page را انتخاب میکنیم؛ مانند تصویر زیر:
ویژوال استودیو به صورت پیش فرض نام ViewStart.cshtml_ را به عنوان نام آن قرار میدهد؛ شما گزینهی Create را در این حالت انتخاب کنید. محتویات فایل ایجاد شده به صورت زیر میباشد:
@{ Layout = "_Layout"; }
@{ Layout = "_BasicLayout"; }
@model Product @{ ViewBag.Title = "Product"; }
شما همچنین میتوانید چندین فایل View Start را برای تنظیم مقادیر پیش فرض قسمتهای مختلف برنامه، استفاده کنید. یک فایل Razor همواره توسط نزدیکترین فایل View start، پردازش میشود. به این معنا که شما میتوانید تنظیمات پیش فرض را با افزودن یک فایل View Start به پوشه Views / Home و یا Views / Shared لغو کنید.
نکته: درک تفاوت میان حذف محتویات فایل View Start یا مساوی Null قرار دادن آن مهم است. اگر View شما مستقل است و شما نمیخواهید از آن استفاده کنید، بنابراین مقدار Layout آنرا صریحا برابر Null قرار دهید. اگر مقدار دهی صریح شما مشخصه Layout را نادیده بگیرید، Mvc فرض میکند که میخواهید layout را داشته باشید و مقدار آن را از فایل View Start تامین میکند.
استفاده از عبارتهای شرطی در Razor
حالا که من اصول و مبانی View و Layout را به شما نشان دادم، قصد دارم به انواع مختلفی از اصطلاحات که Razor آنها را پشتیبانی میکند و نحوه استفادهی از آنها را برای ایجاد محتوای نمایشی، ارائه دهم. در یک برنامه MVC، بین نقشهایی که توسط View و Action متدها انجام میشود، جدایی روشنی وجود دارد. در اینجا قوانین سادهای وجود دارند که در جدول زیر مشخص شدهاند:
کامپوننت |
انجام میشود |
انجام نمیشود |
Action Method |
یک شیء ViewModel را به View ارسال میکند. |
یک فرمت داده را به View ارسال میکند. |
View |
از شیء ViewModel برای ارائه محتوا به کاربر استفاده میکند. |
هر جنبهای از شیء View Model مشخصات را تغییر میدهد. |
برای به دست آوردن بهترین نتیجه از MVC، نیاز به تفکیک و جداسازی بین قسمتهای مختلف برنامه را دارید. همانطور که میبینید، میتوانید کاملا با Razor کار کنید و این نوع فایلها شامل دستورالعملهای سی شارپ نیز هستند. اما شما نباید از Razor برای انجام منطق کسب و کار استفاده کنید و یا هر گونه اشیاء Domain Model خود را دستکاری کنید. کد زیر نشان میدهد که یک عبارت جدید به View اضافه میشود:
*@ @model Product @{ ViewBag.Title = "Product"; } <p>Product Name: @Model.Name</p> <p>Product Price: @($"{Model.Price:C2}")</p>
پردازش دادهها در مقابل فرمت
تفاوت بین پردازش داده و قالب بندی داده مهم است.
- نمایش فرمت دادهها: به همین دلیل در آموزش قبل من یک نمونه از شیء کلاس Product را برای View ارسال کردهام و نه فرمت خاص یک شیء را به صورت یک رشته نمایشی.
- پردازش داده: انتخاب اشیاء دادهای برای نمایش، مسئولیت کنترلر است و در این حالت مدلی را برای دریافت و تغییر داده مورد نیاز، فراخوانی میکند.
گاهی سخت است که متوجه شویم کدی جهت پردازش داده است و یا فرمت آن.
اضافه نمودن مقدار داده ای
سادهترین کاری را که میتوانید با یک عبارت Razor انجام دهید این است که یک مقدار داده را در نمایش دهید. رایجترین کار برای انجام آن، استفاده از عبارت Model@ است. ویوو Index یک مثال از این مورد است؛ شبیه به این مورد:
<p>Product Name: @Model.Name</p>
using Microsoft.AspNetCore.Mvc; using Razor.Models; namespace Razor.Controllers { public class HomeController : Controller { // GET: /<controller>/ public ViewResult Index() { Product myProduct = new Product { ProductID = 1, Name = "Kayak", Description = "A boat for one person", Category = "Watersports", Price = 275M }; return View(myProduct); } } }
خصوصیت ViewBag یک شیء پویا را باز میگرداند که میتواند برای تعیین خواص دلخواهی مورد استفاده قرار گیرد. از آنجا که ویژگی ViewBag پویا است، لازم نیست که نام خصوصیات را پیش از آن اعلام کنم. اما این بدان معنا است که ویژوال استودیو قادر به ارائه پیشنهادهای تکمیل کننده برای ViewBag نیست.
در مثال زیر از یک مدل نوع دار و مزایای به همراه آن استفاده شدهاست:
<p>Product Name: @Model.Name</p> <p>Product Price: @($"{Model.Price:C2}")</p> <p>Stock Level: @ViewBag.StockLevel</p>
تنظیم مقادیر مشخص
شما همچنین میتوانید از عبارات Razor برای تعیین مقدار عناصر، استفاده کنید:
@model Product @{ ViewBag.Title = "Product"; } p>Product Name: @Model.Name</p> <p>Product Price: @($"{Model.Price:C2}")</p> <p>Stock Level: @ViewBag.StockLevel</p> <div data-productid="@Model.ProductID" data-stocklevel="@ViewBag.StockLevel"> <p>Product Name: @Model.Name</p> <p>Product Price: @($"{Model.Price:C2}")</p> <p>Stock Level: @ViewBag.StockLevel</p> </div>
نکته: ویژگیهای دادهها که نام آنها *-data است، روشی برای ایجاد ویژگیهای سفارشی برای سالها بوده است و بعنوان بخشی از استاندارد HTML5 است. عموما کدهای جاوا اسکریپت از آنها برای یافتن اطلاعات استفاده میکنند.
اگر برنامه را اجرا کنید و به منبع HTML که به مرورگر فرستاده شده نگاهی بیندازید، خواهید دید که Razor مقادیر صفات را تعیین کرده است؛ مانند این:
<div data-productid="1" data-stocklevel="2"> <p>Product Name: Kayak</p> <p>Product Price: £275.00</p> <p>Stock Level: 2</p> </div>
استفاده از عبارتهای شرطی
Razor قادر به پردازش عبارات شرطی است. در ادامه کدهای Index View را که در آن دستورات شرطی اضافه شدهاند میبینید:
@model Product @{ ViewBag.Title = "Product Name"; } <div data-productid="@Model.ProductID" data-stocklevel="@ViewBag.StockLevel"> <p>Product Name: @Model.Name</p> <p>Product Price: @($"{Model.Price:C2}")</p> <p>Stock Level: @switch (ViewBag.StockLevel) { case 0:@:Out of Stock break; case 1: case 2: case 3: <b>Low Stock (@ViewBag.StockLevel)</b> break; default: @: @ViewBag.StockLevel in Stock break; } </p> </div>
برای شروع یک عبارت شرطی، یک علامت @ را در مقابل کلمه کلیدی if یا swicth سی شارپ قرار دهید. سپس بخش کد را داخل } قرار میدهیم. درون قطعه کد Razor، میتوانید عناصر HTML و مقادیر داده را در خروجی نمایش دهید؛ مانند:
<b>Low Stock (@ViewBag.StockLevel)</b>
با این حال، اگر میخواهید متن واقعی را در نظر بگیرید و دستورات Razor را لغو کنید،میتوانید از :@ استفاده کنید تا عین آن عبارت درج شود.
در WPF، زیر ساختهای ComponentModel توسط کلاسی به نام PropertyDescriptor، منابع Binding موجود در قسمتهای مختلف برنامه را در جدولی عمومی ذخیره و نگهداری میکند. هدف از آن، مطلع بودن از مواردی است که نیاز دارند توسط مکانیزمهایی مانند INotifyPropertyChanged و DependencyProperty ها، اطلاعات اشیاء متصل را به روز کنند.
در این سیستم، کلیه اتصالاتی که Mode آنها به OneTime تنظیم نشده است، به صورت اجباری دارای یک valueChangedHandlers متصل توسط سیستم PropertyDescriptor خواهند بود و در حافظه زنده نگه داشته میشوند؛ تا بتوان در صورت نیاز، توسط سیستم binding اطلاعات آنها را به روز کرد.
همین مساله سبب میشود تا اگر قرار نیست خاصیتی برای نمونه توسط مکانیزم INotifyPropertyChanged اطلاعات UI را به روز کند (یک خاصیت معمولی دات نتی است) و همچنین حالت اتصال آن به OneTime نیز تنظیم نشده، سبب مصرف حافظه بیش از حد برنامه شود.
اطلاعات بیشتر
A memory leak may occur when you use data binding in Windows Presentation Foundation
راه حل آن هم ساده است. برای اینکه valueChangedHandler ایی به خاصیت سادهای که قرار نیست بعدها UI را به روز کند، متصل نشود، حالت اتصال آنرا باید به OneTime تنظیم کرد.
سؤال: در یک برنامه بزرگ که هم اکنون مشغول به کار است، چطور میتوان این مسایل را ردیابی کرد؟
برای دستیابی به اطلاعات کش Binding در WPF، باید به Reflection متوسل شد. به این ترتیب در برنامه جاری، در کلاس PropertyDescriptor به دنبال یک کلاس خصوصی تو در توی دیگری به نام ReflectTypeDescriptionProvider خواهیم گشت (این اطلاعات از طریق مراجعه به سورس دات نت و یا حتی برنامههای ILSpy و Reflector قابل استخراج است) و سپس در این کلاس خصوصی داخلی، فیلد خصوصی propertyCache آنرا که از نوع HashTable است استخراج میکنیم:
اکنون به لیست داخلی Binding نگهداری شونده توسط WPF دسترسی پیدا کردهایم. در این لیست به دنبال مواردی خواهیم گشت که فیلد valueChangedHandlers به آنها متصل شده است و در حال گوش فرا دادن به سیستم binding هستند (سورس کامل و طولانی این مبحث را در پروژه پیوست شده میتوانید ملاحظه کنید).
یک مثال: تعریف یک کلاس ساده، اتصال آن و سپس بررسی اطلاعات درونی سیستم Binding
فرض کنید یک کلاس مدل ساده به نحو ذیل تعریف شده است:
سپس این کلاس به صورت یک List، توسط ViewModel برنامه در اختیار View متناظر با آن قرار میگیرد:
تعاریف View برنامه نیز به نحو زیر است:
همه چیز در آن معمولی به نظر میرسد. ابتدا به ViewModel برنامه دسترسی یافته و DataContext را با آن مقدار دهی میکنیم. سپس اطلاعات این لیست را توسط یک ListBox نمایش خواهیم داد.
خوب؛ اکنون اگر اطلاعات HashTable داخلی سیستم Binding را در مورد View فوق بررسی کنیم به شکل زیر خواهیم رسید:
بله. تعداد زیادی خاصیت Name زنده و موجود در حافظه باقی هستند که تحت ردیابی سیستم Binding میباشند.
در ادامه، نکتهی ابتدای بحث را جهت تعیین حالت Binding به OneTime، به View فوق اعمال میکنیم (یک سطر ذیل باید تغییر کند):
در این حالت اگر نگاهی به سیستم ردیابی WPF داشته باشیم، دیگر خبری از اشیاء زنده دارای خاصیت Name در حال ردیابی نیست:
به این ترتیب میتوان در لیستهای طولانی، به مصرف حافظه کمتری در برنامه WPF خود رسید.
بدیهی است این نکته را تنها در مواردی میتوان اعمال کرد که نیاز به بهروز رسانیهای ثانویه اطلاعات UI در کدهای برنامه وجود ندارند.
چطور از این نکته برای پروفایل یک برنامه موجود استفاده کنیم؟
کدهای برنامه را از انتهای بحث دریافت کنید. سپس دو فایل ReflectPropertyDescriptorWindow.xaml و ReflectPropertyDescriptorWindow.xaml.cs آنرا به پروژه خود اضافه نمائید و در سازنده پنجره اصلی برنامه، کد ذیل را فراخوانی نمائید:
کمی با برنامه کار کرده و منتظر شوید تا لیست نهایی اطلاعات داخلی Binding ظاهر شود. سپس مواردی را که دارای HandlerCount بالا هستند، مدنظر قرار داده و بررسی نمائید که آیا واقعا این اشیاء نیاز به valueChangedHandler متصل دارند یا خیر؟ آیا قرار است بعدها UI را از طریق تغییر مقدار خاصیت آنها به روز نمائیم یا خیر. اگر خیر، تنها کافی است نکته Mode=OneTime را به این Bindingها اعمال نمائیم.
دریافت کدهای کامل پروژه این مطلب
WpfOneTime.zip
در این سیستم، کلیه اتصالاتی که Mode آنها به OneTime تنظیم نشده است، به صورت اجباری دارای یک valueChangedHandlers متصل توسط سیستم PropertyDescriptor خواهند بود و در حافظه زنده نگه داشته میشوند؛ تا بتوان در صورت نیاز، توسط سیستم binding اطلاعات آنها را به روز کرد.
همین مساله سبب میشود تا اگر قرار نیست خاصیتی برای نمونه توسط مکانیزم INotifyPropertyChanged اطلاعات UI را به روز کند (یک خاصیت معمولی دات نتی است) و همچنین حالت اتصال آن به OneTime نیز تنظیم نشده، سبب مصرف حافظه بیش از حد برنامه شود.
اطلاعات بیشتر
A memory leak may occur when you use data binding in Windows Presentation Foundation
راه حل آن هم ساده است. برای اینکه valueChangedHandler ایی به خاصیت سادهای که قرار نیست بعدها UI را به روز کند، متصل نشود، حالت اتصال آنرا باید به OneTime تنظیم کرد.
سؤال: در یک برنامه بزرگ که هم اکنون مشغول به کار است، چطور میتوان این مسایل را ردیابی کرد؟
برای دستیابی به اطلاعات کش Binding در WPF، باید به Reflection متوسل شد. به این ترتیب در برنامه جاری، در کلاس PropertyDescriptor به دنبال یک کلاس خصوصی تو در توی دیگری به نام ReflectTypeDescriptionProvider خواهیم گشت (این اطلاعات از طریق مراجعه به سورس دات نت و یا حتی برنامههای ILSpy و Reflector قابل استخراج است) و سپس در این کلاس خصوصی داخلی، فیلد خصوصی propertyCache آنرا که از نوع HashTable است استخراج میکنیم:
var reflectTypeDescriptionProvider = typeof(PropertyDescriptor).Module.GetType("System.ComponentModel.ReflectTypeDescriptionProvider"); var propertyCacheField = reflectTypeDescriptionProvider.GetField("_propertyCache", BindingFlags.Static | BindingFlags.NonPublic);
اکنون به لیست داخلی Binding نگهداری شونده توسط WPF دسترسی پیدا کردهایم. در این لیست به دنبال مواردی خواهیم گشت که فیلد valueChangedHandlers به آنها متصل شده است و در حال گوش فرا دادن به سیستم binding هستند (سورس کامل و طولانی این مبحث را در پروژه پیوست شده میتوانید ملاحظه کنید).
یک مثال: تعریف یک کلاس ساده، اتصال آن و سپس بررسی اطلاعات درونی سیستم Binding
فرض کنید یک کلاس مدل ساده به نحو ذیل تعریف شده است:
namespace WpfOneTime.Models { public class User { public string Name { set; get; } } }
using WpfOneTime.Models; using System.Collections.Generic; namespace WpfOneTime.ViewModels { public class MainWindowViewModel { public IList<User> Users { set; get; } public MainWindowViewModel() { Users = new List<User>(); for (int i = 0; i < 1000; i++) { Users.Add(new User { Name = "name " + i }); } } } }
<Window x:Class="WpfOneTime.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:ViewModels="clr-namespace:WpfOneTime.ViewModels" Title="MainWindow" Height="350" Width="525"> <Window.Resources> <ViewModels:MainWindowViewModel x:Key="vmMainWindowViewModel" /> </Window.Resources> <Grid DataContext="{Binding Source={StaticResource vmMainWindowViewModel}}"> <ListBox ItemsSource="{Binding Users}"> <ListBox.ItemTemplate> <DataTemplate> <TextBlock Text="{Binding Name}" /> </DataTemplate> </ListBox.ItemTemplate> </ListBox> </Grid> </Window>
خوب؛ اکنون اگر اطلاعات HashTable داخلی سیستم Binding را در مورد View فوق بررسی کنیم به شکل زیر خواهیم رسید:
بله. تعداد زیادی خاصیت Name زنده و موجود در حافظه باقی هستند که تحت ردیابی سیستم Binding میباشند.
در ادامه، نکتهی ابتدای بحث را جهت تعیین حالت Binding به OneTime، به View فوق اعمال میکنیم (یک سطر ذیل باید تغییر کند):
<TextBlock Text="{Binding Name, Mode=OneTime}" />
به این ترتیب میتوان در لیستهای طولانی، به مصرف حافظه کمتری در برنامه WPF خود رسید.
بدیهی است این نکته را تنها در مواردی میتوان اعمال کرد که نیاز به بهروز رسانیهای ثانویه اطلاعات UI در کدهای برنامه وجود ندارند.
چطور از این نکته برای پروفایل یک برنامه موجود استفاده کنیم؟
کدهای برنامه را از انتهای بحث دریافت کنید. سپس دو فایل ReflectPropertyDescriptorWindow.xaml و ReflectPropertyDescriptorWindow.xaml.cs آنرا به پروژه خود اضافه نمائید و در سازنده پنجره اصلی برنامه، کد ذیل را فراخوانی نمائید:
new ReflectPropertyDescriptorWindow().Show();
دریافت کدهای کامل پروژه این مطلب
WpfOneTime.zip