نظرات مطالب
پایان پروژه ASP.NET Ajax Control Toolkit !
سلام جناب نصیری یکی از مشکلاتی که برنامه نویسان تازه کار را اذیت می کند اجرای Jquery از سمت سرور هست.
برای حل این ضعف چه توصیه ای دارید
مثلا Ajax پارامتر ها را سمت سرور ست می کردیم و استفاده می کردیم.
اما Jquery باید سمت HTML همه کار را انجام داد.
مطالب
بررسی Transactions و Locks در SQL Server

مقدمه

SQL Server، با هر تقاضا به عنوان یک واحد مستقل رفتار می‌کند. در وضعیت‌های پیچیده ای که فعالیت‌ها توسط مجموعه ای از دستورات SQL انجام می‌شود، به طوری که یا همه باید اجرا شوند یا هیچکدام اجرا نشوند، این روش مناسب نیست. در چنین وضعیت هایی، نه تنها تقاضاهای موجود در یک دنباله به یکدیگر بستگی دارند، بلکه شکست یکی از تقاضاهای موجود در دنباله، به معنای این است که کل تقاضاهای موجود در دنباله باید لغو شوند، و تغییرات حاصل از تقاضاهای اجراشده در آن دنباله خنثی شوند تا بانک اطلاعاتی به حالت قبلی برگردد.

1- تراکنش چیست؟

تراکنش شامل مجموعه ای از یک یا چند دستور SQL است که به عنوان یک واحد عمل می‌کنند. اگر یک دستور SQL در این واحد با موفقیت اجرا نشود، کل آن واحد خنثی می‌شود و داده هایی که در اجرای آن واحد تغییر کرده اند، به حالت اول برگردانده می‌شود. بنابراین تراکنش وقتی موفق است که هر یک از دستورات آن با موفقیت اجرا شوند. برای درک مفهوم تراکنش مثال زیر را در نظر بگیرید: سهامدار A در معامله ای 400 سهم از شرکتی را به سهامدار B می‌فروشد. در این سیستم، معامله وقتی کامل می‌شود که حساب سهامدار A به اندازه 400 بدهکار و حساب سهامدار B همزمان به اندازه 400 بستانکار شود. اگر هر کدام از این مراحل با شکست مواجه شود، معامله انجام نمی‌شود.


2- خواص تراکنش

هر تراکنش دارای چهار خاصیت است (معروف به ACID) که به شرح زیر می‌باشند:


2-1- خاصیت یکپارچگی (Atomicity)

یکپارچگی به معنای این است که تراکنش باید به عنوان یک واحد منسجم (غیر قابل تفکیک) در نظر گرفته شود. در مثال مربوط به مبادله سهام، یکپارچگی به معنای این است که فروش سهام توسط سهامدار A و خرید آن سهام توسط سهامدار B، مستقل از هم قابل انجام نیستند و برای این که تراکنش کامل شود، هر دو عمل باید با موفقیت انجام شوند.
اجرای یکپارچه، یک عمل "همه یا هیچ" است. در عملیات یکپارچه، اگر هر کدام از دستورات موجود در تراکنش با شکست مواجه شوند، اجرای تمام دستورات قبلی خنثی می‌شود تا به جامعیت بانک اطلاعاتی آسیب نرسد.

2-2- خاصیت سازگاری (Consistency)

سازگاری زمانی وجود دارد که هر تراکنش، سیستم را در یک حالت سازگار قرار دهد (چه تراکنش به طور کامل انجام شود و چه در اثر وجود خطایی خنثی گردد). در مثال مبادله سهام، سازگاری به معنای آن است که هر بدهکاری مربوط به حساب فروشنده، موجب همان میزان بستانکاری در حساب خریدار می‌شود.
در SQL Server، سازگاری با راهکار ثبت فایل سابقه انجام می‌گیرد که تمام تغییرات را در بانک اطلاعاتی ذخیره می‌کند و جزییات را برای ترمیم تراکنش ثبت می‌نماید. اگر سیستم در اثنای اجرای تراکنش خراب شود، فرآیند ترمیم SQL Server با استفاده از این اطلاعات، تعیین می‌کند که آیا تراکنش با موفقیت انجام شده است یا خیر، و در صورت عدم موفقیت آن را خنثی می‌کند. خاصیت سازگاری تضمین می‌کند که بانک اطلاعاتی هیچگاه تراکنش‌های ناقص را نشان نمی‌دهد.

2-3- خاصیت تفکیک (Isolation)

تفکیک موجب می‌شود هر تراکنش در فضای خودش و جدا از سایر تراکنش‌های دیگری که در سیستم انجام می‌گیرد، اجرا شود و نتایج هر تراکنش فقط در صورت کامل شدن آن قابل مشاهده است. اگر چندین تراکنش همزمان در سیستم در حال اجرا باشند، اصل تفکیک تضمین می‌کند که اثرات یک تراکنش تا کامل شدن آن، قابل مشاهده نیست. در مثال مربوط به مبادله سهام، اصل تفکیک به معنای این است که تراکنش بین دو سهامدار، مستقل از تمام تراکنش‌های دیگری است که در سیستم به مبادله سهام می‌پردازند و اثر آن وقتی برای افراد قابل مشاهده است که آن تراکنش کامل شده باشد. این اصل در مواردی که سیستم همزمان از چندین کاربر پشتیبانی می‌کند، مفید است.

2-4- پایداری (Durability)

پایداری به معنای این است که تغییرات حاصل از نهایی شدن تراکنش، حتی در صورت خرابی سیستم نیز پایدار می‌ماند. اغلب سیستم‌های مدیریت بانک اطلاعاتی رابطه ای، از طریق ثبت تمام فعالیت‌های تغییر دهنده‌ی داده‌ها در بانک اطلاعاتی، پایداری را تضمین می‌کنند. در صورت خرابی سیستم یا رسانه ذخیره سازی داده ها، سیستم قادر است آخرین بهنگام سازی موفق را هنگام راه اندازی مجدد، بازیابی کند. در مثال مربوط به مبادله سهام، پایداری به معنای این است که وقتی انتقال سهام از سهامدار A به B با موفقیت انجام گردید، حتی اگر سیستم بعداً خراب شد، باید نتیجه‌ی آن را منعکس سازد.

3- مشکلات همزمانی(Concurrency Effects):

3-1- Dirty Read:

زمانی روی می‌دهد که تراکنشی رکوردی را می‌خواند، که بخشی از تراکنشی است که هنوز تکمیل نشده است، اگر آن تراکنش Rollback شود اطلاعاتی از بانک اطلاعاتی دارید که هرگز روی نداده است.
 اگر سطح جداسازی تراکنش (پیش فرض) Read Committed باشد، این مشکل بوجود نمی‌آید.

3-2- Non-Repeatable Read:

زمانی ایجاد می‌شود که رکوردی را دو بار در یک تراکنش می‌خوانید و در این اثنا یک تراکنش مجزای دیگر داده‌ها را تغییر می‌دهد. برای پیشگیری از این مسئله باید سطح جداسازی تراکنش برابر با Repeatable Read یا Serializable باشد.

3-3- Phantoms:

با رکوردهای مرموزی سروکار داریم که گویی تحت تاثیر عبارات Update و Delete صادر شده قرار نگرفته اند. به طور خلاصه شخصی عبارت Insert را درست در زمانی که Update مان در حال اجرا بوده انجام داده است، و با توجه به اینکه ردیف جدیدی بوده و قفلی وجود نداشته، به خوبی انجام شده است. تنها چاره این مشکل تنظیم سطح Serializable است و در این صورت بهنگام رسانی‌های جداول نباید درون بخش Where قرار گیرد، در غیر این صورت Lock خواهند شد.

3-4- Lost Update:

زمانی روی می‌دهد که یک Update به طور موفقیت آمیزی در بانک اطلاعاتی نوشته می‌شود، اما به طور اتفاقی توسط تراکنش دیگری بازنویسی می‌شود. راه حل این مشکل بستگی به کد شما دارد و بایست به نحوی تشخیص دهید، بین زمانی که داده‌ها را می‌خوانید و زمانی که می‌خواهید آنرا بهنگام کنید، اتصال دیگری رکورد شما را بهنگام کرده است.

4- منابع قابل قفل شدن

6 منبع قابل قفل شدن برای SQL Server وجود دارد و آن‌ها سلسله مراتبی را تشکیل می‌دهند. هر چه سطح قفل بالاتر باشد، Granularity  کمتری دارد. در ترتیب آبشاری Granularity عبارتند از:
•  Database: کل بانک اطلاعاتی قفل شده است، معمولاً طی تغییرات Schema بانک اطلاعاتی روی می‌دهد.
•  Table: کل جدول قفل شده است، شامل همه اشیای مرتبط با جدول.
•  Extent: کل Extent (متشکل از هشت Page) قفل شده است.
•  Page: همه داده‌ها یا کلیدهای Index در آن Page قفل شده اند.
•  Key: قفلی در کلید مشخصی یا مجموعه کلید هایی Index وجود دارد. ممکن است سایر کلید‌ها در همان Index Page تحت تاثیر قرار نگیرند.
•  (Row or Row Identifier (RID: هر چند قفل از لحاظ فنی در Row Identifier قرار می‌گیرد ولی اساساً کل ردیف را قفل می‌کند.

5- تسریع قفل (Lock Escalation) و تاثیرات قفل روی عملکرد

اگر تعداد آیتم‌های قفل شده کم باشد نگهداری سطح بهتری از Granularity (مثلاً RID به جای Page) معنی دار است. هرچند با افزایش تعداد آیتم‌های قفل شده، سربار مرتبط با نگهداری آن قفل‌ها در واقع باعث کاهش عملکرد می‌شود، و می‌تواند باعث شود قفل به مدت طولانی‌تری در محل باشد(هر چه قفل به مدت طولانی‌تری در محل باشد، احتمال این که شخصی آن رکورد خاص را بخواهد بیشتر است).
هنگامی که تعداد قفل نگهداری شده به آستانه خاصی برسد آن گاه قفل به بالاترین سطح بعدی افزایش می‌یابد و قفل‌های سطح پایین‌تر نباید به شدت مدیریت شوند (آزاد کردن منابع و کمک به سرعت در مجادله).
توجه شود که تسریع مبتنی بر تعداد قفل هاست و نه تعداد کاربران.

6- حالات قفل (Lock Modes):

همانطور که دامنه وسیعی از منابع برای قفل شدن وجود دارد، دامنه ای از حالات قفل نیز وجود دارد.

6-1- (Shared Locks (S:

زمانی استفاده می‌شود، که فقط باید داده‌ها را بخوانید، یعنی هیچ تغییری ایجاد نخواهید کرد. Shared Lock با سایر Shared Lock‌های دیگر سازگار است، البته قفل‌های دیگری هستند که با Shared Lock سازگار نیستند. یکی از کارهایی که Shared Lock انجام می‌دهد، ممانعت از انجام Dirty Read از طرف کاربران است.

6-2- (Exclusive Locks (X:

این قفل‌ها با هیچ قفل دیگری سازگار نیستند. اگر قفل دیگری وجود داشته باشد، نمی‌توان به Exclusive Lock دست یافت و همچنین در حالی که Exclusive Lock فعال باشد، به هر قفل جدیدی از هر شکل اجازه ایجاد شدن در منبع را نمی‌دهند.
این قفل از اینکه دو نفر همزمان به حذف کردن، بهنگام رسانی و یا هر کار دیگری مبادرت ورزند، پیشگیری می‌کند.

6-3- (Update Locks (U:

این قفل ‌ها نوعی پیوند میان Shared Locks و Exclusive Locks هستند.
برای انجام Update باید بخش Where را (در صورت وجود) تایید اعتبار کنید، تا دریابید فقط چه ردیف هایی را می‌خواهید بهنگام رسانی کنید. این بدان معنی است که فقط به Shared Lock نیاز دارید، تا زمانی که واقعاً بهنگام رسانی فیزیکی را انجام دهید. در زمان بهنگام سازی فیزیکی نیاز به Exclusive Lock دارید.
Update Lock نشان دهنده این واقعیت است که دو مرحله مجزا در بهنگام رسانی وجود دارد، Shared Lock ای دارید که در حال تبدیل شدن به Exclusive Lock است. Update Lock تمامی Update Lock‌های دیگر را از تولید شدن باز می‌دارند، و همچنین فقط با Shared Lock و Intent Shared Lock‌ها سازگار هستند.

6-4- Intent Locks:

با سلسله مراتب شی سر و کار دارد. بدون Intent Lock، اشیای سطح بالاتر نمی‌دانند چه قفلی را در سطح پایین‌تر داشته اید. این قفل‌ها کارایی را افزایش می‌دهند و 3 نوع هستند:

6-4-1- (Intent Shared Lock (IS:

Shared Lock در نقطه پایین‌تری در سلسله مراتب، تولید شده یا در شرف تولید است. این نوع قفل تنها به Table و Page اعمال می‌شود.

6-4-2- (Intent Exclusive Lock (IX:

همانند Intent Shared Lock است اما در شرف قرار گرفتن در آیتم سطح پایین‌تر است.

6-4-3- (Shared With Intent Exclusive (SIX:

Shared Lock در پایین  سلسله مراتب شی تولید شده یا در شرف تولید است اما Intent Lock قصد اصلاح داده‌ها را دارد بنابراین در نقطه مشخصی تبدیل به Intent Exclusive Lock می‌شود.

6-5- Schema Locks:

به دو شکل هستند:

6-5-1- (Schema Modification Lock (Sch-M:

تغییر Schema به شی اعمال شده است. هیچ پرس و جویی یا سایر عبارت‌های Create، Alter و Drop نمی‌توانند در مورد این شی در مدت قفل Sch-M اجرا شوند. با همه حالات قفل ناسازگار است.

6-5-2- (Schema Stability Lock (Sch-S:

بسیار شبیه به Shared Lock است، هدف اصلی این قفل پیشگیری از Sch-M است وقتی که قبلاً قفل هایی برای سایر پرس و جو-ها (یا عبارت‌های Create، Alter و Drop) در شی فعال شده اند. این قفل با تمامی انواع دیگر قفل سازگار است به جز با Sch-M.

6-6- (Bulk Update Locks (BU:

این قفل‌ها بارگذاری موازی داده‌ها را امکان پذیر می‌کنند، یعنی جدول در مورد هر فعالیت نرمال (عبارات T-SQL) قفل می‌شود، اما چندین عمل bcp یا Bulk Insert را می‌توان در همان زمان انجام داد. این قفل فقط با Sch-S و سایر قفل هایBU سازگار است.

7- سطوح جداسازی (Isolation Level):

7-1- Read Committed (وضعیت پیش فرض):

با Read Committed همه Shared Lock‌های ایجاد شده، به محض اینکه عبارت ایجاد کننده آنها تکمیل شود، به طور خودکار آزاد می‌شوند. به طور خلاصه قفل‌های مرتبط با عبارت Select به محض تکمیل عبارت Select آزاد می‌شوند و SQL Server منتظر پایان تراکنش نمی‌ماند. اگر تراکنش پرس و جویی را انجام می‌دهد که داده‌ها را اصلاح می‌کند (Insert، Delete و Update) قفل‌ها برای مدت تراکنش نگه داشته می‌شوند.
با این سطح پیش فرض، می‌توانید مطمئن شوید جامعیت کافی برای پیشگیری از Dirty Read دارید، اما همچنان Phantoms و Non-Repeatable Read می‌تواند روی دهد.

7-2- Read Uncommitted:

خطرناک‌ترین گزینه از میان تمامی گزینه‌ها است، اما بالاترین عملکرد را به لحاظ سرعت دارد. در واقع با این تنظیم سطح تجربه همه مسائل متعدد هم زمانی مانند Dirty Read امکان پذیر است. در واقع با تنظیم این سطح به SQL Server اعلام می‌کنیم هیچ قفلی را تنظیم نکرده و به هیچ قفلی اعتنا نکند، بنابراین هیچ تراکنش دیگری را مسدود نمی‌کنیم.
می‌توانید همین اثر Read Uncommitted را با اضافه کردن نکته بهینه ساز  NOLOCK در پرس و جو‌ها بدست آورید.

7-3- Repeatable Read:

سطح جداسازی را تا حدودی افزایش می‌دهد و سطح اضافی محافظت همزمانی را با پیشگیری از Dirty Read و همچنین Non-Repeatable Read فراهم می‌کند.
پیشگیری از Non-Repeatable Read بسیار مفید است اما حتی نگه داشتن Shared Lock تا زمان پایان تراکنش می‌تواند دسترسی کاربران به اشیا را مسدود کند، بنابراین به بهره وری لطمه وارد می‌کند.
نکته بهینه ساز برای این سطح REPEATEABLEREAD است.

7-4- Serializable:

این سطح از تمام مسائل هم زمانی پیشگیری می‌کند به جز برای Lost Update.
این تنظیم سطح به واقع بالاترین سطح آنچه را که سازگاری نامیده می‌شود، برای پایگاه داده فراهم می‌کند. در واقع فرآیند بهنگام رسانی برای کاربران مختلف به طور یکسان عمل می‌کند به گونه ای که اگر همه کاربران یک تراکنش را در یک زمان اجرا می‌کردند، این گونه می‌شد « پردازش امور به طور سریالی».
با استفاده از نکته بهینه ساز SERIALIZABLE یا HOLDLOCK در پرس و جو شبیه سازی می‌شود.

7-5- Snapshot:

جدترین سطح جداسازی است که در نسخه 2005 اضافه شد، که شبیه ترکیبی از Read Committed و Read Uncommitted است. به طور پیش فرض در دسترس نیست، در صورتی در دسترس است که گزینه ALLOW_SNAPSHOT_ISOLATION برای بانک اطلاعاتی فعال شده  باشد.(برای هر بانک اطلاعاتی موجود در تراکنش)
Snapshot مشابه Read Uncommitted هیچ قفلی ایجاد نمی‌کند. تفاوت اصلی آن‌ها در این است که تغییرات صورت گرفته در بانک اطلاعاتی را در زمان‌های متفاوت تشخیص می‌دهند. هر تغییر در بانک اطلاعاتی بدون توجه به زمان یا Commit شدن آن، توسط پرس و جو هایی که سطح جداسازی Read Uncommitted را اجرا می‌کنند، دیده می‌شود. با Snapshot فقط تغییراتی که قبل از شروع تراکنش، Commit شده اند، مشاهده می‌شود.
از شروع تراکنش Snapshot، تمامی داده‌ها دقیقاً مشاهده می‌شوند، زیرا در شروع تراکنش Commit شده اند.
نکته: در حالی که Snapshot توجهی به قفل‌ها و تنظیمات آنها ندارد، یک حالت خاص وجود دارد. چنانچه هنگام انجام Snapshot یک عمل Rollback (بازیافت) بانک اطلاعاتی در جریان باشد، تراکنش Snapshot قفل‌های خاصی را برای عمل کردن به عنوان یک مکان نگهدار  و سپس انتظار برای تکمیل Rollback تنظیم می‌کند. به محض تکمیل Rollback، قفل حذف شده و Snapshot به طور طبیعی به جلو حرکت خواهد کرد.


 
مطالب
آشنایی با SQL Server Data Tools
مقدمه
یکی از امکانات اضافه شده در Sql Server 2012 ابزار Sql Server Data Tools یا به اختصار SSDT می‌باشد. این ابزار در واقع جایگزین ابزار Business Intelligence Development Studio می باشد که همه امکانات قبلی را داشته و همچنین قابلیت‌های جدیدی نیز به آن اضافه شد است. اما کاربرد این ابزار ایجاد محیطی یکپارچه برای طراحی و توسعه تمامی نسخه‌های پایگاه داده Sql Server با استفاده از Visual Studio می‌باشد. در این مقاله من به بیشتر بر روی امکانات زیر تمرکز دارم:
  • Installation
  • Creating a SQL Server Database Project
  • Modify Database Schema
  • Schema Compare and Update
  • Snapshot Project
  • Publish
  1. نصب Sql Server Data Tools
    آخرین نسخه این ابزار را از این آدرس دانلود کنید یا زمان نصب Sql Server 2012 گزینه آن را انتخاب کنید


  2. ایجاد یک پروژه جدید از نوع  SQL Server Database Project
    پس از نصب SSDT شما از طریق Visual Studio 2012 Shell که همراه SqlServer 2012 نصب می‌شود یا با Visual Stadio 2012 یک پروژه جدید از نوع SQL Server Database Project ایجاد کنید.

     Server Database Project Project به شما امکان توسعه پایگاه داده Sql Server را با استفاده  از محیط یکپارچه Visual Studio با در اختیار گذاشتن ابزار‌های همچون navigation, intellisense, validation, debugging, declarative editing و غیره را می‌دهد. شی پایگاه داده شما در پروژه و فایل‌های مجزا ذخیره می‌شود مثل آن که شما در حال توسعه برنامه #C یا VB.NET هستید.
  3. ورود پایگاه داده موجود به پروژه SQL Server Database
    شما می‌توانید شمای پایگاه داده موجود یا SQL Server DAC Package File (.dacpac) یا هر T-SQL دیگری را با راست کلیک بر روی پروژه و انتخاب گزینه Import به پروژه خود اضافه و تغییرات لازم را اعمال نماید شکل زیر پنجره Import Database  را نشان می‌دهد:
     

      پنجره 
    Solution Explorer امکانات زیر را در اختیار شما قرارمی دهد:

    - اضافه کردن اشیای همچون Table، View  و غیره با راست کیلک کردن بر روی پروژه و انتخاب گزینه Add
    - ویرایش اشیا موجود با دبل کیلک کردن بر روی اشیا
    - مقایسه شمای پایگاه داده با پایگاه داده دیگر و یا Microsoft SQL Server DAC Package File کافیست بر روی پروژه راست کیلک و گزینه Schema Compare را         انتخاب نمایید.
    - ایجاد یک Snapshot از شمای پایگاه داده در یک Microsoft SQL Server DAC Package File . یک snapshot برای ایجاد یک پایگاه داده یا ورود در پروژه ای دیگر یا در مقایسه دو پایگاه داده کاربرد دارد.
    - انتشار پایگاه داده که امکان ایجاد پایگاه داده یر روی یک سرور SQL Server را فراهم می‌نماید.  

  4. مقایسه شمای دو پایگاه داده با هم
    SSDT ابزاری برای مقایسه دو پایگاه داده و بروز رسانی پایگاه داده مقصد از روی شمای منبع از طریق ایجاد یک Script یا به صورت مستقیم را می‌دهد.



  5. انتشار پروژه با استفاده از SQL Server Data Tools
    این ابزار امکان گسترش پروژه شما بر روی SQL Server 2005, 2008, 2008 R2, 2012, یا SQL Azure instance را می‌دهد. برای انتشار کافیت از پنجره Solution Explorer  بر روی پروژه راست کیلک و گزینه Publish را انتخاب کنید.

  6. پنجره SQL Server Object Explorer
    این پنجره با اتصال به سرور SQL Server  امکان دسترسی و مدیریت پایگاه داده را به ما می‌دهد. علاوه بر دسترسی به اجزای یک پایگاه داده امکان مدیریت پوشه‌های Security , Server Objects نیز فراهم می‌باشد. همچنین امکان اتصال به Sql Server Express , localdb نیز وجود دارد. 


  7.   
مطالب
تهیه قالب برای ارسال ایمیل‌ها در ASP.NET Core توسط Razor Viewها
برای ارسال متن ایمیل‌ها، یا می‌توان یک سری رشته را با هم جمع زد و ارسال کرد و یا یک View را به همراه ViewModel آن، طراحی و سپس این View را تبدیل به یک رشته کرد. روش دوم هم قابلیت طراحی بهتری دارد و هم نگهداری و توسعه‌ی آن ساده‌تر است. در ادامه روش تبدیل Razor Viewهای ASP.NET Core را به یک رشته، بررسی می‌کنیم.


تهیه سرویسی برای رندر کردن Razor Viewها به صورت یک رشته

در ادامه کدهای کامل سرویسی را که توسط RazorViewEngine کار رندر کردن یک View و تبدیل آن‌را به رشته انجام می‌دهد، ملاحظه می‌کنید:
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.Razor;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.DependencyInjection;
using System.IO;
using System.Threading.Tasks;
using System;
 
namespace WebToolkit
{
    public static class RazorViewToStringRendererExtensions
    {
        public static IServiceCollection AddRazorViewRenderer(this IServiceCollection services)
        {
            services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
            services.AddScoped<IViewRendererService, ViewRendererService>();
            return services;
        }
    }
 
    public interface IViewRendererService
    {
        Task<string> RenderViewToStringAsync(string viewNameOrPath);
        Task<string> RenderViewToStringAsync<TModel>(string viewNameOrPath, TModel model);
    }
 
    /// <summary>
    /// Modified version of: https://github.com/aspnet/Entropy/blob/dev/samples/Mvc.RenderViewToString/RazorViewToStringRenderer.cs
    /// </summary>
    public class ViewRendererService : IViewRendererService
    {
        private readonly IRazorViewEngine _viewEngine;
        private readonly ITempDataProvider _tempDataProvider;
        private readonly IServiceProvider _serviceProvider;
        private readonly IHttpContextAccessor _httpContextAccessor;
 
        public ViewRendererService(
                    IRazorViewEngine viewEngine,
                    ITempDataProvider tempDataProvider,
                    IServiceProvider serviceProvider,
                    IHttpContextAccessor httpContextAccessor)
        {
            _viewEngine = viewEngine;
            _tempDataProvider = tempDataProvider;
            _serviceProvider = serviceProvider;
            _httpContextAccessor = httpContextAccessor;
        }
 
        public Task<string> RenderViewToStringAsync(string viewNameOrPath)
        {
            return RenderViewToStringAsync(viewNameOrPath, string.Empty);
        }
 
        public async Task<string> RenderViewToStringAsync<TModel>(string viewNameOrPath, TModel model)
        {
            var actionContext = getActionContext();
 
            var viewEngineResult = _viewEngine.FindView(actionContext, viewNameOrPath, isMainPage: false);
            if (!viewEngineResult.Success)
            {
                viewEngineResult = _viewEngine.GetView("~/", viewNameOrPath, isMainPage: false);
                if (!viewEngineResult.Success)
                {
                    throw new FileNotFoundException($"Couldn't find '{viewNameOrPath}'");
                }
            }
 
            var view = viewEngineResult.View;
            using (var output = new StringWriter())
            {
                var viewDataDictionary = new ViewDataDictionary<TModel>(new EmptyModelMetadataProvider(), new ModelStateDictionary())
                {
                    Model = model
                };
 
                var viewContext = new ViewContext(
                    actionContext,
                    view,
                    viewDataDictionary,
                    new TempDataDictionary(actionContext.HttpContext, _tempDataProvider),
                    output,
                    new HtmlHelperOptions());
                await view.RenderAsync(viewContext).ConfigureAwait(false);
                return output.ToString();
            }
        }
 
        private ActionContext getActionContext()
        {
            var httpContext = _httpContextAccessor?.HttpContext;
            if (httpContext != null)
            {
                return new ActionContext(httpContext, httpContext.GetRouteData(), new ActionDescriptor());
            }
 
            httpContext = new DefaultHttpContext { RequestServices = _serviceProvider };
            return new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
        }
    }
}
توضیحات:
اصل این کد متعلق است به مایکروسافت در اینجا. اما در کدهای فوق سه قسمت آن بهبود یافته‌است:
الف) به سازنده‌ی کلاس، سرویس IHttpContextAccessor نیز تزریق شده‌است تا بتوان به HttpContext و اطلاعات آن دسترسی یافت. حالت پیش فرض آن، استفاده از new DefaultHttpContext است. در این حالت اگر در قالب‌های ایمیل‌های خود از Url.Action استفاده کنید، استثنای index out of range مربوط به یافت نشدن مسیریابی‌ها را دریافت خواهید کرد. علت اینجا است که new DefaultHttpContext حاوی اطلاعات مسیریابی درخواست جاری سیستم نیست. به همین جهت توسط IHttpContextAccessor در متد getActionContext، کار مقدار دهی قسمت مسیریابی صورت گرفته‌است.
ب) در کدهای مثال اصلی، فقط viewEngine.FindView ذکر شده‌است. این متد حالت‌های یافتن Viewهایی را به صورت FolderName/ViewName، پشتیبانی می‌کند. اگر بخواهیم یک مسیر کامل را مانند "Areas/Identity/Views/EmailTemplates/_RegisterEmailConfirmation.cshtml/~" ذکر کنیم، کار نمی‌کند. به همین جهت در ادامه، بررسی viewEngine.GetView نیز اضافه شده‌است تا این نقصان را پوشش دهد.
ج) یک overload اضافه‌تر هم جهت رندر یک View بدون مدل نیز به آن اضافه شده‌است.


روش استفاده‌ی از سرویس ViewRenderer

اسمبلی که این سرویس در آن تعریف می‌شود باید دارای وابستگی‌های ذیل باشد:
{ 
    "dependencies": {
        "Microsoft.AspNetCore.Mvc.ViewFeatures": "1.1.0",
        "Microsoft.AspNetCore.Mvc.Razor": "1.1.0"
    }
}
سپس در متد ConfigureServices کلاس آغازین برنامه، سرویس‌های مورد نیاز را اضافه کنید:
public void ConfigureServices(IServiceCollection services)
{
   services.AddRazorViewRenderer();
}
کار متد AddRazorViewRenderer، افزودن سرویس‌های IViewRendererService و همچنین IHttpContextAccessor است.
پس از ثبت سرویس‌های مورد نیاز، اکنون می‌توان سرویس IViewRendererService را به سازنده‌ی کنترلرها و یا کلاس‌های برنامه تزریق و از متدهای RenderViewToStringAsync آن استفاده کرد:
public class RenderController : Controller
{
    private readonly IViewRendererService _viewRenderService;
    public RenderController(IViewRendererService viewRenderService)
    {
        _viewRenderService = viewRenderService;
    }
 
    public async Task<IActionResult> RenderInviteView()
    {
        var viewModel = new InviteViewModel
        {
            UserId = "1",
            UserName = "Vahid"
        };
        var emailBody = await _viewRenderService.RenderViewToStringAsync("EmailTemplates/Invite", viewModel).ConfigureAwait(false);
        //todo: send emailBody
        return Content(emailBody);
    }
}
برای مثال در اینجا در قالب Invite (یا فایل invite.cshtml) واقع در پوشه‌ی EmailTemplates، جهت ساخت متن ایمیل استفاده شده‌است.


چند نکته‌ی تکمیلی در مورد قالب‌های ایمیل

- پیش فرض این سرویس، یافتن Viewها در پوشه‌ی Views است؛ مانند: Views\EmailTemplates\_EmailsLayout.cshtml
مگر اینکه مسیر آن‌را به صورت کامل توسط filename.cshtml/.../~ ذکر کنید و در این حالت ذکر پسوند فایل الزامی است.
- ایمیل‌ها می‌توانند دارای Layout هم باشند. برای مثال فایل Views\EmailTemplates\_EmailsLayout.cshtml را با محتوای ذیل ایجاد کنید:
<!DOCTYPE html>
<html>
<head>
    <meta http-equiv="Content-Language" content="fa" />
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <style type='text/css'>
     .main {
            font-size: 9pt;
            font-family: Tahoma;
     }
    </style>
</head>
<body bgcolor="whitesmoke" style="font-size: 9pt; font-family: Tahoma; background-color: whitesmoke; direction: rtl;">
   <div class="main">@RenderBody()</div>
</body>
</html>
در اینجا RenderBody@ را مشاهده می‌کنید که محل رندر شدن ایمیل‌های برنامه است.
به علاوه در اینجا جهت ارسال ایمیل‌ها باید هر نوع شیوه نامه‌ای، به صورت صریح قید شود (inline css) و نباید فایل css ایی را لینک کنید.
- پس از اینکه فایل layout خاص ارسال ایمیل‌های خودتان را طراحی کردید، اکنون قالب یکی از ایمیل‌های برنامه، یک چنین فرمتی را پیدا می‌کند که Layout در ابتدای آن ذکر شده‌است:
 @using Sample.ViewModels
@model RegisterEmailConfirmationViewModel
@{
Layout = "~/Views/EmailTemplates/_EmailsLayout.cshtml";
}
با سلام
<br />
 اکانت شما با مشخصات ذیل ایجاد گردید:
....
- حتما تولید لینک‌ها را به صورت مطلق و نه نسبی انجام دهید. اینکار توسط قید صریح protocol صورت می‌گیرد:
 <a style="direction:ltr" href="@Url.Action("Index", "Home", values: new { area = "" }, protocol: this.Context.Request.Scheme)">@Model.EmailSignature</a>
مطالب
React 16x - قسمت 12 - طراحی یک گرید - بخش 2 - فیلتر کردن اطلاعات
تا اینجا کامپوننت صفحه بندی را به همراه اعمال آن به لیست نمایش داده شده، پیاده سازی کردیم. در ادامه می‌خواهیم لیست ژانرهای سینمایی را که در فایل fakeGenreService.js تعریف شده‌اند:
export const genres = [
  { _id: "5b21ca3eeb7f6fbccd471818", name: "Action" },
  { _id: "5b21ca3eeb7f6fbccd471814", name: "Comedy" },
  { _id: "5b21ca3eeb7f6fbccd471820", name: "Thriller" }
];

export function getGenres() {
  return genres.filter(g => g);
}
توسط list-group‌های بوت استرپی، در کنار صفحه نمایش داده و سپس به ازای هر گروه انتخابی توسط کاربر، فیلم‌های مرتبط با آن گروه را فیلتر کرده و نمایش دهیم.


بررسی ساختار کامپوننت ListGroup

شبیه به کامپوننت صفحه بندی که در قسمت قبل ایجاد کردیم، می‌خواهیم کامپوننت ListGroup نیز به طور کامل از اشیاء movie مستقل باشد؛ تا در آینده بتوان از آن در جاهای دیگری نیز استفاده کرد. به همین جهت فایل جدید src\components\common\listGroup.jsx را ایجاد کرده و سپس با استفاده از میانبرهای imrc و cc در VSCode، ساختار ابتدایی این کامپوننت را ایجاد می‌کنیم. هرچند می‌توان این کامپوننت را به صورت «Stateless Functional Component» نیز طراحی کرد؛ چون state و متد دیگری بجز render نخواهد داشت و تمام اطلاعات خودش را از والد خود دریافت می‌کند.
سپس به کامپوننت movies مراجعه کرده و این کامپوننت خالی را import می‌کنیم:
import ListGroup from "./common/listGroup";
پس از آن به متد رندر کامپوننت movies مراجعه کرده و با اضافه کردن یک row بوت استرپی دو ستونی، قصد داریم کامپوننت لیست فیلم‌ها را در ستون اول این ردیف نمایش دهیم. به همین جهت المان آن‌را در این محل قرار می‌دهیم تا بتوانیم اینترفیس ابتدایی آن‌را پیش از پیاده سازی آن، طراحی کنیم.
برای این منظور ابتدا React.Fragment موجود را با یک div با "className="row جایگزین می‌کنیم. سپس داخل این row، دو ستون را تعریف خواهیم کرد که در اولی، المان جدید ListGroup قرار می‌گیرد و در دومی، مابقی عناصری که تاکنون اضافه کرده‌ایم؛ مانند جدول، صفحه بندی و نمایش تعداد آیتم‌ها:
    return (
      <div className="row">
        <div className="col-2">
          <ListGroup />
        </div>
        <div className="col">
          ...
        </div>
      </div>
    );
این listGroup، حداقل نیاز به لیست آیتم‌هایی را دارد که باید نمایش دهد. این لیست نیز از fakeGenreService و متد getGenres آن تامین می‌شود که به صورت یک خاصیت جدید در state به نحو زیر درج خواهد شد:
import { getGenres } from "../services/fakeGenreService";
// ...

class Movies extends Component {
  state = {
    // ...
    genres: getGenres()
  };
همانطور که در قسمت 9 این سری نیز بررسی کردیم، اگر getGenres قرار است از سمت سرور و توسط یک درخواست Ajax ای تامین شود، محل صحیح قرارگیری آن در متد lifecycle hook ویژه‌ای به نام componentDidMount است. اما در اینجا چون genres یک لیست درون حافظه‌ای است، مقدار دهی فوق، مشکلی را ایجاد نمی‌کند. هرچند می‌توان هم اکنون نیز تعریف فوق را کمی اصولی‌تر نوشت. برای اینکار متد componentDidMount را اضافه کرده و به نحو زیر تنظیم می‌کنیم:
class Movies extends Component {
  state = {
    movies: [],
    pageSize: 4,
    currentPage: 1,
    genres: []
  };

  componentDidMount() {
    this.setState({ movies: getMovies(), genres: getGenres() });
  }
ابتدا آرایه‌های مورد نیاز movies و genres را در state تعریف کرده و آن‌ها را با یک آرایه‌ی خالی، مقدار دهی اولیه می‌کنیم. از این جهت که تا رسیدن به مرحله‌ی componentDidMount که اندکی طول می‌کشد، خطاهای زمان اجرای عدم دسترسی به این آرایه‌ها در برنامه رخ ندهد. سپس زمانیکه وهله‌ای از این کامپوننت در DOM رندر شد، متد componentDidMount فراخوانی شده و دو خاصیت state را با مقادیر دریافتی، به روز رسانی می‌کند.

پس از آن می‌توان ویژگی جدید items این کامپوننت را به آرایه‌ی genres دریافتی از state، تنظیم کرد:
<ListGroup items={this.state.genres} />
در این مرحله، ورودی دیگری به نظر نمی‌رسد که مورد نیاز باشد. اکنون این سؤال مطرح می‌شود که چه رخ‌دادهایی را قرار است از این کامپوننت دریافت کنیم یا به عبارتی خروجی آن چیست؟
بهتر است هر زمانیکه کاربر، آیتمی را از این لیست انتخاب کرد، توسط بروز رخدادی مانند onItemSelect از وقوع آن مطلع شد و سپس نسبت به آن توسط متد handleGenreSelect، واکنش نشان داد؛ مانند فیلتر کردن لیست فیلم‌ها بر اساس آیتم انتخابی و نمایش آن. به همین جهت ویژگی onItemSelect را به تعریف المان ListGroup اضافه می‌کنیم:
<ListGroup
  items={this.state.genres}
  onItemSelect={this.handleGenreSelect}
/>
و سپس متد handleGenreSelect متصل به آن‌‌را به نحو زیر تعریف خواهیم کرد:
  handleGenreSelect = genre => {
    console.log("handleGenreSelect", genre);
  };
تا اینجا اینترفیس کامپوننت ListGroup را پیش از پیاده سازی آن تعریف کردیم (تعیین ورودی و خروجی آن). در مرحله‌ی بعد، این کامپوننت را تکمیل می‌کنیم.


پیاده سازی نمایش آیتم‌ها در کامپوننت ListGroup

پیاده سازی ابتدایی کامپوننت ListGroup را در اینجا مشاهده می‌کنید:
import React, { Component } from "react";

class ListGroup extends Component {
  render() {
    return (
      <ul className="list-group">
        {this.props.items.map(item => (
          <li key={item._id} className="list-group-item">
            {item.name}
          </li>
        ))}
      </ul>
    );
  }
}

export default ListGroup;
کار با درج یک ul که با کلاس list-group مزین شده‌است، شروع می‌شود. سپس باید liهای آن‌را که نمایانگر آیتم‌های این لیست است، به صورت پویا با کلاس‌های list-group-item رندر کرد. برای اینکار از آرایه‌ی دریافتی this.props.items و فراخوانی متد map بر روی آن کمک می‌گیریم. در اینجا key هر ردیف با استفاده از خاصیت id هر آیتم و برچسب هر کدام از طریق خاصیت name هر شیء دریافتی، تامین می‌شود.

تا اینجا اگر برنامه را ذخیره کرده و در مرورگر نمایش دهیم، به خروجی زیر می‌رسیم:


البته به نظر عرض ستون آن نامناسب است. به همین جهت به کامپوننت movies مراجعه کرده و col-2 ستون آن‌را به col-3 تبدیل می‌کنیم.


پویا سازی انتخاب نام خواص شیء دریافتی، در کامپوننت ListGroup

در حال حاضر پیاده سازی کامپوننت ListGroup، به شیءای دقیقا با خواص id_ و name وابسته‌است و اگر شیء دیگری را که دارای خواصی معادل این نام‌ها نیست، به آن ارسال کنیم، دیگر کار نخواهد کرد. به همین جهت در محل تعریف المان این کامپوننت در کامپوننت movies، دو ویژگی دیگر نام خواص شیء مدنظر را تنظیم می‌کنیم تا بتوانیم با هر نوع شیءای در اینجا کار کنیم:
<ListGroup
  items={this.state.genres}
  textProperty="name"
  valueProperty="_id"
  onItemSelect={this.handleGenreSelect}
/>
پس از این تغییر و افزودن textProperty و valueProperty، برای پویا سازی نام‌های خواص دریافتی در کامپوننت ListGroup، از روش کار با []، جهت دسترسی پویای به خواص یک شیء، استفاده می‌کنیم تا دیگر این کامپوننت به شیء خاص genre، وابستگی نداشته باشد و قابلیت استفاده‌ی مجدد از آن افزایش یابد:
import React, { Component } from "react";

class ListGroup extends Component {
  render() {
    return (
      <ul className="list-group">
        {this.props.items.map(item => (
          <li key={item[this.props.valueProperty]} className="list-group-item">
            {item[this.props.textProperty]}
          </li>
        ))}
      </ul>
    );
  }
}

export default ListGroup;


تعیین مقادیر پیش‌فرضی برای خواص props

با زیاد شدن تعداد خواص props، اینترفیس کامپوننت‌ها پیچیده‌تر می‌شوند. در یک چنین حالتی می‌توان در کامپوننت‌ها defaultProps را تعریف کرد و توسط آن مقادیر پیش‌فرضی را برای خواص props درنظر گرفت. به این صورت در حین تعریف المان این کامپوننت، اگر مقادیر مدنظر با مقادیر پیش‌فرض تعیین شده یکی باشند، دیگر نیازی به ذکر این پارامترها نخواهد بود. برای مثال در انتهای کامپوننت ListGroup، خاصیت جدید defaultProps را تعریف می‌کنیم (املای آن باید دقیقا به همین شکل باشد؛ و گرنه شناخته نخواهد شد). سپس در اینجا خواصی را که می‌خواهیم مقادیر پیش‌فرضی را برای آن‌ها تعیین کنیم، ذکر خواهیم کرد:
ListGroup.defaultProps = {
  textProperty: "name",
  valueProperty: "_id"
};

export default ListGroup;
برای نمونه در اینجا دو خاصیت جدید textProperty و valueProperty را به همان مقادیر name و id_ مورد استفاده‌ی در این مثال تنظیم کرده‌ایم. پس از این تعریف، می‌توان به کامپوننت movies که از این ویژگی‌ها استفاده می‌کند مراجعه کرده و آن‌هایی را که با defaultProps تطابق دارند، از لیست ویژگی‌های ذکر شده حذف کرد؛ یعنی تعریف المان ListGroup به صورت زیر ساده می‌شود:
<ListGroup
  items={this.state.genres}
  onItemSelect={this.handleGenreSelect}
/>
بدیهی است اگر در آینده با اشیاء دیگری سر و کار داشتیم، می‌توان مجددا این خواص پیش‌فرض را بر اساس ساختار این اشیاء، مقدار دهی و تعیین کرد.


مدیریت انتخاب گروه‌های فیلم‌ها

در ادامه می‌خواهیم رخ‌داد onClick بر روی هر li این لیست را مدیریت کنیم و سبب بروز رخ‌دادی به نام onItemSelect شویم که در ابتدای بحث، آن‌را به عنوان خروجی این کامپوننت تعریف کردیم. این رخداد نیز در کامپوننت movies به متد handleGenreSelect متصل است. به همین جهت تعریف ویژگی onClick را که سبب انتقال شیء جاری رندر شده، توسط رویداد onItemSelect به خارج از آن می‌شود، به المان li کامپوننت ListGroup اضافه می‌کنیم:
<li
  key={item[this.props.valueProperty]}
  className="list-group-item"
  onClick={() => this.props.onItemSelect(item)}
  style={{ cursor: "pointer" }}
>
  {item[this.props.textProperty]}
</li>
پس از این تغییرات و ذخیره‌ی برنامه، اگر به خروجی برنامه در مرورگر مراجعه کرده و بر روی هر کدام از آیتم‌های لیست گروه‌های فیلم‌ها کلیک کنیم، شیء مرتبط با آن آیتم در کنسول توسعه دهنده‌های مرورگر، لاگ می‌شود که نشان از برقراری صحیح ارتباطات این قسمت را دارد.

پس از فعالسازی امکان کلیک بر روی هر آیتم لیست رندر شده، اکنون می‌خواهیم با انتخاب هر گروه، این گروه در این لیست، به صورت انتخاب شده، همانند شماره صفحه‌ی انتخاب شده‌ی در کامپوننت صفحه بندی، تغییر رنگ دهد و متمایز نمایش داده شود تا مشخص باشد که هم اکنون با کدام آیتم در حال کار هستیم. برای اینکار تنها کافی است کلاس active را به صورت پویا به className هر li، اضافه یا کم کنیم. البته برای این منظور این کامپوننت باید از آیتم انتخاب شده مطلع باشد؛ به همین جهت selectedItem را در لیست ویژگی‌های اینترفیس تعریف این المان اضافه می‌کنیم. برای اینکار ابتدا selectedGenre را با هربار فراخوانی handleGenreSelect که به onItemSelect کامپوننت متصل است، با فراخوانی متد setState به روز رسانی می‌کنیم:
  handleGenreSelect = genre => {
    console.log("handleGenreSelect", genre);
    this.setState({selectedGenre: genre});
  };
در یک چنین حالتی الزامی به تعریف selectedGenre در خاصیت state ابتدای کامپوننت نیست. چون با فراخوانی متد setState اگر یکی از خواص منتسب به شیء state به روز شده باشد، آن خاصیت نیز به روز می‌شود و یا اگر این خاصیت جدید باشد، با state موجود یکی خواهد شد؛ هرچند آن‌را به صورت زیر نیز می‌توان تعریف کرد که با یک شیء خالی مقدار دهی شده‌است:
class Movies extends Component {
  state = {
    // ...
    selectedGenre: {}
  };
سپس ویژگی selectedItem کامپوننت را به این مقدار تغییر یافته‌ی this.state.selectedGenre تنظیم می‌کنیم تا با هر بار فراخوانی setState که سبب رندر مجدد کامپوننت Movies در DOM مجازی React می‌شود، کامپوننت از selectedItem تغییر یافته مطلع شده و با افزودن کلاس active به آن آیتم، واکنش نشان دهد:
<ListGroup
  items={this.state.genres}
  onItemSelect={this.handleGenreSelect}
  selectedItem={this.state.selectedGenre}
/>
اکنون به کامپوننت ListGroup مراجعه کرده و بر اساس ویژگی جدید selectedItem، تغییرات زیر را به className اعمال می‌کنیم:
<li
  key={item[this.props.valueProperty]}
  className={
    item === this.props.selectedItem
      ? "list-group-item active"
      : "list-group-item"
  }
  style={{ cursor: "pointer" }}
  onClick={() => this.props.onItemSelect(item)}
>
  {item[this.props.textProperty]}
</li>
در اینجا اگر item در حال رندر با this.props.selectedItem دریافتی یکی باشد، کلاس active به کلاس list-group-item اضافه می‌شود و برعکس.



مدیریت فیلتر کردن اطلاعات گروه فیلم انتخابی

در قسمت قبل، در ابتدای متد رندر کامپوننت movies، از متد paginate برای صفحه بندی اطلاعات استفاده کردیم. فیلتر گروه جاری انتخاب شده را باید پیش از این متد قرار دارد؛ چون تعداد صفحات و اطلاعات نمایش داده شده‌ی در هر کدام باید بر اساس لیست فیلم‌های فیلتر شده باشد.
برای انجام اینکار تغییرات زیر را اعمال خواهیم کرد:
الف) بجای متد paginate، از متد getPagedData زیر استفاده می‌کنیم:
  getPagedData() {
    const {
      pageSize,
      currentPage,
      selectedGenre,
      movies: allMovies
    } = this.state;

    const filteredMovies =
      selectedGenre && selectedGenre._id
        ? allMovies.filter(m => m.genre._id === selectedGenre._id)
        : allMovies;

    const first = (currentPage - 1) * pageSize;
    const last = first + pageSize;
    const pagedMovies = filteredMovies.slice(first, last);

    return { totalCount: filteredMovies.length, data: pagedMovies };
  }
- در اینجا بجای اینکه مدام this.stat‌ها را جهت دریافت خواص آن تکرار کنیم، با استفاده از ویژگی Object Destructuring، خواصی را که نیاز داریم یکبار انتخاب کرده و سپس به دفعات از آن‌ها استفاده می‌کنیم. به همین جهت در این قطعه کد، فقط یکبار this.state را مشاهده می‌کنید که بسیار تمیزتر است و همچنین کارآیی آن نیز به علت عدم انتخاب مداوم مقدار خاصیتی از یک شیء، بالاتر از حالت قبل است.
- در حین Object Destructuring، نام خاصیت movies را نیز به allMovies تغییر داده‌ایم تا واضح‌تر باشد.
- در ادامه با استفاده از متد filter جاوااسکریپت، بر اساس id هر گروه انتخاب شده، اشیاء مرتبط با آن، از allMovies جدا شده و بازگشت داده می‌شود. البته اگر id هم انتخاب نشده باشد (اولین بار نمایش صفحه)، تمام رکوردها یعنی allMovies، مورد استفاده قرار می‌گیرد.
- پس از آن، همان کدهای صفحه بندی اطلاعات را که در قسمت قبل بررسی کردیم، مشاهده می‌کنید که اینبار بجای allMovies قسمت قبل، بر روی filteredMovies اعمال شده‌است.
- در آخر، این متد، یک شیء را با دو خاصیت که بیانگر تعداد کل رکوردهای انتخاب شده و داده‌های فیلتر شده‌ی صفحه بندی شده‌است، بازگشت می‌دهد.

ب) تغییرات متد رندر کامپوننت movies به صورت زیر است:
- ابتدا متد getPagedData فوق، فراخوانی شده و شیء دریافتی از آن با استفاده از ویژگی Object Destructuring، به دو خاصیت totalCount و movies انتساب داده می‌شود:
  render() {
    const { length: count } = this.state.movies;

    if (count === 0) return <p>There are no movies in the database.</p>;

    const { totalCount, data: movies } = this.getPagedData();
- از آرایه‌ی movies، در قسمت قبل برای رندر لیست فیلم‌ها استفاده شد. به همین جهت در اینجا تغییر نام data به movies را مشاهده می‌کنید.
- همچنین کامپوننت صفحه بندی، اینبار باید totalCount آیتم‌های فیلتر شده را نمایش دهد و نه totalCount تمام فیلم‌های موجود را:
<Pagination
    itemsCount={totalCount}
در اینجا برچسب نمایش تعداد آیتم‌های موجود نیز باید تغییر کند:
<p>Showing {totalCount} movies in the database.</p>
ج) ممکن است در اولین بار مشاهده‌ی صفحه، کاربر صفحه‌ی شماره‌ی 3 را انتخاب کند که سبب تغییر currentPage موجود در state، به عدد 3 می‌شود. اکنون اگر کاربر نمایش فیلتر شده‌ی فیلم‌های یک گروه خاص را انتخاب کند، باید این شماره، به عدد 1 مجددا تنظیم شود:
  handleGenreSelect = genre => {
    console.log("handleGenreSelect", genre);
    this.setState({ selectedGenre: genre, currentPage: 1 });
  };



افزودن گزینه‌ی نمایش تمام اطلاعات به لیست گروه‌های فیلم‌ها

در ادامه قصد داریم به بالای لیست گروه‌های موجود، گزینه‌ی All Genres را نیز اضافه کنیم تا با کلیک بر روی آن، مجددا بتوان لیست تمام فیلم‌های موجود را مشاهده کرد.


برای این منظور در جائیکه لیست getGenres را دریافت و نمایش می‌دهیم، یعنی متد componentDidMount، اندکی تغییر ایجاد کرده و یک آرایه‌ی جدید را ایجاد می‌کنیم؛ بطوریکه اولین عنصر آن، گزینه‌ی جدید All Genres باشد و سپس توسط spread operator، مابقی عناصر آرایه‌ی گروه‌ها را به این آرایه‌ی جدید اضافه می‌کنیم:
  componentDidMount() {
    const genres = [{ _id: "", name: "All Genres" }, ...getGenres()];
    this.setState({ movies: getMovies(), genres });
  }
همین اندازه تغییر برای فعالسازی این گزینه کفایت می‌کند؛ از این جهت که در متد getPagedData، ابتدا بررسی می‌شود که اگر آیتمی انتخاب شده بود و همچنین دارای id نیز بود، آنگاه کار فیلتر کردن صورت گیرد، درغیراینصورت، تمام رکوردها را بازگشت دهد:
const filteredMovies =
      selectedGenre && selectedGenre._id
        ? allMovies.filter(m => m.genre._id === selectedGenre._id)
        : allMovies;

کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید:  sample-12.zip
نظرات مطالب
فرم‌های مبتنی بر قالب‌ها در Angular - قسمت چهارم - اعتبارسنجی ورودی‌ها
یک نکته‌ی تکمیلی
در صورتی که تعداد فیلد‌های فرم زیاد باشد با غیر فعال کردن دکمه submit کاربر نمی‌تواند تشخیص دهد که کدام المان ورودی را باید مقدار دهی کند یک راه حل این است که در کنار المان‌های که required  می‌باشند یک * قرمز رنگ قرار دهیم . 
راه حل دوم این است که کاربر با زدن submit  خطاهای فرم را مشاهده کند : 
// student-model.ts
export interface Student {
    id: number;
    name: string;
}
کامپوننت : 
  // app.component.ts
  public model: Student;

  ngOnInit(): void {
    this.setDefaultValueForModel();
  }

  saveForm(form: NgForm, evetn: Event) {
    evetn.preventDefault();
    if (form.valid) {
      alert('everything is ok');
    }
  }

  setDefaultValueForModel() {
    this.model = {
      id: 1,
      name: ''
    };
  }
و در نهایت محتوای app.component.html
        <form #form="ngForm" novalidate (submit)="saveForm(form,$event)">
          <div>
            <label>Name</label>
            <input type="text" required name="name" autocomplete="off" [(ngModel)]="model.name" #name="ngModel">

            <p [hidden]="name.valid || (name.pristine && !form.submitted)">
              Name is required and should be minimum 4 characters.
            </p>

          </div>

          <div>
            <input type="submit" value="submit">
          </div>
        </form>
مطالب
مدیریت پیشرفته‌ی حالت در React با Redux و Mobx - قسمت ششم - MobX چیست؟
پیش از بحث در مورد «مدیریت حالت»، باید با مفهوم «حالت» آشنا شد. «حالت» در اینجا همان لایه‌ی داده‌های برنامه است. زمانیکه بحث React و کتابخانه‌های مدیریت حالت آن مطرح می‌شود، می‌توان گفت حالت، شیءای است حاوی اطلاعاتی که برنامه با آن سر و کار دارد. برای مثال اگر برنامه‌ای قرار است لیستی از موارد را نمایش دهد، حالت برنامه، حاوی اشیاء متناظری خواهد بود. حالت، بر روی نحوه‌ی رفتار و رندر کامپوننت‌های React تاثیر می‌گذارد. بنابراین مدیریت حالت، روشی است برای ردیابی و مدیریت داده‌های مورد استفاده‌ی در برنامه و تقریبا تمام برنامه‌ها به نحوی نیاز به آن‌را خواهند داشت.
داشتن یک کتابخانه‌ی مدیریت حالت برای برنامه‌های React بسیار مفید است؛ خصوصا اگر این برنامه پیچیده باشد و برای مثال در آن نیاز به اشتراک گذاری داده‌ها، بین دو کامپوننت یا بیشتر که در یک رده سلسه مراتبی قرار نمی‌گیرند، وجود داشته باشد. اما حتی اگر از یک کتابخانه‌ی مدیریت حالت استفاده شود، شاید راه حلی را که ارائه می‌کند آنچنان تمیز و قابل انتظار نباشد. با MobX می‌توان از ساختارهای پیچیده‌ی شیءگرا به سادگی استفاده کرد (mutation مستقیم اشیاء در آن مجاز است) و همچنین برای کار با آن به همراه React، نیاز به کدهای کمتری است نسبت به Redux. در اینجا از مفاهیم Reactive programming استفاده می‌شود؛ اما سعی می‌کند پیچیدگی‌های آن‌را مخفی کند. در نام MobX، حرف X به Reactive بودن آن اشاره می‌کند (مانند RxJS) و ob آن از observable گرفته شده‌است. M هم به حرف ابتدای نام شرکتی اشاره می‌کند که این کتابخانه را ایجاد کرده‌است.


خواص محاسبه شده در جاوا اسکریپت

برای کار با MobX، نیاز است تا ابتدا با یکسری از مفاهیم آن آشنا شد؛ مانند خواص محاسبه شده (computed properties). برای مثال در اینجا یک کلاس متداول جاوا اسکریپتی را داریم:
class Person {
    constructor(firstName, lastName) {
       this.firstName = firstName;
       this.lastName = lastName;
    }

    fullName() {
      return `${this.firstName} ${this.lastName}`;
    }
}
که در آن از طریق سازنده، دو پارامتر نام و نام خانوادگی دریافت شده و سپس به دو خاصیت جدید، نسبت داده شده‌اند. اکنون برای محاسبه‌ی نام کامل، که حاصل جمع این دو است، می‌توان متد fullName را به این کلاس اضافه کرد. روش کار با آن نیز به صورت زیر است:
const person = new Person('Vahid', 'N');
person.firstName; // 'Vahid'
person.lastName; // 'N'
person.fullName; // function fullName() {…}
اگر بر اساس متغیر person که بیانگر وهله‌ای از شیء Person است، سعی در خواندن مقادیر خواص شیء ایجاد شده کنیم، آن‌ها را دریافت خواهیم کرد. اما ذکر person.fullName (بدون هیچ () در مقابل آن)، تنها اشاره‌گری را به آن متد بازگشت می‌دهد و نه نام کامل را که البته یکی از ویژگی‌های جالب جاوا اسکریپت است و امکان ارسال آن‌را به سایر متدها، به صورت پارامتر میسر می‌کند.
در ES6 برای اینکه تنها با ذکر person.fullName بدون هیچ پرانتزی در مقابل آن بتوان به مقدار کامل fullName رسید، می‌توان از روش زیر و با ذکر واژه‌ی کلیدی get، در پیش از نام متد، استفاده کرد:
class Person {
    constructor(firstName, lastName) {
       this.firstName = firstName;
       this.lastName = lastName;
    }

    get fullName() {
      return `${this.firstName} ${this.lastName}`;
    }
}
در اینجا هرچند fullName هنوز یک متد است، اما اینبار فراخوانی person.fullName، حاصل جمع دو خاصیت را بازگشت می‌دهد و نه اشاره‌گری به آن متد را.
اگر شبیه به همین قطعه کد را بخواهیم در ES5 پیاده سازی کنیم، روش آن به صورت زیر است:
function Person(firstName, lastName) {
   this.firstName = firstName;
   this.lastName = lastName;
}

Object.defineProperty(Person.prototype, 'fullName', {
   get: function () {
      return this.firstName + ' ' + this.lastName;
   }
});
به این ترتیب می‌توان یک خاصیت محاسبه شده‌ی ویژه‌ی ES5 را تعریف کرد.

اکنون فرض کنید قسمتی از state برنامه‌ی React، قرار است خاصیت ویژه‌ی fullName را نمایش دهد. برای اینکه UI برنامه با تغییرات نام و نام خانوادگی، متوجه تغییرات fullName که یک خاصیت محاسباتی است، شود و آن‌را رندر مجدد کند، باید در طی یک حلقه‌ی بی‌نهایت، مدام آن‌را فراخوانی کند و نتیجه‌ی جدید را با نتیجه‌ی قبلی محاسبه کرده و تغییرات را نمایش دهد. اینجا است که MobX یک چنین پیاده سازی‌هایی را به کمک مفهوم decorators، ساده می‌کند.


Decorators در جاوا اسکریپت

تزئین کننده‌ها یا decorators در سایر زبان‌های برنامه نویسی نیز وجود دارند؛ اما پیاده سازی آن‌ها در جاوا اسکریپت هنوز در مرحله‌ی آزمایشی است. Decorators در جاوا اسکریپت چیزی نیستند بجز بیان زیبای higher-order functions.
higher-order functions، توابعی هستند که توابع دیگر را با ارائه‌ی قابلیت‌های بیشتری، محصور می‌کنند. به همین جهت هر کاری را که بتوان با تزئین کننده‌ها انجام داد، همان را با توابع معمولی جاوا اسکریپتی نیز می‌توان انجام داد. یک نمونه از این higher-order functions را در سری جاری تحت عنوان higher-order components با متد connect کتابخانه‌ی react-redux مشاهده کرده‌ایم. متد connect، متدی است که متدهای نگاشت state به props و نگاشت dispatch به props را دریافت کرده و سپس یک کامپوننت را نیز دریافت می‌کند و آن‌را به صورت محصور شده‌ای ارائه می‌دهد تا بجای کامپوننت اصلی مورد استفاده قرار گیرد؛ به یک چنین کامپوننت‌هایی، higher-order components گفته می‌شود.

برای تعریف تزئین کننده‌ها، به نحوه‌ی پیاده سازی Object.defineProperty در مثال فوق دقت کنید:
Object.defineProperty(Person.prototype, 'fullName', {
    enumerable: false,
    writable: false,
    get: function () {
      return this.firstName + ' ' + this.lastName;
    }
});
در اینجا Person.prototype یک target است. ثابت fullName، یک کلید است. سایر خواص ذکر شده، مانند enumerable، writable و get، تحت عنوان Descriptor شناخته می‌شوند.
در ذیل روش تعریف یک تزئین کننده را مشاهده می‌کنید که دقیقا از یک چنین الگویی پیروی می‌کند:
function decoratorName(target, key, descriptor) {
 // …
}
برای مثال در اینجا روش پیاده سازی تزئین کننده‌ی readonly را ملاحظه می‌کنید:
function readonly(target, key, descriptor) {
   descriptor.writable = false;
   return descriptor;
}
سپس روش اعمال آن به یک خاصیت محاسباتی در کلاس Person به صورت زیر است:
class Person {
    constructor(firstName, lastName) {
       this.firstName = firstName;
       this.lastName = lastName;
    }

    @readonly get fullName() {
      return `${this.firstName} ${this.lastName}`;
    }
}
ذکر یک تزئین کننده با @ شروع می‌شود. سپس متد fullName را دریافت کرده و نگارش جدیدی از آن‌را بازگشت می‌دهد؛ بطوریکه readonly باشد.


مثال‌هایی از تزئین کننده‌ها

برای نمونه می‌توان تزئین کننده‌ی bindThis@ را طراحی کرد تا کار bind شیء this را به متدهای کامپوننت‌های React انجام دهد و یا کتابخانه‌ای به نام core-decorators وجود دارد که به صورت زیر نصب می‌شود:
> npm install core-decorators
و به همراه این تزئین کننده‌ها می‌باشد:
@autobind
@deprecate
@readonly
@memoize
@debounce
@profile
برای مثال autobind آن، همان کار bind شیء this را انجام می‌دهد. deprecate جهت نمایش یک اخطار، در کنسول توسعه دهندگان مرورگر، جهت گوشزد کردن منسوخ بودن قسمتی از برنامه، استفاده می‌شود.

نمونه‌ی دیگری از این کتابخانه‌ها lodash-decorators است که تعدادی دیگر از تزئین کننده‌ها را ارائه می‌کند.


MobX چگونه کار می‌کند؟

انجام یکسری از کارها با Redux مشکل است؛ برای مثال تغییر دادن یک شیء تو در توی پیچیده که شامل تهیه‌ی یک کپی از آن، اعمال تغییرات و غیره‌است. اما با MobX می‌توان با اشیاء جاوا اسکریپتی به همان صورتی که هستند کار کرد. برای مثال آرایه‌ای را با متدهای push و pop تغییر داد (mutation اشیاء مجاز است) و یا خواص اشیاء را به صورت مستقیم ویرایش کرد، در این حالت MobX اعلام می‌کند که ... من می‌دانم که چه تغییری صورت گرفته‌است. بنابراین سبب رندر مجدد UI خواهم شد.


ایجاد یک برنامه‌ی خالی React برای آزمایش MobX

در اینجا برای بررسی MobX، یک پروژه‌ی جدید React را ایجاد می‌کنیم:
> create-react-app state-management-with-mobx-part1
> cd state-management-with-mobx-part1
> npm start
در ادامه کتابخانه‌ی mobx را نیز نصب می‌کنیم. برای این منظور پس از باز کردن پوشه‌ی اصلی برنامه توسط VSCode، دکمه‌های ctrl+` را فشرده (ctrl+back-tick) و دستور زیر را در ترمینال ظاهر شده وارد کنید:
> npm install --save mobx
البته برای کار با MobX، الزاما نیازی به طی مراحل فوق نیست؛ ولی چون این قالب، یک محیط آماده‌ی کار با ES6 را فراهم می‌کند، به سادگی می‌توان فایل index.js آن‌را خالی کرد و سپس شروع به کدنویسی و آزمایش MobX نمود.


مثالی از MobX، مستقل از React

در اینجا نیز همانند روشی که در بررسی Redux در پیش گرفتیم، ابتدا MobX را به صورت کاملا مستقل از React، با یک مثال بررسی می‌کنیم و سپس در قسمت‌های بعد آن‌را به React متصل می‌کنیم. برای این منظور ابتدا فایل src\index.js را به صورت زیر تغییر می‌دهیم:
import { autorun, observable } from "mobx";

import React from "react";
import ReactDOM from "react-dom";

ReactDOM.render(
  <div>
    <input type="text" id="text-input" />
    <div id="text-display"></div>
    <div id="text-display-uppercase"></div>
  </div>,
  document.getElementById("root")
);

const input = document.getElementById("text-input");
const textDisplay = document.getElementById("text-display");
const loudDisplay = document.getElementById("text-display-uppercase");

console.log({ observable, autorun, input, textDisplay, loudDisplay });
در اینجا یک text-box، به همراه دو div، در صفحه رندر خواهند شد که قرار است با ورود اطلاعاتی در text-box، یکی از آن‌ها (text-display) این اطلاعات را به صورت معمولی و دیگری (text-display-uppercase) آن‌را به صورت uppercase نمایش دهد. روش کار انجام شده هم مستقل از React است و به صورت مستقیم، با استفاده از DOM API و document.getElementById عمل شده‌است. همچنین در ابتدای این فایل، دو import را از کتابخانه‌ی mobx مشاهده می‌کنید.
- با استفاده از observable می‌خواهیم تغییرات یک شیء جاوا اسکریپتی را تحت نظر قرار داده و هر زمانیکه تغییری در شیء رخ داد، از آن مطلع شویم.
برای مثال شیء ساده‌ی جاوا اسکریپتی زیر را در نظر بگیرید:
{
  value: "Hello world!",
  get uppercase() {
    return this.value.toUpperCase();
  }
}
این شیء دارای دو خاصیت است که یکی معمولی و دیگری به صورت یک خاصیت محاسباتی، تعریف شده‌است. مشکلی که با این شیء وجود دارد این است که اگر مقدار خاصیت value آن تغییر کند، از آن مطلع نخواهیم شد تا پس از آن برای مثال در مورد رندر مجدد DOM، تصمیم گیری شود. چون از دیدگاه React، مقدار ارجاع به این شیء با تغییر خواص آن، تغییری نمی‌کند. به همین جهت اگر نحوه‌ی مقایسه، بر اساس مقایسه‌ی ارجاعات به اشیاء باشد (strict === reference check)، چون شیء تغییر یافته نیز به همان شیء اصلی اشاره می‌کند، بنابراین دارای ارجاع یکسانی خواهند بود و سبب رندر مجدد DOM نمی‌شوند.
به همین جهت اینبار شیء فوق را توسط یک observable ارائه می‌دهیم، تا بتوانیم به تغییرات خواص آن گوش فرا دهیم:
const text = observable({
  value: "Hello world!",
  get uppercase() {
    return this.value.toUpperCase();
  }
});
در ادامه یک EventListener را به text-box تعریف شده اضافه کرده و در رخ‌داد keyup آن، سبب تغییر خاصیت value شیء text فوق، بر اساس مقدار تایپ شده می‌شویم:
input.addEventListener("keyup", event => {
   text.value = event.target.value;
});
اکنون چون شیء text، یک observable است، هر زمانیکه که خاصیتی از آن تغییر می‌کند، می‌خواهیم بر اساس آن، DOM را به صورت دستی به روز رسانی کنیم. برای اینکار نیاز به متد autorun دریافتی از mobx خواهیم داشت:
autorun(() => {
   textDisplay.textContent = text.value;
   loudDisplay.textContent = text.uppercase;
});
هر زمانیکه شیء observable ای که داخل متد autorun تحت نظر قرار گرفته شده، تغییر کند، سبب اجرای callback method ارسالی به آن خواهد شد. برای مثال در اینجا چون text.value را به event.target.value متصل کرده‌ایم، هربار که کلیدی فشرده می‌شود، سبب بروز تغییری در خاصیت value خواهد شد. در نتیجه‌ی آن، autorun اجرا شده و مقادیر درج شده‌ی در divهای صفحه را بر اساس خواص value و uppercase شیء text، تغییر می‌دهد:

برای آزمایش آن، برنامه را اجرا کرده و متنی را داخل textbox وارد کنید:


نکته‌ی جالب اینجا است که هرچند فقط خاصیت value را تغییر داده‌ایم (تغییر مستقیم خواص یک شیء؛ بدون نیاز به ساخت یک clone از آن)، اما خاصیت محاسباتی uppercase نیز به روز رسانی شده‌است.

زمانیکه mobx را به یک برنامه‌ی React متصل می‌کنیم، قسمت autorun، از دید ما مخفی خواهد بود. در این حالت فقط یک شیء معمولی جاوا اسکریپتی را مستقیما تغییر می‌دهیم و ... در نتیجه‌ی آن رندر مجدد UI صورت خواهد گرفت.


یک observable چگونه کار می‌کند؟

در اینجا یک شبه‌کد را که بیانگر نحوه‌ی عملکرد یک observable است، مشاهده می‌کنید:
const onChange = (oldValue, newValue) => {
  // Tell MobX that this value has changed.
}

const observable = (value) => {
  return {
    get() { return value; },
    set(newValue) {
      onChange(this.get(), newValue);
      value = newValue;
    }
  }
}
یک observable هنگامیکه شی‌ءای را در بر می‌گیرد. هر زمانیکه مقدار جدیدی را به خاصیتی از آن نسبت دادیم، سبب فراخوانی متد onChange شده و به این صورت است که کتابخانه‌ی MobX متوجه تغییرات می‌گردد و بر اساس آن امکان ردیابی تغییرات را میسر می‌کند.


کدهای کامل این قسمت را می‌توانید از اینجا دریافت کنید: state-management-with-mobx-part1.zip
نظرات مطالب
ساخت DropDownList های مرتبط به کمک jQuery Ajax در MVC
سلام و تشکر از آموزش خوبتون

من به خوبی از این مورد استفاده می‌کنم فقط من از روش شما برای ۴ عدد dropdownlist استفاده می‌کنم ( استان > شهر > منطقه > محله ) مشکلم این هست که بعد از انتخاب استان تمامی شهر‌ها رو واسه من می‌آره ولی مثلا اولین شهرم که تهران هست داخل استان تهران مناطق رو نمی‌آره و حق هم داره چون ما در رویداد change گفتیم که این کارو بکنه اگر بخوام اولین گزینه لیست بعد از این انتخاب مثلا بعد از انتخاب استان که لیست شهرها می‌آن اولین گزینه --- انتخاب کنید --- باشه باید چکار کنم ؟

با تشکر
مطالب
WF:Windows Workflow #۵
در این قسمت به پیاده سازی یک فرآیند سفارش ساده می‌پردازیم. ابتدا یک پروژه از نوع Workflow Console Application را ایجاد کرده و نام آن را Order Process می‌گذاریم و سپس کلاس‌های زیر را به آن اضافه می‌کنیم:
public class OrderItem
    {
        public int OrderItemID { get; set; }
        public int Quantity { get; set; }
        public string ItemCode { get; set; }
        public string Description { get; set; }
    }

    public class Order
    {
        public Order()
        {
            Items = new List<OrderItem>();
        }
        public int OrderID { get; set; }        
        public string Description { get; set; }         
        public decimal TotalWeight { get; set; }         
        public string ShippingMethod { get; set; }
        public List<OrderItem> Items { get; set; } 
    }
در اینجا دوکلاس تعریف شده است؛ یکی به نام OrderItem می‌باشد که شامل اطلاعات مربوط به میزان سفارش بوده و دیگری کلاس Order می‌باشد که شامل مشخصات سفارش است. سپس فایل OrderWF.xaml را باز کرده و شروع به ساخت فرآیند مورد نظر می‌کنیم. ابتدا یک Sequence را به درون صفحه کشیده و پس از آن در قسمت Arguments دو متغییر را تعریف می‌کنیم. یکی به نام TotalAmount و از نوع Decimal و Out می‌باشد و دیگری به نام OrderInfo که از نوع کلاس Order و In می‌باشد. سپس  یک کنترل WriteLine را به آن اضافه می‌کنیم و در خاصیت Text آن رشته "Order Received" را قرار می‌دهیم. در ادامه یک کنترل Assign را در زیر آن قرار داده و مقدار متغییر TotalAmount را مساوی صفر وارد می‌کنیم.

نکته : برای اینکه نوع متغییر OrderInfo را از نوع کلاس Order قرار دهیم٬ ابتدا DropDown مربوطه را انتخاب کرده و گزینه Browse For Type را انتخاب می‌کنیم تا پنجره مورد نظر باز شود و از طریق آن، کلاس مورد نظر را انتخاب می‌کنیم. اگر در این قسمت کلاس مورد نظر یافت نشد، نیاز است ابتدا عمل Build Project را یک بار انجام دهیم.

 بعضی از کنترل‌های Workflow در قسمت Toolbox موجود نمی‌باشند. از جمله این کنترل‌ها می‌توان به کنترل Add اشاره کرد. برای استفاده از این کنترل، ابتدا باید آن را به لیست کنترل‌ها اضافه نمود. جهت این امر٬ ابتدا در قسمت Toolbox یک Tab جدید را با نام دلخواه ایجاد کرده و سپس بر روی Tab کلیک راست نموده و گزینه Choose Items را انتخاب می‌کنیم. سپس از قسمت System.Activities.Components کنترل Add را انتخاب کرده و سپس بر روی دکمه OK کلیک می‌نمائیم. حال کنترل Add به لیست کنترل‌ها در Tab مورد نظر اضافه شده است.
در ادامه یک کنترل Switch را به فرایند خود اضافه کرده و مقدار T آن را برابر String قرار می‌دهیم؛ زیرا نوع داده‌ای که در قسمت Expression کنترل Switch قرار می‌گیرد، از نوع رشته می‌باشد. پس از اضافه کردن کنترل مورد نظر، کد زیر را به قسمت Expression کنترل اضافه خواهیم کرد:
OrderInfo.ShippingMethod
سپس در کنترل Switch، بر روی قسمت Add new case کلیک کرده و رشته‌های مورد نظر را اضافه می‌کنیم که شامل  "" NextDay"" و  ""2ndDay"" می‌باشند. اکنون در بدنه هر دو Case، کنترل Add را اضافه می‌کنیم. در هنگام اضافه کردن باید برای سه خصوصیت، نوع مشخص شود و نوع هر سه را برابر Decimal قرار می‌دهیم.
در ادامه کنترل Add را انتخاب کرده و به خاصیت Right آنها به ترتیب مقدار های 10.0m و 15.0m را اضافه می‌کنیم و برای خصوصیت Result هر دو کنترل، متغیر TotalAmount را انتخاب می‌کنیم. سپس یک کنترل Assign را به صفحه اضافه کرده و در قسمت To، متغییر  TotalAmount را قرار می‌دهیم و در قسمت Value کد زیر را:
TotalAmount + (OrderInfo.TotalWeight * 0.50m) 
و در آخر با ستفاده از کنترل WriteLine به چاپ محتوای متغییر TotalAmount می‌پر‌دازیم.

اکنون برای اینکه بتوانیم برنامه را اجرا کنیم، کد زیر را به کلاس Program.cs اضافه می‌کنیم:
static void Main(string[] args)
        {
            Order myOrder = new Order
            {
                OrderID = 1,
                Description = "Need some stuff",
                ShippingMethod = "2ndDay",
                TotalWeight = 100
            };
            IDictionary<String, object> input = new Dictionary<String, Object>
            {
                { "OrderInfo",myOrder}
            };
            IDictionary<String, Object> output = WorkflowInvoker.Invoke(new OrderWF(), input);
            Decimal total = (Decimal)output["TotalAmount"];
            Console.WriteLine("Workflow returned ${0} for my order total", total);
            Console.WriteLine("Press ENTER to exit"); 
            Console.ReadLine();

            //Activity workflow1 = new OrderWF();
            //WorkflowInvoker.Invoke(workflow1);
        }
در اینجا علت استفاده از IDictionary، نوع خروجی متد Invoke می‌باشد. در ادامه به کامل کردن این مثال پرداخته می‌شود.
مطالب
Vue.js - نصب و راه‌اندازی اولیه - قسمت اول
Vue.js چیست؟
ویو‌جی‌اس یا وو جی اس (Vue.js) یکی از فریم‌ورک‌های جاوااسکریپتی است که به ما در ساختن اپلیکیشن‌های تحت وب و تحت معماری MVVM یا همان (Model–view–viewmodel) کمک می‌کند.
در حالیکه مدت کمی از انتشار Vue.js می‌گذرد، اما این فریم‌ورک به شدت محبوب شده و طرفداران زیادی پیدا کرده است.

پیش‌نیازها :
  • آشنایی کامل با جاوااسکریپت
  • آشنایی با Object-oriented در جاوااسکریپت 

اولین قدم برای استفاده از Vue.js، نصب آن می‌باشد که مرحله به مرحله توضیح خواهم داد. جهت نصب، سایت Vue.js به ما چندین راه پیشنهاد می‌دهد که ما آنها را در ذیل بررسی می‌کنیم:
1- CDN
در قسمت CDN به آدرس زیر مراجعه کرده و لینک آن را در صفحه‌ای که نیاز است از آن استفاده کنیم، قرار می‌دهیم. هرچندکه به صورت لوکال هم می‌توانید کدها را در یک فایل ذخیره کرده و از آن استفاده کنید.
ابتدا باید آدرس زیر، درون تگ < script> قرار بگیرد
https://cdnjs.cloudflare.com/ajax/libs/vue/2.2.0/vue.js
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.2.0/vue.js"></script>

2- NPM
طبق همین دستور می‌توانید Vue.js را نصب کنید 
$ npm install vue

3- CLI 
 با استفاده از کامند لاین هم به راحتی قابل نصب می‌باشد؛ با استفاده از دستورات زیر
$ npm install -g vue-cli
$ vue init webpack my-project
$ cd my-project
$ npm install
$ npm run dev

توجه: بهترین روش نصب، استفاده از CDN می‌باشد که توصیه می‌شود جهت سهولت و جلوگیری از دردسرهای نصب فریم‌ورک، از مورد اول استفاده کنید.
حال پس از نصب، چنین کدی آماده خواهد شد.
<html> 
    <body> 

      <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.2.0/vue.js"></script> 
    </body> 
</html>
آخرین نسخه از ویو جی اس، 2.2.0 است.