[MvcAuthorize(DependencyActionNames = "Edit,Create")] [OutputCache(NoStore = true, Duration = 0, VaryByParam = "*")] public virtual JsonResult UserNameExist(string userName, int? id)
BEGIN TRANSACTION; INSERT INTO SP RELATION {S# S#(‘S5’), P# P#(‘P1’), QTY QTY(1000)}}; IF any error occurred THEN GOTO UNDO; END IF; UPDATE P WHERE P# = P#(‘P1’) TOTAL:=TOTAL + QTY(1000); IF any error occurred THEN GOTO UNDO; END IF; COMMIT; GOTO FINISH; UNDO: ROLLBACK; FINISH: RETURN;
Read Sav_Amt Sav_Amt := Sav-Amt - 500 if Sav-Amt <0 then do put (“insufficient fund”) rollback end else do Write Sav_Amt Read Chk_Amt Chk_Amt := Chk_Amt + 500 Write Chk-Amt put (“transfer complete”) End transaction
tx_begin(); execute T1 //at site D execute T2 //at site C Execute T3 //at site B … tX_commit ();
begin transaction(); s1; sp1:= create savepoint(0); s2; sp2:= create savepoint(0); if (condition) rollback (spi); … … commit
Alter table, Create, delete, insert, open, drop, fetch, grant, revoke, select, truncate table, update
Set implicit_transaction on
SET TRANSACTION READ ONLY
SET TRANSACTION READ WRITE
READ UNCOMMITTED, READ COMMITTED, REPEATABLE READ, SERIALIZABLE
CREATE DATABASE DNTSampleDB GO USE DNTSampleDB GO CREATE TABLE Cars( ID int PRIMARY KEY, Name VARCHAR(255) NOT NULL, NumberPlate VARCHAR(255) UNIQUE,--شماره پلاک Model INT);
INSERT INTO Cars VALUES (1,'Mazda','ABC 123',199), (2,'Mazda','ABC 345',207), (3,'Mazda','ABC 758',305), (4,'Mazda','ABC 741',306), (5,'Mazda','ABC 356',124)
INSERT INTO Cars VALUES (2,'Mazda','ABC 123111',199)
INSERT INTO Cars VALUES (6,'Mazda','ABC 741',200)
USE DNTSampleDB GO sp_help cars
در زبانهای CLR شما دیگر وقت خود را به موضوعاتی چون مدیریت حافظه، هماهنگ سازی تردها و مباحث امنیتی و صدور استثناء در سطوح پایینتر نمیدهید و فرقی هم نمیکند که از چه زبانی استفاده میکنید. بلکه CLR هست که این امور را انجام میدهد و این مورد بین تمامی زبانهای CLR مشترک است. برای مثال کاربری که قرار است در زمان اجرا استثناءها را صادر کند، در واقع مهم نیست که از چه زبانی برای آن استفاده میکند. بلکه آن CLR است که مدیریت آن را به عهده دارد و روال کار CLR برای همه زبانها یکی است. پس این سوال پیش میآید که وقتی مبنا و زیر پایهی همه زبانهای CLR یکی است، چرا تعدد زبان دیده میشود و مزیت هر کدام بر دیگری چیست؟ اولین مورد syntax آن است. هر کاربر رو به چه زبانی کشیده میشود و شاید تجربهی سابق در قدیم با یک برنامهی مشابه بوده است که همچنان همان رویه سابق را ادامه میدهد و یا اینکه نحوهی تحلیل و آنالیز کردن کدهای آن زبان است که کاربر را به سمت خود جذب کرده است. گاهی اوقات بعضی از زبانها با تمرکز در انجام بعضی از کارها چون امور مالی یا ریاضیات، موارد فنی و ... باعث جذب کاربران آن گروه کاری به سمت خود میشوند. البته بعدا در آینده متوجه میشویم که بسیاری از زبانها مثل سی شارپ و ویژوال بیسیک هر کدام قسمتی از امکانات CLR را پوشش میدهند نه تمام آن را.
زبانهای CLR چگونه کار میکنند؟
در اولین گام بعد از نوشتن برنامه، کامپایلر آن زبان دست به کار شده و برنامه را برای شما کامپایل میکند. ولی اگر تصور میکنید که برنامه را به کد ماشین تبدیل میکند و از آن یک فایل اجرایی میسازد، سخت در اشتباه هستید. کامپایلر هر زبان CLR، کدها را به یک زبان میانی Intermediate Language به اختصار IL تبدیل میکند. فرقی نمیکند چه زبانی کار کردهاید، کد شما تبدیل شده است به یک زبان میانی مشترک. CLR نمیتواند برای تک تک زبانهای شما یک مفسر داشته باشد. در واقع هر کمپایلر قواعد زبان خود را شناخته و آن را به یک زبان مشترک تبدیل میسازد و حالا CLR میتواند حرف تمامی زبانها را بفهمد. به فایل ساخته شده managed module گویند و به زبانهایی که از این قواعد پیروی نمیکنند unmanaged گفته میشود؛ مثل زبان سی ++ که در دات نت هم managed و هم unmanaged داریم که اولی بدون فریم ورک دات نت کار میکند و مستقیما به کد ماشین تبدیل میشود و دومی نیاز به فریم ورک دات نت داشته و به زبان میانی کامپایل میشود. جدول زیر نشان میدهد که کد همهی زبانها تبدیل به یک نوع شده است.
فایل هایی که ساخته میشوند بر دو نوع هستند؛ یا بر اساس استاندارد windows Portable Executable 32bits برای سیستمهای 32 بیتی و 64 بیتی هستند و یا بر اساس windows Portable Executable 64bits مختص سیستمهای 64 بیتی هستند که به ترتیب PE32 و +PE32 نامیده میشوند که CLR بر اساس این اطلاعات آنها را به کد اجرایی تبدیل میکند. زبانهای CLR همیشه این مزیت را داشتهاند که اصول امنیتی چون DEP یا Data Execution Prevention و همچنین ASLR یا Address Space Layout Randomization در آنها لحاظ شده باشد.
درخت متوازن
همانطور که دیدید، عملیات جستجو روی درخت جستجوی دو دویی به مراتب راحت و آسانتر است؛ ولی با این حال این درخت در عملیاتی چون درج و حذف، یک نقص فنی دارد و آن هم این است که نمیتواند عمق خود را کنترل کند و همینطور به سمت عمقهای بیشتر و بزرگتر حرکت میکند. مثلا ساختار ترتیبی زیر را برای مقداریهای 1 و 2 و 3 و 4 و 5و 6 در نظر بگیرید:
در این حالت دیگر درخت مانند یک درخت رفتار نمیکند و بیشتر شبیه لیست پیوندی است و عملیات جستجو همینطور کندتر و کندتر میشود و دیگر مثل سابق نخواهد بود، پیچیدگی برنامه بیشتر خواهد شد و از (Log(n به n میرسد. از آنجا که دوست داریم برای عملیاتهای رایجی چون درج و جستجو و حذف، همین پیچیدگی لگاریتمی را حفظ کنیم، از ساختاری جدیدتر بهره خواهیم برد.
درخت دودویی متوازن: درختی است که در آن هیچ برگی، عمقش از هیچ برگی بیشتر نیست.
درخت دودویی متوازن کامل: درختی که تفاوتش در تعداد گرههای چپ یا راست است و حداقل یک فرزند دارند.
درخت
دودویی متوازن حتی اگر کامل هم نباشد، در عملیات پایهای چون افزودن، حذف و
جستجو در بدترین حالت هم با پیچیدگی لگاریتمی تعداد گامها همراه است.
برای اینکه این درخت با به هم ریختگی توازنش روبرو نشود، باید حین انجام
عملیات پایه، جایگاه تعداد المانهای آن بررسی و اصلاح شود که به این
عملیات چرخش یا دوران Rotation میگویند. انجام این عملیات بستگی دارد که پیاده سازی
این درخت به چه شکلی باشد و به چه صورتی پیاده سازی شده باشد. از پیاده
سازیهای این درخت میتوان به درخت سرخ-سیاه Black Red Tree ، ای وی ال AVL Tree ، اسپلی Splay و ... اشاره کرد.
با توجه به موارد بالا میتوانیم به نتایج زیر برسیم که چرا این درخت در هر حالت، پیچیدگی زمانی خودش را در لگاریتم n حفظ میکند:
- المانها و عناصرش را مرتب شده نگه میدارد.
- خودش را متوازن نگه داشته و اجازه نمیدهد عمقش بیشتر از لگاریتم n شود.
نمونه ای از درخت جستجوی دو دویی
همان درخت ولی به صورت متوازن با پیاده سازی AVL
درختهای متوازن هم میمتوانند دو دویی یا باینری باشند و هم غیر باینری non-Binary
درختهای دو دویی در انجام عملیات بسیار سریع هستند و در بالا به تعدادی از انواع پیاده سازیهای آن اشاره کردیم.
درختهای غیر باینری نیز مرتب و متوازن بوده، ولی میتوانند بیش از یک کلید داشته باشند و همچنین بیشتر از دو فرزند. این درختها نیز عملیات خود را بسیار سریع انجام میدهند. از نمونههای این درخت میتوان به B Tree,B+ Tree,Interval Tree اشاره کرد.
از آنجا که پیادهسازی این نوع درخت کمی دشوار و پیچیده و طولانی است و همچنین پیاده سازیهای مختلفی دارد؛ تعدادی از منابع موجود را در زیر معرفی میکنیم:
در خود دات نت در فضای نام system.collection.generic کلاس TreeSet یک نوع پیاده سازی از این درخت است که این پیاده سازی از نوع درخت سرخ سیاه میباشد و جالب است بدانید، در طی بیست گام میتواند در یک میلیون آیتم به جستجو بپردازد ولی خبر بد اینکه استفاده مستقیم از این کلاس ممکن نیست چرا که این کلاس به صورت داخلی internal برای استفادهی خود کتابخانه طراحی شده است ولی خبر خوب اینکه کلاس sortedDictionary از این کلاس بهره برده است و به صورت عمومی در دسترس ما قرار گرفته است. همچنین کلاس SortedSet هم از دات نت 4 نیز در دسترس است.
کتابخانههای خارجی جهت استفاده در دات نت که به پیادهسازی درختهای متوازن پرداختهاند:
پیاده سازی Splay Tree با سی شارپ
پیاده سازی AVL به صورت بهینه و آسان برای استفاده
پیاده سازی درخت AVL با کارایی بالا
پیاده سازی درخت AVL به همراه نمایش گرافیکی آن
کتابخانه ای متن باز برای درخت سرخ-سیاه
درخت متوازن به همراه جست و جو و حذف و پیمایش ها
غیر دودوییها
پیاده سازی Interval Tree در سی ++
شما میتوانید این کلاس را به یک GridView یا کنترلهای دیگر بایند کرده و کلیدهای موجود در حافظه کش را مشاهده کنید، و در صورتی که خواستید یک کلید خاص را از حافظه کش حذف نمایید (البته این کلاس بیشتر برای مدیر نرم فزار کاربرد دارد).
میتوانید فایل مورد نظر را از طریق لینک کلاس کمکی جهت مشاهده آیتمهای موجود در حافظه کش و حذف آنها دانلود نمایید.
در کلاس زیر هر کدام از قسمتها را شرح میدهیم.
using System; using System.Collections.Generic; using System.ComponentModel; using System.Web; using System.Web.Caching; namespace PWS.BLL { /// <summary> /// کلاس آیتمهای حافظه کش /// </summary> [DataObject(true)] public class CacheItems { #region Constructors (2) /// <summary> /// سازنده اصلی /// </summary> /// <param name="cacheItem">عنوان آیتم ذخیره شده در حافظه کش</param> public CacheItems(String cacheItem) { CacheItem = cacheItem; } /// <summary> /// سازنده پیش فرض /// </summary> public CacheItems(){} #endregion Constructors #region Properties (2) /// <summary> /// کش کانتکست جاری /// </summary> /// <value> /// The cache. /// </value> private static Cache Cache { get {return HttpContext.Current.Cache; } } /// <summary> /// عنوان آیتم ذخیره شده در حافظه کش /// </summary> public String CacheItem{ get; set;} #endregion Properties #region Methods (4) // Public Methods (3) /// <summary> /// لیست تمام آیتمهای ذخیره شده در حافظه کش /// </summary> /// <returns></returns> public List<CacheItems> GetCaches() { var items = new List<CacheItems>(); //بازیابی کل کلیدهای موجود در حافظه کش و اضافه کردن آن به لیست مربوطه var enumerator = Cache.GetEnumerator(); while (enumerator.MoveNext()) { items.Add(new CacheItems(enumerator.Key.ToString())); } return items; } /// <summary> /// حذف آیتم جاری از حافظه کش /// </summary> public void RemoveItemFromCache() { RemoveItemFromCache(CacheItem); } /// <summary> /// حذف کردن یک آیتم از حافظه کش /// </summary> /// <param name="key">کلید ذخیره شده در حافظه کش</param> public static void RemoveItemFromCache(string key) { PurgeCacheItems(key); } // Private Methods (1) /// <summary> /// حذف کردن یک ایتم از حافظه کش با پشوند وارد شده /// </summary> /// <param name="prefix">پیشوندی از کلید موجود در حافظه کش</param> private static void PurgeCacheItems(String prefix) { prefix = prefix.ToLower(); var itemsToRemove = new List<String>(); //لیست آیتمهای موجود در حافظه کش var enumerator = Cache.GetEnumerator(); while (enumerator.MoveNext()) {
//در صورتی که کلید مورد نظر با پارامتر وارد شده شروع شده باشد آن را به یک لیست اضافه میکنیم
if (enumerator.Key.ToString().ToLower().StartsWith(prefix)) itemsToRemove.Add(enumerator.Key.ToString()); } //لیست مورد نظر را پیمایش کرده و گزینههای آن را از حافظه کش حذف میکنیم foreach (var itemToRemove in itemsToRemove) Cache.Remove(itemToRemove); } #endregion Methods } }
در این مقاله با یکی از مهمترین ویژگیهای git یعنی بازیابی تغییرات فایلها، آشنا میشویم. اما در ابتدا نگاهی میکنیم به چگونگی ایجاد تغییر در آخرین commit:
تغییر آخرین commit:
در
git این امکان وجود دارد که آخرین فرمان commit با استفاده از اصلاحکننده
amend تغییر کند. علت تاکید بر روی آخرین دستور این است که git به دلیل
ساختاری که دارد نمیتواند commitهای قبل را تغییر دهد. اگر مقالات ابتدایی
آموزش git را مطالعه کرده باشید، به خاطر دارید که هر commit دارای یک کد
منحصر به فرد SHA-1 است، که این کد از هش کردن BLOBها به همراه خود مقادیر
commit یعنی مشخصات ایجاد کننده آن و از همه مهمتر SHA-1 پدر ایجاد میشود.
در نتیجه تغییر commitیی که نقش برگ را ندارد، یعنی در ساختار درختی git
دارای فرزند است، سبب میشود کد SHA-1 آن تغییر کند. این تغییر، commitهای فرزند را مجاب میکند برای حفظ صحت دادهها مقدار SHA-1 خود را تغییر دهند.
به این ترتیب این تغییرات در کل repository پخش خواهد شد. به همین دلیل git
جز آخرین commit امکان اصلاح دیگر commitها را نخواهد داد.
برای اصلاح آخرین commit کافی است دستور commit خود را با amend-- بیاورید
دستورات بازیابی فایل:
دستور checkout:
این فرمان یکی از مهمترین فرمانهای git است که دارای دو کاربرد است:
۱) بازیابی فایلی از repository و یا stage
۲) تغییر شاخه (این مورد را در مقالات مربوط به branch بررسی خواهیم کرد)
با
استفاده از این دستور میتوان فایلی را از repository به stage یا working
tree و یا هر دو بیاوریم. عملکرد این دستور با اصلاح کنندههای گوناگون
متفاوت خواهد بود. در ادامه روشهای مختلف فراخوانی این دستور و کاربرد هر
کدام آورده شده است:
در صورتی که بخواهیم فایلی را از محلی که head اکنون به آن اشاره میکند به working tree بیاوریم از دستور زیر استفاده میکنیم:
git checkout --[filename]
در حالت فوق فایل مستقیما به working tree آورده شده و در stage قرار نمیگیرد
تذکر:
-- در دستور بالا اختیاری بوده، اما استفاده از آن توصیه میشود. زیرا در
صورتیکه نام فایل به اشتباه وارد شود و یا فایل موجود نباشد، git اقدام به
تعویض شاخه میکند. زیرا همانطور که گفته شد، این دستور کاربرد دوگانه دارد.
در این حالت ممکن است به علت سهل انگاری مشکلاتی ایجاد شود علامت -- تاکید میکند که مقدار نوشته نام فایل است.
حال اگر بخواهیم فایلی را از commitهای قبل بازیابی کنیم، میتوانیم از دستور زیر استفاده کنیم:
git checkout [SHA-1] [filename]
در این حالت فایل هم در stage و هم در working tree قرار میگیرد.
دستور reset:
در
صورتیکه بخواهید تعداد زیادی فایل را به وضعیت مشخصی در زمان قبل
برگدانید، reset فرمان مناسبی خواهد بود. البته استفاده از این دستور باید با
احتیاط کامل صورت گیرد. زیرا در صورت اشتباه، این امکان وجود دارد که دیگر
نتوانید به بخشی از سوابق فایلهای خود دسترسی داشته باشید. بنابراین این
دستور همانقدر که کاربردی است، به همان اندازه نیز خطرناک است.
دستور reset را میتوان به ۳ صورت اجرا نمود:
۱) soft
۲) mixed (حالت پیشفرض)
۳) hard
۱)در
حالت soft تنها head به commit گفته شده منتقل میشود و working tree و
همچنین stage تغییری نمیکند. دقیقا مانند آنکه هد یک نوار خوان ویدئویی به
جای آنکه به آخرین محل ضبط اشاره کند، به عقب برگشته و به قسمتی در قبل
برود. در این حالت در صورتیکه دستور commit جدیدی ایجاد نشود که باعث پاک
شدن commitهای از آنجا به بعد شود، میتوان با اجرای مجدد دستور reset و اشاره
به آخرین commit، مجددا head را به سر جای اول برگرداند. البته توجه کنید در
صورتیکه در هنگام برگرداندن head به commitهای قبلی، فایلهایی تغییر کرده
باشند، آنها به صورت خودکار به stage اضافه میشوند.
۲) در حالت mixed
که پیش فرض این دستور نیز است، working tree بدون تغییر میماند. اما stage
تغییر کرده و دقیقا مانند وضعیت commit میشود.
۳) در این حالت هم
working tree و هم stage تغییر میکند و عینا وضعیت commitیی را میگیرند
که اکنون head به آن اشاره میکند. استفاده از این اصلاح کننده بسیار
خطرناکتر از موارد قبل است.
در هر یک از موارد فوق تا زمانیکه دستور
commit جدیدی را اجرا نکرده باشید، میتوانید به وضعیت قبل برگردید. اما اگر
commit جدید اجرا شود دیگر امکان بازگشت به commitهای صورت گرفته بعد از
آن وجود ندارد.
نکته مهم:
علیرغم
آنکه میتوان به commitهای گذشته در صورت عدم داشتن commit جدید مراجعه
کرد، اما یک اشکال فنی وجود دارد و آن این است که شما نمیتوانید SHA-1های آن commitها را با دستوراتی نظیر log ببینید. بنابراین بهتر است مقدار آنها
را قبل از اجرای دستور، ذخیره و تا اطمینان از وضعیت فعلی در محلی نگه دارید.
شکل زیر نمایانگر وضعیتهای مختلف دستور reset در هنگام بازگشت به سه commit قبل نسبت به وضعیت فعلی Head است:
(JSON (JavaScript Object Notation یک راه مناسب برای نگهداری اطلاعات است و از لحاظ ساختاری شباهت زیادی به XML، رقیب قدیمی خود دارد.
وب سرویس و آجاکس برای انتقال اطلاعات از این روش استفاده میکنند و بعضی از پایگاههای داده مانند RavenDB بر مبنای این تکنولوژی پایه گذاری شده اند.
هیچ چیزی نمیتواند مثل یک مثال؛ خوانایی ، سادگی و کم حجم بودن این روش را نشان دهد :
اگر یک شئ با ساختار زیر در سی شارپ داشته باشید :
class Customer { public int Id { get; set; } public string FirstName { get; set; } public string LastName { get; set; } }
ساختار JSON متناظر با آن ( در صورت این که مقدار دهی شده باشد ) به صورت زیر است:
{ "Id":1, "FirstName":"John", "LastName":"Doe" }
و در یک مثال پیچیدهتر :
class Customer { public int Id { get; set; } public string FirstName { get; set; } public string LastName { get; set; } public Car Car { get; set; } public IEnumerable<Location> Locations { get; set; } } class Location { public int Id { get; set; } public string Address { get; set; } public int Zip { get; set; } } class Car { public int Id { get; set; } public string Model { get; set; } }
{ "Id":1, "FirstName":"John", "LastName":"Doe", "Car": { "Id":1, "Model":"Nissan GT-R" }, "Locations":[ { "Id":1, "Address":"30 Mortensen Avenue, Salinas", "Zip":93905 }, { "Id":2, "Address":"65 West Alisal Street, #210, Salinas", "Zip":95812 } ] }
ساختار JSON را مجموعه ای از ( نام - مقدار ) تشکیل میدهد. ساختار مشابه آن در زبان سی شارپ KeyValuePair است.
مشاهده این تصاویر، بهترین درک را از ساختار JSON به شما میدهد.
Json.net یکی از بهترین کتابخانه هایی است که برای کار با این تکنولوژی در net. ارائه شده است. بهترین روش اضافه نمودن آن به پروژه NuGet است.برای این کار دستور زیر را در Package Manager Console وارد کنید.
PM> Install-Package Newtonsoft.Json
با استفاده از کد زیر میتوانید یک Object را به فرمت JSON تبدیل کنید.
var customer = new Customer { Id = 1, FirstName = "John", LastName = "Doe", Car = new Car { Id = 1, Model = "Nissan GT-R" }, Locations = new[] { new Location { Id = 1, Address = "30 Mortensen Avenue, Salinas", Zip = 93905 }, new Location { Id = 2, Address = "65 West Alisal Street, #210, Salinas", Zip = 95812 }, } };
var data = Newtonsoft.Json.JsonConvert.SerializeObject(customer);
خروجی تابع SerializeObject رشته ای است که محتوی آن را در چهارمین بلاک کد که در بالاتر آمده است، میتوانید مشاهده کنید.
برای Deserialize کردن (Cast اطلاعات با فرمت JSON به کلاس موردنظر) از روش زیر بهره میگیریم :
var customer = Newtonsoft.Json.JsonConvert.DeserializeObject<Customer>(data);
آشنایی با این تکنولوژی، پیش درآمدی برای چشیدن طعم NoSQL و معرفی کارآمدترین روشهای آن است که در آینده خواهیم آموخت...
خوشحال میشوم اگر نظرات شما را در باره این موضوع بدانم.
پیش از ادامهی مثال قسمت قبل، قصد داریم تمام کدهای موجود در فایل Pages\Index.razor را به یک فایل اختصاصی آنها منتقل کرده و مسیریابی و منوی آنرا تکمیل کنیم. به همین جهت در پوشهی Pages، یک پوشهی جدید را به نام LearnBlazor ایجاد کرده و درون آن، فایل خالی BindProp.razor را ایجاد میکنیم. سپس تمام محتوای فایل Pages\Index.razor را cut کرده و به درون فایل جدید Pages\LearnBlazor\BindProp.razor، منتقل و Paste میکنیم.
پس از این تغییرات، در فایل Pages\Index.razor، مهمترین سطر آن، همان اولین سطر تعریف مسیریابی آن خواهد بود و هر محتوای دلخواهی که علاقمند بودید:
@page "/" <h1>Hello, world!</h1>
@page "/bindprop"
<li class="nav-item px-3"> <NavLink class="nav-link" href="bindprop"> <span class="oi oi-list-rich" aria-hidden="true"></span> Bind Properties </NavLink> </li>
نمایش لیست اتاقهای تعریف شده، به همراه ویژگیهای آنها
در قسمت قبل، نمایش ردیفی لیست اتاقهای تعریف شده را مشاهده کردید. در این قسمت میخواهیم هر اتاق تعریف شده را در یک card جداگانه نمایش دهیم. هدف این است که در ابتدا به یک UI متداول شلوغ برسیم و بعد شروع کنیم به Refactoring این UI پیچیده، به کامپوننتهای کوچکتر تشکیل دهندهی آن، جهت مدیریت سادهتر این UI و درک بهتر آن. بنابراین در ابتدا با یک کامپوننت کلی شلوغ، شروع خواهیم کرد.
به همین جهت فایل جدید Pages\LearnBlazor\DemoHotel.razor را برای نمایش لیست اتاقهای موجود اضافه میکنیم. سپس محتوای آنرا به صورت زیر تغییر خواهیم داد:
@page "/demoHotel" <h3>Hotel Rooms</h3> <div class="border p-2 mt-2" style="background-color:azure"> <h2 class="text-info">Rooms List</h2> <div class="row container"> @foreach (var room in Rooms) { <div class="bg-light border p-2 col-5 ml-2"> <h4 class="text-secondary">Room - @room.Id</h4> @room.Name<br /> @room.Price.ToString("c")<br /> <input type="checkbox" @bind-value="room.IsActive" checked="@(room.IsActive?"checked":null)" /> Is Active<br /> <span>This room is @(room.IsActive?"Active": "InActive")</span> @if (room.IsActive) { @foreach (var roomProp in room.RoomProps) { <p>@roomProp.Name - @roomProp.Value</p> } } <input type="button" class="btn btn-danger" value="Delete" /> <input type="button" class="btn btn-success" value="Edit" /> </div> } </div> </div>
- سپس مسیریابی منتهی به این کامپوننت، به آدرس demoHotel/ تنظیم شدهاست. این مسیریابی را در کامپوننت Shared\NavMenu.razor به صورت زیر مورد استفاده قرار خواهیم داد تا مدخل منوی جدیدی برای آن تهیه شود:
<li class="nav-item px-3"> <NavLink class="nav-link" href="demoHotel"> <span class="oi oi-list-rich" aria-hidden="true"></span> Demo Hotel </NavLink> </li>
تبدیل دکمههای حذف و ویرایش هر اتاق به یک کامپوننت جدید
اکنون میخواهیم کامپوننت شلوغ Pages\LearnBlazor\DemoHotel.razor را به چند زیر کامپوننت بشکنیم تا هر کدام وظایف خاص خود را انجام دهند و در نهایت به یک UI قابل درکتر برسیم. برای مثال میخواهیم دکمههای حذف و ویرایش هر اتاق را به یک کامپوننت جدید منتقل کنیم تا هم این UI خلوتتر شود و هم اگر در قسمت دیگری از برنامه نیاز به یک چنین دکمههایی بود، بتوان از آن کامپوننت اختصاصی، استفادهی مجدد کرد.
برای این منظور ابتدا پوشهی جدید Pages\LearnBlazor\LearnBlazorComponents را افزوده و سپس در داخل آن، فایل جدید کامپوننت EditDeleteButton.razor را نیز ایجاد میکنیم. در این فایل جدید در ابتدا کدهای دو دکمهی تعریف شده را از کامپوننت DemoHotel.razor انتخاب و cut کرده و سپس در این فایل جدید paste میکنیم. در این کامپوننت جدید، نیازی به تعریف page@ و مسیریابی آن نیست. به این معنا که این کامپوننت، یک کامپوننت اشتراکی است و routable نیست و قرار است در داخل یک کامپوننت دیگر مورد استفاده قرار گیرد.
بنابراین تا اینجا محتوای کامپوننت EditDeleteButton.razor فقط از دو سطر زیر تشکیل میشود:
<input type="button" class="btn btn-danger" value="Delete" /> <input type="button" class="btn btn-success" value="Edit" />
<BlazorServerSample.Pages.LearnBlazor.LearnBlazorComponents.EditDeleteButton></BlazorServerSample.Pages.LearnBlazor.LearnBlazorComponents.EditDeleteButton>
@using BlazorServerSample.Pages.LearnBlazor.LearnBlazorComponents
اکنون میتوان تعریف مدخل کامپوننت را به صورت زیر خلاصه کرد:
<EditDeleteButton></EditDeleteButton>
ارسال پارامترها به یک کامپوننت
فرض کنید قصد داریم دکمههای ویرایش و حذف را تنها به کاربران ادمین نمایش دهیم. به همین جهت نیاز است بتوان پارامتری مانند IsAdmin را به کامپوننت EditDeleteButton ارسال کرد. برای اینکار کامپوننت Pages\LearnBlazor\LearnBlazorComponents\EditDeleteButton.razor را به صورت زیر ویرایش میکنیم:
@if (IsAdmin) { <input type="button" class="btn btn-danger" value="Delete" /> <input type="button" class="btn btn-success" value="Edit" /> } @code { [Parameter] public bool IsAdmin { get; set; } }
پس از تعریف این پارامتر ورودی، روش استفادهی از آن در کامپوننت DemoHotel به صورت زیر است:
<EditDeleteButton IsAdmin="true"></EditDeleteButton>
انتقال هر اتاق به کامپوننت مجزای خاص خودش
در ادامه میخواهیم محتوای حلقهی foreach (var room in Rooms)@ کامپوننت DemoHotel را به طور کامل cut کرده و در یک کامپوننت جدید paste کنیم تا به حلقهای خواناتر و با مسئولیتهای کمتری برسیم. نگهداری کدهایی که قسمتهای مختلف آن از هم ایزوله شدهاند و دامنهی تغییرات آنها کاملا مشخص و محدود است، در طول زمان بسیار سادهتر از نگهداری کدهای UI ای در هم تنیدهاست.
به همین جهت ابتدا کامپوننت جدید Pages\LearnBlazor\LearnBlazorComponents\IndividualRoom.razor را ایجاد میکنیم و سپس، هر آنچه داخل حلقهی foreach یاد شده قرار دارد را انتخاب و cut کرده و درون این کامپوننت جدید paste میکنیم:
<div class="bg-light border p-2 col-5 offset-1"> <h4 class="text-secondary">Room - @Room.Id</h4> @Room.Name<br /> @Room.Price.ToString("c")<br /> <input type="checkbox" @bind-value="Room.IsActive" checked="@(Room.IsActive?"checked":null)" /> Is Active<br /> <span>This room is @(Room.IsActive?"Active": "InActive")</span> @if (Room.IsActive) { @foreach (var roomProp in Room.RoomProps) { <p>@roomProp.Name - @roomProp.Value</p> } } <EditDeleteButton IsAdmin="true"></EditDeleteButton> </div> @code { [Parameter] public BlazorRoom Room { get; set; } }
پس از این تغییر، کدهای حلقهی foreach کامپوننت DemoHotel.razor به صورت زیر خلاصه میشوند. در اینجا روش ارسال یک شیء را به پارامتر Room نیز مشاهده میکنید (البته ذکر @ در اینجا الزامی نیست و میشد از روش مقدار دهی "Room="room نیز استفاده کرد):
<div class="row container"> @foreach (var room in Rooms) { <IndividualRoom Room="@room"></IndividualRoom> } </div>
یک تمرین: نمایش لیست امکانات رفاهی هتل
پس از نمایش لیست اتاقهای یک هتل، قصد داریم لیست امکانات رفاهی آنرا نیز نمایش دهیم:
مدل این امکانات را به صورت زیر به پوشهی Models برنامه اضافه میکنیم:
namespace BlazorServerSample.Models { public class BlazorAmenity { public int Id { get; set; } public string Name { get; set; } public string Description { get; set; } } }
@code{ List<BlazorAmenity> AmenitiesList = new List<BlazorAmenity>(); // ... protected override void OnInitialized() { base.OnInitialized(); // ... AmenitiesList.Add(new BlazorAmenity { Id = 111, Name = "Gym", Description = "24x7 gym room is available." }); AmenitiesList.Add(new BlazorAmenity { Id = 222, Name = "Swimming Pool", Description = "Pool room is open from 6am to 10pm." }); AmenitiesList.Add(new BlazorAmenity { Id = 333, Name = "Free Brakfast", Description = "Enjoy free breakfast at out hotel." }); } }
اکنون برای نمایش تک تک عناصر این لیست، ابتدا یک کامپوننت منحصر به یک BlazorAmenity را به نام Pages\LearnBlazor\LearnBlazorComponents\IndividualAmenity.razor ایجاد میکنیم با این محتوا:
<div class="bg-light border p-2 col-5 offset-1 mt-2"> <h4 class="text-secondary">Amenity - @Amenity.Id</h4> @Amenity.Name<br /> @Amenity.Description<br /> </div> @code { [Parameter] public BlazorAmenity Amenity { get; set; } }
در آخر پس از تعریف کامپوننت IndividualAmenity.razor، روش استفادهی از آن در کامپوننت DemoHotel به صورت زیر است:
<div class="col-12 mt-4"> <h4 class="text-info">Hotel Amenities</h4> </div> @foreach (var amenity in AmenitiesList) { <IndividualAmenity Amenity="@amenity"></IndividualAmenity> }
کدهای کامل این مطلب را از اینجا میتوانید دریافت کنید: Blazor-5x-Part-05.zip
حالتهای مختلف ذخیره سازی اطلاعات در مرورگر کاربر
Web Storage و یا Client-side storage در دو حالت کلی session storage و local storage قابل دسترسی است:
الف) session storage
در این حالت اطلاعات ذخیره شدهی در session storage، پس از بسته شدن مرورگر، به صورت خودکار حذف خواهند شد.
ب) local storage
اطلاعات ذخیره شدهی در local storage پس از بسته شدن مرورگر نیز باقی مانده و قابل دسترسی و بازیابی مجدد هستند. تاریخ انقضای آنها صرفا بر اساس خالی شدن دستی کش مرورگر توسط کاربر و یا حذف دستی اطلاعات آن توسط کدهای برنامه تعیین میشود.
هر دو حالت فوق به صورت ایزوله ارائه میشوند؛ با محدودیت حجم 10 مگابایت (جمع حجم نهایی هر دو حالت با هم، محدود به 10 مگابایت است). به این معنا که برنامههای هر دومین، تنها به محل ذخیره سازی خاص همان دومین دسترسی خواهند داشت.
همچنین API دسترسی به آنها synchronous است و کار کردن با آنها سادهاست.
البته Client-side storage به دو مورد فوق خلاصه نمیشود و شامل File Storage ،WebSQL ،IndexedDB و کوکیهای مرورگر نیز هست.
- File Storage هنوز مراحل آزمایشی خودش را طی میکند و مناسب برنامههای دنیای واقعی نیست.
- WebSQL قرار بود بر اساس بانک اطلاعاتی معروف SQLite ارائه شود؛ اما W3C در سال 2010 این استاندارد را منسوخ شده اعلام کرد و با IndexedDB جایگزین شد. دسترسی به آن async است و میتواند موضوع بحثی مجزا باشد.
- کوکیهای مرورگرها نیز یکی دیگر از روشهای ذخیره سازی اطلاعات در مرورگرها هستند و تنها به ذخیره سازی حداکثر 4096 بایت اطلاعات محدود هستند. کوکیها نیز همانند local storage پس از بسته شدن مرورگر باقی میمانند؛ اما برخلاف آن، دارای تاریخ انقضاء و همچنین قابلیت ارسال بین دومینها را نیز دارا میباشند. اگر تاریخ انقضای یک کوکی تعیین نشود، همانند session storage، در پایان کار مرورگر و بسته شدن آن، حذف خواهد شد.
تهیه یک سرویس Angular برای کار با Web Storage
جهت کپسوله سازی نحوهی کار با session storage و local storage میتوان سرویسی را برای اینکار تهیه کرد:
import { Injectable } from "@angular/core"; @Injectable() export class BrowserStorageService { getSession(key: string): any { const data = window.sessionStorage.getItem(key); return JSON.parse(data); } setSession(key: string, value: any): void { const data = value === undefined ? null : JSON.stringify(value); window.sessionStorage.setItem(key, data); } removeSession(key: string): void { window.sessionStorage.removeItem(key); } removeAllSessions(): void { for (const key in window.sessionStorage) { if (window.sessionStorage.hasOwnProperty(key)) { this.removeSession(key); } } } getLocal(key: string): any { const data = window.localStorage.getItem(key); return JSON.parse(data); } setLocal(key: string, value: any): void { const data = value === undefined ? null : JSON.stringify(value); window.localStorage.setItem(key, data); } removeLocal(key: string): void { window.localStorage.removeItem(key); } removeAllLocals(): void { for (const key in window.localStorage) { if (window.localStorage.hasOwnProperty(key)) { this.removeLocal(key); } } } }
در حالت setItem اطلاعاتی را که مرورگرها ذخیره میکنند باید رشتهای باشد. به همین جهت توسط متد JSON.stringify میتوان یک شیء را تبدیل به رشته کرد و ذخیره نمود و در حالت getItem توسط متد JSON.parse، میتوان این رشته را مجددا به همان شیء پیشین خود تبدیل کرد و بازگشت داد.
محل صحیح تعریف BrowserStorageService
همانطور که در مطلب «سازماندهی برنامههای Angular توسط ماژولها» بررسی شد، محل صحیح تعریف این سرویس سراسری مشترک در بین کامپوننتها و ماژولهای برنامه، در CoreModule و پوشهی src\app\core\browser-storage.service.ts است:
import { BrowserStorageService } from "./browser-storage.service"; import { NgModule } from "@angular/core"; import { CommonModule } from "@angular/common"; import { RouterModule } from "@angular/router"; @NgModule({ imports: [CommonModule, RouterModule], exports: [], // components that are used in app.component.ts will be listed here. declarations: [], // components that are used in app.component.ts will be listed here. providers: [BrowserStorageService] // singleton services of the whole app will be listed here. }) export class CoreModule { };
و CoreModule نیز به AppModule اضافه میشود:
import { CoreModule } from "./core/core.module"; @NgModule({ imports: [ //... CoreModule, //... RouterModule.forRoot(appRoutes) ], //... }) export class AppModule { }
بنابراین یکی دیگر از روشهای به اشتراک گذاری اطلاعات در بین قسمتهای مختلف برنامه، ذخیره سازی آنها در session/local storage و سپس بازیابی آنها بر اساس کلیدهای مشخص آنها است.
مثالی از نحوهی کاربرد BrowserStorageService
برای آزمایش سرویس تهیه شده، از کامپوننت و قالب ذیل استفاده خواهیم کرد. در اینجا سرویس BrowserStorageService به سازندهی کلاس تزریق شدهاست و سپس دو حالت session storage و local storage مورد بررسی قرار گرفتهاند:
import { BrowserStorageService } from "./../../core/browser-storage.service"; import { Component, OnInit } from "@angular/core"; @Component({ selector: "app-browser-storage-sample-test", templateUrl: "./browser-storage-sample-test.component.html", styleUrls: ["./browser-storage-sample-test.component.css"] }) export class BrowserStorageSampleTestComponent implements OnInit { fromSessionStorage = ""; fromLocalStorage = "" sessionStorageKey = "sessionStorageKey1"; localStorageKey = "localStorageKey1" constructor(private browserStorage: BrowserStorageService) { } ngOnInit() { } sessionStorageSetItem() { this.browserStorage.setSession(this.sessionStorageKey, "Val1"); } sessionStorageGetItem() { this.fromSessionStorage = this.browserStorage.getSession(this.sessionStorageKey); } localStorageSetItem() { this.browserStorage.setLocal(this.localStorageKey, { key1: "val1", key2: 2 }); } localStorageGetItem() { this.fromLocalStorage = JSON.stringify(this.browserStorage.getLocal(this.localStorageKey)); } }
<h1>Browser storage sample</h1> <div class="panel"> <button class="btn btn-primary" (click)="sessionStorageSetItem()" type="button">sessionStorage -> Set Item</button> <button class="btn btn-success" (click)="sessionStorageGetItem()" type="button">sessionStorage -> Get Item</button> <div class="alert alert-info" *ngIf="fromSessionStorage"> {{fromSessionStorage}} </div> </div> <div class="panel"> <button class="btn btn-warning" (click)="localStorageSetItem()" type="button">localStorage -> Set Item</button> <button class="btn btn-success" (click)="localStorageGetItem()" type="button">localStorage -> Get Item</button> <div class="alert alert-info" *ngIf="fromLocalStorage"> {{fromLocalStorage}} </div> </div>
در این حالت اگر برنامه را اجرا کنیم، یک چنین خروجی قابل مشاهده خواهد بود:
و اگر به برگهی Application کنسول ابزارهای توسعه دهندههای مرورگرها نیز مراجعه کنیم، این مقادیر ثبت شده را در دو حالت استفادهی از session storage و local storage، میتوان مشاهده کرد:
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید.