نظرات مطالب
متغیرها در ES 6
- این مثال‌ها با کروم اجرا شدند. مثال اول را با فایرفاکس اجرا کردید؟ مثال دوم که به همین نحو باید باشد. 
اجرا شدن مثال اول با فایرفاکس به این معنا است که هنوز پیاده سازی استاندارد ES 6 در فایرفاکس، توابع قرار گرفته‌ی در یک block را block-scoped نمی‌داند (و این پیاده سازی هنوز ناقص است).
- وجود let به نوعی پیاده سازی مفهوم fail fast است. هر چه سریعتر مشکلات مشخص شوند، بهتر از این است که در آینده با گنگ بودن میدان دید متغیرها، مشکلات دیباگ برنامه بیشتر شوند.
نظرات مطالب
پیاده سازی Option یا Maybe در #C
 مزیتهای برنامه نویسی تابعی برکسی پوشیده نیست، حتی زبان شی گرای سی شارپ^ ، پشتیبانی نسبتا خوبی از مفاهیم برنامه نویسی فانکشنال ارائه میدهد.(LINQ)
اما در مورد مثال ارائه شده کاربرد چشمگیری از مفاهیم برنامه نویسی تابعی عنوان نشد. در واقع یک الگو زمانی کارساز است که همسو با کاربرد مرتبط (نیل به هدف) مورد استفاده قرار گیرد تا هرچه بیشتر مثمر ثمر واقع شود.
نظرات مطالب
Globalization در ASP.NET MVC - قسمت ششم
با سلام
من این حالت رو میخوام با uow میخوام پیاده سازی کنم. میخوام یک سری تغییرات تو ساختار جدول بدم.
یک جدول برای مدیریت اضافه و حذف زبان (نام، Culture، ISO، RTL و ...)  و جدول دیگم برای ریسورس ها(کلید، اسم، مقدار)
در واقع میخوام مقادیر ریسورس‌ها با اضافه و حذف شدن یک زبان به سیستم مدیریت بشه. 
میخواستم ببینم که چه پیشنهادی برای این حالت دارید؟
مطالب
String.format در جاوا اسکریپت
مقدمه 
با اینکه زبان برنامه نویسی جاوا اسکریپت زبانی بسیار قدرتمند و با امکانات زیاد است، اما فقدان برخی متدهای کمکی پرمصرف در آن در برخی موارد باعث دردسرهایی می‌شود. امکانی برای فرمت‌بندی رشته‌ها یکی از این نیازهای نسبتا پرکاربرد است.
متدی که در این مطلب قصد توضیح پیاده‌سازی آنرا داریم، String.format نام دارد که فرایندی مشابه متد متناظر در دات نت را انجام می‌دهد. هم‌چنین سعی شده است تا نحوه پیاده‌سازی این متد کمکی از ابتدایی‌ترین نمونه‌ها تا نسخه‌های پیشرفته‌تر برای درک بهتر مطلب نشان داده شود.
.
پیاده‌سازی متد String.format
1. در این پیاده‌سازی از اولین فرایندی که ممکن است به ذهن یک برنامه‌نویس خطور کند استفاده شده است. این پیاده‌سازی بسیار ساده به صورت زیر است:
String.format = function () {
  var s = arguments[0];
  for (var i = 0; i < arguments.length - 1; i++) {
    s = s.replace("{" + i + "}", arguments[i + 1]);
  }
  return s;
};

2. پیاده‌سازی مشابهی هم با استفاده از نوع دیگری از حلقه for که تقریبا! مشابه با حلقه foreach در #C است به صورت زیر می‌توان درنظر گرفت:
String.format = function () {
  var s = arguments[0];
  for (var arg in arguments) {
    var i = parseInt(arg);
    s = s.replace("{" + i + "}", arguments[i + 1]);
  }
  return s;
};
در این متدها ابتدا فرمت واردشده توسط کاربر از لیست آرگومان‌های متد خوانده شده و در متغیر s ذخیره می‌شود. سپس درون یک حلقه به ازای هر توکن موجود در رشته فرمت، یک عملیات replace با مقدار متناظر در لیست آرگومان‌های متد انجام می‌شود. نحوه استفاده از این متد نیز به صورت زیر است:
console.log(String.format("{0} is nice!", "donettips.info"));
هر دو متد خروجی یکسانی دارند، به صورت زیر:
donettips.info is nice!
تا اینجا به نظر می‌رسد که عملیات به‌درستی پیش می‌رود. اما اولین و بزرگ‌ترین مشکل در این دو متد نحوه کارکردن متد replace در جاوا اسکریپت است. این متد با این نحوه فراخوانی تنها اولین توکن موجود را یافته و عملیات جایگزینی را برای آن انجام می‌دهد. برای روشن‌تر شدن موضوع به مثال زیر توجه کنید:
console.log(String.format("{0} is {1} nice! {0} is {1} nice!", "donettips.info", "very"));
با اجرای این مثال نتیجه زیر حاصل می‌شود:
donettips.info is very nice! {0} is {1} nice!
همان‌طور که می‌بنید عملیات replace برای سایر توکن‌ها انجام نمی‌شود.

3. برای حل مشکل فوق می‌توان از روش ساده زیر استفاده کرد:
String.format = function () {
  var original = arguments[0],
      replaced;
  for (var i = 0; i < arguments.length - 1; i++) {
    replaced = '';
    while (replaced != original) {
      original = replaced || original;
      replaced = original.replace("{" + i + "}", arguments[i + 1]);
    }
  }
  return replaced;
};
در این روش عملیات replace تا زمانی‌که تغییری در رشته جاری ایجاد نشود ادامه می‌یابد. با استفاده از این متد، خروجی مثال قبل درست و به صورت زیر خواهد بود:
donettips.info is very nice! donettips.info is very nice!

4. راه حل دیگر استفاده از امکانات شی RegExp در دستور replace است. نکته مهم استفاده از modifier کلی یا global (که با حرف g مشخص می‌شود) در شی تولیدی از RegExp است (^ و ^ و ^، برای جلوگیری از دورشدن از بحث اصلی، جستجو برای کسب اطلاعات بیشتر در این زمینه به خوانندگان واگذار می‌شود). برای استفاده از این شی متد ما به صورت زیر تغییر می‌کند:
String.format = function () {
  var s = arguments[0];
  for (var i = 0; i < arguments.length - 1; i++) {
    s = s.replace(new RegExp("\\{" + i + "\\}", "g"), arguments[i + 1]);
  }
  return s;
};
استفاده از این متد هم نتیجه درستی برای مثال آخر ارائه می‌دهد.

5. روش دیگری که کمی از دو متد قبلی سریع‌تر اجرا می‌شود (به دلیل استفاده از حلقه while) به صورت زیر است:
String.format = function () {
  var s = arguments[0],
      i = arguments.length - 1;
  while (i--) {
    s = s.replace(new RegExp('\\{' + i + '\\}', 'g'), arguments[i + 1]);
  }
  return s;
};
این متد نیز نتیجه مشابهی ارائه می‌کند. حال به مثال زیر توجه کنید:
console.log(String.format("{0}:0 {1}:1 {2}:2", "zero", "{2}", "two"));
خروجی صحیح مثال فوق باید به صورت زیر باشد:
zero:0 {2}:1 two:2
درصورتی‌که رشته‌ای که دو متد از سه متد آخر (3 و 4) به عنوان خروجی ارائه می‌دهند به‌صورت زیر است:
zero:0 two:1 two:2
برای آخرین متد که ازحلقه while (درواقع با اندیس معکوس) استفاده می‌کند (5) مثالی که خطای مورد بحث را نشان می‌دهد به صورت زیر است:
console.log(String.format("{0}:0 {1}:1 {2}:2", "zero", "one", "{1}"));
که خروجی اشتباه زیر را برمی‌گرداند:
zero:0 one:1 one:2
درصورتی‌که باید مقدار زیر را برگشت دهد:
zero:0 one:1 {1}:2
دلیل رخدادن این خطا اجرای عملیات replace به صورت جداگانه و کامل برای هر توکن، از اول تا آخر برای رشته‌های replace شده جاری است که کار را خراب می‌کند.

