مطالب
مبانی TypeScript؛ متغیرها و نوع‌ها
روش‌های مختلف تعریف متغیرها در TypeScript

تمام توسعه دهنده‌های JavaScript با واژه‌ی کلیدی var آشنایی دارند؛ اما TypeScript واژه‌های کلیدی let و const را نیز اضافه کرده‌است (که جزئی از ES 6 نیز می‌باشند). تفاوت مهم بین var و let، در میدان دید متغیرهای تعریف شده‌ی توسط آن‌ها خلاصه می‌شود. پیشتر در سری مباحث بررسی ES 6، مطلب «متغیرها در ES 6» را نیز بررسی کردیم که در TypeScript نیز صادق می‌باشند؛ با این تفاوت که TypeScript را می‌توان به ES 5 نیز کامپایل کرد و بدون مشکل با تمام مرورگرهای موجود، اجرا نمود.
- متغیرهایی که با var تعریف می‌شوند، به صورت سراسری در متدی که تعریف شده‌اند، قابل دسترسی هستند؛ حتی اگر 5 سطح داخل ifهای تو در تو تعریف شده باشند. اما let و const تنها در block و قطعه‌ای که معرفی شده‌اند، معتبر بوده و خارج از آن تعریف نشده‌اند. در اینجا یک block توسط {} معرفی می‌شود.
- متغیرهای از نوع var به دلیل مفهومی به نام hoisting توسط runtime جاوا اسکریپت، به بالاترین سطح متد منتقل می‌شوند. به همین دلیل عمق تعریف آن‌ها، اثری در دسترسی به این متغیرها ندارد. اما hoisting در مورد let و const اعمال نمی‌شود.
- متغیرهای var را می‌توان چندبار مجددا تعریف کرد (هرچند این روش توصیه نمی‌شود؛ اما مجاز است). یک چنین تعریف مجددی با متغیرهای از نوع let و const مجاز نیست.
برای توضیحات بیشتر به مثال ذیل دقت کنید:
function ScopeTest() {
    if (true) {
        var foo = 'use anywhere';
        let bar = 'use in this block';
    }
    console.log(foo); // works!
    console.log(bar); // error!
}
در اینجا داخل قطعه‌ی if تعریف شده، یک متغیر، از نوع var و متغیر دیگری از نوع let تعریف شده‌است. خارج از بدنه‌ی این متد، متغیر foo هنوز قابل دسترسی است، اما متغیر bar خیر.


نوع‌های پایه‌ی TypeScript

نوع‌های پایه‌ی TypeScript شامل موارد ذیل هستند:
Boolean: برای ذخیره سازی true یا false.
let isDone: boolean = false;
Number: معرف مقادیر عددی اعشاری هستند.
let decimal: number = 6;
let hex: number = 0xf00d;
let binary: number = 0b1010;
let octal: number = 0o744;
String: مقادیر رشته‌ای را ذخیره می‌کند.
let name: string = "bob";
name = 'smith';
Array: روش‌های متفاوتی برای تعریف آرایه‌ها در TypeScript وجود دارند که در ادامه آن‌ها را بیشتر بررسی خواهیم کرد.
let list: number[] = [1, 2, 3];
Enum: نوع‌های شمارشی؛ جهت دادن نام‌هایی بهتر به مجموعه‌ی مشخصی از مقادیر.
enum Color {Red, Green, Blue};
let c: Color = Color.Green;
Any: به این معنا که یک متغیر می‌تواند به هر نوع دلخواهی اشاره کند. هدف از وجود این نوع، امکان استفاده‌ی از کتابخانه‌های فعلی جاوا اسکریپتی است که نوعی برای متغیرهای آن‌ها تعریف نشده‌اند و در اساس هر نوعی می‌توانند باشند. برای مثال در جاوا اسکریپت مجاز است در سطر اول متغیری را تعریف کرد و در سطر دوم به آن یک رشته و در سطر سوم به آن یک عدد را انتساب داد.
let notSure: any = 4;
notSure = "maybe a string instead";
notSure = false; // okay, definitely a boolean
Void: به معنای فقدان یک نوع است. برای مثال مشخص کردن اینکه متدی، خروجی ندارد.
 function warnUser(): void {
        alert("This is my warning message");
}

یک نکته: قابلیت Template string در ES 6 نیز در TypeScript پشتیبانی می‌شود.



مفهوم Type Inference در TypeScript

در TypeScript الزاما نیازی نیست تا نوع متغیری را صریحا مشخص کرد. در اینجا اگر نوع متغیری را در ابتدای کار تعریف نکنید، نوع آن در اولین باری که مقدار دهی می‌شود، مشخص خواهد شد که به آن Type Inference نیز می‌گویند.
let myString= 'this is a string';
myString= 42; // error!
در مثال فوق، نوع متغیر myString در زمان تعریف آن مشخص نشده‌است؛ اما با یک رشته، مقدار دهی و آغاز شده‌است. بر این اساس، TypeScript نوع این متغیر را رشته‌ای در نظر می‌گیرد و در سطر بعدی، از انتساب یک مقدار عددی به آن جلوگیری خواهد کرد.
و یا در مثال ذیل، نوع خروجی متد ReturnNumber به صورت صریح مشخص نشده‌است:
function ReturnNumber() {
   return 42;
}
let anotherString = 'this is also a string';
anotherString = ReturnNumber(); // error!
اما با توجه به اینکه خروجی آن عددی است، نوع آن نیز عددی درنظر گرفته شده و از انتساب آن به یک متغیر رشته‌ای جلوگیری می‌شود.


تعریف صریح نوع‌ها در TypeScript با استفاده از Type Annotations

برای لغو Type Inference و تعیین صریح نوع‌ها در TypeScript می‌توان به صورت زیر عمل کرد:
let myString : string = 'this is a string';
myString = 42; // error!

function ReturnNumber() : number {
   return 42;
}
let anotherString : string = 'this is also a string';
anotherString = ReturnNumber(); // error!
با قراردادن یک کولن پس از نام متغیر و سپس تعریف نوع داده‌ی مدنظر، می‌توان نوع‌ها را در TypeScript به صورت صریحی تعریف کرد. در مورد متدها نیز به همین صورت است. کولن پس از پایان بسته شدن پرانترها قرار می‌گیرد و سپس نوع بازگشتی متد مشخص می‌گردد.


نوع‌های شمارشی در TypeScript

Enums در بسیاری از زبان‌های برنامه نویسی متداول وجود دارند و هدف از آن‌ها، دادن نام‌هایی بهتر و مشخص، به مجموعه‌ای از مقادیر است:
 enum Category { Biography, Poetry, Fiction }; // 0, 1, 2
در مثال فوق یک enum جهت تعریف گروه‌های کتاب‌ها تعریف شده‌است. در اینجا بجای استفاده از مقادیر نامفهوم 0 تا 2، نام‌هایی مشخص و بهتر، جهت معرفی آن‌ها ارائه شده‌اند. به این ترتیب می‌توان در نهایت به کدهایی خواناتر رسید.
اگر می‌خواهید این مقادیر با اعداد دیگری شروع شوند (بجای صفر پیش فرض)، می‌توان مقدار اولین نام را به صورت صریحی مشخص کرد:
 enum Category { Biography = 1, Poetry, Fiction }; // 1, 2, 3
و یا الزامی به استفاده از مقادیر افزایش یابنده‌ای در اینجا نیست. در صورت نیاز می‌توان مقدار هر المان را جداگانه تعریف کرد:
 enum Category{ Biography = 5, Poetry = 8, Fiction = 9 }; // 5, 8, 9
پس از اینکه نوع enum تعریف شده، استفاده از آن همانند سایر نوع‌های پایه‌ی TypeScript است:
 let favoriteCategory: Category = Category.Biography;
در اینجا متغیر favoriteCategory از نوع enum گروه کتاب‌ها تعیین شده‌است؛ با مقدار اولیه‌ی Category.Biography.
در این حالت اگر مقدار favoriteCategory را چاپ کنیم، خروجی عددی 5 نمایش داده می‌شود:
 console.log(favoriteCategory); // 5
اگر نیاز به بازگشت مقدار رشته‌ای این آیتم است، می‌توان از ایندکس‌های یک enum استفاده کرد:
let categoryString= Category[favoriteCategory]; // Biography


آرایه‌ها در TypeScript

در حالت عمومی، آرایه‌ها در TypeScript همانند جاوا اسکریپت تعریف می‌شوند؛ البته به همراه تعدادی استثناء. در TypeScript سه روش برای تعریف آرایه‌ها وجود دارند:
الف) در مثال زیر آرایه‌ای از رشته‌ها تعریف شده‌است. در اینجا نوع آرایه به همراه [] مشخص می‌شود:
let strArray1: string[] = ['here', 'are', 'strings'];
ب) روش دوم تعریف آرایه‌ها در TypeScript با استفاده از مفهوم Generics است که در بحثی جداگانه به آن خواهیم پرداخت. در اینجا از واژه‌ی کلیدی Array به همراه مشخص سازی نوع آن در داخل <> استفاده شده‌است:
let strArray2: Array<string> = ['more', 'strings', 'here'];
ج) اگر نیاز به آرایه‌هایی شبیه به جاوا اسکریپت دارید که می‌توانند حاوی انواع و اقسام المان‌هایی با نوع‌های مختلف باشند، نوع آرایه را []any تعریف کنید:
let anyArray: any[] = [42, true, 'banana'];


نوع Tuples در TypeScript

Tuples در TypeScript نوع خاصی از آرایه‌ها هستند که نوع مقادیر اعضای آن‌ها به صورت صریح مشخص می‌شوند:
 let myTuple: [number, string] = [25, 'truck'];
برای مثال در اینجا نوع متغیر myTuple به صورت آرایه‌ای از دو عنصر عددی و رشته‌ای تعریف شده‌است.
اکنون برای دسترسی به مقادیر این المان‌ها، همانند کار با آرایه‌های معمولی، از ایندکس‌های آرایه‌ی تعریف شده استفاده می‌شود:
 let firstElement= myTuple[0]; // 25
let secondElement= myTuple[1]; // truck

یک نکته: می‌توان به آرایه‌ی تعریف شده، عناصر جدیدی را نیز افزود؛ با این شرط که نوع آن‌ها، یکی از نوع‌های مشخص شده‌ی در تعریف Tuple باشند:
 // other elements can have numbers or strings
myTuple[2] = 100;
myTuple[2] = 'this works!';


مفهوم Type assertions در TypeScript

حتما با مفهوم cast و تبدیل نوع‌های مختلف به یکدیگر، در زبان‌های دیگر برنامه نویسی آشنا هستید. در TypeScript نیز این مفهوم تحت عنوان Type assertions پشتیبانی می‌شود و دو روش برای تعریف آن وجود دارد:
الف) تعریف cast توسط angle-bracket syntax که در آن نوع مدنظر داخل یک <> قرار می‌گیرد:
 let someValue: any = "this is a string";
let strLength: number = (<string>someValue).length;
در اینجا نوع نامشخص any به string تبدیل شده و سپس امکان دسترسی به خاصیت طول آن که تحت کنترل کامپایلر است و قابل انتساب به یک مقدار عددی، میسر شده‌است.
ب) تعریف cast توسط as syntax به نحو ذیل:
 let someValue: any = "this is a string";
let strLength: number = (someValue as string).length;
هر دو حالت تعریف شده معادل هستند. فقط باید دقت داشت در حین کار با ReactJS توسط TypeScript، فقط حالت as syntax پشتیبانی می‌شود.
نظرات مطالب
شروع به کار با AngularJS 2.0 و TypeScript - قسمت اول - نصب پیشنیازها
- نکات مطلب «مبانی TypeScript؛ تنظیمات TypeScript در ویژوال استودیو» را پیگیری کنید.
+ VS 2013 دیگر پشتیبانی جدیدی از TypeScript را نخواهد داشت و آخرین نگارش‌های AngularJS 2.0 از TypeScript 2.0 استفاده می‌کنند که فقط برای VS 2015 ارائه شده‌است.
آخرین نگارشی که برای VS 2013 ارائه شده 1.8.5 است و آخرین نگارشی که برای VS 2015 ارائه شده 2.0.6 است.
اشتراک‌ها
TypeScript و IIS
mime type آن تعریف نشده‌است:
<system.webServer>
   <staticContent>
       <remove fileExtension=".ts" />
       <mimeMap fileExtension=".ts" mimeType="text/x-typescript" />
   </staticContent>
<!-- Some other content -->
</system.webServer>
TypeScript و  IIS
اشتراک‌ها
TypeScript 3.4 منتشر شد

Let’s dive in and see what’s new in TypeScript 3.4!

- Faster subsequent builds with the --incremental flag
- Higher order type inference from generic functions
- Improvements for ReadonlyArray and readonly tuples
- const assertions
- Type-checking for globalThis
- Convert parameters to destructured object
- Breaking changes 

TypeScript 3.4 منتشر شد
مطالب
پیاده سازی پروژه‌های React با TypeScript - قسمت چهارم - تعیین نوع هوک‌های useState و useRef
پس از بررسی روش تعیین نوع‌های خواص props در قسمت‌های قبل، اکنون نوبت به بررسی روش تعیین نوع‌های انواع React Hooks است. در این قسمت دو هوک پرکاربرد useState و useRef را بررسی می‌کنیم.


روش تعیین نوع useState Hook

برای این منظور در ابتدا فایل جدید src\components\Input.tsx را ایجاد کرده و به صورت زیر تکمیل می‌کنیم:
import React, { useState } from "react";

export const Input = () => {
  const [name, setName] = useState("");
  return <input value={name} onChange={(e) => setName(e.target.value)} />;
};
همچنین تعریف المان آن‌را نیز به فایل src\App.tsx جهت نمایش </ Input> با ذکر "import { Input } from "./components/Input، انجام می‌دهیم.

پس از این تعاریف ... برنامه بدون مشکل کار می‌کند و کامپایل می‌شود. اکنون سؤال اینجا است که آیا تایپ‌اسکریپت در ایجا اصلا کاری را هم انجام می‌دهد؟ برای درک این موضوع، سطر useState را به صورت زیر تغییر می‌دهیم:
const [name, setName] = useState(0);
بلافاصله خطای زیر ظاهر می‌شود:

عنوان می‌کند که مقدار رشته‌ای e.target.value، به مقدار عددی name قابل انتساب نیست. به عبارتی TypeScript از قابلیت Type Inference خود در اینجا استفاده می‌کند. درست است که به ظاهر نوعی را برای useState و خروجی منتسب به آن تعیین نکرده‌ایم، اما بر اساس نوع مقدار پیش‌فرض آن، نوع name و setName به صورت خودکار مشخص می‌شوند و نیازی به ذکر صریح این نوع، نیست. برای مثال در حالت اول چون مقدار پیش‌فرض useState را یک رشته‌ی خالی معرفی کرده بودیم، نوع name نیز string درنظر گرفته شده بود. در حالت دوم بر اساس مقدار پیش‌فرض عددی useState، اینبار نوع name نیز یک number خواهد بود و دیگر نمی‌توان یک مقدار رشته‌ای را مانند e.target.value به آن انتساب داد. مزیت کار کردن با TypeScript در اینجا، مشاهده‌ی آنی خطای یک چنین استفاده‌ها و انتساب‌های نادرستی است.

مفهوم Type Inference را در تصاویر زیر بهتر می‌توان مشاهده کرد. اشاره‌گر ماوس را به تعریف useState نزدیک کنید. در توضیحاتی که ظاهر می‌شود، بر اساس نوع مقدار پیش‌فرض آن، نوع آرگومان جنریک متد useState نیز به صورت خودکار تغییر می‌کند:



و نکته‌ی مهم اینجا است که نیازی به ذکر صریح این نوع جنریک، مانند مثال زیر نیست:
const [name, setName] = useState<string>("");
و سطر فوق با سطر زیر که بیانگر Type Inference است، دقیقا یکی است:
const [name, setName] = useState("");

سؤال: اگر مقدار اولیه‌ی useState را null تعیین کردیم و یا اصلا تعریف نکردیم و undefined بود، چطور؟
در یک چنین حالتی که نوع دقیق state، از طریق مقدار اولیه‌ی آن قابل استنتاج نیست، نیاز هست همانند تصاویر فوق، تعریف جنریک useState را به نحو صریحی ذکر کرده و آن‌را با union types تکمیل کنیم:
const [name, setName] = useState<string | null>(null);
به این ترتیب عنوان کرده‌ایم که نوع name در اینجا می‌تواند رشته‌ای و یا نال باشد.


روش تعیین نوع useRef Hook

در ادامه می‌خواهیم نحوه‌ی تعیین نوع DOM Elements را در React بررسی کنیم. با استفاده از useRef می‌توان به ارجاعی از یک DOM Element دسترسی یافت.
import React, { useRef, useState } from "react";

export const Input = () => {
  const [name, setName] = useState("");
  const inputRef = useRef(null);

  if (inputRef && inputRef.current) {
    console.log("ref", inputRef.current.value);
  }
  return (
    <input
      ref={inputRef}
      value={name}
      onChange={(e) => setName(e.target.value)}
    />
  );
};
برای اینکار ابتدا useRef را با یک مقدار اولیه‌ی null، توسط ویژگی ref، به یک DOM Element خاص متصل می‌کنیم. تا اینجا برنامه بدون مشکل کار می‌کند؛ اما زمانیکه خواستیم برای مثال به inputRef.current.value دسترسی پیدا کنیم، دیگر تعریف ساده‌ی useRef(null) پاسخگو نبوده و خطای زیر گزارش می‌شود:


عنوان می‌کند نوعی که inputRef.current دارد، نال است و نال به همراه خاصیت value نیست. برای اینکه نوع inputRef را بهتر بتوانیم بررسی کنیم، دقیقا بر روی آن کلیک راست کرده و گزینه‌ی Go to Type Definition را انتخاب کنید. بلافاصله به تعریف زیر هدایت خواهیم شد:
interface MutableRefObject<T> {
   current: T;
}
inputRef، از نوع MutableRefObject جنریک است که تنها دارای یک خاصیت current است. نوع T آن هم در اینجا با توجه به مقدار اولیه‌ی آن، null درنظر گرفته شده‌است. به همین جهت هرچند می‌دانیم inputRef.current به المان input صفحه اشاره می‌کند، اما نمی‌توانیم به خواص و متدهای آن دسترسی پیدا کنیم.
برای رفع این مشکل فقط کافی است نوع المان مدنظر را صریحا به عنوان آرگومان جنریک useRef معرفی کنیم:
const inputRef = useRef<HTMLInputElement>(null);
نحوه‌ی تشخیص این نوع هم ساده‌است. فقط کافی است اشاره‌گر ماوس را بر روی المانی خاص قرار دهید. بلافاصله نوع آن مشخص می‌شود:



فعال بودن Strict Null Checking در پروژه‌های TypeScript ای React

نکات مطلب «نوع‌های نال نپذیر در TypeScript» به صورت پیش‌فرض در پروژه‌های تایپ اسکریپتی React هم فعال هستند؛ چون پرچم strict واقع در فایل tsconfig.json پروژه، به صورت پیش‌فرض به true تنظیم شده‌است و این پرچم، Strict Null Checking را نیز شامل می‌شود. برای آزمایش فعال بودن آن، کدهای فوق را به صورت زیر تغییر دهید تا صرفا if آن حذف شود:
//if (inputRef && inputRef.current) {
  console.log("ref", inputRef.current.value);
//}
بلافاصله خطای امکان نال بودن inputRef.current در اولین بار رندر کامپوننت جاری را دریافت خواهید کرد:

که برای رفع آن، همانند قبل باید ذکر if بررسی نال بودن inputRef و خاصیت current آن‌را اضافه کرد؛ تا دیگر در زمان اجرای برنامه، با شانس نال بودن یکی از این‌دو مواجه نشویم و به کیفیت بالاتری در برنامه‌ی خود برسیم.
روش بررسی if (inputRef && inputRef.current) معادل ساده‌تری را نیز در TypeScript 3.7 به بعد دارد که به Optional Chaining معروف است؛ به صورت زیر:
console.log("ref", inputRef?.current?.value);
در این حالت دیگر نیازی به ذکر if یاد شده نیست و وجود .? به معنای ادامه‌ی این زنجیره، در صورت نال نبودن قطعه‌ی قبلی است.
مطالب
آموزش TypeScript #2
در این پست قصد دارم به بررسی چند نکته که از پیش نیاز‌های کار با TypeScript  است بپردازم. همان طور که در پست قبلی مشاهده شد بعد از دانلود و نصب افزونه  TypeScript در VS.Net یک Template به نام Html Application With TypeScript به Installed Template اضافه خواهد شد. بعد از انتخاب این قسمت شما به راحتی می‌توانید در هر فایل با پسوند ts کد‌های مورد نظر به زبان TypeScript را نوشته و بعد از build پروژه این کد‌ها تبدیل به کد‌های JavaScript خواهند شد. بعد کافیست فایل مورد نظر را با استفاده از تک Script در فایل خود رفرنس دهید. دقت کنید که پسوند فایل حتما باید js باشد(به دلیل اینکه بعد از build پروژه فایل‌های ts تبدیل به js می‌شوند).
برای مثال:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <meta charset="utf-8" />
    <title>TypeScript HTML App</title>
    <link rel="stylesheet" href="app.css" type="text/css" />
    <script src="app.js"></script>
</head>
<body>
    <h1>Number Type in TypeScript</h1>
    <div id="content"/>
</body>
</html>

اما اگر یک پروژه وب نظیر Asp.Net MVC داشته باشیم و می‌خواهیم یک یا چند فایل که حاوی کد‌های typeScript هستند را به این پروژه اضافه کرده و از آن‌ها در صفخات وب خود استفاده کنیم باید به این صورت عمل نمود:
بعد از اضافه کردن فایل‌های مورد نیاز، پروژه مورد نظر را Unload کنید. بعد به صورت زیر فایل پروژه(csproj) رو با یک ویرایشگر متنی باز کنید:

در این مرحله باید دو قسمت اضافه شود. یک بخش  ItemGroup است که هر فایلی که در پروژه شما دارای پسوند ts است باید در این جا تعریف شود. در واقع این قسمت فایل هایی را که باید کامپایل شده تا در نهایت تبدیل به فایل‌های JavaScript شوند را مشخص می‌کند.
بخش دوم target است که مراحل Build پروژه را برای این فایل‌های مشخص شده تعیین می‌کند. برای مثال:

همان طور که می‌بینید در قسمت ItemGroup تمام فایل‌های با پسوند ts در پروژه include شده اند. در قسمت target دستور کامپایل این فایل‌ها تعیین شد. اما نکته مهم این است که TypeScript به صورت پیش فرض از ECMAScript 3 در هنگام کامپایل کد‌ها استفاده می‌کند.(ECMAScript 3 در سال 1999 منتشر شد و تقریبا با تمام مرورگرها سازگاری دارد اما از امکانات جدید در Javascript پشتیبانی نمی‌کند). اگر قصد دارید که از ECMAScript 5 در هنگام کامپایل کد‌ها استفاده نمایید کافیست دستور زیر را اضافه نمایید:
--target ES5
مثال:

اما به این نکته دقت داشته باشید که ECMAScript 5 در سال 2009 منتشر شده است در نتیجه فقط با مرورگرهای جدید سازگار خواهد بود و ممکن است کد‌های شما در مرورگرهای قدیمی با مشکل مواجه شود.
مرورگرهایی که از ECMAScript 5 پشتیبانی می‌کنند عبارتند از:
  • IE 9 و نسخه‌های بعد از آن؛
  • FireFox 4 و نسخه‌های بعد از آن؛ 
  • Opera 12 و نسخه‌های بعد از آن؛ 
  • Safari 5.1 و نسخه‌های بعد از آن؛ 
  • Chrome 7 و نسخه‌های بعد از آن.
ادامه دارد...
مطالب
مقایسه value type و reference type
در سی شارپ دو نوع class و struct وجود دارد که تقریباً مشابه یکدیگرند در حالیکه یکی از آنها-value type و دیگری reference-type است.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

try
{
   ...
}

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


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

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

Form myForm = new Form();

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

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

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

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

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

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

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

