مطالب
استفاده از SQLDom برای آنالیز عبارات T-SQL، قسمت دوم
مدتی قبل مطلبی را در مورد کتابخانه‌ی ویژه SQL Server که یک T-SQL Parser تمام عیار است، در این سایت مطالعه کردید. در این قسمت، همان مطلب را به نحو بهتر و ساده‌تری بازنویسی خواهیم کرد.
مشکلی که در دراز مدت با SQLDom وجود خواهد داشت، مواردی مانند SelectStarExpression و CreateProcedureStatement و امثال آن هستند. این‌ها را از کجا باید تشخیص داد؟ همچنین مراحل بررسی این اجزاء، نسبتا طولانی هستند و نیاز به یک راه حل عمومی‌تر در این زمینه وجود دارد.

راه حلی برای این مشکل در مطلب «XML ‘Visualizer’ for the TransactSql.ScriptDom parse tree» ارائه شده‌است. در اینجا تمام اجزای TSqlFragment توسط Reflection مورد بررسی و استخراج قرار گرفته و نهایتا یک فایل XML از آن حاصل می‌شود.
اگر نکات ذکر شده در این مقاله را تبدیل به یک برنامه با استفاده مجدد کنیم، به چنین شکلی خواهیم رسید:


این برنامه را از اینجا می‌توانید دریافت کنید:
DomToXml.zip

همانطور که در تصویر مشاهده می‌کنید، اینبار به سادگی، SelectStarExpression قابل تشخیص است و تنها کافی است در T-SQL پردازش شده، به دنبال SelectStarExpression‌ها بود. برای اینکار جهت ساده شدن آنالیز می‌توان با ارث بری از کلاس پایه TSqlFragmentVisitor شروع کرد:
using System;
using System.Linq;
using Microsoft.SqlServer.TransactSql.ScriptDom;

namespace DbCop
{
    public class SelectStarExpressionVisitor : TSqlFragmentVisitor
    {
        public override void ExplicitVisit(SelectStarExpression node)
        {
            Console.WriteLine(
                  "`Select *` detected @StartOffset:{0}, Line:{1}, T-SQL: {2}",
                  node.StartOffset,
                  node.StartLine,
                  string.Join(string.Empty, node.ScriptTokenStream.Select(x => x.Text)).Trim());

            base.ExplicitVisit(node);
        }
    }
}
در کلاس پایه TSqlFragmentVisitor به ازای تمام اشیاء شناخته شده‌ی ScriptDom، یک متد ExplicitVisit قابل بازنویسی درنظر گرفته شده‌است. در اینجا برای مثال نمونه‌ی SelectStarExpression آن را بازنویسی کرده‌ایم.
مرحله‌ی بعد، اجرای این کلاس Visitor است:
    public static class GenericVisitor
    {
        public static void Start(string tSql, TSqlFragmentVisitor visitor)
        {
            IList<ParseError> errors;
            TSqlScript sqlFragment;
            using (var reader = new StringReader(tSql))
            {
                var parser = new TSql120Parser(initialQuotedIdentifiers: true);
                sqlFragment = (TSqlScript)parser.Parse(reader, out errors);
            }

            if (errors != null && errors.Any())
            {
                var sb = new StringBuilder();
                foreach (var error in errors)
                    sb.AppendLine(error.Message);

                throw new InvalidOperationException(sb.ToString());
            }
            sqlFragment.Accept(visitor);
        }
    }
در اینجا متد Accept کلاس TSql120Parser، امکان پذیرش یک Visitor را دارد. به این معنا که Parser در حال کار، هر زمانیکه در حال آنالیز قسمتی از T-SQL دریافتی بود، نتیجه را به اطلاع یکی از متدهای کلاس پایه TSqlFragmentVisitor نیز خواهد رساند. بنابراین دیگر نیازی به نوشتن حلقه و بررسی تک تک اجزای خروجی TSql120Parser نیست. اگر نیاز به بررسی SelectStarExpression داریم، فقط کافی است Visitor آن‌را طراحی کنیم.

مثالی از نحوه‌ی استفاده از کلاس GenericVisitor فوق را در اینجا ملاحظه می‌کنید:
 var tsql = @"WITH ctex AS (
SELECT * FROM sys.objects
)
SELECT * FROM ctex";
GenericVisitor.Start(tsql, new SelectStarExpressionVisitor());
مطالب
React 16x - قسمت 1 - معرفی و شروع به کار
React یک کتابخانه‌ی جاوا اسکریپتی، برای ساخت رابط‌های کاربری سریع و تعاملی است. توسعه‌ی آن از سال 2011 در فیسبوک شروع شد و در حال حاضر محبوب‌ترین کتابخانه‌ی جاوا اسکریپتی در این رده‌است:


به همین جهت اگر می‌خواهید رزومه‌ی غنی‌تری را ارائه دهید، فراگیری React می‌تواند موقعیت‌های شغلی بیشتری را نصیب شما کند.


ساختار کلی یک برنامه‌ی React

کامپوننت‌ها (جزئی از یک رابط کاربری) قلب هر برنامه‌ی React ای را تشکیل می‌دهند. برای ساخت یک برنامه‌ی React، تعدادی کامپوننت مستقل را تهیه و با هم ترکیب می‌کنیم تا به رابط کاربری نهایی برسیم.
هر برنامه‌ی React، حداقل از یک کامپوننت تشکیل می‌شود که به آن Root component هم می‌گویند. این کامپوننت بیانگر کل برنامه‌است و دربرگیرنده‌ی مابقی Child components برنامه است. بنابراین ساختار هر برنامه‌ی React، شبیه به درختی از کامپوننت‌ها است. اگر با Angular 2 به بعد کار کرده باشید، این مفهوم برای شما آشنا است.
یک مثال: فرض کنید می‌خواهیم UI برنامه‌ای را به مانند رابط کاربری Twitter، ایجاد کنیم. هر قسمت یک صفحه‌ی توئیتر، به کامپوننت‌هایی شکسته می‌شود؛ مانند منوی راهبری، نمایش پروفایل شخص، نمایش لیست آخرین اخبار مورد علاقه‌ی شخص و نمایش فید. اگر بخواهیم این ساختار را توسط یک برنامه‌ی React شبیه سازی کنیم، در بالاترین سطح، کامپوننت root را خواهیم داشت که کار ترکیب و نمایش سایر کامپوننت‌های برنامه مانند nav bar ، trends ، profile و feed را انجام می‌دهد. اکنون در این ساختار ایجاد شده، برای مثال کامپوننت feed نیز می‌تواند از چندین کامپوننت مجزا تشکیل شود؛ مانند کامپوننت‌های tweet و like.
بنابراین هر کامپوننت، قسمتی از UI را تشکیل می‌دهد. هر کدام از آن‌ها به صورت مجزای از دیگری ساخته شده و سپس در کنار هم قرار می‌گیرند تا UI نهایی را شکل دهند:



هر کامپوننت در React به صورت یک کلاس ES6، با ساختاری که دارای یک شیء state و متد render است، تشکیل می‌شود:
class Tweet {
 state = {};
 
 render() {
 } 
}
state در اینجا همان اطلاعاتی است که قرار است در زمان نمایش این کامپوننت، رندر شود. کار متد render نیز همانطور که از نام آن نیز مشخص است، بیان نحوه‌ی تشکیل و رندر UI است. خروجی این متد، یک React Element است که در حقیقت یک شیء جاوا اسکریپتی خالص است و در نهایت به المان‌های DOM، نگاشت می‌شود. یک React Element، یک DOM Element واقعی نیست؛ بلکه تنها یک شیء جاوا اسکریپتی بیانگر DOM Element، در حافظه‌است. بنابراین یک برنامه‌ی React تشکیل شده‌است از لیستی از React Elementها در حافظه که به آن Virtual DOM هم گفته می‌شود.
مزیت کارکردن با Virtual DOM، سادگی ایجاد، تغییر و به روز رسانی آن در مقایسه با DOM واقعی است که در نهایت کار رندر عناصر UI را در مرورگر انجام می‌دهد. زمانیکه در state کامپوننتی تغییری رخ می‌دهد، یک React Element جدید تولید می‌شود. سپس React این شیء جدید را با نمونه‌ی قبلی آن مقایسه کرده و تغییرات رخ‌داده را محاسبه می‌کند. در آخر این تغییرات را به DOM واقعی اعمال می‌کند تا با Virtual DOM موجود هماهنگ شود.
بنابراین در حین کار با React، دیگر همانند کار با جاوا اسکریپت خالص و یا jQuery، مستقیما عناصر UI و DOM واقعی را تغییر نمی‌دهیم. در اینجا فقط state یک کامپوننت را تغییر می‌دهیم و سپس React، کار ایجاد شیء UI درون حافظه‌ای متناظر با آن و سپس اعمال آن‌را به UI نهایی قابل مشاهده‌ی در مرورگر، انجام می‌دهد. به همین جهت به این کتابخانه React می‌گویند! چون به تغییرات state کامپوننت‌ها واکنش نشان می‌دهد و سپس DOM واقعی را به روز می‌کند.


Angular یا React؟!

هر دوی React و Angular از لحاظ طراحی کامپوننت‌ها بسیار شبیه به هم هستند؛ اما Angular یک فریم‌ورک است و React تنها یک کتابخانه. تنها کاری را که React انجام می‌دهد، رندر View است و هماهنگ نگه داشتن آن با state کامپوننت‌ها. این تمام کاری است که React انجام می‌دهد؛ نه بیشتر و نه کمتر! بنابراین یادگیری React، بسیار سریع‌تر و ساده‌تر از Angular است. بدیهی است یک برنامه‌ی تک صفحه‌ای وب، از اجزای دیگری مانند مسیریابی و یا کار با سرویس‌های HTTP نیز تشکیل می‌شود. در React شما مختار هستید که کتابخانه‌های جانبی فراهم شده‌ی برای آن‌را خودتان انتخاب کرده و استفاده کنید؛ برخلاف روشی که در Angular مرسوم است و به صورت مشخص و ثابتی به همراه این فریم‌ورک ارائه می‌شوند.


برپایی محیط توسعه‌ی React

اولین برنامه‌ای را که برای کار با React باید نصب کنید، node.js است. البته ما در این سری قرار نیست با node.js کار کنیم؛ اما از یکی از اجزای آن به نام node package manager یا npm، برای نصب کتابخانه‌ی جاوا اسکریپتی ثالث، زیاد استفاده خواهیم کرد. پس از نصب آن، به خط فرمان مراجعه کرد و دستور زیر را صادر کنید:
> npm install -g npm@latest
این دستور npm قدیمی موجود بر روی سیستم را به روز رسانی می‌کند (اگر پیشتر یک node.js قدیمی را نصب و اکنون آن‌را به روز رسانی کرده‌اید).