6. برای حل مشکل بالا نیز می‌توان از یکی دیگر از امکانات دستور replace استفاده کرد که به صورت زیر است:
String.format = function () {
  var args = arguments;
  return args[0].replace(/{(\d+)}/g, function (match, number) { return args[parseInt(number) + 1]; });
};
در اینجا از قابلیت سفارشی‌سازی عملیات جایگزینی در دستور replace استفاده شده است. با استفاده از این ویژگی عملیات replace برای هر توکن جداگانه انجام می‌شود و بنابراین تغییرات اعمالی در حین عملیات تاثیر مستقیمی برای ادامه روند نخواهد گذاشت.
دقت کنید که برای بکاربردن RegExp درون دستور replace به جای تولید یک نمونه از شی RegExp می‌توان عبارت مربوطه را نیز مستقیما بکار برد. در اینجا از عبارتی کلی برای دریافت تمامی توکن‌های با فرمتی به صورت {عدد} استفاده شده است.
متد سفارشی مربوطه نیز شماره ردیف توکن یافته‌شده به همراه خود عبارت یافته‌شده را به عنوان آرگومان ورودی دریافت کرده و مقدار متناظر را از لیست آرگومان‌های متد اصلی پس از تبدیل شماره ردیف توکن به یک عدد، برگشت می‌دهد (در اینجا نیز برای جلوگیری از دورشدن از بحث اصلی، جستجو برای کسب اطلاعات بیشتر در این زمینه به خوانندگان واگذار می‌شود).
برای جلوگیری از تداخل بین آرگومان‌های متد اصلی و متد تهیه‌شده برای سفارشی‌سازی عملیات جایگزینی، در ایتدای متد اصلی، لیست آرگومان‌های آن درون متغیر جداگانه‌ای (args) ذخیره شده است.
با استفاده از این متد خروجی درست نشان داده می‌شود. حال مثال زیر را درنظر بگیرید:
console.log(String.format("{0} is {1} nice!", "donettips.info"));
خروجی این مثال به‌صورت زیر است:
donettips.info is undefined nice!
پیاده‌سازی زیر برای حل این مشکل استفاده می‌شود.

7. برای کنترل بیشتر و رفع خطاهای احتمالی در متد بالا، می‌توان ابتدا از وجود آرگومان مربوطه در متغیر args اطمینان حاصل کرد تا از جایگزینی مقدار undefined در رشته نهایی جلوگیری کرد. مانند نمونه زیر:
String.format = function () {
  var s = arguments[0],
      args = arguments;
  return s.replace(/{(\d+)}/g, function (match, number) {
    var i = parseInt(number);
    return typeof args[i + 1] != 'undefined' ? args[i + 1] : match;
  });
};
با استفاده از این متد جدید خروجی مثال‌های قبل درست خواهد بود.
در فرمت بندی رشته‌ها برای نمایش خود کاراکتر { یا } از تکرار آن‌ها (یعنی {{ یا }}) استفاده می‌شود. اما متد ما تا این لحظه این امکان را ندارد. برای مثال:
console.log(String.format("{0}:0 {1}:1 {2}:2, {{0}} {{{1}}}  {{{{2}}}}   {2}", "zero", "{2}", "two"));
که خروجی زیر را ارائه می‌دهد:
zero:0 {2}:1 two:2, {zero} {{{2}}}  {{{two}}}   two
.
8. برای پیاده‌سازی امکان اشاره‌شده در بالا می‌توان از کد زیر استفاده کرد:
String.format = function () {
  var s = arguments[0],
      args = arguments;
  return s.replace(/\{\{|\}\}|\{(\d+)\}/g, function (match, number) {
    if (match == "{{") { return "{"; }
    if (match == "}}") { return "}"; }
    var i = parseInt(number);
    return typeof args[i + 1] != 'undefined'
                              ? args[i + 1]
                              : match;
  });
};
در اینجا با استفاده از یک عبارت RegExp پیچیده‌تر و کنترل تکرار کاراکترهای { و } در متد سفارشی جایگزینی در دستور replace، پیاده‌سازی اولیه این ویژگی ارائه شده است.
این متد خروجی صحیح زیر را برای مثال آخر ارائه می‌دهد:
zero:0 {2}:1 two:2, {0} {{2}}  {{2}}   two

پیاده‌سازی به‌صورت یک خاصیت prototype
تمامی متدهای نشان داده‌شده تا اینجا به‌صورت مستقیم از طریق String.format در دسترس خواهند بود (تعریفی شبیه به متدهای استاتیک در دات نت). درصورتی‌که بخواهیم از این متدها به صورت یک خاصیت prototype شی string استفاده کنیم (چیزی شبیه به متدهای instance در اشیای دات نت) می‌توانیم از تعریف زیر استفاده کنیم:
String.prototype.format = function () {
   ...
}
تنها فرق مهم این پیاده‌سازی این است که رشته مربوط به فرمت وارده در این متد از طریق شی this در دسترس است و بنابراین شماره اندیس آرگومان‌های متد یکی کمتر از متدهای قبلی است که باید مدنظر قرار گیرد. مثلا برای متد آخر خواهیم داشت:
String.prototype.format = function () {
  var s = this.toString(),
      args = arguments;
  return s.replace(/\{\{|\}\}|\{(\d+)\}/g, function (match, number) {
    if (match == "{{") { return "{"; }
    if (match == "}}") { return "}"; }
    return typeof args[number] != 'undefined'
                              ? args[number]
                              : match;
  });
};

نکته: در تمامی خواص prototype هر شی در جاوا اسکریپت، متغیر this از نوع object است. بنابراین برای جلوگیری از وقوع هر خطا بهتر است ابتدا آن‌را به نوع مناسب تبدیل کرد. مثل استفاده از متد toString در متد فوق که موجب تبدیل آن به رشته می‌شود.

ازآنجاکه نیاز به تغییر اندیس در متد سفارشی عملیات replace وجود ندارد، بنابراین خط مربوط به تبدیل آرگومان number به یک مقدار عددی (با دستور parseInt) حذف شده است و از این متغیر به صورت مستقیم استفاده شده است. در این حالت عملیات تبدیل توسط خود جاوا اسکریپت مدیریت می‌شود که کار را راحت‌تر می‌سازد.
بنابراین متد ما به صورت زیر قابل استفاده است:
console.log("{0}:0 {1}:1 {2}:2, {{0}} {{{1}}}  {{{{2}}}}   {2}".format("zero", "{2}", "two"));

پیاده‌سازی با استفاده از توکن‌های غیرعددی
برای استفاده از توکن‌های غیرعددی می‌توانیم به صورت زیر عمل کنیم:
String.format = function () {
  var s = arguments[0],
      args = arguments[1];
  for (var arg in args) {
    s = s.replace(new RegExp("{" + arg + "}", "g"), args[arg]);
  }
  return s;
};
برای حالت prototype نیز داریم:
String.prototype.format = function () {
  var s = this.toString(),
      args = arguments[0];
  for (var arg in args) {
    s = s.replace(new RegExp("{" + arg + "}", "g"), args[arg]);
  }
  return s;
};
با استفاده از این دو متد داریم:
console.log(String.format("{site} is {adj}! {site} is {adj}!", { site: "donettips.info", adj: "nice" }));
console.log("{site} is {adj}! {site} is {adj}!".format({ site: "donettips.info", adj: "nice" }));
.
تا اینجا متدهایی نسبتا کامل برای نیازهای عادی برنامه‌نویسی تهیه شده است. البته کار توسعه این متد برای پشتیبانی از امکانات پیشرفته‌تر فرمت‌بندی رشته‌ها می‌تواند ادامه پیدا کند.