پس بنابراین کد بالا را به کد زیر اصلاح می‌کنیم:
myForm.ClientSize = new Size (myForm.ClientSize.Width, myForm.ClientSize.Height * 2);
برای اصلاح خطای کامپایلر، ما باید یک شیء جدیدی را برای اندازه فرم تخصیص بدهیم.
مطالب
معرفی کتابخانه‌ی OxyPlot
برای ترسیم نمودار در برنامه‌های WPF، چندین کتابخانه‌ی سورس باز مانند GraphIT ، Sparrow Toolkit ، Dynamic Data Display و ... OxyPlot وجود دارند. در بین این‌ها، کتابخانه‌ی OxyPlot دارای این مزایا است:
- دارای مجوز MIT است. (مجاز هستید از آن در هر نوع برنامه‌ای استفاده کنید)
- cross-platform است. به این معنا که دات نت، WinRT و Xamarin را به خوبی پشتیبانی می‌کند.
- WPF و همچنین WinForms تا Xamarin.Android را پوشش می‌دهد.
- بسته‌های اصلی NuGet آن تا به امروز نزدیک به 40 هزار بار دریافت شده‌اند.
- انجمن فعالی دارد.
- بسیار بسیار غنی است. تا حدی که مرور سطحی مجموعه مثال‌های آن شاید چند ساعت وقت را به خود اختصاص دهد.
- طراحی آن به نحوی است که با الگوی MVVM کاملا سازگاری دارد.
- به صورت متناوبی به روز شده و نگهداری می‌شود.


این برنامه (تصویر فوق) که حاوی مرورگر مثال‌های آن است، در پوشه‌ی Source\Examples\WPF\ExampleBrowser سورس‌های آن قرار دارد.

در ادامه نگاهی خواهیم داشت به نحوه‌ی استفاده از OxyPlot در برنامه‌های WPF جهت رسم نموداری بلادرنگ که اطلاعات آن در زمان اجرای برنامه تهیه شده و در همین حین نیز تغییر می‌کنند.


دریافت بسته‌های نیوگت OxyPlot

برای دریافت دو بسته‌ی OxyPlot.Core و OxyPlot.Wpf تنها کافی است دستور ذیل را در کنسول پاورشل نیوگت اجرا کنیم:
 PM> install-package OxyPlot.Wpf


افزودن تعاریف چارت به View

<Window x:Class="OxyPlotWpfTests.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:oxy="http://oxyplot.org/wpf"
        xmlns:oxyPlotWpfTests="clr-namespace:OxyPlotWpfTests"
        Title="MainWindow" Height="350" Width="525">
    <Window.Resources>
        <oxyPlotWpfTests:MainWindowViewModel x:Key="MainWindowViewModel" />
    </Window.Resources>
    <Grid DataContext="{Binding Source={StaticResource MainWindowViewModel}}">
        <oxy:PlotView Model="{Binding PlotModel}"/>
    </Grid>
</Window>
ابتدا باید فضای نام oxy اضافه شود. پس از آن oxy:PlotView به صفحه اضافه شده و سپس Model آن از ViewModel برنامه تعذیه می‌گردد.


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

کار ViewModel متصل شده به View فوق، مقدار دهی PlotModel است.
public class MainWindowViewModel
{
   public PlotModel PlotModel { get; set; } 

یک نکته‌ی کاربردی

اگر هیچ ایده‌ای نداشتید که این PlotModel را چگونه باید مقدار دهی کرد، به همان برنامه‌ی ExampleBrowser ابتدای مطلب مراجعه کنید.


مثال‌های اجرای شد‌ه‌ی آن یک برگه‌ی نمایشی و یک برگه‌ی Code دارند. خروجی این متدها را اگر به خاصیت PlotModel فوق انتساب دهید ... یک چارت کامل خواهید داشت.


مراحل ساخت یک  PlotModel

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

