نظرات مطالب
استفاده از reCAPTCHA در ASP.NET
این مورد بیشتر به تداخل css کلی سایت با این کنترل مربوط می‌شود. بهترین کار استفاده از firebug است. ابتدا در برگه net آن بررسی کنید پیام 404 ایی مشاهده می‌شود؟ ممکن است چیزی از قلم افتاده باشد. سپس به وسیله firebug به صورت live شروع به ویرایش css المان انتخاب شده کنید تا به نتیجه برسید.

مطالب
مدیریت پیشرفته‌ی حالت در 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
مطالب
CSS پویا در ASP.NET MVC
سناریو هایی وجود دارد که نیاز است مشتری ، خود شیوه نامه هایی (CSS) را برای قسمت‌های مختلف سایت انتخاب کند. برای مثال تنظیماتی را برای منوی سایت در نظر گرفته ایم که مشتری بتواند رنگ و قلم و ... را متناسب با سلیقه‌ی خود تغییر دهد و یا یک قسمت کلی برای اعمال شیوه نامه‌ها به سایت ایجاد کرده ایم که در همه‌ی قسمت‌های سایت اعمال شود. بدین شکل در صورتی که مشتری، اطلاعات اندکی هم در مورد CSS داشته باشد میتواند ظاهر سایت خود را به آسانی تغییر دهد و تا حدودی بار را از روی دوش پشتیبان سایت بر میدارد.
و برای همه‌ی این‌ها نیاز است تا فیلدی در دیتابیس برای ذخیره‌ی شیوه نامه‌های مشتری ایجاد شود و یا در یک فایل متنی ذخیره شود که بسته به سیاست برنامه نویس دارد.
در این مطلب تصمیم داریم این سناریو را به صورت ساده در یک پروژه ASP.NET MVC پیاده سازی کنیم.
ایتدا یک پروژه از نوع ASP.NET MVC 4 ایجاد میکنیم. سپس قطعه کد زیر را به فایل  Layout.cshtml_ موجود در مسیر Views/Shared به صورت زیر اضافه میکنیم :
<head>
    @*سایر شیوه نامه‌ها و اسکریپت ها*@
    @RenderSection("styles", required: false)
</head>
RenderSection این امکان را میدهد که ما بتوانیم شیوه نامه‌ی دیگری را در صفحات دیگر به MasterPage خود تزریق کنیم.
سپس کنترلری به نام Home ایجاد میکنیم :
namespace DynamicCssExample.Controllers
{
    public class HomeController : Controller
    {

        public ActionResult Index()
        {
            return View();
        }

        public string GetStyle()
        {
            Response.ContentType = "text/css";
            //در این قسمت میتوانیم به دیتابیس متصل شویم و شیوه نامه‌ی مورد نظر را واکشی کنیم و بازگشت دهیم
            return "h2{color:yellow}";
        }

    }
}
در اینجا یک متد به نام GetStyle داریم که وظیفه‌ی بازگشت یک رشته را بر عهده دارد و نوع این مقدار بازگشتی از نوع text/css است و میتواند شیوه نامه هایی که در دیتابیس و یا یک فایل متنی ذخیره کرده ایم را واکشی و به استریم ارسال کند.
در انتها برای استفاده از این متد کافی است در View مربوطه بدین شکل عمل کنیم :
@{
    ViewBag.Title = "Index";
    Layout = "~/Views/Shared/_Layout.cshtml";
}

@section styles
{
    <link rel="stylesheet" href="@Url.Action("GetStyle", "Home")" type="text/css"/>
}

<h2>Index</h2>
در سکشن Style لینک شیوه نامه ای تعریف کرده ایم که ویژگی href آن به اکشن GetStyle در کنترلر Home اشاره میکند و این اکشن نیز محتوای شیوه نامه را برگشت میدهد.
مطالب
کش کردن اطلاعات غیر پویا در ASP.Net - قسمت چهارم

قسمت‌های اول تا سوم این مقاله: + و + و +

در قسمت چهارم قصد داریم هدر مربوط به Content Expiration Date را توسط یک Http module به محتوای غیرپویای سایت مانند تصاویر ، فایل‌های CSS و غیره اعمال کنیم. این روش از روش قسمت دوم ساده‌تر است و جامع‌تر.
ابتدا یک پروژه‌ی Class library جدید را به نام StaticContentCacheModule ایجاد کرده و سپس ارجاعی را به اسمبلی استاندارد System.Web.dll به آن خواهیم افزود. سپس کدهای مرتبط با این ماژول به شرح زیر هستند:

//StaticCache .cs
using System;
using System.Web;

namespace StaticContentCacheModule
{
public class StaticCache : IHttpModule
{
public void Init(HttpApplication context)
{
context.PreSendRequestHeaders += context_PreSendRequestHeaders;
}

static void context_PreSendRequestHeaders(object sender, EventArgs e)
{
//capture the current Response
var currentResponse = ((HttpApplication)sender).Response;

if (CacheManager.ShouldCache(currentResponse.ContentType))
{
currentResponse.AddHeader("cache-control", "public");
currentResponse.AddHeader("Expires", DateTime.Now.Add(TimeSpan.FromDays(30)).ToString());
}
}

public void Dispose() { }
}
}

در اینجا ContentType تک تک عناصری که توسط وب سرور ارائه خواهند شد، بررسی می‌شود. اگر نیازی به کش شدن آن‌ها وجود داشت (توسط کلاس CacheManager این امر مشخص می‌گردد)، هدر مربوطه اضافه می‌گردد.

//CacheManager.cs
using System;

namespace StaticContentCacheModule
{
class CacheManager
{
public static bool ShouldCache(string contentType)
{
contentType = contentType.ToLower();
string[] parts =
contentType.Split(
new[] { ';' },
StringSplitOptions.RemoveEmptyEntries
);

if (parts.Length > 0)
contentType = parts[0];

bool cache = false;

switch (contentType)
{
case "text/css":
cache = true; break;
case "text/javascript":
case "text/jscript":
cache = true; break;
case "image/jpeg":
cache = true; break;
case "image/gif":
cache = true; break;
case "application/octet-stream":
cache = true; break;
default:
{
if (contentType.Contains("javascript"))
cache = true;
if (contentType.Contains("css"))
cache = true;
if (contentType.Contains("image"))
cache = true;
if (contentType.Contains("application"))
cache = true;
}
break;
}

return cache;
}
}
}