کتابخانه‌های موجود
یکی از کامل‌ترین کتابخانه‌های کار با رشته‌ها همان کتابخانه معروف Microsoft Ajax Client Libray است که بیشتر امکانات موجود کار با رشته‌ها در دات نت را در خود دارد. صرفا جهت آشنایی، پیاده‌سازی متد String.format در این کتابخانه در زیر آورده شده است:
String.format = function String$format(format, args) {
  /// <summary locid="M:J#String.format" />
  /// <param name="format" type="String"></param>
  /// <param name="args" parameterArray="true" mayBeNull="true"></param>
  /// <returns type="String"></returns>
//  var e = Function._validateParams(arguments, [
//    { name: "format", type: String },
//    { name: "args", mayBeNull: true, parameterArray: true }
//  ]);
//  if (e) throw e;
  return String._toFormattedString(false, arguments);
};
String._toFormattedString = function String$_toFormattedString(useLocale, args) {
  var result = '';
  var format = args[0];
  for (var i = 0; ; ) {
    var open = format.indexOf('{', i);
    var close = format.indexOf('}', i);
    if ((open < 0) && (close < 0)) {
      result += format.slice(i);
      break;
    }
    if ((close > 0) && ((close < open) || (open < 0))) {
      if (format.charAt(close + 1) !== '}') {
        throw Error.argument('format', Sys.Res.stringFormatBraceMismatch);
      }
      result += format.slice(i, close + 1);
      i = close + 2;
      continue;
    }
    result += format.slice(i, open);
    i = open + 1;
    if (format.charAt(i) === '{') {
      result += '{';
      i++;
      continue;
    }
    if (close < 0) throw Error.argument('format', Sys.Res.stringFormatBraceMismatch);
    var brace = format.substring(i, close);
    var colonIndex = brace.indexOf(':');
    var argNumber = parseInt((colonIndex < 0) ? brace : brace.substring(0, colonIndex), 10) + 1;
    if (isNaN(argNumber)) throw Error.argument('format', Sys.Res.stringFormatInvalid);
    var argFormat = (colonIndex < 0) ? '' : brace.substring(colonIndex + 1);
    var arg = args[argNumber];
    if (typeof (arg) === "undefined" || arg === null) {
      arg = '';
    }
    if (arg.toFormattedString) {
      result += arg.toFormattedString(argFormat);
    }
    else if (useLocale && arg.localeFormat) {
      result += arg.localeFormat(argFormat);
    }
    else if (arg.format) {
      result += arg.format(argFormat);
    }
    else
      result += arg.toString();
    i = close + 1;
  }
  return result;
}
دقت کنید قسمت ابتدایی این متد که برای بررسی اعتبار آرگومان‌های ورودی است، برای سادگی عملیات کامنت شده است. همان‌طور که می‌بینید این متد پیاده‌سازی نسبتا مفصلی دارد و امکانات بیشتری نیز در اختیار برنامه نویسان قرار می‌دهد. البته سایر متدهای مربوطه بدلیل طولانی بودن در اینجا آورده نشده است. برای مثال امکانات پیشرفته‌تری مثل زیر با استفاده از این کتابخانه در دسترس هستند:
console.log(String.format("{0:n}, {0:c}, {0:p}, {0:d}", 100.0001));
// result:   100.00, ¤100.00, 10,000.01 %, 100.0001

console.log(String.format("{0:d}, {0:t}", new Date(2015, 1, 1, 10, 45)));
// result:   02/01/2015, 10:45
آخرین نسخه این کتابخانه از اینجا قابل دریافت است (این متدها درون فایل MicrosoftAjax.debug.js قرار دارند). این کتابخانه دیگر به این صورت و با این نام توسعه داده نمیشود و چند سالی است که تصمیم به توسعه ویژگی‌های جدید آن به صورت پلاگین‌های jQuery گرفته شده است.

کتابخانه دیگری که می‌توان برای عملیات فرمت‌بندی رشته‌ها در جاوا اسکریپت از آن استفاده کرد، کتابخانه معروف jQuery Validation است. این کتابخانه یک متد نسبتا خوب با نام format برای فرمت کردن رشته‌ها دارد. نحوه استفاده از این متد به صورت زیر است:
var template = jQuery.validator.format("{0} is not a valid value");
console.log(template("abc"));
// result: 'abc is not a valid value'

کتابخانه نسبتا کامل دیگری که وجود دارد، با عنوان Stringformat از اینجا قابل دریافت است. برای استفاده از این کتابخانه باید به صورت زیر عمل کرد:
String.format([full format string], [arguments...]);
// or:
[date|number].format([partial format string]);
همان‌طور که می‌بینید این کتابخانه امکانات کامل‌تری نیز دارد. مثال‌های مربوط به این کتابخانه به صورت زیر هستند که توانایی‌های نسبتا کامل آن‌را نشان می‌دهد:
// Object path
String.format("Welcome back, {username}!", 
{ id: 3, username: "JohnDoe" });
// Result: "Welcome back, JohnDoe!"

// Date/time formatting
String.format("The time is now {0:t}.", 
new Date(2009, 5, 1, 13, 22));
// Result: "The time is now 01:22 PM."

// Date/time formatting (without using a full format string)
var d = new Date();
d.format("hh:mm:ss tt");
// Result: "02:28:06 PM"

// Custom number format string
String.format("Please call me at {0:+##0 (0) 000-00 00}.", 4601111111);
// Result: "Please call me at +46 (0) 111-11 11."

// Another custom number format string
String.format("The last year result was {0:+$#,0.00;-$#,0.00;0}.", -5543.346);
// Result: "The last year result was -$5,543.35."

// Alignment
String.format("|{0,10:PI=0.00}|", Math.PI);
// Result: "|   PI=3.14|"

// Rounding
String.format("1/3 ~ {0:0.00}", 1/3);
// Result: "1/3 ~ 0.33"

// Boolean values
String.format("{0:true;;false}", 0);
// Result: "false"

// Explicitly specified localization
// (note that you have to include the .js file for used cultures)
msf.setCulture("en-US");
String.format("{0:#,0.0}", 3641.667);
// Result: "3,641.7"

msf.setCulture("sv-SE");
String.format("{0:#,0.0}", 3641.667);
// Result: "3 641,7"

یک کتابخانه دیگر نیز از این آدرس قابل دریافت است. این کتابخانه با عنوان String.format نام‌گذاری شده است. نحوه استفاده از این کتابخانه نیز به صورت زیر است:
//inline arguments
String.format("some string with {0} and {1} injected using argument {{number}}", 'first value', 'second value');
//returns: 'some string with first value and second value injected argument {number}'

//single array
String.format("some string with {0} and {1} injected using array {{number}}", [ 'first value', 'second value' ]);
//returns: 'some string with first value and second value injected using array {number}'

//single object
String.format("some string with {first} and {second} value injected using {{propertyName}}",{first:'first value',second:'second value'});
//returns: 'some string with first value and second value injected using {propertyName}'
کتابخانه نسبتا معروف و کامل sprintf نیز در اینجا وجود دارد. این کتابخانه امکانات بسیاری همچون متدهای متناظر در زبان C دارد.

منابع

نظرات مطالب
اعمال تزریق وابستگی‌ها به مثال رسمی ASP.NET Identity
با سلام؛ من همین روش رو رفتم و توی پروژه م اضافه کردم. اما وقتی میخوام Add-Migration بزنم ، به چنین اروری برمی خوردم:
One or more validation errors were detected during model generation:
DataLayer.Context.CustomUserRole: : EntityType 'CustomUserRole' has no key defined. Define the key for this EntityType.
DataLayer.Context.CustomUserLogin: : EntityType 'CustomUserLogin' has no key defined. Define the key for this EntityType.
CustomUserRoles: EntityType: EntitySet 'CustomUserRoles' is based on type 'CustomUserRole' that has no keys defined.
CustomUserLogins: EntityType: EntitySet 'CustomUserLogins' is based on type 'CustomUserLogin' that has no keys defined.
آیا ممکنه بخاطر آپدیت کردن پکیج‌های مربوطه باشه؟
نظرات مطالب
Asp.Net Identity #2
همراه با  Identity 2  با استفاده از روش  code first  چند جدول اضافه کردم و بین اونا رو ارتباط دادم . ولی متاسفانه پیغام زیر رو دریافت میکنم
MyProject.Models.IdentityUserRole: : EntityType 'IdentityUserRole' has no key defined. Define the key for this EntityType.
MyProject.Models.IdentityUserLogin: : EntityType 'IdentityUserLogin' has no key defined. Define the key for this EntityType.
IdentityUserRoles: EntityType: EntitySet 'IdentityUserRoles' is based on type 'IdentityUserRole' that has no keys defined.
IdentityUserLogins: EntityType: EntitySet 'IdentityUserLogins' is based on type 'IdentityUserLogin' that has no keys defined.
ولی زمانی که ارتباط رو با جداول  Identity  حذف میکنم دیگه مشکلی نیست .
 
مطالب دوره‌ها
الگوی Matching
الگوی Matching در واقع همون switch در اکثر زبان‌ها نظیر #C یا ++C است با این تفاوت که بسیار انعطاف پذیرتر و قدرتمندتر است. در برنامه نویسی تابع گرا، هدف اصلی از ایجاد توابع دریافت ورودی و اعمال برخی عملیات مورد نظر بر روی مقادیر با استفاده از تعریف حالات مختلف برای انتخاب عملیات است. الگوی Matching این امکان رو به ما می‌ده که با استفاده از حالات مختلف یک عملیات انتخاب شود و با توجه به ورودی یک سری دستورات رو اجرا کنه. ساختار کلی تعریف آن به شکل زیر است:
match expr with
| pat1 -> result1
| pat2 -> result2
| pat3 when expr2 -> result3
| _ -> defaultResult
راحت‌ترین روش استفاده از الگوی Matching هنگام کار با مقادیر است. اولین مثال رو هم در فصل قبل در بخش توابع بازگشتی با هم دیدیم.
let booleanToString x =
match x with false -> "False" 
| _ -> "True"
در تابع بالا ورودی ما اگر false باشد "False" و اگر true باشد "True" برگشت داده می‌شود. _ در مثال بالا دقیقا همون default در switch سایر زبان هاست.
let stringToBoolean x =
match x with
| "True" | "true" -> true
| "False" | "false" -> false
| _ -> failwith "unexpected input"
در این مثال (دقیقا بر عکس مثال بالا ) ابتدا یک string دریافت  می‌شود اگر برابر "True" یا "true" بود مقدار true برگشت داده میشود و اگر برابر "False" یا "false" بود مقدار false برگشت داده می‌شود در غیر این صورت یک FailureException  پرتاب می‌شود. خروجی مثال بالا در حالات مختلف به شکل زیر است:
printfn "(booleanToString true) = %s"
(booleanToString true)
printfn "(booleanToString false) = %s"
(booleanToString false)
printfn "(stringToBoolean \"True\") = %b"
(stringToBoolean "True")
printfn "(stringToBoolean \"false\") = %b"
(stringToBoolean "false")
printfn "(stringToBoolean \"Hello\") = %b"
(stringToBoolean "Hello")
خروجی :
(booleanToString true) = True
(booleanToString false) = False
(stringToBoolean "True") = true
(stringToBoolean "false") = false
Microsoft.FSharp.Core.FailureException: unexpected input
at FSI_0005.stringToBoolean(String x)
at <StartupCode$FSI_0005>.$FSI_0005.main@()
هم چنین علاوه بر اینکه امکان استفاده از چند شناسه در این الگو وجود دارد، امکان استفاده از And , Or نیز در این الگو میسر است.
let myOr b1 b2 =
match b1, b2 with
| true, _ -> true  //b1 true , b2 true or false
| _, true -> true // b1 true or false , b2 true
| _ -> false
printfn "(myOr true false) = %b" (myOr true false) printfn "(myOr false false) = %b" (myOr false false)
خروجی برای کد‌های بالا به صورت زیر است:
(myOr true false) = true
(myOr false false) = false
استفاده از عبارت و شروط در الگوی Matching 
در الگوی Matching اگر در بررسی ورودی الگو با یک مقدار نیاز شما را برطرف نمی‌کند استفاده از فیلتر‌ها و شروط مختلف هم مجاز است. برای مثال
let sign = function
    | 0 -> 0
    | x when x < 0 -> -1
    | x when x > 0 -> 1
مثال بالا برای تعیین علامت هر عدد ورودی به کار می‌رود. -1 برای عدد منفی و 1 برای عدد مثبت و 0 برای عدد 0.

عبارت if … then … else
استفاده از if در #F کاملا مشابه به استفاده از if در #C است و نیاز به توضیح ندارد. تنها تفاوت در else if است که در #F به صورت elif نوشته می‌شود.
ساختار کلی
if expr then
    expr
elif expr then
    expr
elif expr then
    expr
...
else
    expr
 برای مثال الگوی Matching پایین رو به صورت if خواهیم نوشت.
let result =
match System.DateTime.Now.Second % 2 = 0 with
| true -> "heads"
| false -> "tails"
#با استفاده از if
let result =
if System.DateTime.Now.Second % 2 = 0 then
box "heads"
else
box false
printfn "%A" result
در پایان یک مثال مشترک رو به وسیله دستور swith case در #C و الگوی matching در #F پیاده سازی می‌کنیم.


مطالب
کار با Razor در ASP.NET Core 2.0
پیش نویس: این مقاله ترجمه شده فصل 5 کتاب Pro Asp.Net Core MVC2 می‌باشد.


ایجاد یک پروژه با استفاده Razor

در ادامه با هم یک مثال را با استفاده از Razor ایجاد می‌کنیم. یک پروژه جدید را با قالب Empty و با نام Razor ایجاد می‌کنیم.

مراحل:

1- ابتدا در کلاس startup قابلیت MVC را فعال می‌کنیم؛ با قرار دادن کد زیر در متد ConfigureServices:
 services.AddMvc();
و بعد کد زیر را که مربوط به اجرای پروژه‌ی hello Word است ، از متد Configure حذف می‌کنیم:
app.Run(async (context) =>
{
   await context.Response.WriteAsync("Hello World!");
});
در نهایت محتویات  فایل StartUp به صورت زیر می‌باشد:

namespace Razor
{
    public class Startup
    {
        // This method gets called by the runtime. Use this method to add services to the container.
        // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            //app.Run(async (context) =>
            //{
            //    await context.Response.WriteAsync("Hello World!");
            //});
        }
    }
}


ایجاد یک Model
 یک پوشه جدید را به نام Models ایجاد و بعد در این پوشه یک کلاس را به نام Product ایجاد می‌کنیم و کدهای زیر را در آن قرار میدهیم:
namespace Razor.Models
{
    public class Product
    {
        public int ProductID { get; set; }
        public string Name { get; set; }
        public string Description { get; set; }
        public decimal Price { get; set; }
        public string Category { set; get; }
    }
}

ایجاد Controller
تنظیمات پیشفرض را در فایل Startup انجام داده‌ایم. درخواست‌هایی را که توسط کاربر ارسال میشوند، به controller پیشفرضی که نامش در اینجا Home است، ارسال می‌کند. حالا ما یک پوشه جدید را به نام Controllers ایجاد می‌کنیم و در آن یک کنترلر جدید را به نام HomeController ایجاد می‌کنیم و کدهای زیر را در آن قرار میدهیم:
namespace Razor.Controllers
{
    public class HomeController : Controller
    {
        // GET: /<controller>/
        public ViewResult Index()
        {
            Product myProduct = new Product
            {
                ProductID = 1,
                Name = "Kayak",
                Description = "A boat for one person",
                Category = "Watersports",
                Price = 275M
            };
            return View(myProduct);
        }
    }
}
در این کلاس یک Action Method را به نام index ایجاد می‌کنیم. سپس در آن یک شیء را از مدل ایجاد و مقدار دهی و آن‌را به View ارسال می‌کنیم تا در زمان بارگذاری View از این شیء استفاده نماییم. نیاز نیست نام View را مشخص کنید. به صورت پیشفرض نام View با نام اکشن متد یکسان می‌باشد.

 
ایجاد View
 برای ایجاد یک View پیشفرض برای Action Method فوق در پوشه Views/Home یک MVC View Page (Razor View Page) را به نام Index.schtml ایجاد می‌کنیم.
- نکته1: پوشه View و داخل آن Home را ایجاد کنید.
- نکته2: معادل MVC View Page در نسخه جدید، Razor View می‌باشد. اگر در لیست این آیتم را انتخاب کنید، در توضیحات پنل سمت راست میتوانید این مطلب را مشاهده کنید.
- نکته3: دقت نمایید برای اینکه پروژه net Core2. باشد و تمام مشخصات موردنظر را داشته باشد، باید نگارش ویژوال استودیو VS 2017.15.6.6 و یا بیشتر باشد.
 
@model Razor.Models.Product
@{
    Layout = null;
}

<!DOCTYPE html>
<html>
<head>
    <meta name="viewport" content="width=device-width"/>
    <title>Index</title>
</head>
<body>
    Content will go here
</body>
</html>

تا اینجا ما یک پروژه ساده را ایجاد نموده‌ایم که قابلیت استفاده‌ی از Razor را هم دارد. در ادامه نحوه‌ی استفاده از امکانات Razor شرح داده میشوند.


استفاده از Model در یک View
برای استفاده از شیء مدل در View، باید در View به آن شیء و مشخصات آن دسترسی داشته باشیم که این دسترسی را Razor با استفاده از کاراکتر @ برای ما ایجاد می‌کند. برای اتصال به Model از عبارت model@ (حتما باید حروف کوچک باشد) استفاده می‌کنیم و برای دسترسی به مشخصات مدل از عبارت Model@ (حتما باید حرف اول آن بزرگ باشد) استفاده می‌کنیم. به کد زیر دقت کنید:

@model Razor.Models.Product
@{
    Layout = null;
}

<!DOCTYPE html>
<html>
<head>
    <meta name="viewport" content="width=device-width"/>
    <title>Index</title>
</head>
<body>
  @Model.Name
</body>
</html>
خط اولی که در View تعریف شده است، با استفاده از عبارت model@ مانند تعریف نوع مدل می‌باشد و کار اتصال مدل به View را انجام میدهد و همین خط باعث میشود زمانی که شما در تگ body عبارت Model@ وبعد دات (.) را میزنید، لیست خصوصیات آن مدل ظاهر میشوند. لیست شدن خصوصیات بعد از دات(.) یکی از کارهای پیشفرض ویژوال استودیو می‌باشد؛ برای اینکه از خطاهای احتمالی کاربر جلوگیری کند.

نتیجه خروجی بالا مانند زیر می‌باشد:

 



معرفی View Imports

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

در پوشه View راست کلیک کرده و گزینه Add و بعد New Item را انتخاب می‌کنیم و در کادر باز شده، آیتم MVC View Import Page (در نسخه جدید نام آن  Razor View Imports است) انتخاب می‌کنیم. ویژوال استودیو به صورت پیش فرض نام ViewImports.cshtml_ را برای آن قرار میدهد.


نکته: استاندارد نام گذاری این View این می‌باشد که ابتدای آن کاراکتر (_) حتما وجود داشته باشد.
 
در کلاس تعریف شده با استفاده از عبارت using@ فضای نام‌های خود را قرار میدهیم؛ مانند زیر:
 @using Razor.Models
در این کلاس شما فقط میتوانید فضاهای نام را مانند بالا قرار دهید. پس از آم قسمت فضاهای نام اضافی در Viewها قابل حذف میشوند و در این حالت فقط نام کلاس مدل را در بالای فرم قرار میدهیم مانند زیر:
@model Product
@{
    Layout = null;
}

<!DOCTYPE html>
<html>
<head>
    <meta name="viewport" content="width=device-width"/>
    <title>Index</title>
</head>
<body>
  @Model.Name
</body>
</html>


Layout ها

یکی دیگر از عبارت‌های مهم Razor که در فایل Index وجود دارد، عبارت زیر است:
@{
    Layout = null;
}
شما می‌توانید در بین {} کدهای سی شارپ را قرار دهید. حالا مقدار Layout را مساوی نال قرار داده‌ایم که بگوییم View مستقلی است و از قالب مشخصی استفاده نمی‌کند.

از Layout برای طراحی الگوی Viewها استفاده می‌کنیم. اگر بخواهیم برای View ها یک قالب طراحی کنیم و این الگو بین تمام یا چندتای از آن‌ها مشترک باشد، کدهای مربوط به الگو را با استفاده از Layout ایجاد می‌کنیم و از آن در View ها استفاده می‌کنیم. اینکار برای جلوگیری از درج کدهای تکراری قالب در برنامه انجام میشود. با اینکار اگر بخواهیم در الگو تغییری را انجام دهیم، این تغییر را در یک قسمت انجام میدهم و سپس به تمام Viewها اعمال میشود.
 
Layout
طرحبندی  Viewهای برنامه بطور معمول بین چند View مشترک است و طبق استاندارد ویژوال استودیو در پوشه‌ی Views/Shared قرار میگیرد. برای ایجاد Layout، روی پوشه Views/shared راست کلیک کرده و بعد گزینه Add وبعد NewItem و سپس گزینه MVC View Layout Page (نام آن در نسخه جدید Razor Layout است) را انتخاب می‌کنیم و ابتدای نام آن را به صورت پیشفرض کاراکتر (_) قرار میدهیم.
 


هنگام ایجاد این فایل توسط ویژوال استودیو، کدهای زیر به صورت پیش فرض در فایل ایجاد شده وجود دارند: 
<!DOCTYPE html>

<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>@ViewBag.Title</title>
</head>
<body>
    <div>
        @RenderBody()
    </div>
</body>
</html>
طرحبندی‌ها فرم خاصی از View هستند و دو عبارت @ در کدهای آن وجود دارد. در اینجا فراخوانی RenderBody@ سبب درج محتویات View مشخص شده توسط Action Method در این مکان می‌شود. عبارت دیگری که در اینجا وجود دارد، ViewBag است که برای مشخص کردن عنوان در اینجا استفاده شده‌است.
ViewBag ویژگی مفیدی است که اجازه می‌دهد تا مقادیر و داده‌ها در برنامه گردش داشته باشند و در این مورد بین یک View و Layout منتقل شوند. در ادامه خواهید دید وقتی Layout را به یک نمایه اعمال می‌کنیم، این مورد چگونه کار می‌کند.

عناصر HTML در یک Layout به هر View که از آن استفاده می‌کند، اعمال و توسط آن یک الگو برای تعریف محتوای معمولی ارائه می‌شود؛ مانند کدهای زیر. من برخی از نشانه گذاری‌های ساده را به Layout اضافه کردم تا اثر قالب آن آشکارتر شود:
<!DOCTYPE html>

<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>@ViewBag.Title</title>
    <style>
        #mainDiv {
            padding: 20px;
            border: solid medium black;
            font-size: 20pt
        }
    </style>
</head>
<body>
    <h1>Product Information</h1>
    <div id="mainDiv">
        @RenderBody()
    </div>
</body>
</html>
در اینجا یک عنصر عنوان و همچنین بعضی از CSS‌ها را به عنصر div که حاوی عبارت RenderBody@ است، اضافه کرده‌ام؛ فقط برای اینکه مشخص شود، چه محتوایی از طرحبندی سایت می‌آید و چه چیزی از View.
 

اعمال Layout

برای اعمال کردن Layout به یک View، نیاز است مشخصه Layout آن‌را مقدار دهی و سپس Htmlهای اضافی موجود در آن‌را مانند المنت‌های head و Body حذف کنید؛ همانند کدهای زیر:
@model Product
@{
    Layout = "_BasicLayout";
    ViewBag.Title = "Product";
}
در خاصیت Layout، مقدار را برابر نام فایل Layout، بدون پسوند cshtml آن قرار میدهیم. Razor در مسیر پوشه Views/shared و پوشه Views/Home فایل Layout را جستجو می‌کند.
در اینجا عبارت ViewBag.Title را نیز مقدار دهی می‌کنیم. زمانیکه فایل فراخوانی میشود، عنوان آن صفحه با این مقدار، جایگزین خواهد شد.
تغییرات این View بسیار چشمگیر است؛ حتی برای چنین برنامه ساده‌ای. طرحبندی شامل تمام ساختار مورد نیاز برای هر پاسخ HTML است که View را به صورت یک محتوای پویا ارائه می‌دهد و داده‌ها را به کاربر منتقل می‌کند. هنگامیکه MVC فایل Index.cshtmal را پردازش می‌کند، این طرحبندی برای ایجاد پاسخ HTML نهایی یکپارچه می‌شود؛ مانند عکس زیر:
 


 
View Start

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

برای ایجاد یک فایل شروع مشاهده، روی پوشه‌ی Views کلیک راست کرده و گزینه add->New Items را انتخاب می‌کنیم و از پنجره باز شده گزینه ( Razor View Start ) Mvc View Start Page را انتخاب می‌کنیم؛ مانند تصویر زیر:


ویژوال استودیو به صورت پیش فرض نام ViewStart.cshtml_ را به عنوان نام آن قرار میدهد؛ شما گزینه‌ی Create را در این حالت انتخاب کنید. محتویات فایل ایجاد شده به صورت زیر می‌باشد:
@{
    Layout = "_Layout";
}
برای اعمال Layout جدید به تمام Viewها، مقدار Layout را معادل طرحبندی خود تغییر میدهیم؛ مانند کد زیر: 
@{
    Layout = "_BasicLayout";
}
از آنجا که فایل View Start دارای مقداری برای Layout می‌باشد، می‌توانیم عبارت‌های مربوطه را در Index.cshtml‌ها حذف کنیم:
@model Product
@{
    ViewBag.Title = "Product";
}
در اینجا لازم نیست مشخص کنیم که من می‌خواهم از فایل View Start استفاده کنم. MVC این فایل را پیدا خواهد کرد و از محتویات آن به طور خودکار استفاده می‌کند. البته باید دقت داشت که مقادیر تعریف شده‌ی در فایل View اولویت دارند و باعث میشوند با معادل‌های فایل View Start جایگزین شوند.

شما همچنین می‌توانید چندین فایل View Start را برای تنظیم مقادیر پیش فرض قسمت‌های مختلف برنامه، استفاده کنید. یک فایل Razor همواره توسط نزدیک‌ترین فایل View start، پردازش می‌شود. به این معنا که شما می‌توانید تنظیمات پیش فرض را با افزودن یک فایل View Start به پوشه Views / Home و یا Views / Shared لغو کنید.

نکته: درک تفاوت میان حذف محتویات فایل View Start یا مساوی Null قرار دادن آن مهم است. اگر View شما مستقل است و شما نمی‌خواهید از آن استفاده کنید، بنابراین مقدار Layout آن‌را صریحا برابر Null قرار دهید. اگر مقدار دهی صریح شما مشخصه Layout را نادیده بگیرید، Mvc فرض می‌کند که میخواهید layout را داشته باشید و مقدار آن را از فایل View Start تامین می‌کند.
 

استفاده از عبارت‌های شرطی در Razor
 
حالا که من اصول و مبانی View و Layout را به شما نشان دادم، قصد دارم به انواع مختلفی از اصطلاحات که Razor آن‌ها را پشتیبانی می‌کند و نحوه استفاده‌ی از آنها را برای ایجاد محتوای نمایشی، ارائه دهم. در یک برنامه MVC، بین نقش‌هایی که توسط View و Action متدها انجام می‌شود، جدایی روشنی وجود دارد. در اینجا قوانین ساده‌ای وجود دارند که در جدول زیر مشخص شده‌اند:

کامپوننت 
انجام میشود 
انجام نمیشود 
  Action Method    یک شیء ViewModel را به View ارسال می‌کند.
  یک فرمت داده را به View ارسال می‌کند.
  View    از شیء ViewModel برای ارائه محتوا به کاربر استفاده می‌کند.
  هر جنبه‌ای از شیء View Model مشخصات را تغییر می‌دهد.
 
برای به دست آوردن بهترین نتیجه از MVC، نیاز به تفکیک و جداسازی بین قسمت‌های مختلف برنامه را دارید. همانطور که می‌بینید، می‌توانید کاملا با Razor کار کنید و این نوع فایل‌ها شامل دستورالعمل‌های سی شارپ نیز هستند. اما شما نباید از Razor برای انجام منطق کسب و کار استفاده کنید و یا هر گونه اشیاء Domain Model خود را دستکاری کنید. کد زیر نشان میدهد که یک عبارت جدید به View اضافه میشود:
*@
@model Product
@{

    ViewBag.Title = "Product";
}
<p>Product Name: @Model.Name</p> <p>Product Price: @($"{Model.Price:C2}")</p>
می‌توان برای خصوصیت price، در اکشن متد فرمتی را تعریف و بعد آن را به View ارسال کنیم. این روش کار می‌کند، اما استفاده از این رویکرد منافع الگوی MVC را تضعیف می‌کند و توانایی من برای پاسخ دادن به تغییرات در آینده را کاهش می‌دهد. باید به یاد داشته باشید که در ASP NET Core MVC، استفاده مناسب از الگوی MVC اجتناب ناپذیر است و شما باید از تاثیر تصمیمات طراحی و کدگذاری که انجام می‌دهید مطلع باشید.
 

پردازش داده‌ها در مقابل فرمت

تفاوت بین پردازش داده و قالب بندی داده مهم است.
- نمایش فرمت داده‌ها: به همین دلیل در آموزش قبل من یک نمونه از شیء کلاس Product را برای View ارسال کرده‌ام و نه فرمت خاص یک شیء را به صورت یک رشته نمایشی.
- پردازش داده: انتخاب اشیاء داده‌‌ای برای نمایش، مسئولیت کنترلر است و در این حالت مدلی را برای دریافت و تغییر داده مورد نیاز، فراخوانی می‌کند.
گاهی سخت است که متوجه شویم کدی جهت پردازش داده است و یا فرمت آن.


اضافه نمودن مقدار داده ای

ساده‌ترین کاری را که می‌توانید با یک عبارت Razor انجام دهید این است که یک مقدار داده را در نمایش دهید. رایج‌ترین کار برای انجام آن، استفاده از عبارت Model@ است. ویوو Index یک مثال از این مورد است؛ شبیه به این مورد:
 <p>Product Name: @Model.Name</p>
شما همچنین می‌توانید یک مقدار را با استفاده قابلیت ViewBag نیز به View ارسال نمایید که از این قابلیت در Layout برای تنظیم کردن محتوای عنوان استفاده کردیم. اما در حالت زیر یک مدل نوع دار را به سمت View ارسال کرده‌ایم:
using Microsoft.AspNetCore.Mvc;
using Razor.Models;


namespace Razor.Controllers
{
    public class HomeController : Controller
    {
        // GET: /<controller>/
        public ViewResult Index()
        {
            Product myProduct = new Product
            {
                ProductID = 1,
                Name = "Kayak",
                Description = "A boat for one person",
                Category = "Watersports",
                Price = 275M
            };
            return View(myProduct);
        }
    }
}

خصوصیت ViewBag یک شیء پویا را باز می‌گرداند که می‌تواند برای تعیین خواص دلخواهی مورد استفاده قرار گیرد. از آنجا که ویژگی ViewBag پویا است، لازم نیست که نام خصوصیات را پیش از آن اعلام کنم. اما این بدان معنا است که ویژوال استودیو قادر به ارائه پیشنهادهای تکمیل کننده برای ViewBag نیست.
در مثال زیر از یک مدل نوع دار و مزایای به همراه آن استفاده شده‌است: 
 <p>Product Name: @Model.Name</p> <p>Product Price: @($"{Model.Price:C2}")</p> <p>Stock Level: @ViewBag.StockLevel</p>
نتیجه آن‌را در زیر می‌توانید مشاهده کنید:



تنظیم مقادیر مشخص

شما همچنین می‌توانید از عبارات Razor برای تعیین مقدار عناصر، استفاده کنید:
@model Product
@{

    ViewBag.Title = "Product";
}
p>Product Name: @Model.Name</p> <p>Product Price: @($"{Model.Price:C2}")</p> 
<p>Stock Level: @ViewBag.StockLevel</p>
<div data-productid="@Model.ProductID" data-stocklevel="@ViewBag.StockLevel">    
<p>Product Name: @Model.Name</p>    
<p>Product Price: @($"{Model.Price:C2}")</p>   
 <p>Stock Level: @ViewBag.StockLevel</p> 
</div>
در اینجا از عبارات Razor، برای تعیین مقدار برای برخی از ویژگی‌های داده در عنصر div استفاده کرده‌ام.

نکته: ویژگی‌های داده‌ها که نام آنها *-data است، روشی برای ایجاد ویژگی‌های سفارشی برای سال‌ها بوده است و بعنوان بخشی از استاندارد HTML5 است. عموما کدهای جاوا اسکریپت از آن‌ها برای یافتن اطلاعات استفاده می‌کنند.

اگر برنامه را اجرا کنید و به منبع HTML که به مرورگر فرستاده شده نگاهی بیندازید، خواهید دید که Razor مقادیر صفات را تعیین کرده است؛ مانند این:
<div data-productid="1" data-stocklevel="2">    <p>Product Name: Kayak</p>    <p>Product Price: £275.00</p>    <p>Stock Level: 2</p> </div>


استفاده از عبارت‌های شرطی

Razor قادر به پردازش عبارات شرطی است. در ادامه کدهای Index View را که در آن دستورات شرطی اضافه شده‌اند می‌بینید:

@model Product
@{ ViewBag.Title = "Product Name"; }
<div data-productid="@Model.ProductID" data-stocklevel="@ViewBag.StockLevel">  
  <p>Product Name: @Model.Name</p>   
 <p>Product Price: @($"{Model.Price:C2}")</p> 
   <p>Stock Level:       
 @switch (ViewBag.StockLevel)
{
    case 0:@:Out of Stock                break;           
    case 1:          
    case 2:        
    case 3:            
    <b>Low Stock (@ViewBag.StockLevel)</b>         
       break;      
    default:            
    @: @ViewBag.StockLevel in Stock          
      break;      
  }    
</p>
</div>


برای شروع یک عبارت شرطی، یک علامت @ را در مقابل کلمه کلیدی if یا swicth سی شارپ قرار دهید. سپس بخش کد را داخل } قرار می‌دهیم. درون قطعه کد Razor، می‌توانید عناصر HTML و مقادیر داده را در خروجی نمایش دهید؛ مانند:
 <b>Low Stock (@ViewBag.StockLevel)</b>