        private void createPlotModel()
        {
            PlotModel = new PlotModel
            {
                Title = "سری خطوط",
                Subtitle = "Pan (right click and drag)/Zoom (Middle click and drag)/Reset (double-click)"
            };
            PlotModel.MouseDown += (sender, args) =>
            {
                if (args.ChangedButton == OxyMouseButton.Left && args.ClickCount == 2)
                {
                    foreach (var axis in PlotModel.Axes)
                        axis.Reset();

                    PlotModel.InvalidatePlot(false);
                }
            };
        }
PlotModel در برگیرنده‌ی محورها، نقاط و تمام ناحیه‌ی چارت است. در اینجا عنوان و زیرعنوان نمودار، مقدار دهی شده‌اند. همچنین در همین ViewModel بدون نیاز به مراجعه به View، می‌توان به رخدادهای مختلف OxyPlot دسترسی داشت. برای مثال می‌خواهیم اگر کاربر دو بار بر روی چارت کلیک کرد، کلیه اعمال zoom و pan آن به حالت اول برگردانده شوند.
برای pan، کافی است دکمه‌ی سمت راست ماوس را نگه داشته و بکشید. به این ترتیب می‌توانید نمودار را بر روی محورهای X و Y حرکت دهید.
برای zoom نیاز است دکمه‌ی وسط ماوس را نگه داشته و بکشید. ناحیه‌ای که در این حالت نمایان می‌گردد، محل بزرگنمایی نهایی خواهد بود.
این دو قابلیت به صورت توکار در OxyPlot قرار دارند و نیازی به کدنویسی برای فعال سازی آن‌ها نیست.



افزودن محورهای X و Y

محور X در مثال ما، از نوع LinearAxis است. بهتر است متغیر آن‌را در سطح کلاس تعریف کرد تا بتوان از آن در سایر قسمت‌های چارت نیز بهره گرفت:
       readonly LinearAxis _xAxis = new LinearAxis();
        private void addXAxis()
        {
            _xAxis.Minimum = 0;
            _xAxis.MaximumPadding = 1;
            _xAxis.MinimumPadding = 1;
            _xAxis.Position = AxisPosition.Bottom;
            _xAxis.Title = "X axis";
            _xAxis.MajorGridlineStyle = LineStyle.Solid;
            _xAxis.MinorGridlineStyle = LineStyle.Dot;
            PlotModel.Axes.Add(_xAxis);
        }
در اینجا مقدار خاصیت Position، مشخص می‌کند که این محور در کجا باید قرار گیرد. اگر مقدار دهی نشود، محور Y را تشکیل خواهد داد.
مقدار دهی GridlineStyleها سبب ایجاد یک Grid خاکستری در نمودار می‌شوند.
در آخر نیاز است این محور به محورهای  PlotModel اضافه شود.

تعریف محور Y نیز به همین نحو است. اگر مقدار خاصیت Position ذکر نشود، این محور در سمت چپ صفحه قرار می‌گیرد:
        readonly LinearAxis _yAxis = new LinearAxis();
        private void addYAxis()
        {
            _yAxis.Minimum = 0;
            _yAxis.Title = "Y axis";
            _yAxis.MaximumPadding = 1;
            _yAxis.MinimumPadding = 1;
            _yAxis.MajorGridlineStyle = LineStyle.Solid;
            _yAxis.MinorGridlineStyle = LineStyle.Dot;
            PlotModel.Axes.Add(_yAxis);
        }


افزودن تعاریف سری‌های خطوط

در تصویر فوق، دو سری خط را ملاحظه می‌کنید. تعاریف پایه سری اول آن به این صورت است:
        readonly LineSeries _lineSeries1 = new LineSeries();
        private void addLineSeries1()
        {
            _lineSeries1.MarkerType = MarkerType.Circle;
            _lineSeries1.StrokeThickness = 2;
            _lineSeries1.MarkerSize = 3;
            _lineSeries1.Title = "Start";
            _lineSeries1.MouseDown += (s, e) =>
            {
                if (e.ChangedButton == OxyMouseButton.Left)
                {
                    PlotModel.Subtitle = "Index of nearest point in LineSeries: " + Math.Round(e.HitTestResult.Index);
                    PlotModel.InvalidatePlot(false);
                }
            };
            PlotModel.Series.Add(_lineSeries1);
        }
مقدار خاصیت MarkerType، نحوه‌ی نمایش نقاط اضافه شده را مشخص می‌کند. خاصیت Title، عنوان آن‌را که در کنار صفحه نمایش داده شده، تعیین کرده و در آخر، این سری نیز باید به سری‌های PlotModel اضافه گردد.
هر سری دارای خاصیت MouseDown نیز هست. برای مثال اگر علاقمندید که کلیک کاربر بر روی نقاط مختلف را دریافت کرده و سپس بر این اساس، اطلاعات خاصی را نمایش دهید، می‌توانید از مقدار e.HitTestResult.Index استفاده کنید. در اینجا ایندکس نزدیک‌ترین نقطه به محل کلیک کاربر یافت می‌شود.

تعاریف اولیه سری دوم نیز به همین ترتیب هستند:
        readonly LineSeries _lineSeries2 = new LineSeries();
        private void addLineSeries2()
        {
            _lineSeries2.MarkerType = MarkerType.Circle;
            _lineSeries2.Title = "End";
            _lineSeries2.StrokeThickness = 2;
            _lineSeries2.MarkerSize = 3;
            _lineSeries2.MouseDown += (s, e) =>
            {
                if (e.ChangedButton == OxyMouseButton.Left)
                {
                    PlotModel.Subtitle = "Index of nearest point in LineSeries: " + Math.Round(e.HitTestResult.Index);
                    PlotModel.InvalidatePlot(false);
                }
            };
            PlotModel.Series.Add(_lineSeries2);
        }


به روز رسانی دستی OxyPlot

پس از نمایش اولیه OxyPlot، هر تغییری که در اطلاعات آن صورت گیرد، نمایش داده نخواهد شد. برای به روز رسانی آن فقط کافی است متد PlotModel.InvalidatePlot را فراخوانی نمائید. برای نمونه در متدهای فوق، کلیک ماوس، پس از رسم نمودار انجام می‌شود. بنابراین اگر نیاز است زیرعنوان نمودار تغییر کند، باید متد PlotModel.InvalidatePlot نیز فراخوانی گردد.


ایجاد یک تایمر برای افزودن نقاط به صورت پویا

در ادامه می‌خواهیم نقاطی را به صورت پویا به نمودار اضافه کنیم. نمایش یکباره نمودار، نکته‌ی خاصی ندارد. تنها کافی است توسط lineSeries1.Points.Add یک سری DataPoint را اضافه کنید. این نقاط در زمان نمایش View، به یکباره نمایش داده خواهند شد. اما در اینجا ابتدا یک چارت خالی نمایش داده می‌شود و سپس قرار است نقاطی به آن اضافه شوند.
        private int _xMax;
        private int _yMax;
        private bool _haveNewPoints;
        private void addPoints()
        {
            var timer = new DispatcherTimer {Interval = TimeSpan.FromSeconds(1)};
            var rnd = new Random();
            var x = 1;
            updateXMax(x);
            timer.Tick += (sender, args) =>
            {
                var y1 = rnd.Next(100);
                updateYMax(y1);
                _lineSeries1.Points.Add(new DataPoint(x, y1));

                var y2 = rnd.Next(100);
                updateYMax(y2);
                _lineSeries2.Points.Add(new DataPoint(x, rnd.Next(y2)));

                x++;

                updateXMax(x);
                _haveNewPoints = true;
            };
            timer.Start();
        }

        private void updateXMax(int value)
        {
            if (value > _xMax)
            {
                _xMax = value;
            }
        }