در این کلاس، contentType دریافتی بررسی می‌شود. اگر نوع محتوای قابل ارائه از نوع CSS ، JavaScript ، تصویر و یا Application بود، یک مقدار true بازگشت داده خواهد شد.
نهایتا برای استفاده از این Http module جدید در IIS6 به قبل در وب کانفیگ برنامه خواهیم داشت:

<httpModules>
<add name="StaticContentCacheModule" type="StaticContentCacheModule.StaticCache, StaticContentCacheModule"/>
</httpModules>

و یا در IIS7 این تغییرات به صورت زیر می‌تواند باشد:

<system.webServer>
<modules>
<add name="StaticContentCacheModule" type="StaticContentCacheModule.StaticCache, StaticContentCacheModule"/>
</modules>

اکنون اگر یک پروژه‌ی آزمایشی جدید ASP.Net را گشوده و فایل css ساده‌ای را به آن اضافه کنیم، بررسی هدر نهایی توسط افزونه‌ی YSlow به صورت زیر خواهد بود:



مطالب
آموزش فایرباگ - #7 - CSS Panel
پنل CSS مانند پنل جانبیِ Style که در مقاله‌ی قبل بررسی کردیم است با این تفاوت که امکانات بیشتری برای افرادی که قصد تصریف استایل دارند محیا کرده است.
در این پنل می‌توان به اضافه ، ویرایش و حذف استایل هایی که به صفحه‌ی جاری توسط فایل‌های مختلف اضافه شده اند و یا داخل خود صفحه تعریف شده اند پرداخت.



همچنین در این پنل امکان ویرایش یک فایل css بصورت کامل وجود دارد ، به این صورت که می‌توانید تمام محتویات فایل مورد نظر را در یک Text area ویرایش کنید.

مطابق با روالی که در قسمت قبل پیش گرفتیم عمل می‌کنیم و به ترتیب به تشریح ابزار پنل ، خود پنل ، منوی راست کلیک و پنل جانبی ِ Elements می‌پردازیم.


Options Menu
با راست کلیک کردن بروی تب CSS و یا کلیک کردن بروی فلش کوچک روی تب CSS قابل دسترس است و امکانات زیر را محیا می‌کند:

  • Expand Shorthand Properties : نمایش دستورات css بصورت کامل ، به این معنی که دستوراتی مانند margin که هم بصورت خلاصه و هم بصورت کامل تعریف می‌شوند را بصورت کامل نمایش می‌دهد. مثلا margin را چهار مقدار margin-top , margin-right , margin-bottom , margin-left نمایش می‌دهد.

  • Color As Hex , Color As RGB , Color As HSL : با انتخاب یکی از سه مقدار ذکر شده ، فرمت نمایش رنگ‌ها به حالت انتخاب شده تغییر می‌کند.
    مثلا دستور color : #000000 به این صورت نمایش داده می‌شود :‭ color : rgb(0, 0, 0) 

  • Refresh : این گزینه محتویات پنل را بروز می‌کند.

Panel Toolbar
ابزاری موجود در این پنل مشابه پنل‌های دیگر در بالای پنل و در زیر تب پنل‌ها قرار دارد و شامل ابزارهای زیر می‌شود:

  • Edit Button : این ابزار به دو صورت Live Edit و Source Edit در دسترس هست که با کلیک بروی فلش کوچکی که در سمت راست دکمه قرار دارد می‌توان نوع آن را تغییر داد.
    انتخاب حالت Source Edit ، باعث می‌شود سورس فایل به همان صورتی که به مرورگر ارسال شده نمایش داده شود. ( کامنت‌ها ، فرمت فایل و ... حفظ می‌شود. )
    حالت Live Edit هم باعث نمایش محتویات فایل بصورت مرتب شده می‌شود. ( کامنت‌ها و دستوراتی که توسط مرورگر تفسیر نشده اند نمایش داده نمی‌شوند. )



  • CSS Location Menu : نام فایل جاری که در پنل در حال نمایش است را نمایش می‌دهد و همچنین با کلیک بروی آن ، فایل‌های استایل دیگری که در صفحه بارگزاری شده اند را نمایش می‌دهد. با کلیک بروی هرکدام از فایل‌های نمایش داده شده ، همان فایل در پنل باز می‌شود.

    استایل هایی که در خود صفحه تعریف شده اند با نام خود صفحه در این قسمت نمایش داده می‌شوند و اگر استایل‌های در چندین تگ style تعریف شده باشند ، دومین تگ با #2 ، سومین با #3 و ... مشخص می‌شوند.

    هنگام باز بودن :
    1- فایل هایی که از پوشه‌های مختلف در صفحه بارگذاری شده اند ، با نام پوشه از هم تفکیک شده اند:



    2- با تایپ کردن عبارت دلخواه می‌توان نتایج را محدود کرد:



Panel
منظور از پنل ، قسمتی هست که استایل بصورت فرمت شده و نمایش داده شده است.


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

 Editing rules
برای ویرایش تعاریف CSS شامل Selector‌ها ، دستورات و مقادیر ، این پنل ابزارهای مفیدی ارائه می‌کند.
برای ویرایش یک selector ، دستور یا مقدار آن بروی آن عبارت کلیک کنید ، در همین حال یک text box ظاهر می‌شود و می‌توانید مقدار جدید را وارد کنید. پس از انجام ویرایش مورد نظر بروی قسمتی از صفحه کلیک کنید یا کلید Tab سپس Esc را بزنید. برای ویرایش دستورات بعدی ، کلید Tab را بزنید و برای لغو تغییر جاری ، کلید Esc را بزنید.

برای ایجاد یک rule جدید ، بروی قسمتی از پنل راست کلیک کرده و سپس گزینه‌ی "New Rule..." را برگزینید.
برای ایجاد یک property هم چند راه وجود دارد:

  • بروی قسمت از فضای خالی تعریف یک استایل دوبار کلیک کنید.
  • در قسمتی از تعریف یک استایل راست کلیک کرده و گزینه‌ی "New Property..." را انتخاب کنید.
  • بروی مقدار آخرین property کلیک کرده و کلید Tab را بزنید.


هنگام ویرایش یا ایجاد یک Rule , Propery یا مقدار بصورت inline ، حاشیه‌ی text box متناسب با مقدار وارد شده رنگی را که نمایانگر حالت ذخیره‌ی مقدار وارد شده است نمایش می‌دهد. برای مثال اگر مقداری که برای یک selector وارد شده است نامعتبر باشد ، رنگ حاشیه‌ی text box قرمز می‌شود.
جدول زیر حالت‌های مختلف را شرح می‌دهد:

رنگ حاشیه
Selectorها
نام Properyها
مقادیر Propertyها و Ruleها
 خاکستری تغییری ایجاد نشده استتغییری ایجاد نشده است
تغییری ایجاد نشده است
 قرمز selector نامعتبر است
نام نامعتبر است
مقدار نامعتبر است
 زرد selector صحیح است اما بروی Element فعلی تاثیر ندارد
نام صحیح است اما مقدار Property غیر مجاز است یا وارد نشده است
 
 سبز selector صحیح است نام و مقدار صحیح هستند
مقدار صحیح است


Auto-completion
زمانی که در حال ایجاد/ویراش کردن یک Rule, Propery یا مقدار آنها هستید ، میتوانید از این قابلیت استفاده کنید. مثلا با وارد کردن # تمام Selector هایی که می‌توانند با # شروع شوند در دسترس شما هستند و با دکمه‌های Up/Down می‌توانید مقادیر ممکن را مرور کنید.
اگر در حال ویرایش یک مقدار عددی هستید ، می‌توانید با دکمه‌های Up/Down مقادیر را یک واحد یک واحد افزایش دهید. با نگه داشتن کلید Shift و فشردن Up/Down می‌توانید مقادیر را 10تا 10تا تغییر دهید و با نگه داشتن Ctrl بجای Sihft ، یک دهم یک دهم.

Toggling styles
با این امکان می‌توانید یک Property را بطور موقت فعال/غیرفعال کنید. با حرکت موس از یک Property یک آیکون قرمز رنگ در کنار آن نمایش داده می‌شود که با کلیک بروی آن ، Property و مقدار آن کمرنگ شده و آیکون قرمز رنگ کنار آن ثابت می‌شود. با کلیک مجدد بروی آن ، Property فعال می‌شود.



Context Menu
این منو زمانی که در پنل راست کلیک کنید ظاهر می‌شود و نسبت به منطقه (Context)ای که در آن راست کلیک کرده اید ، گزینه‌های متفاوتی را مشاهده خواهید کرد. در جدول زیر ، گزینه‌ها ، Contextشان و توضیح هر گزینه آمده است.

 گزینهContext
توضیحات
Copy Location
CSS Location Menuآدرس فایل استایل را در حافظه کپی می‌کند.
Open in New TabCSS Location Menuفایل استایل را در یک تب جدید باز می‌کند.
Copy Image Location
image valuesآدرس تصویر را در حافظه کپی می‌کند.
Open Image in New Tabimage valuesتصویر را در یک تب جدید باز می‌کند.
Copy Colorcolor valuesمقدار رنگ را در حافظه کپی می‌کند.
Copy Rule DeclarationCSS selectorSelector و Propertyها را در حافظه کپی می‌کند.
Copy Style DeclarationCSS selectorفقط Propertyها را در حافظه کپی می‌کند.
New Rule...
همه جای پنل
یک Rule جدید بالای قسمتی که راست کلیک شده ایجاد می‌کند.
Delete "<selector>"
CSS selectorRule را حذف می‌کند.
New Property...
CSS rule
یک Property جدید در Ruleی که در آن راست کلیک شده ایجاد می‌کند.
Edit "<property name>"...
CSS property
Property فعلی به حالت ویرایش درمی آید. ( راه ساده‌تر ، کلیک بروی Property است. )
Delete "<property name>"...CSS propertyProperty فعلی را حذف می‌کند.
Disable "<property name>"...
CSS property
Property فعلی را غیرفعال می‌کند.
Refreshهمه جای پنل
محتویات پنل را بروز رسانی می‌کند.
Inspect in DOM panel
CSS Location Menu, CSS rule
فایل استایل یا Rule را در پنل DOM باز می‌کند.


Elements Side Panel
در این پنل که سمت راست پنل CSS قرار دارد ، با وارد کردن یک CSS Selector می‌توانید Elementهایی که در صفحه با آن مطابقت دارند را مشاهده کنید.
برای وارد کردن CSS Selector هم می‌توان مقدار مورد نظر را در قسمت Try a selector... وارد کرد هم می‌توان بروی یکی از Selectorهای پنل راست کلیک کرد و گزینه‌ی Get Matching Elements را برگزید.
Context Menu این قسمت هم مشابه Context Menu پنل HTML هست و فقط در ورژن 1.11 گزینه‌ی Paste HTML اضافه شده که در این کامنت از مقاله‌ی آموزش فایرباگ - #5 - HTML Panel توضیح داده شده است.

مطالب
نحوه استفاده از Text template ها در دات نت - قسمت اول
 یکی از امکانات کمتر شناخته شده در دات نت، امکان تولید اتوماتیک کد (Code Generator)، توسط فایلهایی به عنوان Text template می‌باشد. اگر چه فایلهای Text template با پسوند tt  از Visual Studio 2008 بطور آشکار به IDE اضافه گردیده‌اند، این امکان پیش از این نیز بصورت توکار در Visual Studio جهت تولید کد دات نت برای ابزارهایی مانند Dataset ، Report  و ... وجود داشته است. در حال حاضر Visual Studio بصورت توکار  از این امکان برای تولیدکلاسها و  کد‌های EntityFramework ( edmx ) ،dbml  ، Dataset  و ... استفاده می‌نماید. 
شما نیز با دانستن قواعد ساده کد نویسی برای Text template‌ها می‌توانید از این امکان برای تولید اتوماتیک کلاسها، HTML ، XML  و بطور کلی هر نوع فایل متنی استفاده نمایید. کاربردهای عملی Text template بیشتر برای تولید کد از روی دیتابیس و مثلا بر اساس فیلد‌های هر جدول و امثال آن می‌باشد. اما با کمی خلاقیت استفاده‌های بسیار زیاد و جالبی را می‌توانید از آن مشاهده نمایید.
کاربردهایی مانند : تولید خود کار فایل Config، تولید خودکار  Unit Test، تولید خودکار کلاسهای CRUD برای هر موجودیت در لایه Data، تولید خودکار SQL برای StoredProcedure ها، تولید خودکار Html و Js، تولید خودکار فرمهای ASPX و ... برای فرمهای ثابت و تکراری با فرآیند مشخص، تولید خودکار مستندات پروژه در قالب Word Document  , …  ،  تولید خودکار فایلهای Excel از اطلاعات خاص و تولید خودکار فرمهای xaml و .....

در این آموزش با قواعد اصلی نوشتن کد برای Text templateها آشنا می‌شویم و چند نمونه از کاربردهای آن را به عنوان مثال کاربردی مورد بررسی قرار خواهیم داد.
برای شروع قبل از توضیح در مورد قواعد کد نویسی Text template، یک فایل Text template ایجاد نمایید. برای ایجاد، از پنجره Add New Item  یک فایل Text template به پروژه اضافه نمایید:
 


توجه داشته باشید که نام فایل خروجی دقیقا هم نام با فایل tt مشخص شده خواهد بود. اما پسوند آن بسته به تنظیمات داخل آن متفاوت است. با اضافه کردن Text template داخل فایل، باید کد‌های زیر را مشاهده نمایید؛ در غیر این صورت آن را بنویسید:
 <#@ template debug="false" hostspecific="false" language="C#" #>
<#@ output extension=".txt" #>
Language زبانی را مشخص می‌کند که می‌خواهید با آن داخل فایل Text template کد نویسی نمایید. تعیین آن برای کامپایل الزامی است.
Extension نیز پسوند فایل خروجی تولید شده را مشخص می‌نماید . 
اگر در ادامه متن Hello dotnettips را بنویسید و فایل را Save کنید، کنار فایل tt مذکور، یک فایل با همان نام و پسوند txt ایجاد خواهد شد که داخل آن نوشته است:
Hello dotnettips

برای ایجاد فایل خروجی همچنین میتوانید روی فایل tt  کلیک راست نموده و از منوی باز شده  Run Custom Tool  را انتخاب نمایید. توجه داشته باشید که در تنظیمات Custom Tool باید مقدار  TextTemplatingFileGenerator وجود داشته باشد:
 

خوب؛ برای ادامه، باید با قواعد کد نویسی در Text template آشنا شوید. جلسه بعدی قواعد کد نویسی T4  را بررسی خواهیم کرد.

اشتراک‌ها
نمایش لیست کشورها به همراه پرچم آنها به صورت dropdown با استفاده از select2
بعد از دانلود و افزودن  select2 ، برای پرچم‌ها سراغ semantic-ui  می‌رویم و بعد از دانلود آن، فایل flag.min.css در آدرس Semantic-Ui/components/flag.min.css را به پروژه اضافه می‌کنیم.
<div>
    <div>
        <select ></select>
    </div>
</div>

 $(document).ready(function () {

            $('.js-example-basic-single').select2({
                data: [
                    { id: "al", text: "Albania" },
                    { id: "dz", text: "Algeria" },
                    { id: "as", text: "American Samoa" },
                    { id: "ad", text: "Andorra" },
                    { id: "ao", text: "Angola" },
                    { id: "ai", text: "Anguilla" },
                    { id: "ag", text: "Antigua" },
                    { id: "ar", text: "Argentina" },
                    { id: "am", text: "Armenia" },
                    { id: "aw", text: "Aruba" },
                    { id: "au", text: "Australia" },
                    { id: "at", text: "Austria" },
                    { id: "az", text: "Azerbaijan" },
                    { id: "bs", text: "Bahamas" },
                    { id: "bh", text: "Bahrain" },
                    { id: "bd", text: "Bangladesh" },
                    { id: "bb", text: "Barbados" },
                    { id: "by", text: "Belarus" },
                    { id: "be", text: "Belgium" },
                    { id: "bz", text: "Belize" },
                    { id: "bj", text: "Benin" },
                    { id: "bm", text: "Bermuda" },
                    { id: "bt", text: "Bhutan" },
                    { id: "bo", text: "Bolivia" },
                    { id: "ba", text: "Bosnia" },
                    { id: "bw", text: "Botswana" },
                    { id: "bv", text: "Bouvet Island" },
                    { id: "br", text: "Brazil" },
                    { id: "vg", text: "British Virgin Islands" },
                    { id: "bn", text: "Brunei" },
                    { id: "bg", text: "Bulgaria" },
                    { id: "bf", text: "Burkina Faso" },
                    { id: "mm", text: "Burma" },
                    { id: "bi", text: "Burundi" },
                    { id: "tc", text: "Caicos Islands" },
                    { id: "kh", text: "Cambodia" },
                    { id: "cm", text: "Cameroon" },
                    { id: "ca", text: "Canada" },
                    { id: "cv", text: "Cape Verde" },
                    { id: "ky", text: "Cayman Islands" },
                    { id: "cf", text: "Central African Republic" },
                    { id: "td", text: "Chad" },
                    { id: "cl", text: "Chile" },
                    { id: "cn", text: "China" },
                    { id: "cx", text: "Christmas Island" },
                    { id: "cc", text: "Cocos Islands" },
                    { id: "co", text: "Colombia" },
                    { id: "km", text: "Comoros" },
                    { id: "cg", text: "Congo Brazzaville" },
                    { id: "cd", text: "Congo" },
                    { id: "ck", text: "Cook Islands" },
                    { id: "cr", text: "Costa Rica" },
                    { id: "ci", text: "Cote Divoire" },
                    { id: "hr", text: "Croatia" },
                    { id: "cu", text: "Cuba" },
                    { id: "cy", text: "Cyprus" },
                    { id: "cz", text: "Czech Republic" },
                    { id: "dk", text: "Denmark" },
                    { id: "dj", text: "Djibouti" },
                    { id: "dm", text: "Dominica" },
                    { id: "do", text: "Dominican Republic" },
                    { id: "ec", text: "Ecuador" },
                    { id: "eg", text: "Egypt" },
                    { id: "sv", text: "El Salvador" },
                    { id: "gb", text: "England" },
                    { id: "gq", text: "Equatorial Guinea" },
                    { id: "er", text: "Eritrea" },
                    { id: "ee", text: "Estonia" },
                    { id: "et", text: "Ethiopia" },
                    { id: "eu", text: "European Union" },
                    { id: "fk", text: "Falkland Islands" },
                    { id: "fo", text: "Faroe Islands" },
                    { id: "fj", text: "Fiji" },
                    { id: "fi", text: "Finland" },
                    { id: "fr", text: "France" },
                    { id: "gf", text: "French Guiana" },
                    { id: "pf", text: "French Polynesia" },
                    { id: "tf", text: "French Territories" },
                    { id: "ga", text: "Gabon" },
                    { id: "gm", text: "Gambia" },
                    { id: "ge", text: "Georgia" },
                    { id: "de", text: "Germany" },
                    { id: "gh", text: "Ghana" },
                    { id: "gi", text: "Gibraltar" },
                    { id: "gr", text: "Greece" },
                    { id: "gl", text: "Greenland" },
                    { id: "gd", text: "Grenada" },
                    { id: "gp", text: "Guadeloupe" },
                    { id: "gu", text: "Guam" },
                    { id: "gt", text: "Guatemala" },
                    { id: "gw", text: "Guinea-Bissau" },
                    { id: "gn", text: "Guinea" },
                    { id: "gy", text: "Guyana" },
                    { id: "ht", text: "Haiti" },
                    { id: "hm", text: "Heard Island" },
                    { id: "hn", text: "Honduras" },
                    { id: "hk", text: "Hong Kong" },
                    { id: "hu", text: "Hungary" },
                    { id: "is", text: "Iceland" },
                    { id: "in", text: "India" },
                    { id: "io", text: "Indian Ocean Territory" },
                    { id: "id", text: "Indonesia" },
                    { id: "ir", text: "Iran" },
                    { id: "iq", text: "Iraq" },
                    { id: "ie", text: "Ireland" },
                    { id: "il", text: "Israel" },
                    { id: "it", text: "Italy" },
                    { id: "jm", text: "Jamaica" },
                    { id: "jp", text: "Japan" },
                    { id: "jo", text: "Jordan" },
                    { id: "kz", text: "Kazakhstan" },
                    { id: "ke", text: "Kenya" },
                    { id: "ki", text: "Kiribati" },
                    { id: "kw", text: "Kuwait" },
                    { id: "kg", text: "Kyrgyzstan" },
                    { id: "la", text: "Laos" },
                    { id: "lv", text: "Latvia" },
                    { id: "lb", text: "Lebanon" },
                    { id: "ls", text: "Lesotho" },
                    { id: "lr", text: "Liberia" },
                    { id: "ly", text: "Libya" },
                    { id: "li", text: "Liechtenstein" },
                    { id: "lt", text: "Lithuania" },
                    { id: "lu", text: "Luxembourg" },
                    { id: "mo", text: "Macau" },
                    { id: "mk", text: "Macedonia" },
                    { id: "mg", text: "Madagascar" },
                    { id: "mw", text: "Malawi" },
                    { id: "my", text: "Malaysia" },
                    { id: "mv", text: "Maldives" },
                    { id: "ml", text: "Mali" },
                    { id: "mt", text: "Malta" },
                    { id: "mh", text: "Marshall Islands" },
                    { id: "mq", text: "Martinique" },
                    { id: "mr", text: "Mauritania" },
                    { id: "mu", text: "Mauritius" },
                    { id: "yt", text: "Mayotte" },
                    { id: "mx", text: "Mexico" },
                    { id: "fm", text: "Micronesia" },
                    { id: "md", text: "Moldova" },
                    { id: "mc", text: "Monaco" },
                    { id: "mn", text: "Mongolia" },
                    { id: "me", text: "Montenegro" },
                    { id: "ms", text: "Montserrat" },
                    { id: "ma", text: "Morocco" },
                    { id: "mz", text: "Mozambique" },
                    { id: "na", text: "Namibia" },
                    { id: "nr", text: "Nauru" },
                    { id: "np", text: "Nepal" },
                    { id: "an", text: "Netherlands Antilles" },
                    { id: "nl", text: "Netherlands" },
                    { id: "nc", text: "New Caledonia" },
                    { id: "pg", text: "New Guinea" },
                    { id: "nz", text: "New Zealand" },
                    { id: "ni", text: "Nicaragua" },
                    { id: "ne", text: "Niger" },
                    { id: "ng", text: "Nigeria" },
                    { id: "nu", text: "Niue" },
                    { id: "nf", text: "Norfolk Island" },
                    { id: "kp", text: "North Korea" },
                    { id: "mp", text: "Northern Mariana Islands" },
                    { id: "no", text: "Norway" },
                    { id: "om", text: "Oman" },
                    { id: "pk", text: "Pakistan" },
                    { id: "pw", text: "Palau" },
                    { id: "ps", text: "Palestine" },
                    { id: "pa", text: "Panama" },
                    { id: "py", text: "Paraguay" },
                    { id: "pe", text: "Peru" },
                    { id: "ph", text: "Philippines" },
                    { id: "pn", text: "Pitcairn Islands" },
                    { id: "pl", text: "Poland" },
                    { id: "pt", text: "Portugal" },
                    { id: "pr", text: "Puerto Rico" },
                    { id: "qa", text: "Qatar" },
                    { id: "re", text: "Reunion" },
                    { id: "ro", text: "Romania" },
                    { id: "ru", text: "Russia" },
                    { id: "rw", text: "Rwanda" },
                    { id: "sh", text: "Saint Helena" },
                    { id: "kn", text: "Saint Kitts and Nevis" },
                    { id: "lc", text: "Saint Lucia" },
                    { id: "pm", text: "Saint Pierre" },
                    { id: "vc", text: "Saint Vincent" },
                    { id: "ws", text: "Samoa" },
                    { id: "sm", text: "San Marino" },
                    { id: "gs", text: "Sandwich Islands" },
                    { id: "st", text: "Sao Tome" },
                    { id: "sa", text: "Saudi Arabia" },
                    { id: "sn", text: "Senegal" },
                    { id: "cs", text: "Serbia" },
                    { id: "rs", text: "Serbia" },
                    { id: "sc", text: "Seychelles" },
                    { id: "sl", text: "Sierra Leone" },
                    { id: "sg", text: "Singapore" },
                    { id: "sk", text: "Slovakia" },
                    { id: "si", text: "Slovenia" },
                    { id: "sb", text: "Solomon Islands" },
                    { id: "so", text: "Somalia" },
                    { id: "za", text: "South Africa" },
                    { id: "kr", text: "South Korea" },
                    { id: "es", text: "Spain" },
                    { id: "lk", text: "Sri Lanka" },
                    { id: "sd", text: "Sudan" },
                    { id: "sr", text: "Suriname" },
                    { id: "sj", text: "Svalbard" },
                    { id: "sz", text: "Swaziland" },
                    { id: "se", text: "Sweden" },
                    { id: "ch", text: "Switzerland" },
                    { id: "sy", text: "Syria" },
                    { id: "tw", text: "Taiwan" },
                    { id: "tj", text: "Tajikistan" },
                    { id: "tz", text: "Tanzania" },
                    { id: "th", text: "Thailand" },
                    { id: "tl", text: "Timorleste" },
                    { id: "tg", text: "Togo" },
                    { id: "tk", text: "Tokelau" },
                    { id: "to", text: "Tonga" },
                    { id: "tt", text: "Trinidad" },
                    { id: "tn", text: "Tunisia" },
                    { id: "tr", text: "Turkey" },
                    { id: "tm", text: "Turkmenistan" },
                    { id: "tv", text: "Tuvalu" },
                    { id: "ug", text: "Uganda" },
                    { id: "ua", text: "Ukraine" },
                    { id: "ae", text: "United Arab Emirates" },
                    { id: "us", text: "United States" },
                    { id: "uy", text: "Uruguay" },
                    { id: "um", text: "Us Minor Islands" },
                    { id: "vi", text: "Us Virgin Islands" },
                    { id: "uz", text: "Uzbekistan" },
                    { id: "vu", text: "Vanuatu" },
                    { id: "va", text: "Vatican City" },
                    { id: "ve", text: "Venezuela" },
                    { id: "vn", text: "Vietnam" },
                    { id: "wf", text: "Wallis and Futuna" },
                    { id: "eh", text: "Western Sahara" },
                    { id: "ye", text: "Yemen" },
                    { id: "zm", text: "Zambia" },
                    { id: "zw", text: "Zimbabwe" }
                ],
                placeholder: "کشور مورد نظرتان را انتخاب نمایید",
                language: "fa",
                theme: "bootstrap",
                dir: "rtl",
                tokenSeparators: [',', ' '],
                multiple: false,
                templateResult:format,
                templateSelection: format,
                escapeMarkup: function (m) { return m; }
            });

        });
    </script>
در اینجا ما اطلاعات را به صورت دستی وارد کرده ایم. شما می‌توانید این اطلاعات را از سمت سرور دریافت نمایید.
در templateResult و templateSelection شما می‌توانید ساختار مورد نظرتان را پیاده سازی کنید. اولی در لیستی از dropdown به عنوان یک option و دومی وقتی که انتخاب می‌شود.
تابع fotmat به صورت زیر می‌باشد.
 function format(state) {
            var $state = $(
                '<span>' + state.text + ' <i class="' + state.id + ' flag"> ' +
                '</i></span>'
            );
            return $state;
        };

لازم به ذکر است کلاس لازم برای ایجاد  پرچم کشوری مثل ایران با استفاده از Semantic-Ui به صورت زیر می‌باشد.
<i class="ir flag"></i>
و در نهایت خروجی آن به شکل زیر می‌باشد

نمایش لیست کشورها به همراه پرچم آنها به صورت dropdown با استفاده از select2
بازخوردهای دوره
افزونه‌ای برای کپسوله سازی نکات ارسال یک فرم ASP.NET MVC به سرور توسط jQuery Ajax
با سلام.
اطلاعات کنترلر من بصورت زیر است:
using MvcApplication3.Models;
...

namespace MvcApplication3.Controllers
{
    public class StudentController : Controller
    {
        public ActionResult Index()
        {
            //var data = new StudentsList();
            return View();
        }
        public ActionResult DataList()
        {
            var data = new StudentsList();
            return PartialView("Pv_DataList", data);
        }

        #region Edit 

        [HttpGet]
        public ActionResult Edit(int? id)
        {
            var data = new StudentsList().FirstOrDefault(p => p.Id == id);
            return PartialView("Pv_Edit", data);
        }
        [HttpPost]
        [AjaxOnly]
        [ValidateAntiForgeryToken]
        public ActionResult Edit(StudentModel model)
        { 
            Thread.Sleep(1000);
            if (this.ModelState.IsValid)
            {
                return Json("ok", JsonRequestBehavior.AllowGet);
            }
            return Json("error");
        }
        
        #endregion
    }
}
اطلاعات ویو :
@using MvcApplication3.Models
@{
    ViewBag.Title = "Student Index";
}
<style>
    #div_StudentEditDialogContainer {
        padding: 15px;
        background-color: silver;
        border: 1px solid gray;
        -webkit-border-radius: pxpx;
        -moz-border-radius: pxpx;
        border-radius: 10px;
        display: none;
        position: absolute;
        -webkit-box-shadow: 0 1px 3px rgba(0,0,0,0.3);
        -moz-box-shadow: 0 1px 3px rgba(0,0,0,0.4);
        box-shadow: 0 1px 3px rgba(0,0,0,0.5);
    }
</style>
<h2>Student Index</h2>
<div id="div_StudentListViewContainer">
    @{ Html.RenderAction("DataList", "Student");}
</div>
<div id="div_StudentEditDialogContainer">
    @{ Html.RenderAction("Edit", "Student");}
</div>
<div id="div_StudentAddDialogContainer">
</div>
<div id="div_StudentRemoveDialogContainer">
</div>
<div id="div_StudentSearchDialogContainer">
</div>

@section JavaScript{
    <script type="text/javascript">
        $(document).ready(function () {
            $("#div_StudentListViewContainer table input[type='submit']").click(function (e) {
                //show edit dialog
                e.preventDefault();
                var id = $(this).parent().parent().attr('data-studentid');
                var url = '@Url.Action("Edit", "Student")';
                $.ajax({
                    type: "GET",
                    url: url,
                    data: { id: id },
                    beforeSend: function () {
                        //$(waitingPanel).css("display", "block");
                    },
                    success: function (html) {
                        if (html == "nodata") {
                            $("#div_StudentEditDialogContainer").html("دانشجویی با این مشخصات یافت نشد!");
                        } else {
                            $("#div_StudentEditDialogContainer").css("display", "block");
                            $("#div_StudentEditDialogContainer").html("").append(html);
                        }
                    },
                    complete: function () {
                        //$(waitingPanel).css("display", "none");
                    }
                });
            });
        });
    </script>
}

و یک دیالوگ برای ویرایش را بصورت داینامیک در صفحه ظاهر میکنم، اما اعتبارسنجی سمت کاربر برای آن کار نمیکند:
@using MvcApplication3.Models
@model StudentModel
@{
    var postUrl = Url.Action(actionName: "Edit", controllerName: "Student");
}

@using (Html.BeginForm(actionName: "Edit", controllerName: "Student",
                       method: FormMethod.Post,
                       htmlAttributes: new { id = "frm_studentEdit" }))
{
    @Html.ValidationSummary(true)
    @Html.AntiForgeryToken()
    
    <div class="editor-label">
        @Html.LabelFor(model => model.Id)
    </div>
    <div class="editor-field">
        @Html.EditorFor(model => model.Id)
        @Html.ValidationMessageFor(model => model.Id)
    </div>

    <div class="editor-label">
        @Html.LabelFor(model => model.Code)
    </div>
    <div class="editor-field">
        @Html.EditorFor(model => model.Code)
        @Html.ValidationMessageFor(model => model.Code)
    </div>

    <div class="editor-label">
        @Html.LabelFor(model => model.FullName)
    </div>
    <div class="editor-field">
        @Html.EditorFor(model => model.FullName)
        @Html.ValidationMessageFor(model => model.FullName)
    </div>

    <div class="editor-label">
        @Html.LabelFor(model => model.BirthDate)
    </div>
    <div class="editor-field">
        @Html.EditorFor(model => model.BirthDate)
        @Html.ValidationMessageFor(model => model.BirthDate)
    </div>

    <div class="editor-label">
        @Html.LabelFor(model => model.IsMale)
    </div>
    <div class="editor-field">
        @Html.EditorFor(model => model.IsMale)
        @Html.ValidationMessageFor(model => model.IsMale)
    </div>

    <p>
        <input type="submit" id="btn_Save" value="ذخیره" />
        <input type="submit" id="btn_Cancel" value="انصراف" />
    </p>
}

<script type="text/javascript">
    $(function () {
        $("#btn_Save").click(function (e) {
            e.preventDefault();
            var button = $(this);
            $("#frm_studentEdit").PostMvcFormAjax({
                postUrl: '@postUrl',
                loginUrl: '/login',
                beforePostHandler: function () {
                    //غیرفعال سازی دکمه ارسال
                    button.attr('disabled', 'disabled');
                    button.val("...");
                },
                completeHandler: function (data) {
                    //فعال سازی مجدد دکمه ارسال
                    button.removeAttr('disabled');
                    button.val("ذخیره");
                },
                errorHandler: function () {
                    alert('خطایی رخ داده است');
                }
            });
        });
        $("#btn_Cancel").click(function (e) {
            e.preventDefault();
            var button = $(this);
            $(".editDialog").parent("div").css("display", "none");
        });
    });
</script>
با تشکر.
مطالب
پیاده سازی ویژگی مشابه asp-append-version در برنامه‌های Blazor SSR

عموما برای درج فایل‌های ثابت اسکریپت‌ها و شیوه‌نامه‌های سایت، از روش متداول زیر استفاده می‌شود:

<link rel="stylesheet" href="/css/site.css"  />    
<script src="/js/site.js"></script>    

مشکلی که به همراه این روش وجود دارد، مطلع سازی کاربران و مرورگر، از تغییرات آن‌هاست؛ چون این فایل‌های ثابت، توسط مرورگرها کش شده و با فشردن دکمه‌هایی مانند Ctrl+F5 و به‌روز شدن کش مرورگر، به نگارش جدید،‌ ارتقاء پیدا می‌کنند. برای رفع این مشکل حداقل دو روش وجود دارد:

الف) هربار نام این فایل‌ها را تغییر دهیم. برای مثال بجای نام قدیمی site.css، از نام جدید site.v.1.1.css استفاده کنیم.

ب) یک کوئری استرینگ متغیر را به نام ثابت این فایل‌ها، اضافه کنیم.

که در این بین، روش دوم متداول‌تر و معقول‌تر است. برای این منظور، ASP.NET Core به همراه ویژگی توکاری است به نام asp-append-version که اگر آن‌‌را به تگ‌های اسکریپت و link اضافه کنیم:

<link rel="stylesheet" href="~/css/site.css" asp-append-version="true" />    
<script src="~/js/site.js" asp-append-version="true"></script>    

این کوئری استرینگ را به صورت خودکار محاسبه کرده و به آدرس فایل درج شده اضافه می‌کند؛ با خروجی‌هایی شبیه به مثال زیر:

<link rel="stylesheet" href="/css/site.css?v=AAs5qCYR2ja7e8QIduN1jQ8eMcls-cPxNYUozN3TJE0" />    
<script src="/js/site.js?v=NO2z9yI9csNxHrDHIeTBBfyARw3PX_xnFa0bz3RgnE4"></script>  

ASP.NET Core در اینجا هش فایل‌های یافت شده را با استفاده از الگوریتم SHA256 محاسبه و url encode کرده و به صورت یک کوئری استرینگ، به انتهای آدرس فایل‌ها اضافه می‌کند. به این ترتیب با تغییر محتوای این فایل‌ها، این هش نیز تغییر می‌کند و مرورگر بر این اساس، همواره آخرین نگارش ارائه شده را از سرور دریافت خواهد کرد. نتیجه‌ی این محاسبات نیز به صورت خودکار کش می‌شود و همچنین با استفاده از یک File Watcher در پشت صحنه، تغییرات این فایل‌ها هم بررسی می‌شوند. یعنی اگر فایلی تغییر کرد، نیازی به ‌ری‌استارت برنامه نیست و محاسبات جدید و کش شدن مجدد آن‌ها، به صورت خودکار انجام می‌شود.

البته این ویژگی هنوز به Blazor اضافه نشده‌است؛ اما امکان استفاده‌ی از زیر ساخت ویژگی asp-append-version با کدنویسی مهیا است که در ادامه با استفاده از آن، کامپوننتی را مخصوص Blazor SSR، تهیه می‌کنیم.

دسترسی به زیر ساخت محاسباتی ویژگی asp-append-version با کدنویسی

زیرساخت محاسباتی ویژگی asp-append-version، با استفاده از سرویس توکار IFileVersionProvider به صورت زیر قابل دسترسی است:

public static class FileVersionHashProvider
{
    private static readonly string ProcessExecutableModuleVersionId =
        Assembly.GetEntryAssembly()!.ManifestModule.ModuleVersionId.ToString("N");

    public static string GetFileVersionedPath(this HttpContext httpContext, string filePath, string? defaultHash = null)
    {
        ArgumentNullException.ThrowIfNull(httpContext);

        var fileVersionedPath = httpContext.RequestServices.GetRequiredService<IFileVersionProvider>()
            .AddFileVersionToPath(httpContext.Request.PathBase, filePath);

        return IsEmbeddedOrNotFound(fileVersionedPath, filePath)
            ? QueryHelpers.AddQueryString(filePath, new Dictionary<string, string?>(StringComparer.Ordinal)
            {
                {
                    "v", defaultHash ?? ProcessExecutableModuleVersionId
                }
            })
            : fileVersionedPath;
    }

    private static bool IsEmbeddedOrNotFound(string fileVersionedPath, string filePath)
        => string.Equals(fileVersionedPath, filePath, StringComparison.Ordinal);
} 

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

ساخت کامپوننت‌هایی برای درج خودکار هش فایل‌های اسکریپت‌ها

یک نمونه روش استفاده‌ی از متد الحاقی GetFileVersionedPath فوق را در کامپوننت DntFileVersionedJavaScriptSource.razor زیر می‌توانید مشاهده کنید:

@if (!string.IsNullOrWhiteSpace(JsFilePath))
{
    <script src="@HttpContext.GetFileVersionedPath(JsFilePath)" type="text/javascript"></script>
}

@code{
    [CascadingParameter] public HttpContext HttpContext { set; get; } = null!;

    [Parameter]
    [EditorRequired]
    public required string JsFilePath { set; get; }
}

با استفاده از HttpContext مهیای در برنامه‌های Blazor SSR، متد الحاقی GetFileVersionedPath به همراه مسیر فایل js. مدنظر، در صفحه درج می‌شود.

برای مثال یک نمونه از استفاده‌ی آن، به صورت زیر است:

<DntFileVersionedJavaScriptSource JsFilePath="/lib/quill/dist/quill.js"/>

در نهایت با اینکار، یک چنین خروجی در صفحه درج خواهد شد که با تغییر محتوای فایل quill.js، هش متناظر با آن به صورت خودکار به‌روز خواهد شد:

<scriptsrc="/lib/quill/dist/quill.js?v=5q7uUOOlr88Io5YhQk3lgYcoB_P3-5Awq1lf0rRa7-Y" type="text/javascript"></script>

شبیه به همین کار را برای شیوه‌نامه‌ها هم می‌توان تکرار کرد و کدهای آن، تفاوت آنچنانی با کامپوننت فوق ندارند.

مطالب
تغییرات بوجود آمده در Razor -MVC4
همانطوری که میدونید نسخه    MVC 4 RC   در دسترس قرار گرفته و خالی از لطف نیست که یک بررسی درباره امکانات جدیدش انجام بشه.ابتدا سعی میکنم یک لیست کلی از امکانان این تکنولوژی داشته باشیم و بعد نگاهی هم به Razor  و تغییراتش خواهیم داشت.
 
ASP.NET Web API
Refreshed and modernized default project templates
New mobile project template
Many new features to support mobile apps
Recipes to customize code generation
Enhanced support for asynchronous methods 
لیست فوق شامل برترین ویژگی‌های این نسخه است که در پستهای آینده هر کدام از این‌ها بررسی خواهند شد.

تغییرات Razor
Razor از نسخه MVC4 Beta شاهد تغییرات و بهبود هایی بود که این تغییرات بنیادی و رادیکالی نبودند و فقط درجهت بهبود حس کاربری آن صورت گرفت. این حس از آن جهت است که شما نیاز به نوشتن کد کمتری دارید.

استفاده نکردن از  Url.Content@ 
در نسخه قبلی از امکان ذکر شده برای مشخص کرده مسیر‌های CSS و فایلهای .JS هم استفاده میشد حالا بجای استفاده از آن میشود:
در نسخه‌های قبلی:
<script src="@Url.Content("~/Scripts/Site.js")"></script>
 در نسخه 4:
<script src="~/Scripts/Site.js"></script> 
زمانی که Razor  حرف را ~/   تشخیص می‌دهد خروجی یکسانی با حالت قبلی برای ما درست می‌کند.

شرط ها(Conditions)
در نسخه قبلی برای استفاده از attribute ها که ممکن بود Null باشند مجبور به چک کردن آنها بودیم:
<div @{if (myClass != null) { <text>class="@myClass"</text> } }>Content</div>
اما حالا با خیال راحت می‌توان نوشت:
<div class="@myClass">Content</div>
که اگر attribute ما null باشد به صورت اتوماتیک تشخیص داده میشود و کد زیر رندر میشود
<div>Content</div>
در ضمناً کد بالا فقط مربوط به چک کردن Nullable نیست و از آن می‌توان در Boolean‌ها هم استفاده کرد.
<input checked="@ViewBag.Checked" type="checkbox"/>
که اگر مقدار True نباشد:
<input type="checkbox"/>
بطور خلاصه میشه گفت MVC4 تغییراتش نسبت به نسخه قبلی تو خیلی از زمینه‌ها مربوط میشه بهبود ابزارهای موجود در کل کار با این ایزار بسیار برای من لذت بخشه.
ادامه دارد...