در اینجا لازم نیست عناصر یا عبارات را در نقل قول قرار دهیم و یا آنها را به روش خاصی تعریف کنیم. موتور Razor این را به عنوان خروجی برای پردازش تفسیر خواهد کرد.
با این حال، اگر می‌خواهید متن واقعی را در نظر بگیرید و دستورات Razor را لغو کنید،‌می‌توانید از :@ استفاده کنید تا عین آن عبارت درج شود.
مطالب
غنی سازی کامپایلر C# 9.0 با افزونه‌ها
از زمانیکه کامپایلر #C، تحت عنوان Roslyn بازنویسی شد، قابلیت افزونه‌پذیری نیز پیدا کرد. برای مثال می‌توان آنالیز کننده‌ای را طراحی کرد که در پروسه‌ی کامپایل متداول کدهای  #C مورد استفاده قرار گرفته و خطاها و یا اخطارهایی را صادر کند که جزئی از پیام‌های استاندارد کامپایلر #C نیستند. در این مطلب نحوه‌ی معرفی آن‌ها را به پروژه‌های جدید NET 5.0.، بررسی می‌کنیم.


معرفی تعدادی آنالیز کننده‌ی کد که به عنوان افزونه‌ی کامپایلر #C قابل استفاده هستند

Microsoft.CodeAnalysis.NetAnalyzers
این افزونه جزئی از SDK دات نت 5 است و نیازی به نصب مجزا را ندارد. البته اگر می‌خواهید نگارش‌های جدیدتر آن‌را پیش از یکی شدن با SDKهای بعدی مورد آزمایش قرار دهید، می‌توان آن‌را به صورت صریحی نیز به کامپایلر معرفی کرد. این افزونه‌ی جایگزین FxCop است و پس از ارائه‌ی آن، FxCop را منسوخ شده اعلام کردند.

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

Microsoft.VisualStudio.Threading.Analyzers
این افزونه نکاتی را در مورد مشکلات Threading موجود در کدها، گوشزد می‌کند.

Microsoft.CodeAnalysis.BannedApiAnalyzers
با استفاده از این افزونه می‌توان استفاده‌ی از یکسری کدها را ممنوع کرد. برای مثال استفاده‌ی از System.DateTimeOffset.DateTime، در سراسر کدها ممنوع شده و استفاده‌ی از System.DateTimeOffset.UtcDateTime پیشنهاد شود.

AsyncFixer و Asyncify
این دو افزونه، مشکلات متداول در حین کار با کدهای async را گوشزد می‌کنند.

ClrHeapAllocationAnalyzer
این افزونه مکان‌هایی از کد را مشخص می‌کنند که در آن‌ها تخصیص حافظه صورت گرفته‌است. کاهش این مکان‌ها می‌تواند به بالا رفتن کارآیی برنامه کمک کنند.

SonarAnalyzer.CSharp
مجموعه‌ی معروف Sonar، که تعداد قابل ملاحظه‌ای بررسی کننده‌ی کد را به پروژه‌ی شما اضافه می‌کنند.


روش معرفی سراسری افزونه‌های فوق به تمام پروژه‌های یک Solution

می‌توان تنظیمات زیر را به یک تک پروژه اعمال کرد که برای اینکار نیاز است فایل csproj آن‌را ویرایش نمود و یا می‌توان یک تک فایل ویژه را به نام Directory.Build.props ایجاد کرد و آن‌را به صورت زیر تکمیل نمود. محل قرارگیری این فایل، در ریشه‌ی Solution و در کنار فایل sln می‌باشد.
<Project>
  <PropertyGroup>
    <AnalysisLevel>latest</AnalysisLevel>
    <AnalysisMode>AllEnabledByDefault</AnalysisMode>
    <CodeAnalysisTreatWarningsAsErrors>true</CodeAnalysisTreatWarningsAsErrors>
    <EnableNETAnalyzers>true</EnableNETAnalyzers>
    <EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
    <Nullable>enable</Nullable>
    <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
    <RunAnalyzersDuringBuild>true</RunAnalyzersDuringBuild>
    <RunAnalyzersDuringLiveAnalysis>true</RunAnalyzersDuringLiveAnalysis>
    <!--
      CA2007: Consider calling ConfigureAwait on the awaited task
      MA0004: Use Task.ConfigureAwait(false) as the current SynchronizationContext is not needed
      CA1056: Change the type of property 'Url' from 'string' to 'System.Uri'
      CA1054: Change the type of parameter of the method to allow a Uri to be passed as a 'System.Uri' object
      CA1055: Change the return type of method from 'string' to 'System.Uri'
    -->
    <NoWarn>$(NoWarn);CA2007;CA1056;CA1054;CA1055;MA0004</NoWarn>
    <NoError>$(NoError);CA2007;CA1056;CA1054;CA1055;MA0004</NoError>
    <Deterministic>true</Deterministic>
    <Features>strict</Features>
    <ReportAnalyzer>true</ReportAnalyzer>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.CodeAnalysis.NetAnalyzers" Version="5.0.1">
      <PrivateAssets>all</PrivateAssets>
      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
    </PackageReference>

    <PackageReference Include="Meziantou.Analyzer" Version="1.0.639">
      <PrivateAssets>all</PrivateAssets>
      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
    </PackageReference>

    <PackageReference Include="Microsoft.VisualStudio.Threading.Analyzers" Version="16.8.55">
      <PrivateAssets>all</PrivateAssets>
      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
    </PackageReference>

    <PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="3.3.2">
      <PrivateAssets>all</PrivateAssets>
      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
    </PackageReference>

    <PackageReference Include="AsyncFixer" Version="1.3.0">
      <PrivateAssets>all</PrivateAssets>
      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
    </PackageReference>

    <PackageReference Include="Asyncify" Version="0.9.7">
      <PrivateAssets>all</PrivateAssets>
      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
    </PackageReference>

    <PackageReference Include="ClrHeapAllocationAnalyzer" Version="3.0.0">
      <PrivateAssets>all</PrivateAssets>
      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
    </PackageReference>

    <PackageReference Include="SonarAnalyzer.CSharp" Version="8.16.0.25740">
      <PrivateAssets>all</PrivateAssets>
      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
    </PackageReference>
  </ItemGroup>

  <ItemGroup>
    <AdditionalFiles Include="$(MSBuildThisFileDirectory)BannedSymbols.txt" Link="Properties/BannedSymbols.txt" />
  </ItemGroup>
</Project>
توضیحات:
- در تنظیمات فوق، مواردی مانند AnalysisLevel، در مطلب «کامپایلر C# 9.0، خطاها و اخطارهای بیشتری را نمایش می‌دهد» پیشتر بررسی شده‌اند.
- در اینجا Nullable به true تنظیم شده‌است. اگر قرار است یک پروژه‌ی جدید را شروع کنید، بهتر است این ویژگی را نیز فعال کنید. بسیاری از API‌های دات نت 5 جهت مشخص سازی خروجی نال و یا غیرنال آن‌ها، بازنویسی و تکمیل شده‌اند و بدون استفاده از این ویژگی، بسیاری از راهنمایی‌های ارزنده‌ی دات نت 5 را از دست خواهید داد. اساسا بدون فعالسازی این ویژگی، از قابلیت‌های #C مدرن استفاده نمی‌کنید.
- وجود این PackageReference ها، به معنای بالا رفتن حجم نهایی قابل ارائه‌ی پروژه نیست؛ چون به صورت PrivateAssets و analyzers تعریف شده‌اند و فقط در حین پروسه‌ی کامپایل، جهت ارائه‌ی راهنمایی‌های بیشتر، تاثیرگذار خواهند بود.
- این تنظیمات طوری چیده شده‌اند که تا حد ممکن «درد آور» باشند! برای اینکار CodeAnalysisTreatWarningsAsErrors و TreatWarningsAsErrors به true تظیم شده‌اند تا حتی اخطارها نیز به صورت خطای کامپایلر گزارش شوند؛ تا مجبور به رفع آن‌ها شویم.
- در اینجا فایل BannedSymbols.txt را نیز مشاهده می‌کنید که مرتبط است به BannedApiAnalyzers. می‌توان در کنار فایل Directory.Build.props، فایل جدید BannedSymbols.txt را با این محتوا ایجاد کرد:
# https://github.com/dotnet/roslyn-analyzers/blob/master/src/Microsoft.CodeAnalysis.BannedApiAnalyzers/BannedApiAnalyzers.Help.md
P:System.DateTime.Now;Use System.DateTime.UtcNow instead
P:System.DateTimeOffset.Now;Use System.DateTimeOffset.UtcNow instead
P:System.DateTimeOffset.DateTime;Use System.DateTimeOffset.UtcDateTime instead
در این حالت برای مثال، از استفاده‌ی از DateTime.Now منع شده و وادار به استفاده‌ی از DateTime.UtcNow می‌شوید.


روش کاهش تعداد خطاهای نمایش داده شده

اگر از فایل Directory.Build.props فوق استفاده کرده و یکبار دستور dotnet restore را جهت بازیابی وابستگی‌های آن اجرا کنید، با تعداد خطاهایی که در IDE خود مشاهده خواهید کرد، شگفت‌زده خواهید شد! به همین جهت برای کنترل آن‌ها می‌توان فایل جدید editorconfig. را به نحو زیر در کنار فایل Directory.Build.props ایجاد و تکمیل کرد:
[*.cs]

# MA0026 : Complete the task
dotnet_diagnostic.MA0026.severity = suggestion

# CA1308: In method 'urlToLower', replace the call to 'ToLowerInvariant' with 'ToUpperInvariant' (CA1308)
dotnet_diagnostic.CA1308.severity = suggestion

# CA1040: Avoid empty interfaces
dotnet_diagnostic.CA1040.severity = suggestion

# CA1829 Use the "Count" property instead of Enumerable.Count()
dotnet_diagnostic.CA1829.severity = suggestion

# Use 'Count' property here instead.
dotnet_diagnostic.S2971.severity = suggestion

# S1135 : Complete the task
dotnet_diagnostic.S1135.severity = suggestion

# S2479: Replace the control character at position 7 by its escape sequence
dotnet_diagnostic.S2479.severity = suggestion

# CA2007: Consider calling ConfigureAwait on the awaited task
dotnet_diagnostic.CA2007.severity = none

# MA0004: Use Task.ConfigureAwait(false) as the current SynchronizationContext is not needed
dotnet_diagnostic.MA0004.severity = none

# CA1056: Change the type of property 'Url' from 'string' to 'System.Uri'
dotnet_diagnostic.CA1056.severity = suggestion

# CA1054: Change the type of parameter of the method to allow a Uri to be passed as a 'System.Uri' object
dotnet_diagnostic.CA1054.severity = suggestion

# CA1055: Change the return type of method from 'string' to 'System.Uri'
dotnet_diagnostic.CA1055.severity = suggestion

# S4457: Split this method into two, one handling parameters check and the other handling the asynchronous code.
dotnet_diagnostic.S4457.severity = none

# AsyncFixer01: Unnecessary async/await usage
dotnet_diagnostic.AsyncFixer01.severity = suggestion

# AsyncFixer02: Long-running or blocking operations inside an async method
dotnet_diagnostic.AsyncFixer02.severity = error

# VSTHRD103: Call async methods when in an async method
dotnet_diagnostic.VSTHRD103.severity = error

# AsyncFixer03: Fire & forget async void methods
dotnet_diagnostic.AsyncFixer03.severity = error

# VSTHRD100: Avoid async void methods
dotnet_diagnostic.VSTHRD100.severity = error

# VSTHRD101: Avoid unsupported async delegates
dotnet_diagnostic.VSTHRD101.severity = error

# VSTHRD107: Await Task within using expression
dotnet_diagnostic.VSTHRD107.severity = error

# AsyncFixer04: Fire & forget async call inside a using block
dotnet_diagnostic.AsyncFixer04.severity = error

# VSTHRD110: Observe result of async calls
dotnet_diagnostic.VSTHRD110.severity = error

# VSTHRD002: Avoid problematic synchronous waits
dotnet_diagnostic.VSTHRD002.severity = suggestion

# MA0045: Do not use blocking call (make method async)
dotnet_diagnostic.MA0045.severity = suggestion

# AsyncifyInvocation: Use Task Async
dotnet_diagnostic.AsyncifyInvocation.severity = error

# AsyncifyVariable: Use Task Async
dotnet_diagnostic.AsyncifyVariable.severity = error

# VSTHRD111: Use ConfigureAwait(bool)
dotnet_diagnostic.VSTHRD111.severity = none

# MA0022: Return Task.FromResult instead of returning null
dotnet_diagnostic.MA0022.severity = error

# VSTHRD114: Avoid returning a null Task
dotnet_diagnostic.VSTHRD114.severity = error

# VSTHRD200: Use "Async" suffix for async methods
dotnet_diagnostic.VSTHRD200.severity = suggestion

# MA0040: Specify a cancellation token
dotnet_diagnostic.MA0032.severity = suggestion

# MA0040: Flow the cancellation token when available
dotnet_diagnostic.MA0040.severity = suggestion

# MA0079: Use a cancellation token using .WithCancellation()
dotnet_diagnostic.MA0079.severity = suggestion

# MA0080: Use a cancellation token using .WithCancellation()
dotnet_diagnostic.MA0080.severity = error

#AsyncFixer05: Downcasting from a nested task to an outer task.
dotnet_diagnostic.AsyncFixer05.severity = error

# ClrHeapAllocationAnalyzer ----------------------------------------------------
# HAA0301: Closure Allocation Source
dotnet_diagnostic.HAA0301.severity = suggestion

# HAA0601: Value type to reference type conversion causing boxing allocation
dotnet_diagnostic.HAA0601.severity = suggestion

# HAA0302: Display class allocation to capture closure
dotnet_diagnostic.HAA0302.severity = suggestion

# HAA0101: Array allocation for params parameter
dotnet_diagnostic.HAA0101.severity = suggestion

# HAA0603: Delegate allocation from a method group
dotnet_diagnostic.HAA0603.severity = suggestion

# HAA0602: Delegate on struct instance caused a boxing allocation
dotnet_diagnostic.HAA0602.severity = suggestion

# HAA0401: Possible allocation of reference type enumerator
dotnet_diagnostic.HAA0401.severity = silent

# HAA0303: Lambda or anonymous method in a generic method allocates a delegate instance
dotnet_diagnostic.HAA0303.severity = silent

# HAA0102: Non-overridden virtual method call on value type
dotnet_diagnostic.HAA0102.severity = silent

# HAA0502: Explicit new reference type allocation
dotnet_diagnostic.HAA0502.severity = none

# HAA0505: Initializer reference type allocation
dotnet_diagnostic.HAA0505.severity = silent
روش کار هم به صورت است که برای مثال در IDE خود (حتی با VSCode هم کار می‌کند)، خطای کامپایلر مثلا CA1308 را مشاهده می‌کنید که عنوان کرده‌است بجای ToLowerInvariant از ToUpperInvariant استفاده کنید. اگر با این پیشنهاد موافق نیستید (عین خطا را به صورت C# CA1308 در گوگل جستجو کنید؛ توضیحات مایکروسافت را در مورد آن خواهید یافت)، یک سطر شروع شده‌ی با dotnet_diagnostic و سپس ID خطا را به صورت زیر، به فایل editorconfig. یاد شده، اضافه کنید:
dotnet_diagnostic.CA1308.severity = suggestion
به این ترتیب هنوز هم این مورد را به صورت یک پیشنهاد مشاهده خواهید کرد، اما دیگر جزو خطاهای کامپایلر گزارش نمی‌شود. اگر خواستید که به طور کامل ندید گرفته شود، مقدار آن‌را بجای suggestion به none تغییر دهید.

یک نکته: در ویندوز نمی‌توانید یک فایل تنها پسوند دار را به صورت معمولی در windows explorer ایجاد کنید. نام این فایل را به صورت .editorconfig. با دو نقطه‌ی ابتدایی و انتهایی وارد کنید. خود ویندوز نقطه‌ی پایانی را حذف می‌کند.


روش صرفنظر کردن از یک خطا، تنها در یک قسمت از کد

فرض کنید نمی‌خواهید خطای CA1052 را تبدیل به یک suggestion سراسری کنید و فقط می‌خواهید که در قطعه‌ی خاصی از کدهای خود، آن‌را خاموش کنید. به همین جهت بجای اضافه کردن آن به فایل editorconfig.، باید از ویژگی SuppressMessage به صورت زیر استفاده نمائید:
[SuppressMessage("Microsoft.Usage", "CA1052:Type 'Program' is a static holder type but is neither static nor NotInheritable",
  Justification = "We need it for our integration tests this way.")]
[SuppressMessage("Microsoft.Usage", "RCS1102:Type 'Program' is a static holder type but is neither static nor NotInheritable",
  Justification = "We need it for our integration tests this way.")]
[SuppressMessage("Microsoft.Usage", "S1118:Type 'Program' is a static holder type but is neither static nor NotInheritable",
  Justification = "We need it for our integration tests this way.")]
public class Program { }
در اینجا پارامتر اول با Microsoft.Usage مقدار دهی می‌شود. پارامتر دوم آن باید حاوی ID خطا باشد. در صورت تمایل می‌توانید دلیل خاموش کردن این خطا را در قسمت Justification وارد کنید.