        private void updateYMax(int value)
        {
            if (value > _yMax)
            {
                _yMax = value;
            }
        }
چند نکته در اینجا حائز اهمیت هستند:
- افزودن نقاط جدید توسط متدهای lineSeries1.Points.Add انجام می‌شوند.
- مقادیر max محورهای x و y را نیز ذخیره می‌کنیم. اگر نقاط برنامه پویا نباشند، OxyPlot به صورت خودکار نمودار را با مقیاس درستی ترسیم می‌کند. اما اگر نقاط پویا باشند، نیاز است حداکثر محورهای x و y را به صورت دستی در آن تنظیم کنیم. به همین جهت متدهای updateXMax و updateYMax در اینجا فراخوانی شده‌اند.
- به روز رسانی ظاهر چارت، توسط متد زیر انجام می‌شود:
        private readonly Stopwatch _stopwatch = new Stopwatch();
        private void updatePlot()
        {
            CompositionTarget.Rendering += (sender, args) =>
            {
                if (_stopwatch.ElapsedMilliseconds > _lastUpdateMilliseconds + 2000 && _haveNewPoints)
                {
                    if (_yMax > 0 && _xMax > 0)
                    {
                        _yAxis.Maximum = _yMax + 3;
                        _xAxis.Maximum = _xMax + 1;
                    }

                    PlotModel.InvalidatePlot(false);

                    _haveNewPoints = false;
                    _lastUpdateMilliseconds = _stopwatch.ElapsedMilliseconds;
                }
            };
        }
کل کاری که در اینجا انجام شده، فراخوانی کنترل شده‌ی PlotModel.InvalidatePlot هر دو ثانیه یکبار است. CompositionTarget.Rendering بر اساس رندر View، عمل کرده و از آن می‌توان برای به روز رسانی نمایشی چارت استفاده کرد. اگر متد PlotModel.InvalidatePlot را دقیقا در زمان افزودن نقاط فراخوانی کنیم به CPU Usage بالایی خواهیم رسید. به همین جهت نیاز است فراخوانی آن کنترل شده و در فواصل زمانی مشخصی باشد. همچنین اگر نقطه‌ای اضافه نشده (بر اساس مقدار haveNewPoints)، به روز رسانی انجام نخواهد شد.
نکته‌ی دیگری که در متد updatePlot فوق درنظر گرفته شده‌است، تغییر مقدار Maximum محورهای x و y بر اساس حداکثرهای نقاط اضافه شده‌است. به این ترتیب نمودار به صورت خودکار جهت نمایش کل اطلاعات، تغییر اندازه خواهد داد.
البته همانطور که عنوان شد، تمام این تهمیدات برای نمایش نمودارهای بلادرنگ است. اگر کار مقدار دهی Points.Add را فقط یکبار در سازنده‌ی ViewModel انجام می‌دهید، نیازی به این نکات نخواهید داشت.

کدهای کامل این مثال را از اینجا می‌توانید دریافت کنید:
OxyPlotWpfTests.zip
مطالب
تولید هدرهای Content Security Policy توسط ASP.NET Core برای برنامه‌های Angular
پیشتر مطلب «افزودن هدرهای Content Security Policy به برنامه‌های ASP.NET» را در این سایت مطالعه کرده‌اید. در اینجا قصد داریم معادل آن‌را برای ASP.NET Core تهیه کرده و همچنین نکات مرتبط با برنامه‌های Angular را نیز در آن لحاظ کنیم.


تهیه میان افزار افزودن هدرهای Content Security Policy

کدهای کامل این میان افزار را در ادامه مشاهده می‌کنید:
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;

namespace AngularTemplateDrivenFormsLab.Utils
{
    public class ContentSecurityPolicyMiddleware
    {
        private readonly RequestDelegate _next;

        public ContentSecurityPolicyMiddleware(RequestDelegate next)
        {
            _next = next;
        }

        public Task Invoke(HttpContext context)
        {
            context.Response.Headers.Add("X-Frame-Options", "SAMEORIGIN");
            context.Response.Headers.Add("X-Xss-Protection", "1; mode=block");
            context.Response.Headers.Add("X-Content-Type-Options", "nosniff");

            string[] csp =
            {
              "default-src 'self'",
              "style-src 'self' 'unsafe-inline'",
              "script-src 'self' 'unsafe-inline' 'unsafe-eval'",
              "font-src 'self'",
              "img-src 'self' data:",
              "connect-src 'self'",
              "media-src 'self'",
              "object-src 'self'",
              "report-uri /api/CspReport/Log" //TODO: Add api/CspReport/Log
            };
            context.Response.Headers.Add("Content-Security-Policy", string.Join("; ", csp));
            return _next(context);
        }
    }