اگر هم خیلی پیشترها node.js را نصب کرده‌اید (برای مثال چند سال قبل!)، نصب نگارش جدید آن احتمالا کار نخواهد کرد. حتی عزل و نصب مجدد آن نیز کارساز نیست. در این حالت باید پس از عزل آن، پوشه‌های قدیمی آن‌را یکی یکی یافته و دستی حذف کنید . سپس مجددا آن‌را نصب کنید.

در ادامه در خط فرمان و توسط npm، قالب create-react-app را نصب خواهیم کرد:
> npm i -g create-react-app
در اینجا سوئیچ i به معنای install است و g یعنی نصب global و سراسری بسته‌ی create-react-app. نصب سراسری یک بسته یعنی در هر پوشه‌ای می‌توان به امکانات آن دسترسی یافت و از آن استفاده کرد. اگر از سوئیچ g استفاده نمی‌شد، این بسته تنها در پوشه‌ی جاری و با سطح دید مختص به آن، نصب و قابل استفاده می‌شد.

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

و یا می‌توانید این فایل را اجرا کرده و تعدادی از افزونه‌های مفید VSCode را یکجا نصب کنید: install-addons.zip

همچنین قابلیت فرمت‌کردن پس از Save را نیز در VSCode فعال کنید تا پس از هربار Save، اعمال این افزونه‌ها به صورت خودکار صورت گیرد. برای این منظور گزینه‌ی file->preferences->settings را در VSCode انتخاب کرده و سپس save را جستجو کرده و Format On Save را انتخاب کنید:


علاوه بر این‌ها، جهت کار بهتر با VSCode، بهتر است بررسی کننده‌های کدهای جاوا اسکریپتی (static code analyzers) را نیز با اجرای دستور زیر نصب کنید:
> npm i -g typescript eslint tslint eslint-plugin-react-hooks

پس از این تغییرات، نیاز است یکبار VSCode را بسته و مجددا باز کنید. سپس مجددا گزینه‌ی file->preferences->settings را در VSCode انتخاب کرده و ابتدا eslint را در اینجا جستجو کنید. در صفحه‌ی نمایش تنظیمات آن، گزینه‌ی Auto fix on save آن‌را انتخاب نمائید. در آخر در همین قسمت settings، عبارت prettier را انتخاب کنید. در اینجا اگر گزینه‌ی قدیمی یکپارچگی با eslint آن هنوز وجود دارد، آن‌را از حالت انتخاب شده خارج کنید (به صورت قرمز و deprecated نمایش داده می‌شود) تا افزونه‌ی prettier بدون مشکل و خطا کار کند (disable Prettier ESLint integration).


ایجاد قالب اولین برنامه‌ی React

در ادامه برای ایجاد اولین برنامه‌ی React، از بسته‌ی create-react-app که پیشتر آن‌را نصب کردیم، استفاده می‌کنیم. برای این منظور در خط فرمان دستور زیر را صادر کنید:
> create-react-app sample-01
در اینجا sample-01 یک نام دلخواه است و در حین اجرای این دستور باید به اینترنت متصل باشید تا وابستگی‌های مرتبط با پروژه را نیز دریافت کند. برای بار اول، اجرای آن ممکن است کمی طول بکشد. اما از دفعات آتی، چون بسته‌های مرتبط را در npm-cache سیستم نیز ذخیره می‌کند، اجرای آن بسیار سریع خواهد بود.
این قالب نه تنها React را نصب می‌کند، بلکه یک development server را برای اجرا و مشاهده‌ی سریع برنامه، webpack را برای یکی کردن فایل‌ها (bundling & minification)، Babel را برای کامپایل کدهای فایل‌های JSX و ... نیز نصب می‌کند. بنابراین به این ترتیب، یک پروژه‌ی تنظیم شده و آماده‌ی استفاده و توسعه را شاهد خواهیم بود که نیازی به تنظیمات اولیه‌ی آن نیست.
پس ایجاد برنامه، وارد پوشه‌ی sample-01 شده و دستور npm start را صادر کنید:
> cd sample-01
> npm start
به این ترتیب برنامه بر روی پورت 3000، قابل دسترسی و مشاهده می‌شود:


development server آن، تغییرات فایل‌های برنامه را تحت نظر قرار می‌دهد و با هر تغییری، به صورت خودکار برنامه را در مرورگر بارگذاری مجدد خواهد کرد.


بررسی ساختار اولین پروژه‌ی React ایجاد شده

ساختار پوشه‌ها و فایل‌های مثال اولیه‌ی ایجاد شده توسط قالب create-react-app به صورت زیر است:


البته شما در این تصویر پوشه‌ی node_modules را که در کنار این پوشه‌ها قرار دارد، مشاهده نمی‌کنید. وجود یک چنین پوشه‌ی سنگینی با هزاران فایل داخل آن، کار نمایشی IDEها را با مشکل مواجه می‌کند (مصرف حافظه‌ی بالا، به همراه کند شدن شدید آن). اگر نمی‌خواهید این پوشه نمایش داده شود، در مسیر file->preferences->settings، عبارت npm را جستجو کرده و سپس در قسمت npm: exclude آن، بر روی لینک edit in settings.json کلیک کنید:


 و سپس در فایل باز شده، یک چنین تنظیمی را می‌توانید اضافه و یا ویرایش و تکمیل کنید:
  "files.exclude": {
    "**/.git": true,
    "**/.svn": true,
    "**/.hg": true,
    "**/CVS": true,
    "**/.DS_Store": true,
    "**/node_modules": true,
    "**/wwwroot": true,
    "**/bower_components": true,
    "**/**/bin": true,
    "**/**/obj": true,
    "**/packages": true
  },

در ادامه پوشه‌ی public این پروژه را مشاهده می‌کنید. تمام فایل‌هایی که قرار است به صورت عمومی توسط برنامه ارائه شوند، مانند favicon.ico و غیره، در این پوشه قرار می‌گیرند.
در این پوشه بر روی فایل index.html آن کلیک کنید تا بتوان محتوای آن‌را بهتر بررسی کرد. برای مثال در ابتدای آن، درج تعدادی متادیتا را که یکی از آن‌ها ذکر manifest.json است، مشاهده می‌کنید. کار فایل manifest.json، ارائه‌ی یک سری متادیتای خاص مخصوص دستگاه‌های موبایل است که در آن‌ها بجای favicon.ico، می‌توان از تصاویر و یا آیکن‌های بزرگتری مانند فایل‌های png موجود در پوشه‌ی public، استفاده کرد. در ادامه‌ی این فایل، به تنظیم زیر می‌رسیم:
  <body>
    <noscript>You need to enable JavaScript to run this app.</noscript>
    <div id="root"></div>
div با id مساوی root، محل ارائه‌ی کل برنامه‌ی React ما است.

در پوشه‌ی src و فایل App.js آن، شاهد یک کامپوننت ابتدایی هستید که کار رندر صفحه‌ی مشکی پیش‌فرض این قالب را انجام می‌دهد. در این فایل، شاهد بازگشت یک چنین تگ‌هایی هستیم:
  return (
    <div className="App">
      <header className="App-header">
       ... 
      </header>
    </div>
  );
احتمالا تابحال چنین return ای را در برنامه‌های جاوا اسکریپتی مشاهده نکرده‌اید؛ چون درج آن‌ها در فایل‌های js به این نحو، غیرمجاز است. این تگ‌ها نه رشته‌ای هستند و نه HTML خالص. به آن jsx گفته می‌شود که مخفف JavaScript XML می‌باشد. کار آن ارائه‌ی ساختار UI ای است که قرار است رندر شود. یک چنین کدی برای اینکه قابل تفسیر و اجرا باشد، از درون کامپایلر ویژه‌ای به نام Babel عبور می‌کند و تبدیل به کدهای جاوا اسکریپتی خالصی می‌شود که برای مرورگرها قابل درک و اجرا است.
برای درک بهتر آن به آدرس https://babeljs.io/repl مراجعه کنید. سپس در سمت چپ صفحه، یک قطعه کد jsx را به یک ثابت انتساب دهید:
const element = <h1>Hello World!</h1>;


همانطور که مشاهده می‌کنید، این قطعه کد jsx (که یک رشته‌ی معمولی نیست)، توسط Babel به یک قطعه کد کاملا جاوا اسکریپتی قابل درک برای مرورگر تبدیل شده‌است:
"use strict";

var element = React.createElement("h1", null, "Hello World!");

بدیهی است نوشتن کدهای jsx، ساده‌تر از نوشتن قطعه کد فوق است و درک آن نیز به علت شباهت آن به HTML، آسان‌تر است. به همین جهت در کدهای React، ما از jsx استفاده می‌کنیم و تفسیر آن‌را به Babel واگذار خواهیم کرد.

در پوشه‌ی src، فایل مهم دیگری که وجود دارد، index.js است. این فایل نقطه‌ی آغازین برنامه را مشخص می‌کند. در قسمت‌های بعدی، محتویات این فایل را بیشتر بررسی خواهیم کرد.
در اینجا فایل serviceWorker.js را نیز مشاهده می‌کنید. این فایل به صورت خودکار توسط قالب create-react-app ایجاد شده‌است و کار آن کمک به ارائه‌ی محلی برنامه، توسط development server آن است. بنابراین ما کاری با این فایل نخواهیم داشت.


نوشتن اولین برنامه‌ی React

به پوشه‌ی src ایجاد شده مراجعه کرده و تمام فایل‌های موجود و پیش‌فرض آن‌را حذف کنید. در ادامه خودمان آن‌ها را از صفر ایجاد خواهیم کرد. برای این منظور فایل جدید و خالی src\index.js را ایجاد می‌کنیم. در ابتدای کار نیاز است تعدادی ماژول React را import کنیم.
import React from "react";

const element = <h1>Hello World!</h1>;
console.log(element);
در اینجا شیء React از ماژول react دریافت شده و سپس یک ثابت را با یک عبارت jsx مقدار دهی کرده‌ایم. چون از jsx استفاده می‌کنیم، ذکر import ابتدای فایل الزامی است؛ از این جهت که Babel به کمک آن است که می‌تواند معادل React.createElement را تولید کند.
اگر هنوز برنامه توسط دستور npm start در حال اجرا است، هر بار که فایل index.js را ذخیره می‌کنیم، خروجی نهایی را در مرورگر نمایش می‌دهد (اگر هم آن‌را بسته‌اید، یکبار از طریق خط فرمان، دستور npm start را در ریشه‌ی پروژه، صادر کنید). به این قابلیت hot module reloading هم گفته می‌شود.
در این حالت اگر به مرورگر مراجعه کنید، یک صفحه‌ی سفید را مشاهده خواهید کرد. اکنون دکمه‌ی F12 را فشرده (و یا ctrl+shift+i) و developer console مرورگر را باز کنید.


شیءای را که در اینجا مشاهده می‌کنید، همان حاصل console.log کدهای فوق است؛ به عبارتی Babel، عبارت jsx ما را تبدیل به یک شیء جاوا اسکریپتی قابل فهم برای مرورگر کرده‌است که از دیدگاه React، جزئی از همان Virtual DOM ای است که پیشتر معرفی شد (نمایش درون حافظه‌ای DOM مختص React، جهت محاسبه‌ی تغییرات، با تغییر state هر کامپوننت و سپس اعمال آن‌ها به DOM اصلی در مرورگر).
اکنون می‌خواهیم این المان را در DOM اصلی، رندر کرده و نمایش دهیم:
import React from "react";
import ReactDOM from "react-dom";

const element = <h1>Hello World!</h1>;
console.log(element);

ReactDOM.render(element, document.getElementById("root"));
برای این منظور نیاز است از متد ReactDOM.render استفاده کرد. این شیء در ماژول react-dom قرار دارد؛ به همین جهت در ابتدای فایل import شده‌است. سپس در متد render آن، ابتدا المانی که قرار است رندر شود ذکر خواهد شد و سپس محل درج آن‌را مشخص می‌کنیم که دقیقا به همان div با id مساوی root در فایل public\index.html اشاره می‌کند.
اکنون پس از ذخیره سازی فایل index.js، اگر به مرورگر مراجعه کنید، عبارت Hello World! را مشاهده خواهید کرد:


همانطور که در این تصویر نیز مشخص است، المان h1 ما را داخل div ای با id مساوی root، درج کرده‌است.

هدف از این مثال ساده، نمایش نحوه‌ی کارکرد React، در پشت صحنه بود. در یک برنامه‌ی واقعی، بجای رندر یک المان ساده در DOM، کار رندر App component را انجام خواهیم داد. کامپوننت App، کامپوننت ریشه‌ای برنامه بوده و می‌تواند شامل درختی از کامپوننت‌ها که UI نهایی را تشکیل می‌دهند، شود.


نگاهی به تنظیمات پروژه‌ی ایجاد شده

اگر فایل package.json پروژه را باز کنید، یک چنین بسته‌هایی در آن درج شده‌است:
{
  "name": "sample-01",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "react": "^16.11.0",
    "react-dom": "^16.11.0",
    "react-scripts": "3.2.0"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
  },
  "eslintConfig": {
    "extends": "react-app"
  },
  "browserslist": {
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  }
}
در اینجا صرفا سه بسته‌ی react، react-dom و react-scripts را در قسمت dependencies مشاهده می‌کنید که کل Importهای ما را تشکیل می‌دهند.
بسته‌ی react-scripts است که کار مدیریت چهار جزء قسمت scripts این فایل را انجام می‌دهد. برای نمونه دستور npm start ای که در اینجا تعریف شده، سبب اجرای react-scripts start می‌شود. در ادامه اگر دستور npm run build را اجرا کنیم، یک بسته‌ی نهایی بهینه سازی شده را تولید می‌کند.
آخرین دستور آن eject است. اگر دستور npm run eject را اجرا کنید، امکان سفارشی سازی پشت صحنه‌ی create-react-app را خواهید داشت؛ اما در نهایت به یک فایل package.json بسیار شلوغ خواهیم رسید (اینبار ارجاعات به Babel، Webpack و تمام ابزارهای دیگر نیز ظاهر می‌شوند). همچنین این عملیات نیز یک طرفه‌است. یعنی از این پس قرار است کنترل تمام این پشت صحنه، در اختیار ما باشد و به روز رسانی‌های بعدی create-react-app را با مشکل مواجه می‌کند. این گزینه صرفا مختص توسعه دهندگان پیشرفته‌ی React است.


کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: sample-01.zip

در قسمت بعد، پیشنیازهای جاوا اسکریپتی شروع به کار با React را بررسی می‌کنیم.
مطالب
اثبات قانون مشاهده‌گر در برنامه نویسی
امروز حین کدنویسی به یک مشکل نادر برخورد کردم. کلاسی پایه داشتم (مثلا Person) که یک سری کلاس دیگر از آن ارث بری میکردند (مثلا کلاس‌های Student و Teacher).در اینجا در کلاس پایه بصورت اتوماتیک یک ویژگی(Property) را روی کلاس‌های مشتق شده مقدار دهی میکردم؛ مثلا به این شکل:
 public class Person
    {      
        public Person()
        {
            personId= this.GetType().Name + (new Random()).Next(1, int.MaxValue);          
        }
     }
سپس در یک متد مجموعه‌ای از Studentها و teacher‌ها را ایجاد کرده و به لیستی از Person‌ها اضافه میکنم:
var student1=new Student(){Name="Iraj",Age=21};
var student1=new Student(){Name="Nima",Age=20};
var student1=new Student(){Name="Sara",Age=25};
var student1=new Student(){Name="Mina",Age=22};
var student1=new Student(){Name="Narges",Age=26};
var teacher1=new Student(){Name="Navaei",Age=45};
var teacher2=new Student(){Name="Imani",Age=50};
اما در نهایت اتفاقی که رخ میداد این بود که PersonId همه Student‌ها یکسان می‌شد ولی قضیه به همین جا ختم نشد؛ وقتی خط به خط برنامه را Debug و مقادیر را Watch می‌کردم، مشاهده می‌کردم که PersonId به درستی ایجاد می‌شود.
در فیزیک نوین اصلی هست به نام عدم قطعیت هایزنبرگ که به زبان ساده میتوان گفت نحوه رخداد یک اتفاق، با توجه به وجود یا عدم وجود یک مشاهده‌گر خارجی نتیجه‌ی متفاوتی خواهد داشت.
کم کم داشتم به وجود قانون مشاهده‌گر در برنامه نویسی هم ایمان پیدا میکردم که این کد فقط در صورتیکه آنرا مرحله به مرحله بررسی کنم جواب خواهد داد!
جالب اینکه زمانیکه personId  را نیز ایجاد میکردم، یک دستور برای دیدن خروجی نوشتم مثل این
 public class Person
    {      
        public Person()
        {
            personId= this.GetType().Name + (new Random()).Next(1, int.MaxValue);  
            Debug.Print(personId)       
        }
     }
در این حالت نیز دستورات درست عمل میکردند و personId متفاوتی ایجاد می‌شد!
قبل از خواندن ادامه مطلب شما هم کمی فکر کنید که مشکل کجاست؟
 این مشکل ربطی به قانون مشاهده‌گر و یا دیگر قوانین فیزیکی نداشت. بلکه بدلیل سرعت بالای ایجاد وهله ها(instance) از کلاسی‌های مطروحه (مثلا در زمانی کمتر از یک میلی ثانیه) زمانی در بازه یک کلاک CPU رخ می‌داد.
هر نوع ایجاد کندی (همچون نمایش مقادیر در خروجی) باعث می‌شود کلاک پردازنده نیز تغییر کند و عدد اتفاقی تولید شده فرق کند.
همچنین برای حل این مشکل میتوان از کلاس تولید کننده اعداد اتفاقی، شبیه زیر استفاده کرد:
using System;
using System.Threading;

public static class RandomProvider
{    
    private static int seed = Environment.TickCount;

    private static ThreadLocal<Random> randomWrapper = new ThreadLocal<Random>(() =>
        new Random(Interlocked.Increment(ref seed))
    );

    public static Random GetThreadRandom()
    {
        return randomWrapper.Value;
    }
}

نظرات مطالب
طراحی یک گرید با Angular و ASP.NET Core - قسمت دوم - پیاده سازی سمت کلاینت
پیاده سازی جستجوی بر روی این گرید، شامل موارد زیر است:
اضافه کردن دو خاصیت جدید به کلاس PagedQueryModel سمت کلاینت جهت مشخص سازی ستونی که قرار است بر روی آن جستجو انجام شود و همچنین مقدار آن:
export class PagedQueryModel {
  constructor(
    // ...
    public filterByColumn: string,
    public filterByValue: string,
  ) { }
}
سپس به ProductsListComponent دو متد زیر را اضافه می‌کنیم:
  doFilter() {
    this.queryModel.page = 1;
    this.getPagedProductsList();
  }

  resetFilter() {
    this.queryModel.page = 1;
    this.queryModel.filterByColumn = "";
    this.queryModel.filterByValue = "";
    this.getPagedProductsList();
  }
اولی کار جستجو را انجام می‌دهد و دومی بازگشت حالت گرید به وضعیت اول آن است. متد getPagedProductsList قابلیت واکشی خودکار اطلاعات دو خاصیت جدیدی را که اضافه کردیم دارد و نیازی به تنظیمات اضافه‌تری ندارد. یعنی filterByColumn و filterByValue را به صورت خودکار به سمت سرور ارسال می‌کند.

پس از آن، قالب این گرید (products-list.component.html) جهت افزودن جستجو، به صورت زیر تغییر می‌کند:
<div class="panel panel-default">
  <div class="panel-body">
    <div class="form-group">
      <input type="text" [(ngModel)]="queryModel.filterByValue" placeholder="Search For ..."
        class="form-control" />
    </div>
    <div class="form-group">
      <select class="form-control" name="filterColumn" [(ngModel)]="queryModel.filterByColumn">
        <option value="">Filter by ...</option>
        <option *ngFor="let column of columns" [value]="column.propertyName">
          {{ column.title }}
        </option>
      </select>
    </div>
    <button class="btn btn-primary" type="button" (click)="doFilter()">Search</button>
    <button class="btn btn-default" type="button" (click)="resetFilter()">Reset</button>
  </div>
</div>
که در آن queryModel.filterByColumn و queryModel.filterByValue از کاربر دریافت می‌شوند. همچنین دو متد doFilter و resetFilter را نیز فراخوانی می‌کند.
با این شکل:


تغییرات سمت سرور آن نیز به صورت ذیل است:
ابتدا IPagedQueryModel را با همان دو خاصیت جدید ستون فیلتر شونده و مقدار آن، تکمیل می‌کنیم:
    public interface IPagedQueryModel
    {
    // ....
        string FilterByColumn { get; set; }
        string FilterByValue { get; set; }
    }

    public class ProductQueryViewModel : IPagedQueryModel
    {
        // ... other properties ...

// ...
        public string FilterByColumn { get; set; }
        public string FilterByValue { get; set; }
    }
از این دو خاصیت جدید، جهت افزودن متد اعمال جستجو، همانند متد ApplyOrdering که پیشتر تعریف شد، استفاده می‌کنیم:
    public static class IQueryableExtensions
    {
        public static IQueryable<T> ApplyFiltering<T>(
          this IQueryable<T> query,
          IPagedQueryModel model,
          IDictionary<string, Expression<Func<T, object>>> columnsMap)
        {
            if (string.IsNullOrWhiteSpace(model.FilterByValue) || !columnsMap.ContainsKey(model.FilterByColumn))
            {
                return query;
            }

            var func = columnsMap[model.FilterByColumn].Compile();
            return query.Where(x => func(x).ToString() == model.FilterByValue);
        }
در اینجا همان columnsMap مورد استفاده در متد ApplyOrdering جهت نگاشت نام‌های رشته‌ای ستون‌ها به معادل Expression آن‌ها استفاده شده‌است.

در آخر، به کنترلر ProductController و اکشن متد GetPagedProducts آن مراجعه کرده و پیش از ApplyOrdering، متد جدید ApplyFiltering فوق را اضافه می‌کنیم:
var columnsMap = new Dictionary<string, Expression<Func<Product, object>>>()
            {
                ["productId"] = p => p.ProductId,
                ["productName"] = p => p.ProductName,
                ["isAvailable"] = p => p.IsAvailable,
                ["price"] = p => p.Price
            };
query = query.ApplyFiltering(queryModel, columnsMap);
query = query.ApplyOrdering(queryModel, columnsMap);

کدهای کامل این تغییرات را از اینجا می‌توانید دریافت کنید.
مطالب
مقایسه value type و reference type
در سی شارپ دو نوع class و struct وجود دارد که تقریباً مشابه یکدیگرند در حالیکه یکی از آنها-value type و دیگری reference-type است.

struct چیست؟
structها مشابه classها هستند با این تفاوت که structها finalizer ندارند و از ارث بری پشتیبانی نمی‌کنند. structها کاملا مشابه classها تعریف می‌شوند و در تعریف آنها از کلمه کلیدی struct استفاده می‌شود. آنها شامل فیلدها، متدها، خصوصیت‌ها نیز می‌شوند. در زیر نحوه تعریف آن را مشاهده می‌کنید: 

struct Point
{
   private int x, y;             // private fields
 
   public Point (int x, int y)   // constructor
   {
         this.x = x;
         this.y = y;
   }

   public int X                  // property
   {
         get {return x;}
         set {x = value;}
   }

   public int Y
   {
         get {return y;}
         set {y = value;}
   }
}

value type و reference type
تفاوت دیگری که بین class و struct، از اهمیت ویژه‌ای برخوردار است  آن است که classها reference-type و structها value-type هستند و در زمان اجرا با آنها متفاوت رفتار می‌شود و در ادامه به تشریح آن می‌پردازیم.
وقتی یک وهله از value-type ایجاد شود، یک فضای خالی از حافظه‌ی اصلی (RAM) برای ذخیره سازی مقدار آن تخصیص داده می‌شود. نوع‌های اصلی مانند int, float, bool و char از نوع value type هستند. در ضمن سرعت دسترسی به آنها بسیار بالاست.
ولی وقتی یک وهله از reference-type ایجاد شود، یک فضا برای object و فضایی دیگر برای اشاره‌گر به آن شیء در حافظه اصلی ذخیره می‌شود. در واقع دو فضا از حافظه برای ذخیره سازی آنها اشغال می‌شود. برای درک بهتر به مثال زیر توجه کنید:
Point p1 = new Point();         // Point is a *struct*
Form f1 = new Form();           // Form is a *class*
نکته: Point از نوع struct و Form از نوع reference است. در مورد اول، یک فضا از حافظه برای p1 تخصیص داده می‌شود و در مورد دوم، دو فضا از حافظه اصلی یکی برای ذخیره کردن اشاره‌گر f1 برای اشاره به Form object و دیگری برای ذخیره کردن Form object تخصیص داده می‌شود.
Form f1;                        // Allocate the reference
f1 = new Form();                // Allocate the object
به قطعه کد زیر دقت کنید:
Point p2 = p1;
Form f2 = f1;
همانطور که قبلاً گفته شد p2، یک نوع struct است بنابراین در مورد اول مقدار p2 یک کپی از مقدار p1 خواهد بود ولی در مورد دوم، آدرس f1 را درون f2 کپی می‌کنیم در واقع f1 و f2 به یک شیء اشاره خواهند کرد. (یک شیء با 2 اشاره گر)
در سی شارپ، پارامترها (بصورت پیش فرض) بصورت یک کپی از آنها به متدها ارسال می‌شوند، یعنی اگر پارامتر از نوع value-type باشد یک کپی از آن وهله و اگر پارامتر reference-type یک کپی از آدرس ارسال خواهد شد. برای توضیح بهتر به مثال زیر توجه کنید:
Point myPoint = new Point (0, 0);      // a new value-type variable
Form myForm = new Form();              // a new reference-type variable
Test (myPoint, myForm);                // Test is a method defined below
 
void Test (Point p, Form f)
{
      p.X = 100;                       // No effect on MyPoint since p is a copy
      f.Text = "Hello, World!";        // This will change myForm’s caption since
                                       // myForm and f point to the same object
      f = null;                        // No effect on myForm
}
انتساب null به f درون متد Test هیچی اثری بر روی آدرس myForm ندارد چون f، یک کپی از آدرس myForm است.
حال می‌توانیم روش پیش فرض را با افزودن کلمه کلید ref تغییر دهیم.  وقتی از ref استفاده کنیم متد با پارامترهای فراخوانی کننده (caller's arguments) بصورت مستقیم در تعامل است در کد زیر می‌توانیم تصور کنیم که پارامترهای p و f متد Test همان متغیرهای myPoint و myForm است.
Point myPoint = new Point (0, 0);      // a new value-type variable
Form myForm = new Form();              // a new reference-type variable
Test (ref myPoint, ref myForm);        // pass myPoint and myForm by reference
 
void Test (ref Point p, ref Form f)
{
      p.X = 100;                       // This will change myPoint’s position
      f.Text = “Hello, World!”;        // This will change MyForm’s caption
      f = null;                        // This will nuke the myForm variable!
}
در کد بالا انتساب null به f باعث تهی شدن myForm می‌شود بدلیل اینکه متد مستقیماً به آن دسترسی داشته است.

تخصیص حافظه
CLR اشیاء را در دو قسمت ذخیره می‌کند:
  1. stack یا پشته
  2. heap
ساختار stack یا پشته first-in last-out است که دسترسی به آن سریع است. زمانی که متدی فراخوانی می‌شود، CLR پشته را نشانه گذاری می‌کند. سپس متد data را به پشته جهت اجرا push می‌کند و زمانی که اجرایش به اتمام رسید، CLR پشته را تا محل نشانه گذاری شده مرحله قبل، پاک می‌کند (pop).
ولی ساختار heap بصورت تصادفی است. یعنی اشیاء در محل‌های تصادفی قرار داده می‌شوند بهمین دلیل آنها دارای 2 سربار memory manager و garbage-collector هستند.
برای آشنایی با نحوه استفاده پشته و heap به کد زیر توجه کنید:
void CreateNewTextBox()
{
      TextBox myTextBox = new TextBox();             // TextBox is a class
}
در این متد، ما یک متغیر محلی ایجاد کرده ایم که به یک شیء اشاره می‌کند.

پشته همیشه برای ذخیره سازی موارد زیر استفاده می‌شود:
  • قسمت reference متغیرهای محلی و پارامترهای از نوع reference-typed (مانند myTextBox)
  • متغیرهای محلی و پارامترهای متد از نوع value-typed (مانند integer, bool, char, DateTime و ...)
همچنین از heap برای ذخیره سازی موارد زیر استفاده می‌شود:
  • محتویات شیء از نوع reference-typed
  • هر چیزی که قرار است در شیء از نوع reference-typed ذخیره شود.

آزادسازی حافظه در heap

در کد بالا وقتی اجرای متد CreateNewTextBox به اتمام برسد متغیر myTextBox از دید (Scope) خارج می‌شود. بنابراین از پشته نیز خارج می‌شود ولی با خارج شدن myTextBox از پشته چه اتفاقی برای TextBox object رخ خواهد داد؟! پاسخ در garbage-collector نهفته است. garbage-collector بصورت خودکار عملیات پاکسازی heap را انجام می‌دهد و اشیائی که اشاره گر معتبر ندارند را حذف می‌نماید. در حالت کلی اگر شیء از حافظه خارج شد باید منابع سایر قسمت‌های اشغال شده توسط آن هم آزاد شود، که این آزاد سازی بعهده garbage-collector است.

حال آزاد سازی برای کلاسهایی که اینترفیس IDisposable را پیاده سازی می‌کنند به دو صورت انجام می‌شود:

  1. دستی: با فراخوانی متد Dispose میسر است.
  2. خودکار: افزودن شیء به Net Container. مانند Form, Panel, TabPage یا UserControl. این نگهدارندها این اطمینان را به ما می‌دهند در صورتیکه آنها از حافظه خارج شدند کلیه عضوهای آن هم از حافظه خارج شوند.

برای آزادسازی دستی می‌توانیم مانند کدهای زیر عمل کنیم:

using (Stream s = File.Create ("myfile.txt"))

{
   ...
}
یا
Stream s = File.Create ("myfile.txt");

try
{
   ...
}

finally
{
   if (s != null) s.Dispose();
}


مثالی از Windows Forms
فرض کنید قصد داریم فونت و اندازه یک ویندوز فرم را تغییر دهیم.

Size s = new Size (100, 100);          // struct = value type
Font f = new Font (“Arial”,10);        // class = reference type

Form myForm = new Form();

myForm.Size = s;
myForm.Font = f;
توجه کنید که ما در کد بالا از اعضای myForm استفاده کردیم نه از کلاسهای Font و Size که این دو گانگی قابل قبول است. حال به تصویر زیر که به پیاده سازی کد بالا اشاره دارد توجه کنید.

همانطور که مشاهد می‌کنید محتویات s و آدرس f را در Form object ذخیره کرده ایم که نشان می‌دهد تغییر در s برروی فرم تغییر ایجاد نمی‌کند ولی تغییر در f باعث ایجاد تغییر فرم می‌شود. Form object دو اشاره گر به Font object دارد.

In-Line Allocation (تخصیص درجا)
در قبل گفته شد برای ذخیره متغیرهای محلی از نوع value-typed از پشته استفاده می‌شود آیا شیء Size جدید هم در پشته ذخیره می‌شود؟ خیر، بدلیل اینکه آن متغیر محلی نیست و در شیء دیگر ذخیره می‌شود (در مثال بالا در یک فرم ذخیره شده است) که آن شیء هم در heap ذخیره شده است پس شیء جدید Size هم در heap ذخیر می‌شود که به این نوع ذخیره سازی In-Line گفته می‌شود.

تله (Trap)
فرض کنید کلاس Form بشکل زیر تعریف شده است:
class Form
{
      // Private field members
      Size size;
      Font font;

      // Public property definitions
      public Size Size
      {
            get    { return size; }
            set    { size = value; fire resizing events }
      }

      public Font Font
      {
            get    { return font;  }
            set    { font = value; }
      }
}
حال ما قصد داریم ارتفاع آن را دو برابر کنیم، بنابراین از کد زیر استفاده می‌کنیم:
myForm.ClientSize.Height = myForm.ClientSize.Height * 2;
ولی با خطای کامپایلر زیر روبرو می‌شویم:
Cannot modify the return value of 'System.Windows.Forms.Form.ClientSize' because it is not a variable
علت چیست؟ بدلیل اینکه myForm.ClientSize شیء Size که از نوع Struct است را بر می‌گرداند و این Struct از نوع value-typed است و این شیء یک کپی از اندازه فرم است و ما همزمان قصد دو برابر نمودن آن کپی را داریم که کامپایلر خطای بالا را نمایش می‌دهد.

برای توضیح بیشتر می‌توانید به این سوال مراجعه کنید و در تکمیل آن این لینک را هم بررسی کنید.

پس بنابراین کد بالا را به کد زیر اصلاح می‌کنیم:
myForm.ClientSize = new Size (myForm.ClientSize.Width, myForm.ClientSize.Height * 2);
برای اصلاح خطای کامپایلر، ما باید یک شیء جدیدی را برای اندازه فرم تخصیص بدهیم.
نظرات مطالب
معرفی ASP.NET Identity
کلاس کاربر:
public class AppUser : IdentityUser
{
    public string Email { get; set; }
    public string ConfirmationToken { get; set; }
    public bool IsConfirmed { get; set; }

    public virtual UserProfile Profile { get; set; }
}

کلاس پروفایل کاربر:
public class UserProfile
{
    public int Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }

    public DateTime? Birthday { get; set; }

    public byte[] Avatar { get; set; }
}

کلاس کانتکست دیتابیس:
public class SampleDbContext : IdentityDbContext
{
    public SampleDbContext() : base("DefaultConnection") { }

    static SampleDbContext()
    {
        Database.SetInitializer(new DropCreateDatabaseIfModelChanges<SampleDbContext>());
    }

    public DbSet<UserProfile> UserProfiles { get; set; }
    public DbSet<Customer> Customers { get; set; }
    public DbSet<Product> Products { get; set; }
    ...
}


این کلاس‌ها می‌تونن تو لایه دیگری مثل Domain Models تعریف بشن.
مطالب
React 16x - قسمت 21 - کار با فرم‌ها - بخش 4 - چند تمرین
پس از فراگیری اصول کار کردن با فرم‌ها در React، اکنون می‌خواهیم چند فرم جدید را برای تمرین بیشتر، به برنامه‌ی نمایش لیست فیلم‌ها اضافه کنیم؛ مانند فرم ثبت نام، فرمی برای ثبت و یا ویرایش فیلم‌ها و یک فرم جستجوی سریع در لیست فیلم‌های موجود.

تمرین 1 - ایجاد فرم ثبت نام


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

برای حل این تمرین، فایل جدید registerForm.jsx را در پوشه‌ی components ایجاد می‌کنیم و سپس توسط میانبرهای imrc و cc در VSCode، ساختار ابتدایی کامپوننت RegisterForm را ایجاد کرده و سپس آن‌را به صورت زیر تکمیل می‌کنیم:
- ابتدا در فایل app.js، پس از import ماژول آن:
import RegisterForm from "./components/registerForm";
در ابتدای سوئیچ تعریف شده، مسیریابی آن‌را تعریف می‌کنیم:
<Route path="/register" component={RegisterForm} />
- سپس در فایل src\components\navBar.jsx، لینک به آن‌را، در انتهای لیست اضافه می‌کنیم، تا در منوی راهبری ظاهر شود:
<NavLink className="nav-item nav-link" to="/register">
   Register
</NavLink>
- در ادامه کدهای کامل کامپوننت ثبت نام را ملاحظه می‌کنید:
import Joi from "@hapi/joi";
import React from "react";

import Form from "./common/form";

class RegisterForm extends Form {
  state = {
    data: { username: "", password: "", name: "" },
    errors: {}
  };

  schema = {
    username: Joi.string()
      .required()
      .email({ minDomainSegments: 2, tlds: { allow: ["com", "net"] } })
      .label("Username"),
    password: Joi.string()
      .required()
      .min(5)
      .label("Password"),
    name: Joi.string()
      .required()
      .label("Name")
  };

  doSubmit = () => {
    // Call the server
    console.log("Submitted");
  };

  render() {
    return (
      <div>
        <h1>Register</h1>
        <form onSubmit={this.handleSubmit}>
          {this.renderInput("username", "Username")}
          {this.renderInput("password", "Password", "password")}
          {this.renderInput("name", "Name")}
          {this.renderButton("Register")}
        </form>
      </div>
    );
  }
}

export default RegisterForm;
- ابتدا این کامپوننت را بجای ارث بری از Component خود React، از کامپوننت Form که در قسمت قبل ایجاد کردیم، ارث بری می‌کنیم تا به تمام امکانات آن مانند اعتبارسنجی، مدیریت حالت و متدهای کمکی تعریف فیلدها و دکمه‌ها بهره‌مند شویم.
- سپس state این کامپوننت را با شیءای حاوی دو خاصیت data و error، مقدار دهی اولیه می‌کنیم. خواص متناظر با المان‌های فرم را نیز به صورت یک شیء، به خاصیت data انتساب داده‌ایم.
- پس از آن، خاصیت schema تعریف شده‌است؛ تا قواعد اعتبارسنجی تک تک فیلدهای فرم را به کمک کتابخانه‌ی Joi، مطابق نیازمندی‌هایی که در ابتدای تعریف این تمرین مشخص کردیم، ایجاد کند.
- در ادامه، متد doSubmit را ملاحظه می‌کنید. این متد پس از کلیک بر روی دکمه‌ی Register و پس از اعتبارسنجی موفقیت آمیز فرم، به صورت خودکار فراخوانی می‌شود.
- در آخر، تعریف فرم ثبت‌نام را مشاهده می‌کنید که نکات آن‌را در قسمت قبل، با معرفی کامپوننت Form و افزودن متدهای کمکی رندر input و button به آن، بررسی کردیم و در کل با نکات بررسی شده‌ی در فرم لاگینی که تا به اینجا ایجاد کردیم، تفاوتی ندارد.


تمرین 2- ایجاد فرم ثبت و یا ویرایش یک فیلم


فرم جدید ثبت و ویرایش یک فیلم، نکات بیشتری را به همراه دارد. در اینجا می‌خواهیم در بالای لیست نمایش فیلم‌ها، یک دکمه‌ی new movie را اضافه کنیم تا با کلیک بر روی آن، به فرم ثبت و ویرایش فیلم‌ها هدایت شویم. این فرم، از فیلدهای یک عنوان متنی، انتخاب ژانر از یک drop down list، تعداد موجود (بین 1 و 100) و امتیاز (بین صفر تا 10) تشکیل شده‌است. همچنین تا زمانیکه اعتبارسنجی فرم تکمیل نشده‌است، دکمه‌ی submit فرم باید غیرفعال باقی بماند. پس از ذخیره شدن این فیلم (در لیست درون حافظه‌ای برنامه)، با مراجعه‌ی به لیست فیلم‌ها و انتخاب آن از لیست (با کلیک بر روی لینک آن)، باید مجددا به همین فرم، در حالت ویرایش این رکورد هدایت شویم. به علاوه اگر در بالای صفحه یک id اشتباه وارد شد، باید صفحه‌ی «پیدا نشد» نمایش داده شود.

کامپوننت MovieForm و مسیریابی آن‌را در قسمت 17، تعریف و اضافه کردیم. برای تعریف لینکی به آن، به کامپوننت movies مراجعه کرده و بالای متنی که تعداد کل آیتم‌های موجود در بانک اطلاعاتی را نمایش می‌دهد، المان زیر را اضافه می‌کنیم:
import { Link } from "react-router-dom";
// ...


<div className="col">
  <Link
    to="/movies/new"
    className="btn btn-primary"
    style={{ marginBottom: 20 }}
  >
    New Movie
  </Link>
  <p>Showing {totalCount} movies in the database.</p>
این Link را هم با کلاس btn مزین کرده‌ایم تا شبیه به یک دکمه، به نظر برسد. با کلیک بر روی آن، به آدرس movies/new هدایت خواهیم شد؛ یعنی id جدید این مسیریابی را به "new" تنظیم کرده‌ایم که در ادامه بر اساس آن، تفاوت بین حالت ویرایش و حالت ثبت اطلاعات، مشخص می‌شود.


سپس به کامپوننت src\components\movieForm.jsx که پیشتر آن‌را اضافه کرده بودیم، مراجعه کرده و به صورت زیر آن‌را تکمیل می‌کنیم:
import Joi from "@hapi/joi";
import React from "react";

import { getGenres } from "../services/fakeGenreService";
import { getMovie, saveMovie } from "../services/fakeMovieService";
import Form from "./common/form";

class MovieForm extends Form {
  state = {
    data: {
      title: "",
      genreId: "",
      numberInStock: "",
      dailyRentalRate: ""
    },
    genres: [],
    errors: {}
  };
- ابتدا importهای مورد نیاز به Joi، React و همچنین سرویس‌های لیست فیلم‌ها و لیست ژانرهای سینمایی، به همراه کامپوننت فرم، تعریف شده‌اند.
- سپس این کامپوننت نیز از کامپوننت Form ارث بری می‌کند تا به امکانات ویژه‌ی آن دسترسی پیدا کند.
- در ادامه در خاصیت state، طبق روالی که در کامپوننت فرم درنظر گرفته‌ایم، دو خاصیت data و errors باید حضور داشته باشند. در خاصیت data، شیءای که نام خاصیت‌های آن با فیلدهای فرم تطابق دارد، ذکر شده‌اند. در اینجا برای ذخیره سازی اطلاعات انتخاب شده‌ی از drop down list مرتبط با ژانرهای سینمایی، از خاصیت genreId استفاده می‌شود؛ این تنها اطلاعاتی است که از کل آیتم‌های یک drop down list نیاز داریم. آرایه‌ی genres که آیتم‌های این drop down list را مقدار دهی می‌کند، در روال componentDidMount، از سرویس مرتبطی دریافت و مقدار دهی خواهد شد.

در ادامه‌ی کدهای کامپوننت MovieForm، کدهای schema اعتبارسنجی شیء data را ملاحظه می‌کنید:
  schema = {
    _id: Joi.string(),
    title: Joi.string()
      .required()
      .label("Title"),
    genreId: Joi.string()
      .required()
      .label("Genre"),
    numberInStock: Joi.number()
      .required()
      .min(0)
      .max(100)
      .label("Number in Stock"),
    dailyRentalRate: Joi.number()
      .required()
      .min(0)
      .max(10)
      .label("Daily Rental Rate")
  };
در اینجا، id به required تنظیم نشده‌است؛ چون زمانیکه قرار است یک شیء movie جدید را  ایجاد کنیم، هنوز این id نامشخص است. سایر موارد خاصیت schema، به لطف fluent api کتابخانه‌ی Joi، بسیار خوانا بوده و نیاز به توضیحات خاصی ندارند. برای مثال هر دو خاصیت numberInStock و  dailyRentalRate باید عددی وارد شده و بین بازه‌ی مشخصی قرار گیرند.

اکنون به مرحله‌ی componentDidMount می‌رسیم:
  componentDidMount() {
    const genres = getGenres();
    this.setState({ genres });

    const movieId = this.props.match.params.id;
    if (movieId === "new") return;

    const movie = getMovie(movieId);
    if (!movie) return this.props.history.replace("/not-found");

    this.setState({ data: this.mapToViewModel(movie) });
  }
- در اینجا لیست ژانرهای سینمایی از متد getGenres فایل src\services\fakeGenreService.js دریافت شده و پس از آن کار به روز رسانی خاصیت genres در state را انجام می‌دهیم. این به روز رسانی state، سبب می‌شود تا این خاصیت که آرایه‌ای است، در رندر بعدی این کامپوننت، به لیست options مربوط به drop down list درج شده‌ی در فرم، ارسال شده و در فرم رندر شود.
- پس از آن، نحوه‌ی دریافت پارامتر id مسیریابی رسیده را ملاحظه می‌کنید. این id اگر به "new" تنظیم شده بود، یعنی قرار است، اطلاعات جدیدی ثبت شوند. بنابراین متد جاری را خاتمه می‌دهیم (چون کار ادامه‌ی این متد، مقدار دهی اولیه‌ی تمام فیلدهای فرم، بر اساس اطلاعات شیء دریافت شد‌ه‌ی از سرویس فیلم‌ها است). در غیراینصورت (و با مشخص بودن id)، با استفاده از این id و متد getMovie سرویس src\services\fakeMovieService.js، سعی خواهیم کرد تا اطلاعات شیء movie متناظری را دریافت کنیم. اگر خروجی این متد null بود، یعنی id وارد شده معتبر نیست. به همین جهت کاربر را به صفحه‌ی not-found هدایت می‌کنیم. اگر دقت کنید در اینجا بجای متد push، از متد replace استفاده کرده‌ایم. چون اگر از متد push استفاده می‌کردیم و کاربر بر روی دکمه‌ی back مرورگر کلیک می‌کرد، دوباره به همین صفحه، با id غیرمعتبر قبلی وارد می‌شد و یک حلقه‌ی بی‌پایان رخ می‌داد. همچنین به return ای هم که به همراه متد replace استفاده شده، دقت کنید. کار redirect به یک صفحه‌ی دیگر، به معنای عدم اجرای کدهای پس از آن نیست. بنابراین اگر می‌خواهیم کار این متد با redirect، به پایان برسد، ذکر return الزامی است.
- در پایان این متد، خاصیت data موجود در state را به روز رسانی می‌کنیم؛ تا سبب رندر فرم، با اطلاعات شیء movie یافت شده گردد و چون ساختار شیء movie دریافت شده‌ی از سرویس، با ساختار data تعریف شده‌ی در state یکی نیست، نیاز به نگاشت این دو به هم، توسط متد سفارشی mapToViewModel زیر است:
  mapToViewModel(movie) {
    return {
      _id: movie._id,
      title: movie.title,
      genreId: movie.genre._id,
      numberInStock: movie.numberInStock,
      dailyRentalRate: movie.dailyRentalRate
    };
  }
این سناریو بسیار متداول است و اکثر داده‌های دریافت شده‌ی از سرور، الزاما با ساختار داده‌هایی که در فرم‌های خود تعریف می‌کنیم (که در اینجا view-model نام گرفته)، یکی نیستند و نیاز به نگاشت بین آن‌ها وجود دارد. برای مثال genreId موجود در view-model این فرم (همان شیء منتسب به data در state)، دقیقا به همین نام، در شیء movie تعریف نشده‌است و نیاز به نگاشت این دو به هم است.

در ادامه‌ی کدهای کامپوننت فرم فیلم‌ها، به متد doSubmit می‌رسیم:
  doSubmit = () => {
    saveMovie(this.state.data);

    this.props.history.push("/movies");
  };
این متد پس از کلیک کاربر بر روی دکمه‌ی submit و اعتبارسنجی کامل فرم، فراخوانی می‌شود. در این مرحله می‌توان اطلاعات موجود در شیء data را به متد saveMovie سرویس src\services\fakeMovieService.js ارسال کرد، تا آن‌را به لیست خودش اضافه کند. سپس کاربر را به لیست به روز شده‌ی فیلم‌ها هدایت می‌کنیم.

در انتهای این کامپوننت نیز به متد رندر آن می‌رسیم:
  render() {
    return (
      <div>
        <h1>Movie Form</h1>
        <form onSubmit={this.handleSubmit}>
          {this.renderInput("title", "Title")}
          {this.renderSelect("genreId", "Genre", this.state.genres)}
          {this.renderInput("numberInStock", "Number in Stock", "number")}
          {this.renderInput("dailyRentalRate", "Rate")}
          {this.renderButton("Save")}
        </form>
      </div>
    );
  }
تمام قسمت‌های این فرم را منهای متد جدید renderSelect آن، پیشتر در قسمت قبل، مرور کرده‌ایم و نکته‌ی جدیدی ندارند.
برای تعریف متد جدید renderSelect به این صورت عمل می‌کنیم:
- ابتدا فایل جدید src\components\common\select.jsx را ایجاد کرده و سپس آن‌را جهت نمایش یک drop down list، ویرایش می‌کنیم:
import React from "react";

const Select = ({ name, label, options, error, ...rest }) => {
  return (
    <div className="form-group">
      <label htmlFor={name}>{label}</label>
      <select name={name} id={name} {...rest} className="form-control">
        <option value="" />
        {options.map(option => (
          <option key={option._id} value={option._id}>
            {option.name}
          </option>
        ))}
      </select>
      {error && <div className="alert alert-danger">{error}</div>}
    </div>
  );
};

export default Select;
شبیه به یک چنین کامپوننتی را در قسمت قبل، در فایل src\components\common\input.jsx ایجاد کردیم و ساختار کلی آن‌ها با هم یکی است. ابتدا تمام تگ‌ها و کلاس‌های بوت استرپی مورد نیاز، در این کامپوننت محصور می‌شوند. سپس آرایه‌ای بر روی لیست options رسیده، ایجاد شده و به صورت پویا، لیست نمایش داده شده‌ی توسط drop down آن‌را تشکیل می‌دهد. در پایان آن هم کار نمایش اخطار اعتبارسنجی متناظری، در صورت وجود خطایی، قرار گرفته‌است.

- پس از آن به کامپوننت src\components\common\form.jsx مراجعه کرده و متد رندر آن‌را اضافه می‌کنیم:
import Select from "./select";
// ...

class Form extends Component {

  // ...

  renderSelect(name, label, options) {
    const { data, errors } = this.state;

    return (
      <Select
        name={name}
        value={data[name]}
        label={label}
        options={options}
        onChange={this.handleChange}
        error={errors[name]}
      />
    );
  }
}
کار این متد، مقدار دهی ویژگی‌های مورد نیاز کامپوننت Select، بر اساس نام فیلد، یک برچسب و آیتم‌های ارسالی به آن است. مزیت وجود یک چنین متد کمکی، کم شدن کدهای تکراری Selectهای مورد نیاز و همچنین عدم فراموشی قسمتی از این اتصالات و در نهایت یک‌دست شدن کدهای کل برنامه‌است. این متد در نهایت سبب رندر یک drop down list، بر اساس اطلاعات خاصیت genres موجود در state می‌شود:



تمرین 3- جستجوی در لیست فیلم‌ها


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

برای اینکار ابتدا فایل جدید src\components\searchBox.jsx را ایجاد کرده و به صورت زیر آن‌را تکمیل می‌کنیم:
import React from "react";

const SearchBox = ({ value, onChange }) => {
  return (
    <input
      type="text"
      name="query"
      className="form-control my-3"
      placeholder="Search..."
      value={value}
      onChange={e => onChange(e.currentTarget.value)}
    />
  );
};

export default SearchBox;
این SeachBox، یک controlled component است و دارای state خاص خودش نیست. تمام اطلاعات مورد نیاز خود را از طریق props دریافت کرده و خروجی خود را (اطلاعات تایپ شده‌ی در input box را) از طریق صدور رخ‌دادها، اطلاع رسانی می‌کند.

سپس به کامپوننت movies مراجعه کرده و آن‌را ذیل متن نمایش تعداد رکوردها، درج می‌کنیم:
<p>Showing {totalCount} movies in the database.</p>
<SearchBox value={searchQuery} onChange={this.handleSearch} />
که البته نیاز به import کامپوننت مربوطه، تعریف واژه‌ی جستجو شده در state و مدیریت رخ‌داد onChange را نیز دارد:
import SearchBox from "./searchBox";
//...

class Movies extends Component {
  state = {
    //...
    selectedGenre: {},
    searchQuery: ""
  };


  handleSearch = query => {
    this.setState({ searchQuery: query, selectedGenre: null, currentPage: 1 });
  };

  handleGenreSelect = genre => {
    console.log("handleGenreSelect", genre);
    this.setState({ selectedGenre: genre, searchQuery: "", currentPage: 1 });
  };
در متد handleSearch، اطلاعات وارد شده‌ی توسط کاربر دریافت شده و توسط آن سه خاصیت state به روز رسانی می‌شوند تا توسط آن‌ها در حین رندر مجدد کامپوننت، کار فیلتر صحیح اطلاعات صورت گیرد. همچنین selectedGenre نیز به حالت اول بازگشت داده می‌شود. به علاوه اگر کاربر در حین مشاهده‌ی صفحه‌ی 3 بود، نیاز است currentPage صحیحی را به او نمایش  داد.
متد handleGenreSelect را نیز اندکی تغییر داده‌ایم تا اگر گروهی انتخاب شد، مقدار searchQuery را خالی کند. اگر در اینجا searchQuery را به نال تنظیم می‌کردیم، controlled component جعبه‌ی جستجو، تبدیل به کامپوننت کنترل نشده‌ای می‌شد و در این حالت، React، اخطار تبدیل بین این دو را صادر می‌کرد.

در آخر، ابتدای متد getPageData هم جهت اعمال searchQuery، به صورت زیر تغییر می‌کند:
  getPagedData() {
    const {
      pageSize,
      currentPage,
      selectedGenre,
      movies: allMovies,
      sortColumn,
      searchQuery
    } = this.state;

    let filteredMovies = allMovies;
    if (searchQuery) {
      filteredMovies = allMovies.filter(m =>
        m.title.toLowerCase().startsWith(searchQuery.toLowerCase())
      );
    } else if (selectedGenre && selectedGenre._id) {
      filteredMovies = allMovies.filter(m => m.genre._id === selectedGenre._id);
    }
در اینجا اگر searchQuery مقداری داشته باشد، یک جستجوی غیرحساس به کوچکی و بزرگی حروف، بر روی خاصیت title اشیاء فیلم، انجام می‌شود.



کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: sample-21.zip
مطالب دوره‌ها
ایندکس‌ها در RavenDB
RavenDB یک Document database است و در این نوع بانک‌های اطلاعاتی، اسکیما و ساختار مشخصی وجود ندارد. شاید اینطور به نظر برسد، زمانیکه با دات نت کلاینت RavenDB کار می‌کنیم، یک سری کلاس مشخص دات نتی داشته و این‌ها ساختار اصلی کار را مشخص می‌کنند. اما در عمل RavenDB چیزی از این کلاس‌ها و خواص نمی‌داند و این کلاس‌های دات نتی صرفا کمکی هستند جهت سهولت اعمال Serialization و Deserialization اطلاعات. زمانیکه اطلاعاتی را در RavenDB ذخیره می‌کنیم، هیچ نوع قیدی در مورد ساختار نوع سندی که در حال ذخیره است، اعمال نمی‌شود.
خوب؛ اکنون این سؤال مطرح می‌شود که RavenDB چگونه اطلاعاتی را در این اسناد بدون اسکیما جستجو می‌کند؟ اینجا است که مفهوم و کاربرد ایندکس‌ها مطرح می‌شوند. ما در قسمت قبل که کوئری نویسی مقدماتی را بررسی کردیم، عملا ایندکس خاصی را به صورت دستی جهت انجام جستجو‌ها ایجاد نکردیم؛ از این جهت که خود RavenDB به کمک امکانات dynamic indexing آن، پیشتر اینکار را انجام داده است. برای نمونه به سطر ارسال کوئری به سرور، که در قسمت قبل ارائه شد، دقت کنید. در اینجا ارسال کوئری به indexes/dynamic کاملا مشخص است:
Request #   2: GET     - 3,818 ms - <system>   - 200 - /indexes/dynamic/Questions?&query=Title%3ARaven*&pageSize=128

Dynamic Indexes یا ایندکس‌های پویا

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


Static indexes یا ایندکس‌های ایستا

ایندکس‌های پویا به دلیل وقفه ابتدایی که برای تولید آن‌ها وجود خواهد داشت، شاید آنچنان مطلوب به نظر نرسند. اینجا است که مفهوم ایندکس‌های ایستا مطرح می‌شوند. در این حالت ما به RavenDB خواهیم گفت که چه چیزی را ایندکس کند. برای تولید ایندکس‌های ایستا، از مفاهیم Map/Reduce که در پیشنیازهای دوره جاری در مورد آن بحث شد، استفاده می‌گردد. خوشبختانه تهیه Map/Reduceها در RavenDB پیچیده نبوده و کل عملیات آن توسط کوئری‌های LINQ قابل پیاده سازی است.
تهیه ایندکس‌های پویا نیز در تردهای پس‌زمینه انجام می‌شوند. از آنجائیکه RavenDB برای اعمال Read، بهینه سازی شده است، با ارسال یک کوئری به آن، این بانک اطلاعاتی، کلیه اطلاعات آماده را در اختیار شما قرار خواهد داد؛ صرفنظر از اینکه کار تهیه ایندکس تمام شده است یا خیر.


چگونه یک ایندکس ایستا را ایجاد کنیم؟

اگر به کنسول مدیریتی سیلورلایت RavenDB مراجعه کنیم، حاصل کوئری‌های LINQ قسمت قبل را در برگه‌ی ایندکس‌های آن می‌توان مشاهده کرد:


در اینجا بر روی دکمه Edit کلیک نمائید، تا با نحوه تهیه این ایندکس پویا آشنا شویم:


این ایندکس، یک نام داشته به همراه قسمت Map از پروسه Map/Reduce که توسط یک کوئری LINQ تهیه شده است. کاری که در اینجا انجام شده، ایندکس کردن کلیه سؤالات، بر اساس خاصیت عنوان آن‌ها است.
اکنون اگر بخواهیم همین کار را با کدنویسی انجام دهیم، به صورت زیر می‌توان عمل کرد:
using System;
using System.Linq;
using Raven.Client.Document;
using RavenDBSample01.Models;
using Raven.Client;
using Raven.Client.Linq;
using Raven.Client.Indexes;

namespace RavenDBSample01
{
    class Program
    {
        static void Main(string[] args)
        {
            using (var store = new DocumentStore
            {
                Url = "http://localhost:8080"
            }.Initialize())
            {
                store.DatabaseCommands.PutIndex(
                name: "Questions/ByTitle",
                indexDef: new IndexDefinitionBuilder<Question>
                {
                    Map = questions => questions.Select(question => new { Title = question.Title } )
                });
            }
        }
    }
}
کار با شیء DatabaseCommands یک DocumentStore شروع می‌شود. سپس توسط متد PutIndex آن می‌توان یک ایندکس جدید را تعریف کرد. این متد نیاز به نام ایندکس ایجاد شده و همچنین حداقل، متد Map آن‌را دارد. برای این منظور از شیء IndexDefinitionBuilder برای تعریف نحوه جمع آوری اطلاعات ایندکس کمک خواهیم گرفت. در اینجا خاصیت Map آن‌را باید توسط یک کوئری LINQ که فیلدهای مدنظر را بازگشت می‌دهد، مقدار دهی کنیم.
برنامه را اجرا کرده و سپس به کنسول مدیریتی تحت وب RavenDB، قسمت ایندکس‌های آن مراجعه کنید. در اینجا می‌توان ایندکس جدید ایجاد شده را مشاهده کرد:


هرچند همین اعمال را در کنسول مدیریتی نیز می‌توان انجام داد، اما مزیت آن در سمت کدها، دسترسی به intellisense و نوشتن کوئری‌های strongly typed است.

روش استفاده از store.DatabaseCommands.PutIndex اولین روش تولید Index در RavenDB با کدنویسی است. روش دوم، بر اساس ارث بری از کلاس AbstractIndexCreationTask شروع می‌شود و مناسب است برای حالتیکه نمی‌خواهید کدهای تولید ایندکس، با کدهای سایر قسمت‌های برنامه مخلوط شوند:
    public class QuestionsByTitle : AbstractIndexCreationTask<Question>
    {
        public QuestionsByTitle()
        {
            Map = questions => questions.Select(question => new { Title = question.Title });
        }
    }
در اینجا با ایجاد یک کلاس جدید و ارث بری از کلاس AbstractIndexCreationTask کار شروع می‌شود. سپس در سازنده این کلاس، خاصیت Map را مقدار دهی می‌کنیم. مقدار آن نیز یک کوئری LINQ است که کار Select فیلدهای شرکت دهنده در کار تهیه ایندکس را انجام می‌دهد.
اکنون برای معرفی آن به برنامه باید از متد IndexCreation.CreateIndexes استفاده کرد. این متد، نیاز به دریافت اسمبلی محل تعریف کلاس‌های تولید ایندکس را دارد. به این ترتیب تمام کلاس‌های مشتق شده از AbstractIndexCreationTask را یافته و ایندکس‌های متناظری را تولید می‌کند.
            using (var store = new DocumentStore
            {
                Url = "http://localhost:8080"
            }.Initialize())
            {
                IndexCreation.CreateIndexes(typeof(QuestionsByTitle).Assembly, store);
            }
این روش، قابلیت نگهداری و نظم بهتری دارد.


استفاده از ایندکس‌های ایستای ایجاد شده

تا اینجا موفق شدیم ایندکس‌های ایستای خود را با کد نویسی ایجاد کنیم. در ادامه قصد داریم از این ایندکس‌ها در کوئری‌های خود استفاده نمائیم.
            using (var store = new DocumentStore
            {
                Url = "http://localhost:8080"
            }.Initialize())
            {
                using (var session = store.OpenSession())
                {
                    var questions = session.Query<Question>(indexName: "QuestionsByTitle")
                                           .Where(x => x.Title.StartsWith("Raven")).Take(128);
                    foreach (var question in questions)
                    {
                        Console.WriteLine(question.Title);
                    }
                }
            }
استفاده از ایندکس تعریف شده نیز بسیار ساده می‌باشد. تنها کافی است نام آن‌را به متد Query ارسال نمائیم. اینبار اگر به خروجی کنسول سرور RavenDB دقت کنیم، از ایندکس indexes/QuestionsByTitle بجای ایندکس‌های پویا استفاده کرده است:
Request # 147: GET     -    58 ms - <system>   - 200 - /indexes/QuestionsByTitle?&query=Title%3ARaven*&pageSize=128
        Query: Title:Raven*
        Time: 7 ms
        Index: QuestionsByTitle
        Results: 2 returned out of 2 total.
روش مشخص سازی نام ایندکس با استفاده از رشته‌ها، با هر دو روش store.DatabaseCommands.PutIndex و استفاده از AbstractIndexCreationTask سازگار است. اما اگر ایندکس‌های خود را با ارث بری از AbstractIndexCreationTask ایجاد کرده‌ایم، می‌توان نام کلاس مشتق شده را به صورت یک آرگومان جنریک دوم به متد Query به شکل زیر ارسال کرد تا از مزایای تعریف strongly typed آن نیز بهره‌مند شویم:
                    var questions = session.Query<Question, QuestionsByTitle>()
                                           .Where(x => x.Title.StartsWith("Raven")).Take(128);

ایجاد ایندکس‌های پیشرفته با پیاده سازی Map/Reduce

حالتی را در نظر بگیرید که در آن قصد داریم تعداد عنوان‌های سؤالات مانند هم را بیابیم (یا تعداد مطالب گروه‌های مختلف یک وبلاگ را محاسبه کنیم). برای انجام اینکار با سرعت بسیار بالا، می‌توانیم از ایندکس‌هایی با قابلیت محاسباتی در RavenDB استفاده کنیم. کار با ارث بری از کلاس AbstractIndexCreationTask شروع می‌شود. آرگومان جنریک اول آن، نام کلاسی است که در تهیه ایندکس شرکت خواهد داشت و آرگومان دوم (و اختیاری) ذکر شده، نتیجه عملیات Reduce است:
    public class QuestionsCountByTitleReduceResult
    {
        public string Title { set; get; }
        public int Count { set; get; }
    }

    public class QuestionsCountByTitle : AbstractIndexCreationTask<Question, QuestionsCountByTitleReduceResult>
    {
        public QuestionsCountByTitle()
        {
            Map = questions => questions.Select(question =>
                                                    new
                                                    {
                                                        Title = question.Title,
                                                        Count = 1
                                                    });
            Reduce = results => results.GroupBy(x => x.Title)
                                       .Select(g =>
                                                   new
                                                   {
                                                       Title = g.Key,
                                                       Count = g.Sum(x => x.Count)
                                                   });
        }
    }
در اینجا یک ایندکس پیشرفته را تعریف کرده‌ایم که در آن در قسمت Map، کار ایندکس کردن تک تک عنوان‌ها انجام خواهد شد. به همین جهت مقدار Count در این حالت، عدد یک است. در قسمت Reduce، بر روی نتیجه قسمت Map کوئری LINQ دیگری نوشته شده و تعداد عنوان‌های همانند، با گروه بندی اطلاعات، شمارش گردیده است.
اکنون برای استفاده از این ایندکس، ابتدا توسط متد IndexCreation.CreateIndexes، کار معرفی آن به RavenDB صورت گرفته و سپس متد Query سشن باز شده، دو آرگومان جنریگ را خواهد پذیرفت. اولین آرگومان، همان نتیجه Map/Reduce است و دومین آرگومان نام کلاس ایندکس جدید تعریف شده می‌باشد:
            using (var store = new DocumentStore
            {
                Url = "http://localhost:8080"
            }.Initialize())
            {
                IndexCreation.CreateIndexes(typeof(QuestionsCountByTitle).Assembly, store);

                using (var session = store.OpenSession())
                {
                    var result = session.Query<QuestionsCountByTitleReduceResult, QuestionsCountByTitle>()
                                         .FirstOrDefault(x => x.Title == "Raven") ?? new QuestionsCountByTitleReduceResult();
                    Console.WriteLine(result.Count);
                }
            }
در کوئری فوق چون عملیات بر روی نتیجه نهایی باید صورت گیرد از FirstOrDefault استفاده شده است. این کوئری در حقیقت بر روی قسمت Reduce پیشتر محاسبه شده، اجرا می‌شود.
مطالب
استفاده از قالب ویژوال استودیو 2013 برای برنامه‌های ویندوز فرم
در این نوشتار قصد داریم تا Theme ویژوال استودیو 2013 را برای برنامه‌های ویندوز شبیه سازی کنیم. در مرحله اول یک پروژه از نوع ClassLibrary می‌سازیم و پس از آن یک کلاس که از کلاس ToolStripProfessionalRenderer ارث بری کند را ایجاد می‌کنیم. در اینجا ما نام کلاس را BlueMenuStrip انتخاب می‌کنیم. از این کلاس برای تغییر رنگ منوها استفاده می‌شود. سپس متد OnRenderMenuItemBackground آن‌را Override می‌کنیم. 
using System.Drawing;
using System.Windows.Forms;

namespace Navasser.Theme.VisualStudio
{
    public class BlueMenuStrip:ToolStripProfessionalRenderer
    {
        protected override void OnRenderMenuItemBackground(ToolStripItemRenderEventArgs e)
        {
            var borderColor = ColorTranslator.FromHtml("#E5C365");//Menu Item Border
            var selectedMenuBackColor = ColorTranslator.FromHtml("#FDF4BF");//Menu Item Background
            var menuOpenedBackColor = ColorTranslator.FromHtml("#EAF0FF");
            var borderPen = new Pen(borderColor);   

            if (e.Item.Selected)//اگر آیتمی از منوها انتخاب شد
            {
                var selectedMenuBrush = new SolidBrush(selectedMenuBackColor);
                var selectedItemBounds = new Rectangle(Point.Empty, e.Item.Size);//اقدام به پر کردن یک مستطیل به اندازه ابعاد آیتم انتخاب شده می‌کند
                e.Graphics.FillRectangle(selectedMenuBrush,selectedItemBounds);
                e.Graphics.DrawRectangle(borderPen,0,0,selectedItemBounds.Width-1,selectedItemBounds.Height-1);// بوردر آیتم را رسم میکند
                e.Item.BackColor = menuOpenedBackColor;
            }
            else
            {
                base.OnRenderMenuItemBackground(e);
                e.ToolStrip.BackColor = ColorTranslator.FromHtml("#EAF0FF");
            }           
        }     
    }
}
تکه کد بالا فقط برای تغییر رنگ زمینه‌ی منوها بکار می‌رود. اما برای تغییر رنگ ToolStrip‌ها یک کلاس جدید ایجاد می‌کنیم که از کلاس ToolStripProfessionalRenderer ارث بری کرده باشد و متدهای OnRenderToolStripBackground و OnRenderButtonBackground  آن‌را Override میکنیم.
using System.Drawing;
using System.Windows.Forms;

namespace Navasser.Theme.VisualStudio
{
    public class BlueToolStrip : ToolStripProfessionalRenderer
    {
        protected override void OnRenderToolStripBackground(ToolStripRenderEventArgs e)
        {
            var toolStripBackColor = ColorTranslator.FromHtml("#D6DBE9");
            var toolStripBrush = new SolidBrush(toolStripBackColor);
            e.Graphics.FillRectangle(toolStripBrush,0,0,e.AffectedBounds.Width + 10,e.AffectedBounds.Height);
        }

        protected override void OnRenderButtonBackground(ToolStripItemRenderEventArgs e)
        {
            var borderColor = ColorTranslator.FromHtml("#E5C365");//For border
            var selectedToolItemBackColor = ColorTranslator.FromHtml("#FDF4BF");
            var selectedItemBrush = new SolidBrush(selectedToolItemBackColor);
            var pressedItemBackColor = ColorTranslator.FromHtml("#FFF29D");
            var pressedItemBrush = new SolidBrush(pressedItemBackColor);
            var borderPen = new Pen(borderColor);            

            if (e.Item.Selected)
            {
                e.Graphics.FillRectangle(selectedItemBrush,0,0,e.Item.Width,e.Item.Height);
                e.Graphics.DrawRectangle(borderPen,0,0,e.Item.Width-1,e.Item.Height-1);
            }

            if (e.Item.Pressed)
            {
                e.Graphics.FillRectangle(pressedItemBrush, 0, 0, e.Item.Width, e.Item.Height);
                e.Graphics.DrawRectangle(borderPen, 0, 0, e.Item.Width - 1, e.Item.Height - 1);
            }
        }
    }
}
سپس Dll ایجاد شده را در برنامه خود Reference دهید و از Themeهای ایجاد شده استفاده نمایید. نتیجه‌ی کدهای بالا به شکل زیر است:


 همچنین می‌توانید برای انتخاب رنگ‌های دلخواه خودتان از ابزار ColorSchemer Studio استفاده کنید.

مطالب
یکدست کردن "ی" و "ک" در ASP.NET MVC با پیاده‌سازی یک Model Binder

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

مسئله این است که بایستی تمام ورودی‌های کاربران سایت که از نوع رشته هستند چک و در صورت نیاز اصلاح شوند. بهترین روشی که به ذهن من می‌رسد این است که در فرایند Model Binding این عمل انجام بگیرد. با تعریف یک Model Binder برای نوع رشته می‌توانیم عمل یکدست‌سازی را به صورت عمومی در تمام وب‌سایت اعمال کنیم.

در MVC یک Model Binder پیش‌فرض داریم به نام DefaultModelBinder . ما هم از همین کلاس استفاده می‌کنیم تا تمام کارها را برای ما انجام دهد. تنها بایستی در متد BindModel آن کد موردنظر خود را اضافه کنیم و سپس اجازه دهیم بقیه فرایند به شکل عادی ادامه پیدا کند.

کلاسی به نام StringModelBinder اضافه کرده و کلاس DefaultModelBinder را به عنوان کلاس پایه آن تعریف می‌کنیم. سپس متد BindModel آنرا override کرده، کد مربوط به یکدست‌سازی را اضافه می‌کنیم :

    public class StringModelBinder : DefaultModelBinder
    {
        public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
        {
            object value = base.BindModel(controllerContext, bindingContext);

            if (value == null)
            {
                return value;
            }

            return value.ToString().Replace((char)1610, (char)1740).Replace((char)1603, (char)1705);
        }
    }

ابتدا مقدار به دست آمده توسط متد پیش‌فرض را می‌گیریم. سپس در صورت نیاز یکسان‌سازی را انجام می‌دهیم.

برای فعال کردن این Model Binder بایستی آنرا در رویداد Application_start برنامه، برای نوع رشته به ModelBinder‌های برنامه اضافه کنیم :

ModelBinders.Binders.Add(typeof(string), new StringModelBinder());