    public static class ContentSecurityPolicyMiddlewareExtensions
    {
        /// <summary>
        /// Make sure you add this code BEFORE app.UseStaticFiles();,
        /// otherwise the headers will not be applied to your static files.
        /// </summary>
        public static IApplicationBuilder UseContentSecurityPolicy(this IApplicationBuilder builder)
        {
            return builder.UseMiddleware<ContentSecurityPolicyMiddleware>();
        }
    }
}
که نحوه‌ی استفاده از آن در کلاس آغازین برنامه به صورت ذیل خواهد بود:
public void Configure(IApplicationBuilder app)
{
   app.UseContentSecurityPolicy();

توضیحات تکمیلی

افزودن X-Frame-Options
 context.Response.Headers.Add("X-Frame-Options", "SAMEORIGIN");
از هدر X-FRAME-OPTIONS، جهت منع نمایش و رندر سایت جاری، در iframeهای سایت‌های دیگر استفاده می‌شود. ذکر مقدار SAMEORIGIN آن، به معنای مجاز تلقی کردن دومین جاری برنامه است.


افزودن X-Xss-Protection
 context.Response.Headers.Add("X-Xss-Protection", "1; mode=block");
تقریبا تمام مرورگرهای امروزی قابلیت تشخیص حملات XSS را توسط static analysis توکار خود دارند. این هدر، آنالیز اجباری XSS را فعال کرده و همچنین تنظیم حالت آن به block، نمایش و رندر قسمت مشکل‌دار را به طور کامل غیرفعال می‌کند.


افزودن X-Content-Type-Options
 context.Response.Headers.Add("X-Content-Type-Options", "nosniff");
وجود این هدر سبب می‌شود تا مرورگر، حدس‌زدن نوع فایل‌ها، درخواست‌ها و محتوا را کنار گذاشته و صرفا به content-type ارسالی توسط سرور اکتفا کند. به این ترتیب برای مثال امکان لینک کردن یک فایل غیرجاوا اسکریپتی و اجرای آن به صورت کدهای جاوا اسکریپت، چون توسط تگ script ذکر شده‌است، غیرفعال می‌شود. در غیراینصورت مرورگر هرچیزی را که توسط تگ script به صفحه لینک شده باشد، صرف نظر از content-type واقعی آن، اجرا خواهد کرد.


افزودن Content-Security-Policy
string[] csp =
            {
              "default-src 'self'",
              "style-src 'self' 'unsafe-inline'",
              "script-src 'self' 'unsafe-inline' 'unsafe-eval'",
              "font-src 'self'",
              "img-src 'self' data:",
              "connect-src 'self'",
              "media-src 'self'",
              "object-src 'self'",
              "report-uri /api/CspReport/Log" //TODO: Add api/CspReport/Log
            };
context.Response.Headers.Add("Content-Security-Policy", string.Join("; ", csp));
وجود این هدر، تزریق کدها و منابع را از دومین‌های دیگر غیرممکن می‌کند. برای مثال ذکر self در اینجا به معنای مجاز بودن الصاق و اجرای اسکریپت‌ها، شیوه‌نامه‌ها، تصاویر و اشیاء، صرفا از طریق دومین جاری برنامه است و هرگونه منبعی که از دومین‌های دیگر به برنامه تزریق شود، قابلیت اجرایی و یا نمایشی نخواهد داشت.

در اینجا ذکر unsafe-inline و unsafe-eval را مشاهده می‌کنید. برنامه‌های Angular به همراه شیوه‌نامه‌های inline و یا بکارگیری متد eval در مواردی خاص هستند. اگر این دو گزینه ذکر و فعال نشوند، در کنسول developer مرورگر، خطای بلاک شدن آن‌ها را مشاهده کرده و همچنین برنامه از کار خواهد افتاد.

یک نکته: با فعالسازی گزینه‌ی aot-- در حین ساخت برنامه، می‌توان unsafe-eval را نیز حذف کرد.


استفاده از فایل web.config برای تعریف SameSite Cookies

یکی از پیشنهادهای اخیر ارائه شده‌ی جهت مقابله‌ی با حملات CSRF و XSRF، قابلیتی است به نام  Same-Site Cookies. به این ترتیب مرورگر، کوکی سایت جاری را به همراه یک درخواست ارسال آن به سایت دیگر، پیوست نمی‌کند (کاری که هم اکنون با درخواست‌های Cross-Site صورت می‌گیرد). برای رفع این مشکل، با این پیشنهاد امنیتی جدید، تنها کافی است SameSite، به انتهای کوکی اضافه شود:
 Set-Cookie: sess=abc123; path=/; SameSite

نگارش‌های بعدی ASP.NET Core، ویژگی SameSite را نیز به عنوان CookieOptions لحاظ کرده‌اند. همچنین یک سری از کوکی‌های خودکار تولیدی توسط آن مانند کوکی‌های anti-forgery به صورت خودکار با این ویژگی تولید می‌شوند.
اما مدیریت این مورد برای اعمال سراسری آن، با کدنویسی میسر نیست (مگر اینکه مانند نگارش‌های بعدی ASP.NET Core پشتیبانی توکاری از آن صورت گیرد). به همین جهت می‌توان از ماژول URL rewrite مربوط به IIS برای افزودن ویژگی SameSite به تمام کوکی‌های تولید شده‌ی توسط سایت، کمک گرفت. برای این منظور تنها کافی است فایل web.config را ویرایش کرده و موارد ذیل را به آن اضافه کنید:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <system.webServer>
    <rewrite>
      <outboundRules>
        <clear />
        <!-- https://scotthelme.co.uk/csrf-is-dead/ -->
        <rule name="Add SameSite" preCondition="No SameSite">
          <match serverVariable="RESPONSE_Set_Cookie" pattern=".*" negate="false" />
          <action type="Rewrite" value="{R:0}; SameSite=lax" />
          <conditions></conditions>
        </rule>
        <preConditions>
          <preCondition name="No SameSite">
            <add input="{RESPONSE_Set_Cookie}" pattern="." />
            <add input="{RESPONSE_Set_Cookie}" pattern="; SameSite=lax" negate="true" />
          </preCondition>
        </preConditions>
      </outboundRules>
    </rewrite>
  </system.webServer>
</configuration>


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

اگر به هدر Content-Security-Policy دقت کنید، گزینه‌ی آخر آن، ذکر اکشن متدی در سمت سرور است:
   "report-uri /api/CspReport/Log" //TODO: Add api/CspReport/Log
با تنظیم این مورد، می‌توان موارد بلاک شده را در سمت سرور لاگ کرد. اما این اطلاعات ارسالی به سمت سرور، فرمت خاصی را دارند:
{
  "csp-report": {
    "document-uri": "http://localhost:5000/untypedSha",
    "referrer": "",
    "violated-directive": "script-src",
    "effective-directive": "script-src",
    "original-policy": "default-src 'self'; style-src 'self'; script-src 'self'; font-src 'self'; img-src 'self' data:; connect-src 'self'; media-src 'self'; object-src 'self'; report-uri /api/Home/CspReport",
    "disposition": "enforce",
    "blocked-uri": "eval",
    "line-number": 21,
    "column-number": 8,
    "source-file": "http://localhost:5000/scripts.bundle.js",
    "status-code": 200,
    "script-sample": ""
  }
}
به همین جهت ابتدا نیاز است توسط JsonProperty کتابخانه‌ی JSON.NET، معادل این خواص را تولید کرد:
    class CspPost
    {
        [JsonProperty("csp-report")]
        public CspReport CspReport { get; set; }
    }

    class CspReport
    {
        [JsonProperty("document-uri")]
        public string DocumentUri { get; set; }

        [JsonProperty("referrer")]
        public string Referrer { get; set; }

        [JsonProperty("violated-directive")]
        public string ViolatedDirective { get; set; }

        [JsonProperty("effective-directive")]
        public string EffectiveDirective { get; set; }

        [JsonProperty("original-policy")]
        public string OriginalPolicy { get; set; }

        [JsonProperty("disposition")]
        public string Disposition { get; set; }

        [JsonProperty("blocked-uri")]
        public string BlockedUri { get; set; }

        [JsonProperty("line-number")]
        public int LineNumber { get; set; }

        [JsonProperty("column-number")]
        public int ColumnNumber { get; set; }

        [JsonProperty("source-file")]
        public string SourceFile { get; set; }

        [JsonProperty("status-code")]
        public string StatusCode { get; set; }

        [JsonProperty("script-sample")]
        public string ScriptSample { get; set; }
    }
اکنون می‌توان بدنه‌ی درخواست را استخراج و سپس به این شیء ویژه نگاشت کرد:
namespace AngularTemplateDrivenFormsLab.Controllers
{
    [Route("api/[controller]")]
    public class CspReportController : Controller
    {
        [HttpPost("[action]")]
        [IgnoreAntiforgeryToken]
        public async Task<IActionResult> Log()
        {
            CspPost cspPost;
            using (var bodyReader = new StreamReader(this.HttpContext.Request.Body))
            {
                var body = await bodyReader.ReadToEndAsync().ConfigureAwait(false);
                this.HttpContext.Request.Body = new MemoryStream(Encoding.UTF8.GetBytes(body));
                cspPost = JsonConvert.DeserializeObject<CspPost>(body);
            }

            //TODO: log cspPost

            return Ok();
        }
    }
}
در اینجا نحوه‌ی استخراج Request.Body را به صورت خام را مشاهده می‌کنید. سپس توسط متد DeserializeObject کتابخانه‌ی JSON.NET، این رشته به شیء CspPost نگاشت شده‌است.


کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید.