نظرات اشتراک‌ها
بررسی آینده‌ی Blazor در دات نت 8
خلاصه‌ی این مفهوم «United» به این صورت است:
- blazor server از لحاظ عدم نیاز به دریافت چند صد فایل مرتبط با web assembly و همراه بودن با رندر سمت سرور، در ابتدای کار نمایش برنامه، سریعتر نمایش داده می‌شود و این نمایش اولیه تقریبا آنی است.
- Blazor WASM چون به طور کامل در مرورگر اجرا می‌شود و در جهت رندر صفحات نیازی به رفت و برگشت به سرور ندارد، سرعت اجرایی فوق العاده‌ای دارد؛ اما به همراه بارگذاری تعداد زیادی فایل در ابتدای کار نمایش آن است که ... کمی طول می‌کشد که البته می‌توان با استفاده از فعال سازی per-rendering کمی آن‌را سریعتر کرد که نیاز به تنظیمات قابل توجهی دارد که شاید همه از آن استفاده نکنند.
اکنون قرار است در دات نت 8 بتوان به صورت خودکار ابتدا یک صفحه را با استفاده از server side rendering موجود در Blazor Server، سریع نمایش داد (بدون نیاز به دریافت فایل‌های WASM و منتظر شدن برای آن‌ها) و بعد در همان حال در پشت صحنه و به صورت خودکار، همین برنامه با قالب Blazor web assembly هم دریافت می‌شود تا در بار بعدی که این صفحه قرار است نمایش داده شود، بتوان به حداکثر سرعت کار با Blazor، با استفاده از اجرای کامل آن در مرورگر توسط web assembly رسید. یعنی ترکیب همزمان blazor server و blazor web assembly، بدون نیاز به تنظیمات خاصی در جهت از پیش رندر کردن صفحات و یا منتظر ماندن اولیه و نمایش یک صفحه‌ی خالی در جهت بارگذاری تمام فایل‌های یک برنامه‌ی web assembly؛ هر دو با هم به صورت یکپارچه و یک‌دست. حتی می‌توان مشخص کرد که صفحه‌ای فقط بر اساس blazor server اجرا شود و صفحه‌ای دیگر فقط قسمتی از آن از blazor wasm استفاده کند.
مطالب
React 16x - قسمت 6 - کامپوننت‌ها - بخش 3 - یک تمرین
در این قسمت می‌خواهیم دانسته‌های 5 قسمت قبل را در طی یک تمرین کنار هم قرار داده و مرور کنیم.


برپایی ساختار ابتدایی پروژه‌ی تمرین

ابتدا یک پروژه‌ی جدید React را ایجاد می‌کنیم:
> create-react-app sample-05
> cd sample-05
> npm start
سپس بسته‌های بوت استرپ و font-awesome را نیز در آن نصب می‌کنیم:
> npm install --save bootstrap
> npm install --save font-awesome

در ادامه نیاز است فایل‌های CSS این کتابخانه‌ها و قلم‌های وب را import کنیم. به همین جهت ابتدای فایل index.js را به نحو زیر ویرایش خواهیم کرد:
import "bootstrap/dist/css/bootstrap.css";
import "font-awesome/css/font-awesome.css";
در نهایت کار مدیریت این فایل‌ها و قرار دادن آن‌ها در بسته‌ی نهایی برنامه، توسط webpack به صورت خودکار انجام می‌شود.

همچنین به فایل index.css هم مراجعه کرده و یک padding را به بالای صفحه اضافه می‌کنیم؛ تا اطلاعات نمایش داده شده، با کمی فاصله از لبه‌ی مرورگر رندر شوند:
body {
  margin: 0;
  padding: 20px 0 0 0;
  font-family: sans-serif;
}

پس از نصب و import این کتابخانه‌های ثالث، به فایل App.js مراجعه کرده و کلاس container اصلی بوت استرپ را در آن تعریف می‌کنیم تا در برگیرنده‌ی محتوای برنامه شود:
  return (
    <main className="container">
      <h1>Hello world!</h1>
    </main>
  );
همانطور که در قسمت چهارم نیز بحث شد، برای ذکر classهای عناصر در React، از خاصیت className استفاده می‌شود.


معرفی سرویس‌های داده‌ی برنامه

کدهای نهایی این قسمت را از فایل پیوست شده‌ی در انتهای مطلب، می‌توانید دریافت کنید. در اینجا یک پوشه‌ی src\services تعریف شده‌است که داخل آن دو فایل fakeGenreService.js و fakeMovieService.js قرار دارند. این فایل‌ها، منبع داده‌ی درون حافظه‌ای مثال تمرین ما هستند.
سرویس fakeGenre چنین ساختاری را دارد و ژانرهای سینمایی، مانند اکشن، کمدی و غیره در آن لیست شده‌اند:
export const genres = [
  { _id: "5b21ca3eeb7f6fbccd471818", name: "Action" },
  // ...
];
این سرویس دارای متد ()getGenres، برای بازگشت لیست کامل genres است. علت ذکر خاصیت id با یک _، روش نامگذاری خاصیت id در mongo-db است.

و سرویس fakeMovie که دارای ساختار کلی زیر است، لیست 9 فیلم سینمایی را به همراه دارد:
const movies = [
  {
    _id: "5b21ca3eeb7f6fbccd471815",
    title: "Terminator",
    genre: { _id: "5b21ca3eeb7f6fbccd471818", name: "Action" },
    numberInStock: 6,
    dailyRentalRate: 2.5,
    publishDate: "2018-01-03T19:04:28.809Z"
  },

  //...
];
به علاوه این سرویس دارای متدهای ()getMovies برای دریافت لیست فیلم‌ها، getMovie(id) برای بازگشت یک فیلم خاص، saveMovie(movie) برای افزودن یک فیلم جدید به لیست و deleteMovie(id) برای حذف یک فیلم از لیست درون حافظه‌ای سرویس جاری است.


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

اکنون می‌خواهیم یک کامپوننت جدید را به نام Movies در فایل جدید src\components\movies.jsx ایجاد کنیم، تا لیست فیلم‌های سرویس fakeMovieService را نمایش دهد. برای اینکار مراحل زیر را طی خواهیم کرد:
- نمایش ساده‌ی لیست فیلم‌ها توسط یک جدول. برای دریافت لیست اشیاء موجود در fakeMovieService، از متد ()getMovies آن می‌توان استفاده کرد.
- اضافه کردن یک دکمه‌ی حذف، به هر ردیف، به نحوی که با کلیک بر روی آن، آن ردیف حذف شود.
- نمایش یک پیام بالای جدول که تعداد فیلم‌های موجود در سرویس درون حافظه‌ای را نمایش می‌دهد. همچنین پس از حذف تمام ردیف‌ها، باید پیام «فیلمی موجود نیست» را نمایش دهد.

خروجی نهایی مثال ما به صورت زیر است:


و اگر تمام آیتم‌های آن‌را حذف کنیم، چنین پیامی نمایش داده می‌شود:


پس از ایجاد فایل خالی جدید movies.jsx در پوشه‌ی جدید components، با استفاده از «simple react snippets» نصب شده‌ی در VSCode، یکبار imrc را تایپ کرده (مخفف import react component است) و سپس دکمه‌ی tab را فشار می‌دهیم، در آخر اینکار را برای cc نیز تکرار می‌کنیم (مخفف create class است) تا importها و سپس ساختار ابتدایی کامپوننت React ما تشکیل شوند. نام این کامپوننت را هم Movies که با حرف بزرگ شروع می‌شود، وارد می‌کنیم.

اکنون مجددا به App.js مراجعه می‌کنیم و بجای Hello world ای که نمایش دادیم، کامپوننت Movies را اضافه می‌کنیم. برای این منظور ابتدا import آن‌را به ابتدای فایل اضافه می‌کنیم:
import Movies from "./components/movies";
سپس متد return آن‌را جهت درج المان کامپوننت Movies اصلاح خواهیم کرد:
return (
    <main className="container">
      <Movies />
    </main>
  );


دریافت لیست اشیاء فیلم‌ها از سرویس fakeMovieService

برای دریافت لیست اشیاء فیلم‌ها، ابتدا تعریف سرویس آن‌را به ابتدای کامپوننت Movies اضافه می‌کنیم:
 import { getMovies } from "../services/fakeMovieService";
در اینجا از {} استفاده شده، چون یک named export را import کرده‌ایم.

سپس خاصیت state را جهت تعریف خاصیت movies که با متد ()getMovies سرویس fakeMovieService مقدار دهی می‌شود، به نحو زیر تکمیل می‌کنیم:
 state = {
    movies: getMovies()
  };
البته این روش مقدار دهی اولیه‌ی خاصیت state، برای دریافت اطلاعات سرویس‌ها، هرچند در اینجا بدون مشکل کار می‌کند، اما بهتر است توسط component life cycle hooks مدیریت شود که در قسمت‌های بعدی بیشتر به جزئیات آن‌ها خواهیم پرداخت.


نمایش لیست فیلم‌ها، به همراه مدیریت حذف هر ردیف

در ادامه، کدهای کامل و تکمیل شده‌ی این کامپوننت را ملاحظه می‌کنید:
import React, { Component } from "react";

import { getMovies } from "../services/fakeMovieService";

class Movies extends Component {
  state = {
    movies: getMovies()
  };

  handleDelete = movie => {
    const movies = this.state.movies.filter(m => m._id !== movie._id);
    this.setState({ movies });
  };

  render() {
    const { length: count } = this.state.movies;

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

    return (
      <React.Fragment>
        <p>Showing {count} movies in the database.</p>
        <table className="table">
          <thead>
            <tr>
              <th>Title</th>
              <th>Genre</th>
              <th>Stock</th>
              <th>Rate</th>
              <th />
            </tr>
          </thead>
          <tbody>
            {this.state.movies.map(movie => (
              <tr key={movie._id}>
                <td>{movie.title}</td>
                <td>{movie.genre.name}</td>
                <td>{movie.numberInStock}</td>
                <td>{movie.dailyRentalRate}</td>
                <td>
                  <button
                    onClick={() => this.handleDelete(movie)}
                    className="btn btn-danger btn-sm"
                  >
                    Delete
                  </button>
                </td>
              </tr>
            ))}
          </tbody>
        </table>
      </React.Fragment>
    );
  }
}

export default Movies;
توضیحات:
همانطور که در ابتدای بحث نیز ذکر شد، هدف از این تمرین، مرور قسمت‌های قبل است و تمام نکات زیر را در قسمت‌های پیشین، با جزئیات بیشتری بررسی کرده‌ایم:

- ابتدا خاصیت state و سپس خاصیت movies شیء منتسب به آن، با لیست فیلم‌های موجود در سرویس مرتبط، مقدار دهی شده‌اند.
- سپس در ابتدای متد render، کار رندر شرطی انجام شده‌است. اگر تعداد فیلم‌های دریافتی صفر بود، پیام «فیلمی در بانک اطلاعاتی موجود نیست» نمایش داده می‌شود و در غیراینصورت، جدول اصلی بوت استرپی برنامه رندر خواهد شد.
در اینجا چون از خاصیت طول آرایه‌ی فیلم‌ها در چندین قسمت قرار است استفاده شود، آن‌را توسط Object Destructuring به یک متغیر نسبت داده‌ایم. همچنین توسط یک نام مستعار هم خاصیت length را با نام جدید count استفاده می‌کنیم.
- در ادامه بازگشت React.Fragment را مشاهده می‌کنید. علت اینجا است که نمی‌خواهیم div اضافه‌تری را در UI رندر کنیم. React.Fragment سبب می‌شود تا بتوانیم چندین فرزند را به المان جاری تبدیل شده‌ی به کدهای جاوا اسکریپتی اضافه کنیم، بدون اینکه خودش به المانی ترجمه شود.
- پس از return، یک () قابل مشاهده‌است. چون خروجی return ما چند سطری است، اگر در سطری که return قرار می‌گیرد، اطلاعاتی درج نشود، موتور جاوا اسکریپت آن‌را با یک سمی‌کالن خاتمه خواهد داد! و دیگر سطرهای بعدی دیده نمی‌شوند و پردازش نخواهند شد. به همین جهت از روش ذکر یک () پس از return در فایل‌های jsx زیاد استفاده می‌شود.
- در ابتدای return، همان خاصیت count را نمایش می‌دهیم.
- سپس کار رندر جدول اصلی برنامه که با کلاس‌های جداول بوت استرپ نیز مزین شده، انجام شده‌است. در React برای عدم تداخل ویژگی class با نام از پیش رزرو شده‌ی class، از خاصیت className برای ذکر کلاس‌های CSSای استفاده می‌شود.
- قسمت thead این جدول مشخص است و سرستو‌ن‌های جدول را مشخص می‌کند.
- پس از آن نیاز است ردیف‌های جدول را رندر کنیم. این‌کار را توسط متد Array.map، با نگاشت هر آیتم آرایه‌ی this.state.movies، به یک tr جدول انجام داده‌ایم.
- React برای اینکه بتواند DOM مجازی خودش را کنترل کند، نیاز دارد عناصر موجود در آن‌را به صورت منحصربفردی تشخص دهد. به همین جهت در اینجا ذکر key را بر روی المان tr که با movie._id مقدار دهی شده‌است، مشاهده می‌کنید.
- رندر مقادیر سلو‌ل‌های ردیف‌ها توسط درج {} و سپس ذکر مقداری از شیء movie دریافتی توسط متد Array.map انجام می‌شود.
- در اینجا ستون رندر دکمه‌ی Delete را نیز مشاهده می‌کنید. برای مدیریت this در آن و دسترسی به شیء movie جاری (ارسال پارامتر به رویداد گردان آن) و همچنین دسترسی به شیء this کلاس جاری برای کار با آرایه‌ی this.state.movies، از روش arrow functions برای تعریف رویدادگردان onClick استفاده کرده‌ایم.
- در متد handleDelete، یک آرایه‌ی جدید را که id ردیف‌های آن با id شیء ردیف انتخابی یکی نیست، بازگشت می‌دهیم. انتساب این آرایه‌ی جدید به آرایه‌ی this.state.movies، تغییری را در برنامه‌های React ایجاد نمی‌کند. در اینجا باید توسط متد this.setState که از کلاس پایه‌ی extends Component دریافت می‌شود، خاصیت movies را بازنویسی کرد تا React از تغییرات مطلع شده و DOM مجازی جدیدی را با مقایسه‌ی با نمونه جدید، محاسبه کرده و به DOM اصلی، جهت به روز رسانی UI اعمال کند.
- البته در اینجا this.setState({ movies }) را بجای this.setState({ movies: movies }) مشاهده می‌کنید. علت اینجا است که اگر عبارات key و value یکی باشند، می‌توان تنها همان عبارت key را جهت حذف تکرار واژه‌ها، ذکر کرد.



کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: sample-05.zip
نظرات مطالب
صفحه بندی و مرتب سازی خودکار اطلاعات به کمک jqGrid در ASP.NET MVC
 با سلام؛ خیلی ممنونم. ولی میخواستم اون کاری که میخوام انجام بدم رو بیشتر توضیح بدم تا موضوع روشن‌تر بشه

همون طور که در عکس مشخصه من 3 تا جدول دارم
-Programs برای نگه داری اطلاعات برنامه که به صورت تو در تو است
-PrgElements برای نگه داری اطلاعات مربوط به المان‌های تعریف شده در برنامه (مثل فرم -منو- کنترل و ...) که ElementID به صورت HiearachyID  تعریف شده و متاسفانه Entity Framwork,LingToSql این DataType رو ساپرت نمیکنه برای همین باید  از ADO استفاده کنم.
من میخوام گریدی داشته باشم که کاربر اطلاعات راجع به المنت‌های برنامه رو تو در تو وارد کنه و   این اطلاعات برای کاربر نمایش داده بشه تا کاربر بتونه با انتخاب سطر مربوطه اون رو ویرایش کنه حذف کنه و مورد جدیدی رو اضافه کنه 
یک گریدی به این شکل:


من در قسمت کدهای سمت سرور ، اون قسمتی که row رو مقدار دهی میکنید رو متاسفانه نمیتونم پیاده سازی کنم (در واقع تبدیل داده مورد نظر به فرمت jqGridData جایی که Row  رو مقدار دهی می‌کنیم)
مطالب
آموزش (jQuery) جی کوئری 5#
در ادامه مطلب قبلی  آموزش (jQuery) جی کوئری 4# به ادامه بحث  می‌پردازیم.
در پست قبل به بررسی انتخاب عناصر بر اساس موقعیت پرداختیم، در این پست به بحث "استفاده از انتخاب کننده‌های سفارشی jQuery" خواهیم پرداخت.

4-1- استفاده از انتخاب کننده‌های سفارشی jQuery
در پست‌های قبلی (^ و ^ ) تعدادی از انتخاب کننده‌های CSS که هر کدامشان موجب قدرت و انعطاف پذیری انتخاب اشیا موجود در صفحه می‌شوند را بررسی کردیم. با این وجود  فیلتر‌های انتخاب کننده قدرتمندتری وجود دارند که توانایی ما را برای انتخاب بیشتر می‌کنند.
به عنوان مثال اگر بخواهید از میان تمام چک باکس ها، گزینه هایی را که تیک خورده اند انتخاب نمایید، از آنجا که تلاش برای مطابقت حالت‌های اولیه کنترل‌های HTML را بررسی می‌کنیم، jQuery انتخابگر سفارشی checked: را پیشنهاد می‌کند، که مجموعه از عناصر را که خاصیت checked آنها فعال باشد را برای ما برمی گرداند. براس مثال انتخاب کننده input تمامی المان‌های <input> را انتخاب می‌کند، و انتخاب کننده input:checked تمامی inputهایی را انتخاب می‌کند که checked هستند. انتخاب کننده سفارشی checked:یک انتخاب کننده خصوصیت CSS عمل می‌کند (مانند [foo=bar]). ترکیب این انتخاب کننده‌ها می‌تواند قدرت بیشتری به ما بدهد، انتخاب کننده هایی مانند radio:checked: و checkbox:checked: .
همانطور هم که قبلا بیان شد، jQuery علاوه بر پشتیبانی از انتخاب کننده‌های CSS تعدادی انتخاب کننده سفارشی را نیز شامل می‌شود که در جدول 3-2 شرح داده شده است.

جدول 3-2: انتخاب کننده‌های سفارشی jQuery
 توضیح انتخاب کننده
 عناصری را انتخاب می‌کند که تحت کنترل انیمیشن می‌باشند. در پست‌های بعدی انیمیشن‌ها توضیح داده می‌شوند.
animated:
 عناصر دکمه را انتخاب می‌کند، عناصری مانند (input[type=submit]، input[type=reset]، input[type=button]،  یا button) 
button:
عناصر Checkbox را انتخاب می‌کند، مانند ([input[type=checkbox).
checkbox:
عناصر checkboxها یا دکمه‌های رادیویی را انتخاب می‌کند که در حالت انتخاب باشند.
checked:
عناصری ر انتخاب می‌کند که دارای عبارت foo باشند.
contains(foo) //c:
عناصر در حالت disabled را انتخاب می‌کند. disabled:
عناصر در حالت enabledرا انتخاب می‌کند.
enabled:
عناصر فایل را انتخاب می‌کند، مانند ([input[type=file).
file:
عناصر هدر مانند h1 تا h6 را انتخاب می‌کند.
header:
عناصر مخفی شده را انتهاب می‌کند.
hidden:
عناصر تصویر را انتخاب می‌کند، مانند ([input[type=image).
image:
عناصر فرم مانند input ، select، textarea، button را انتخاب می‌کند.
input:
انتخاب کننده‌ها را برعکس می‌کند.
not(filter)//c:
عناصری که فرزندی دارند را انتخاب می‌کند.
parent:
عناصر password را انتخاب می‌کند، مانند ([input[type=password). password:
عناصر radio را انتخاب می‌کند، مانند ([input[type=radio). 
radio:
دکمه‌های reset را انتخاب می‌کند، مانند  ([input[type=reset یا [button[type=reset).
raset:
عناصری (عناصر option) را انتخاب می‌کند که در وضعیت selected قراردارند.
selected:
دکمه‌های submit را انتخاب می‌کند، مانند  ([input[type=submit یا [button[type=submit). submit:
عناصر text را انتخاب می‌کند، مانند ([input[type=text).   
text:
عناصری را که در وضعیت visibleباشند انتخاب می‌کند.
visible:
بسیاری از انتخاب کننده‌های سفارشی jQuery بررسی شده برای انتخاب عناصر فرم ورود اطلاعات کاربر استفاده می‌شوند. این فیلتر‌ها قابلیت ادغام را دارند، برای مثال در زیر دستوری را به منظور انتخاب آن دسته از گزینه‌های Checkbox که تیک خورده اند و فعال هستند را مشاهده می‌کنید:
:checkbox:checked:enabled

این فیلتر‌ها و انتخاب کننده‌ها کاربردهای وسیعی در صفحات اینترنتی دارند، آیا آنها حالت معکوسی نیز دارند؟

استفاده از فیلتر not:
برای آنکه نتیجه انتخاب کننده‌ها را معکوس کنیم می‌توانیم از این فیلتر استفاده کنیم. برای مثال دستور زیر تمام عناصری را که checkBox نیستند را انتخاب می‌کند:
input:not(:checkbox)
اما استفاده از این فیلتر دقت زیادی را می‌طلبد زیرا به سادگی ممکن است با نتیجه ای غیر منتظره مواجه شویم.

استفاده از فیلتر has:
در اینجا دیدیم که CSS انتخاب کننده قدرتمندی را ارایه کرده است که فرزندران یک عنصر را در هر سطحی که باشند (حتی اگر فرزند مستقیم هم نباشند) انتخاب می‌کند. برای مثال دستور زیر تمام عناصر span را که در div معرفی شده باشند را انتخاب می‌کند:
div span

اما اگر بخواهیم انتخابی برعکس این انتخاب داشته باشیم، باید چه کنیم؟ برای این کار باید تمام divهایی که دارای عنصر span می‌باشد را انتخاب کرد. برای چنین انتخابی از فیلتر has: استفاده می‌کنیم. به دستور زیر توجه نمایید، این دستور تمام عناصر div را که در آنها عنصر span معرفی شده است را انتخاب می‌کند:
div:has(span)

برای برخی انتخاب‌های پیچیده و مشکل، این فیلتر و مکانیزم بسیار کارا می‌باشد و به سادگی ما را به هدف دلخواه می‌رساند. فرض کنید می‌خواهیم آن خانه از جدول که دارای یک عنصر عکس خاص می‌باشد را پیدا کنیم. با توجه به این نکته که آن عکس از طریق مقدار src قابل تشخیص می‌باشد، با استفاده از فیلتر has: دستوری مانند زیر می‌نویسیم:
$('tr:has(img[src$="foo.png"])')

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


موفق و موید باشید.
مطالب
تحلیل و بررسی ده روش آسیب پذیری نرم افزار بر اساس متدولوژی OWASP - قسمت اول SQL Injection
در این سری از مقالات، ده روش برتر آسیب پذیری نرم افزار بر اساس متدولوژی OWASP مورد بررسی قرار میگیرد. یادگیری این روش‌ها منحصر به زبان برنامه نویسی خاصی نیست و رعایت این نکات برای برنامه نویسانی که قصد نوشتن کدی امن دارند، توصیه میشود. کلمه‌ی OWASP مخفف عبارت Open Web Application Security Protocol Project می‌باشد. در واقع OWASP یک متدولوژی و پروژه‌ی متن باز است که معیارهایی را برای ایمن سازی نرم افزار مورد بررسی قرار میدهد.



آسیب پذیری SQL Injection یا به اختصار SQLi

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


عملکرد SQL Injection

برای اجرای SQLهای مخرب در برنامه‌هایی که از پایگاه‌های داده‌ی مبتنی بر SQL مانند (SQL Server ،MySQL ،PostgreSQL ،Oracle و ...) استفاده میکنند، هکر یا مهاجم در اولین گام باید به دنبال ورودی‌هایی در برنامه باشد که درون یک درخواست SQL قرار گرفته باشند (مانند صفحات لاگین، ثبت نام، جستجو و ...).

کد زیر را در نظر بگیرید:

# Define POST variables
uname = request.POST['username']
passwd = request.POST['password']

# SQL query vulnerable to SQLi
sql = "SELECT id FROM users WHERE username='" + uname + "' AND password='" + passwd + "'"

# Execute the SQL statement
database.execute(sql)
در این کد، موارد امنیتی برای جلوگیری از تزریق SQL تعبیه نشده‌است و هکر با اندکی دستکاری پارامترهای ارسالی میتواند نتایج دلخواهی را برای نفوذ، بدست بیاورد. قسمتی از کد که دستور SQL را اجرا میکند و پارامترهای ورودی از کاربر را در خود جای میدهد، بصورت نا امنی کدنویسی شده‌است.

اکنون ورودی password را برای نفوذ، تست میکنیم. مهاجم بدون داشتن نام کاربری، قصد دور زدن احراز هویت را دارد. بجای password عبارت زیر را قرار میدهد:

password' OR 1=1  

در نهایت در بانک اطلاعاتی دستور زیر اجرا میشود:

 SELECT id FROM users WHERE username='username' AND password=  'password' OR 1=1'

میدانیم که 1=1 است. پس بدون در نظر گرفتن اینکه شما برای username و password چه چیزی را وارد نمودید، عبارت درست در نظر گرفته میشود:

شرط اول   and   شرط دوم =
نتیجه  or 1=1
چون 1=1 است 
همیشه شرط کوئری درست خواهد بود


معمولا در بانک اطلاعاتی، اولین کاربری که وارد میکنند Administrator برنامه می‌باشد. پس به احتمال قوی شما میتوانید با مجوز ادمین به برنامه وارد شوید. البته میتوان با دانستن تنها نام کاربری هم به‌راحتی با گذاشتن در قسمت username بدون دانستن password، به برنامه وارد شد؛ زیرا میتوان شرط چک کردن password را کامنت نمود:

-- MySQL, MSSQL, Oracle, PostgreSQL, SQLite
' OR '1'='1' --
' OR '1'='1' /*
-- MySQL
' OR '1'='1' #
-- Access (using null characters)
' OR '1'='1' %00
' OR '1'='1' %16




ابزارهایی برای تست آسیب پذیری SQLi

1) برنامه‌ی DNTProfiler

2) اسکنر اکانتیکس

3) sqlmap
4) روش دستی که بهترین نتیجه را دارد و نیاز به تخصص دارد.


چگونه از SQL Injection جلوگیری کنیم

1) روی داده‌هایی که از کاربر دریافت میگردد، اعتبار سنجی سمت کلاینت و سرور انجام شود. اگر فقط به اعتبارسنجی سمت کلاینت اکتفا کنید، هکر به‌راحتی با استفاده از  پروکسی، داده‌ها را تغییر می‌دهد. ورودی‌ها را فیلتر و پاکسازی و با لیست سفید یا سیاه بررسی کنید ( ^^^^ ).

2) از کوئری‌هایی که بدون استفاده از پارامتر از کاربر ورودی گرفته و درون یک درخواستSQL قرار میگیرند، اجتناب کنید:

[HttpGet]
        [Route("nonsensitive")]
        public string GetNonSensitiveDataById()
        {
            using (SqlConnection connection = new SqlConnection(_configuration.GetValue<string>("ConnectionString")))
            {
                connection.Open();
                SqlCommand command = new SqlCommand($"SELECT * FROM NonSensitiveDataTable WHERE Id = {Request.Query["id"]}", connection);
                using (var reader = command.ExecuteReader())
                {
                    if (reader.Read())
                    {
                        string returnString = string.Empty;
                        returnString += $"Name : {reader["Name"]}. ";
                        returnString += $"Description : {reader["Description"]}";
                        return returnString;
                    }
                    else
                    {
                        return string.Empty;
                    }
                }
            }
        }


با استفاده از پارامتر: (بهتر است نوع دیتا تایپ پارامتر و طول آن ذکر شود)

[HttpGet]
        [Route("nonsensitivewithparam")]
        public string GetNonSensitiveDataByNameWithParam()
        {
            using (SqlConnection connection = new SqlConnection(_configuration.GetValue<string>("ConnectionString")))
            {
                connection.Open();
                SqlCommand command = new SqlCommand($"SELECT * FROM NonSensitiveDataTable WHERE Name = @name", connection);
                command.Parameters.AddWithValue("@name", Request.Query["name"].ToString());
                using (var reader = command.ExecuteReader())
                {
                    if (reader.Read())
                    {
                        string returnString = string.Empty;
                        returnString += $"Name : {reader["Name"]}. ";
                        returnString += $"Description : {reader["Description"]}";
                        return returnString;
                    }
                    else
                    {
                        return string.Empty;
                    }
                }
            }
        }


3) از Stored Procedureها استفاده کنید و بصورت پارامتری داده‌های مورد نیاز را به آن‌ها پاس دهید: (بهتر است نوع دیتا تایپ پارامتر و طول آن ذکر شود)  

 [HttpGet]
        [Route("nonsensitivewithsp")]
        public string GetNonSensitiveDataByNameWithSP()
        {
            using (SqlConnection connection = new SqlConnection(_configuration.GetValue<string>("ConnectionString")))
            {
                connection.Open();
                SqlCommand command = new SqlCommand("SP_GetNonSensitiveDataByName", connection);
                command.CommandType = System.Data.CommandType.StoredProcedure;
                command.Parameters.AddWithValue("@name", Request.Query["name"].ToString());
                using (var reader = command.ExecuteReader())
                {
                    if (reader.Read())
                    {
                        string returnString = string.Empty;
                        returnString += $"Name : {reader["Name"]}. ";
                        returnString += $"Description : {reader["Description"]}";
                        return returnString;
                    }
                    else
                    {
                        return string.Empty;
                    }
                }
            }
        }


4) اگر از داینامیک کوئری استفاده میکنید، داده‌های مورد استفاده‌ی در کوئری را بصورت پارامتری ارسال کنید:

فرض کنید چنین جدولی دارید

CREATE TABLE tbl_Product
(
Name NVARCHAR(50),
Qty INT,
Price FLOAT
)

GO

INSERT INTO tbl_Product (Name, Qty, Price) VALUES (N'Shampoo', 200, 10.0);
INSERT INTO tbl_Product (Name, Qty, Price) VALUES (N'Hair Clay', 400, 20.0);
INSERT INTO tbl_Product (Name, Qty, Price) VALUES (N'Hair Tonic', 300, 30.0);

یک پروسیجر را دارید که عملیات جستجو را انجام میدهد و از داینامیک کوئری استفاده میکند.

ALTER PROCEDURE sp_GetProduct(@Name NVARCHAR(50))
AS
BEGIN
    DECLARE @sqlcmd NVARCHAR(MAX);
    SET @sqlcmd = N'SELECT * FROM tbl_Product WHERE Name = ''' + @Name + '''';
    EXECUTE(@sqlcmd)
END

با اینکه از Stored Procedure استفاده میکنید، باز هم در معرض خطر SQLi می‌باشید. فرض کنید هکر چنین درخواستی را ارسال میکند:

Shampoo'; DROP TABLE tbl_Product; --

نتیجه، تبدیل به دستور زیر میشود:

SELECT * FROM tbl_Product WHERE Name = 'Shampoo'; DROP TABLE tbl_Product; --'

برای جلوگیری از SQLi در کوئریهای داینامیک SP بشکل زیر عمل میکنیم:

ALTER PROCEDURE sp_GetProduct(@Name NVARCHAR(50))
AS
BEGIN
    DECLARE @sqlcmd NVARCHAR(MAX);
    DECLARE @params NVARCHAR(MAX);
    SET @sqlcmd = N'SELECT * FROM tbl_Product WHERE Name = @Name';
    SET @params = N'@Name NVARCHAR(50)';
    EXECUTE sp_executesql @sqlcmd, @params, @Name;
END
در اینجا در پارامتر params@ نوع دیتاتایپ و طول آن را مشخص میکنید و کارکتر quote به صورت خودکار حذف میشود و از SQLi جلوگیری میکند. 


5) میتوان از تنظیمات IIS یا وب سرورهای دیگر برای جلوگیری از SQLi استفاده نمود.

6) استفاده از چند کاربرِ دیتابیس در برنامه و بکارگیری سطح دسترسی محدود و مناسب( ^ , ^ ).

7) از  ORM استفاده کنید و اگر نیاز به سرعت بیشتری دارید از یک Micro ORM استفاده کنید؛ با در نظر داشتن نکات لازم  

مطالب
امکان تعریف ساده‌تر کلاس‌های Immutable در C# 9.0 با معرفی نوع جدید record
در مطلب معرفی خواص init-only، با روش معرفی خواص immutable آشنا شدیم. نوع جدیدی که به C# 9.0 به نام record اضافه شده‌است، قسمتی از آن بر اساس همان خواص init-only کار می‌کند. به همین جهت مطالعه‌ی آن مطلب، پیش از ادامه‌ی بحث جاری، ضروری است.


چرا در C# 9.0 تا این اندازه بر روی سادگی ایجاد اشیاء Immutable تمرکز شده‌است؟

به شیءای Immutable گفته می‌شود که پس از وهله سازی ابتدایی آن، وضعیت آن دیگر قابل تغییر نباشد. همچنین به کلاسی Immutable گفته می‌شود که تمام وهله‌های ساخته شده‌ی از آن نیز Immutable باشند. نمونه‌ی یک چنین شیءای را از نگارش 1 دات نت در حال استفاده هستیم: رشته‌ها. رشته‌ها در دات نت غیرقابل تغییر هستند و هرگونه تغییری بر روی آن‌ها، سبب ایجاد یک رشته‌ی جدید (یک شیء جدید) می‌شود. نوع جدید record نیز به همین صورت عمل می‌کند.

مزایای وجود Immutability:

- اشیاء Immutable یا غیرقابل تغییر، thread-safe هستند که در نتیجه، برنامه نویسی همزمان و موازی را بسیار ساده می‌کنند؛ چون چندین thread می‌توانند با شیءای کار کنند که دسترسی به آن، تنها read-only است.
- اشیاء Immutable از اثرات جانبی، مانند تغییرات آن‌ها در متدهای مختلف در امان هستند. می‌توانید آن‌ها را به هر متدی ارسال کنید و مطمئن باشید که پس از پایان کار، این شیء تغییری نکرده‌است.
- کار با اشیاء Immutable، امکان بهینه سازی حافظه را میسر می‌کنند. برای مثال NET runtime.، هش رشته‌های تعریف شده‌ی در برنامه را در پشت صحنه نگهداری می‌کند تا مطمئن شود که تخصیص حافظه‌ی اضافی، برای رشته‌های تکراری صورت نمی‌گیرد. نمونه‌ی دیگر آن نمایش حرف "a" در یک ادیتور یا نمایشگر است. زمانیکه یک شیء Immutable حاوی اطلاعات حرف "a"، ایجاد شود، به سادگی می‌توان این تک وهله را جهت نمایش هزاران حرف "a" مورد استفاده‌ی مجدد قرار داد، بدون اینکه نگران مصرف حافظه‌ی بالای برنامه باشیم.
- کار با اشیاء Immutable به باگ‌های کمتری ختم می‌شود؛ چون همواره امکان تغییر حالت درونی یک شیء، توسط قسمت‌های مختلف برنامه، می‌تواند به باگ‌های ناخواسته‌ای منتهی شوند.
- Hash list‌ها که در جهت بهبود کارآیی برنامه‌ها بسیار مورد استفاده قرار می‌گیرند، بر اساس کلیدهایی Immutable قابل تشکیل هستند.


روش تعریف نوع‌های جدید record

کلاس ساده‌ی زیر را در نظر بگیرید:
public class User
{
   public string Name { set; get; }
}
برای تبدیل آن به یک نوع جدید record فقط کافی است واژه‌ی کلیدی class آن‌را با record جایگزین کنیم (به آن nominal record هم می‌گویند):
public record User
{
   public string Name { set; get; }
}
نحوه‌ی کار با آن و وهله سازی آن نیز دقیقا مانند کلاس‌ها است:
var user = new User();
user.Name = "User 1";
و ... در اینجا امکان انتساب مقداری به خاصیت Name وجود دارد؛ یعنی این خاصیت به صورت پیش‌فرض Immutable نیست.

روش تعریف دومی نیز در اینجا میسر است (به آن positional record هم می‌گویند):
public record User(string Name);
با این‌کار، به صورت خودکار یک record جدید تشکیل می‌شود که به همراه خاصیت Name است؛ چیزی شبیه به record قبلی که تعریف کردیم (به همین جهت نیاز است نام آن‌را شروع شده‌ی با حروف بزرگ درنظر بگیریم). با این تفاوت که این record، اینبار دارای سازنده است و همچنین خاصیت Name آن از نوع init-only است. در این حالت است که کل record به صورت immutable معرفی می‌شود؛ وگرنه روش تعریف یک خاصیت معمولی که از نوع init-only نیست (مانند مثال اول)، سبب بروز Immutability نخواهد شد.

برای کار با رکورد دومی که تعریف کردیم باید سازند‌ه‌ی این record را مقدار دهی کرد:
var user = new User("User 1");
// Error: Init-only property or indexer 'User.Name' can only be assigned
// in an object initializer, or on 'this' or 'base' in an instance constructor
// or an 'init' accessor. [CS9Features]csharp(CS8852)
user.Name = "User 1";
و همانطور که ملاحظه می‌کنید، چون خاصیت Name از نوع init-only است و در سازنده‌ی record تعریف شده مقدار دهی شده‌است، دیگر نمی‌توان آن‌را مقدار دهی مجدد کرد. همچنین در اینجا امکان استفاده‌ی از object initializers مانند new User { Name = "User 1" } نیز وجود ندارد؛ چون به همراه یک سازنده‌ی به صورت خودکار تولید شده‌است که خاصیتی init-only را مقدار دهی کرده‌است.


نوع جدید record چه اطلاعاتی را به صورت خودکار تولید می‌کند؟

روش دوم تعریف recordها اگر در نظر بگیریم:
public record User(string Name);
و در این حالت برنامه را کامپایل کنیم، به کدهای زیر که حاصل از دی‌کامپایل است، می‌رسیم:
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Text;
using CS9Features;

public class User : IEquatable<User>
{
 protected virtual Type EqualityContract
 {
  [System.Runtime.CompilerServices.NullableContext(1)]
  [CompilerGenerated]
  get
  {
   return typeof(User);
  }
 }

 public string Name
 {
  get;
  set/*init*/;
 }

 public User(string Name)
 {
  this.Name = Name;
  base..ctor();
 }

 public override string ToString()
 {
  StringBuilder stringBuilder = new StringBuilder();
  stringBuilder.Append("User");
  stringBuilder.Append(" { ");
  if (PrintMembers(stringBuilder))
  {
   stringBuilder.Append(" ");
  }
  stringBuilder.Append("}");
  return stringBuilder.ToString();
 }

 protected virtual bool PrintMembers(StringBuilder builder)
 {
  builder.Append("Name");
  builder.Append(" = ");
  builder.Append((object?)Name);
  return true;
 }

 [System.Runtime.CompilerServices.NullableContext(2)]
 public static bool operator !=(User? r1, User? r2)
 {
  return !(r1 == r2);
 }

 [System.Runtime.CompilerServices.NullableContext(2)]
 public static bool operator ==(User? r1, User? r2)
 {
  return (object)r1 == r2 || (r1?.Equals(r2) ?? false);
 }

 public override int GetHashCode()
 {
  return EqualityComparer<Type>.Default.GetHashCode(EqualityContract) * -1521134295 + EqualityComparer<string>.Default.GetHashCode(Name);
 }

 public override bool Equals(object? obj)
 {
  return Equals(obj as User);
 }

 public virtual bool Equals(User? other)
 {
  return (object)other != null && EqualityContract == other!.EqualityContract && EqualityComparer<string>.Default.Equals(Name, other!.Name);
 }

 public virtual User <Clone>$()
 {
  return new User(this);
 }

 protected User(User original)
 {
  Name = original.Name;
 }

 public void Deconstruct(out string Name)
 {
  Name = this.Name;
 }
}
این خروجی به صورت خودکار تولید شده‌ی توسط کامپایلر، چنین نکاتی را به همراه دارد:
- record‌ها هنوز هم در اصل همان class‌های استاندارد #C هستند (یعنی در اصل reference type هستند).
- این کلاس به همراه یک سازنده و یک خاصیت init-only است (بر اساس تعاریف ما).
- متد ToString آن بازنویسی شده‌است تا اگر آن‌را بر روی شیء حاصل، فراخوانی کردیم، به صورت خودکار نمایش زیبایی را از محتوای آن ارائه دهد.
- این کلاس از نوع  <IEquatable<User است که امکان مقایسه‌ی اشیاء record را به سادگی میسر می‌کند. برای این منظور متدهای GetHashCode و Equals آن به صورت خودکار بازنویسی و تکمیل شده‌اند (یعنی مقایسه‌ی آن شبیه به value-type است).
- این کلاس امکان clone کردن اطلاعات جاری را مهیا می‌کند.
- همچنین به همراه یک متد Deconstruct هم هست که جهت انتساب خواص تعریف شده‌ی در آن، به یک tuple مفید است.

بنابراین یک رکورد به همراه قابلیت‌هایی است که سال‌ها در زبان #C وجود داشته‌اند و شاید ما به سادگی حاضر به تشکیل و تکمیل آن‌ها نمی‌شدیم؛ اما اکنون کامپایلر زحمت کدنویسی خودکار آن‌ها را متقبل می‌شود!


ساخت یک وهله‌ی جدید از یک record با clone کردن آن

اگر به کدهای حاصل از دی‌کامپایل فوق دقت کنید، یک قسمت جدید clone هم با syntax خاصی در آن ظاهر شده‌است:
public virtual User <Clone>$()
{
  return new User(this);
}
زمانیکه یک شیء Immutable است، دیگر نمی‌توان مقادیر خواص آن‌را در ادامه تغییر داد. اما اگر نیاز به اینکار وجود داشت، باید چکار کنیم؟ در C# 9.0 برای ایجاد وهله‌ی جدید معادلی از یک record، واژه‌ی کلیدی جدیدی را به نام with، اضافه کرده‌اند. برای نمونه اگر record زیر را در نظر بگیریم که دارای دو خاصیت نام و سن است:
public record User(string Name, int Age);
وهله سازی متداول آن به صورت زیر خواهد بود:
var user1 = new User("User 1", 21);
اما اگر خواستیم خاصیت سن آن‌را تغییر دهیم، می‌توان با استفاده از واژه‌ی کلیدی with، به صورت زیر عمل کرد:
var user2 = user1 with { Age = 31 };
کاری که در اصل در اینجا انجام می‌شود، ابتدا clone کردن شیء user1 است (یعنی دقیقا یک وهله‌ی جدید از user1 را با تمام اطلاعات قبلی آن در اختیار ما قرار می‌دهد که این وهله، ارجاعی را به شیء قبلی ندارد و از آن منقطع است). بنابراین نام user2، دقیقا همان "User 1" است که پیشتر تنظیم کردیم؛ با این تفاوت که اینبار مقدار سن آن متفاوت است. با استفاده از cloning، هنوز شیء user1 که immutable است، دست نخورده باقی مانده‌است و توسط with می‌توان خواص آن‌را تغییر داد و حاصل کار، یک شیء کاملا جدید است که مکان آن در حافظه، با مکان شیء user1 در حافظه، یکی نیست.


مقایسه‌ی نوع‌های record

در کدهای حاصل از دی‌کامپایل فوق، قسمت عمده‌ای از آن به تکمیل اینترفیس <IEquatable<User پرداخته شده بود. به همین جهت اکنون دو رکورد با مقادیر خواص یکسانی را ایجاد می‌کنیم:
var user1 = new User("User 1", 21);
var user2 = new User("User 1", 21);
سپس یکبار آن‌ها را از طریق عملگر == و بار دیگر به کمک متد Equals، مقایسه می‌کنیم:
Console.WriteLine("user1.Equals(user2) -> {0}", user1.Equals(user2));
Console.WriteLine("user1 == user2 -> {0}", user1 == user2);
خروجی هر دو حالت، True است:
user1.Equals(user2) -> True
user1 == user2 -> True
این مورد، یکی از مهم‌ترین تفاوت‌های recordها با classها هستند.
- زمانیکه عملگر == را بر روی شیء user1 و user2 اعمال می‌کنیم، اگر User، از نوع کلاس معمولی باشد، حاصل آن false خواهد بود؛ چون این دو، به یک مکان از حافظه اشاره نمی‌کنند، حتی با اینکه مقادیر خواص هر دو شیء یکی است.
- اما اگر به قطعه کد دی‌کامپایل شده دقت کنید، در یک رکورد که هر چند در اصل یک کلاس است، حتی عملگر == نیز بازنویسی شده‌است تا در پشت صحنه همان متد Equals را فراخوانی کند و این متد با توجه به پیاده سازی اینترفیس <IEquatable<User، اینبار دقیقا مقادیر خواص رکورد را یک به یک مقایسه کرده و نتیجه‌ی حاصل را باز می‌گرداند:
public virtual bool Equals(User? other)
{
   return (object)other != null &&
 EqualityContract == other!.EqualityContract &&
 EqualityComparer<string>.Default.Equals(Name, other!.Name) && 
EqualityComparer<int>.Default.Equals(Age, other!.Age);
}
این متدی است که به صورت خودکار توسط کامپایلر جهت مقایسه‌ی مقادیر خواص رکورد جدید تعریف شده، تشکیل شده‌است. به عبارتی recordها از لحاظ مقایسه، شبیه به value objects عمل می‌کنند؛ هرچند در اصل یک کلاس هستند.

یک نکته: بازنویسی عملگر == در SDK نگارش rc2 فعلی رخ‌داده‌است و در نگارش‌های قبلی preview، اینگونه نبود.


امکان ارث‌بری در recordها

دو رکورد زیر را در نظر بگیرید که اولی به همراه Name است و نمونه‌ی مشتق شده‌ی از آن، خاصیت init-only سن را نیز به همراه دارد:
    public record User
    {
        public string Name { get; init; }

        public User(string name)
        {
            Name = name;
        }
    }

    public record UserWithAge : User
    {
        public int Age { get; init; }

        public UserWithAge(string name, int age) : base(name)
        {
            Age = age;
        }
    }
در اینجا روش دیگر تعریف recordها را ملاحظه می‌کنید که شبیه به کلاس‌ها است و خواص آن init-only هستند. در این حالت اگر مقایسه‌ی زیر را انجام دهیم:
var user1 = new User("User 1");
var user2 = new UserWithAge("User 1", 21);

Console.WriteLine("user1.Equals(user2) -> {0}", user1.Equals(user2));
Console.WriteLine("user1 == user2 -> {0}", user1 == user2);
به خروجی زیر خواهیم رسید:
user1.Equals(user2) -> False
user1 == user2 -> False
علت آن را هم پیشتر بررسی کردیم. تساوی رکوردها بر اساس مقایسه‌ی مقدار تک تک خواص آن‌ها صورت می‌گیرد و چون user1 به همراه سن نیست، مقایسه‌ی این دو، false را بر می‌گرداند.

امکان تعریف ارث‌بری رکوردها به صورت زیر نیز وجود دارد و الزاما نیازی به روش تعریف کلاس مانند آن‌ها، مانند مثال فوق نیست:
public abstract record Food(int Calories);
public record Milk(int C, double FatPercentage) : Food(C);


رکوردها متد ToString را بازنویسی می‌کنند

در مثال قبلی اگر یک ToString را بر روی اشیاء تشکیل شده فراخوانی کنیم:
Console.WriteLine(user1.ToString());
Console.WriteLine(user2.ToString());
به این خروجی‌ها می‌رسیم:
User { Name = User 1 }
UserWithAge { Name = User 1, Age = 21 }
که حاصل بازنویسی خودکار متد ToString در پشت صحنه است.


امکان استفاده‌ی از Deconstruct در رکوردها

دو روش برای تعریف رکوردها وجود دارند؛ یکی شبیه به تعریف کلاس‌ها است و دیگری تعریف یک سطری، که positional record نیز نامیده می‌شود:
public record Person(string Name, int Age);
 فقط در حالت تعریف یک سطری positional record فوق است که خروجی خودکار نهایی تولیدی، به همراه public void Deconstruct نیز خواهد بود:
public void Deconstruct(out string Name, out int Age)
{
  Name = this.Name;
  Age = this.Age;
}
در این حالت می‌توان از tuples نیز برای کار با آن استفاده کرد:
var (name, age) = new Person("User 1", 21);
واژه‌ی «positional» نیز دقیقا به همین قابلیت اشاره می‌کند که بر اساس موقعیت خواص تعریف شده‌ی در رکورد، امکان Deconstruct آن‌ها به متغیرهای یک tuple وجود دارد. حالت تعریف کلاس مانند رکوردها، nominal نام دارد.


امکان استفاده‌ی از نوع‌های record در ASP.NET Core 5x

سیستم model binding در ASP.NET Core 5x، از نوع‌های record نیز پشتیبانی می‌کند؛ یک مثال:
 public record Person([Required] string Name, [Range(0, 150)] int Age);

 public class PersonController
 {
   public IActionResult Index() => View();

   [HttpPost]
   public IActionResult Index(Person person)
   {
    // ...
   }
 }


پرسش و پاسخ

آیا نوع‌های record به صورت value type معرفی می‌شوند؟
پاسخ: خیر. رکوردها در اصل reference type هستند؛ اما از لحاظ مقایسه، شبیه به value types عمل می‌کنند.

آیا می‌توان در یک کلاس، خاصیتی از نوع رکورد را تعریف کرد؟
پاسخ: بله. از این لحاظ محدودیتی وجود ندارد.

آیا می‌توان در رکوردها، از struct و یا کلاس‌ها جهت تعریف خواص استفاده کرد؟
پاسخ: بله. از این لحاظ محدودیتی وجود ندارد.

آیا می‌توان از واژه‌ی کلیدی with با کلاس‌ها و یا structها استفاده کرد؟
پاسخ: خیر. این واژه‌ی کلیدی در C# 9.0 مختص به رکوردها است.

آیا رکوردها به صورت پیش‌فرض Immutable هستند؟
پاسخ: اگر آن‌ها را به صورت positional records تعریف کنید، بله. چون در این حالت خواص تشکیل شده‌ی توسط آن‌ها از نوع init-only هستند. در غیراینصورت، می‌توان خواص غیر init-only را نیز به تعریف رکوردها اضافه کرد.
پاسخ به بازخورد‌های پروژه‌ها
استفاده از فونت های مختلف در گزارش
بله مربوط به Header سفارشی است. ممنون برای Header سفارشی هم من رفتم کد زیر رو نوشتم درست شد
 var fontPath = AppPath.ApplicationPath + "\\Fonts\\BNAZANIN.TTF";
            //Environment.GetEnvironmentVariable("SystemRoot") + "\\fonts\\tahoma.ttf";
        var baseFont = BaseFont.CreateFont(fontPath, BaseFont.IDENTITY_H, BaseFont.EMBEDDED);
        var tahomaFont = new Font(baseFont, 10, Font.NORMAL, BaseColor.BLACK);
ولی اینجا دیگه نمی‌تونم دو فونت یعنی یک فونت برای فارسی و یک فونت برای انگلیسی تعریف کنم؟
نظرات مطالب
معرفی کتابخانه PdfReport
1- در صورتیکه چندین ستون عددی داشته باشیم و بخواهیم برای همه آنها برچسب‌های "نقل از صفحه قبل"، "جمع صفحه"، "جمع کل" داشته باشیم، چگونه امکان پذیر است؟
2- آیا درست متوجه شدم که: جمع ستون دارای AggregateFunction، مقابل برچسب "جمع صفحه" درج می‌شود؟
مطالب
شروع به کار با AngularJS 2.0 و TypeScript - قسمت پنجم - بررسی چرخه‌ی حیات کامپوننت‌ها
در قسمت قبل، نگاهی داشتیم به 4 نوع مختلف data binding در AngularJS 2.0. در قسمت جاری می‌خواهیم کیفیت کدهای کامپوننت لیست محصولات را با strong typing بهبود بخشیده و همچنین چرخه‌ی حیات کامپوننت‌ها را به همراه ایجاد custom pipes بررسی کنیم.


افزودن strong typing به کامپوننت نمایش لیست محصولات

یکی از مزایای کار با TypeScript امکان انتساب نوع‌های مشخص یا سفارشی، به متغیرها و اشیاء تعریف شده‌است. برای مثال تاکنون هر خاصیت تعریف شده‌ای دارای نوع است. اما هنوز نوعی را برای آرایه‌ی محصولات تعریف نکرده‌ایم و نوع آن، نوع پیش فرض any است که برخلاف رویه‌ی متداول کار با TypeScript است.
برای تعریف نوع‌های سفارشی می‌توان از اینترفیس‌های TypeScript استفاده کرد. یک اینترفیس، قراردادی است که نحوه‌ی تعریف تعدادی خاصیت و متد به هم مرتبط را مشخص می‌کند. سپس کلاس‌های مختلف می‌توانند با پیاده سازی این اینترفیس، قرارداد تعریف شده‌ی در آن را عملی کنند. همچنین از اینترفیس‌ها می‌توان به عنوان یک data type جدید نیز استفاده کرد. البته ES 5 و ES 6 از اینترفیس‌ها پشتیبانی نمی‌کنند و تعریف آن‌ها در TypeScript صرفا جهت کمک به کامپایلر، برای یافتن خطاها، پیش از اجرای برنامه است و به کدهای جاوا اسکریپتی معادلی ترجمه نمی‌شوند.

در ادامه برای تکمیل مثال این سری، فایل جدید App\products\product.ts را به پروژه اضافه کنید؛ با این محتوا:
export interface IProduct {
    productId: number;
    productName: string;
    productCode: string;
    releaseDate: string;
    price: number;
    description: string;
    starRating: number;
    imageUrl: string;
}
یک اینترفیس، با واژه‌ی کلیدی interface تعریف شده و سپس نام آن تعریف می‌شود. مرسوم است این نام با I شروع شود؛ هرچند الزامی نیست و در بسیاری از اینترفیس‌های AngularJS 2.0 از این روش نامگذاری استفاده نشده‌است.
همچنین از آنجائیکه این اینترفیس را در یک فایل ts مجزا قرار داده‌ایم، برای اینکه بتوان از آن در سایر قسمت‌های برنامه استفاده کرد، نیاز است در ابتدای آن، واژه‌ی کلیدی export را نیز ذکر کرد.

پس از تعریف این اینترفیس، برای استفاده از آن به عنوان یک data type جدید، ابتدا ماژول آن import خواهد شد و سپس از نام آن به عنوان نوع داده‌ی جدیدی، استفاده می‌شود. برای این منظور فایل product-list.component.ts را گشوده و تغییرات ذیل را به آن اعمال کنید:
import { Component } from 'angular2/core';
import { IProduct } from './product';
 
@Component({
    selector: 'pm-products',
    templateUrl: 'app/products/product-list.component.html'
})
export class ProductListComponent {
    // as before ...

    products: IProduct[] = [
        // as before ...
    ]; 

    // as before ...
}
در اینجا ابتدا IProduct را import و سپس بجای any، نوع جدید IProduct را جهت مشخص سازی نوع آرایه‌ی products تعریف کرده‌ایم.
مزیت اینکار این است که برای مثال در اینجا اگر در لیست اعضای آرایه‌ی products، نام خاصیتی اشتباه تایپ شده باشد یا حتی بجای عدد، از رشته استفاده شده باشد، بلافاصله در ادیتور مورد استفاده، خطای مرتبط گوشزد شده و همچنین این فایل دیگر کامپایل نخواهد شد. به علاوه اینبار برای تعریف خواص اعضای آرایه‌ی products، ادیتور مورد استفاده، intellisense را نیز دراختیار ما قرار می‌دهد و کاملا مشخص است که چه اعضایی مدنظر هستند و نوع آن‌ها چیست.



مدیریت cssهای هر کامپوننت به صورت مجزا

هنگام ساخت یک قالب یا template، در بسیاری از اوقات نیاز است css مرتبط با آن نیز، منحصر به همان قالب بوده و نشتی نداشته باشد. برای مثال زمانیکه یک کامپوننت را درون کامپوننتی دیگر قرار می‌دهیم، باید css آن نیز در دسترس قرار بگیرد و css فعلی کامپوننت دربرگیرنده را بازنویسی نکند. روش‌‌های مختلفی برای مدیریت این مساله وجود دارند:
الف) تعریف شیوه نامه‌ها به صورت inline داخل خود قالب‌ها. این حالت، مشکلات نگهداری و استفاده‌ی مجدد را دارد.
ب) تعریف شیوه نامه‌ها در یک فایل خارجی css و سپس لینک کردن آن به صفحه‌ای اصلی یا index.html
در این حالت به ازای هر فایل، یکبار باید این تعریف در صفحه‌ای اصلی سایت صورت گیرد. همچنین این فایل‌ها می‌توانند مقادیر یکدیگر را بازنویسی کرده و بر روی هم تاثیر بگذارند.
ج) تعریف شیوه نامه‌ها به همراه تعریف کامپوننت. این روشی است که توسط AngularJS توصیه شده‌است و نگهداری و مقیاس پذیری آن ساده‌تر است.
تزئین کننده‌ی Component به همراه دو خاصیت دیگر به نام‌های styles و stylesUrl نیز می‌باشد.
در حالت استفاده از خاصیت styles، شیوه‌نامه‌ی متناظر با کامپوننت، در همانجا به صورت inline تعریف می‌شود:
 @Component({
    //...
    styles: ['thead {color: blue;}']
})
همانطور که مشاهده می‌کنید، خاصیت styles به صورت یک آرایه تعریف شده‌است و امکان پذیرش چندین شیوه نامه‌ی جدا شده‌ی با کاما را دارد.
روش بهتر، استفاده از خاصیت styleUrls است که در آن می‌توان مسیر یک یا چند فایل css را مشخص کرد:
 @Component({
     //...
     styleUrls: ['app/products/product-list.component.css']
})
این خاصیت نیز یک آرایه را می‌پذیرد و در اینجا می‌توان مسیر چندین فایل css را در صورت نیاز ذکر کرد.

برای آزمایش آن فایل جدید product-list.component.css را به پوشه‌ی products مثال این سری اضافه کنید؛ با این محتوا:
thead {
  color: #337AB7;
}
سپس لینک این فایل را به مجموعه خواص کامپوننت لیست محصولات (product-list.component.ts) اضافه می‌کنیم:
@Component({
    selector: 'pm-products',
    templateUrl: 'app/products/product-list.component.html',
    styleUrls: ['app/products/product-list.component.css']
})
export class ProductListComponent {
   //...
در این حالت اگر برنامه را اجرا کنید، رنگ سرستون‌های جدول محصولات به نحو ذیل تغییر کرده‌اند:


یک نکته

شیوه نامه‌ای که به این صورت توسط AngularJS 2.0 اضافه می‌شود، با سایر شیوه نامه‌های موجود تداخل نخواهد کرد. علت آن‌را در تصویر ذیل که با استفاده از developer tools مرورگرها قابل بررسی است، می‌توان مشاهده کرد:


در اینجا AngularJS 2.0، با ایجاد ویژگی‌های سفارشی خودکار (attributes) میدان دید css را کنترل می‌کند. به این ترتیب شیوه نامه‌ی کامپوننت یک، که درون کامپوننت دو قرار گرفته‌است، نشتی نداشته و بر روی سایر قسمت‌های صفحه تاثیری نخواهد گذاشت؛ برخلاف شیوه نامه‌هایی که به صورت متداولی به صفحه‌ی اصلی سایت لینک شده‌‌اند.


بررسی چرخه‌ی حیات کامپوننت‌ها

هر کامپوننت دارای چرخه‌ی حیاتی است که توسط AngularJS 2.0 مدیریت می‌شود و شامل مراحلی مانند ایجاد، رندر، ایجاد و رندر فرزندان آن، پردازش تغییرات آن و در نهایت تخریب آن کامپوننت می‌شود. برای اینکه بتوان با برنامه نویسی به این مراحل چرخه‌ی حیات یک کامپوننت دسترسی یافت، تعدادی life cycle hook طراحی شده‌اند. سه مورد از مهم‌ترین life cycle hooks شامل موارد ذیل هستند:
الف) OnInit: از این hook برای انجام کارهای آغازین یک کامپوننت مانند دریافت اطلاعات از سرور، استفاده می‌شود.
ب) OnChanges: از آن جهت انجام اعمالی پس از تغییرات input properties استفاده می‌شود.
خواص ورودی و همچنین کار با سرور را در قسمت‌های بعدی بررسی خواهیم کرد.
ج) OnDestroy: از آن جهت پاکسازی منابع اختصاص داده شده استفاده می‌شود.

برای استفاده‌ی از این hookها، نیاز است اینترفیس آن‌ها را پیاده سازی کنیم. از آنجائیکه AngularJS 2.0 نیز با TypeScript نوشته شده‌است، به همراه تعدادی اینترفیس از پیش تعریف شده می‌باشد. برای مثال به ازای هر life cycle hook، یک اینترفیس تعریف شده در آن وجود دارد. برای نمونه اینترفیس hook ایی به نام OnInit، دقیقا همان OnInit، نام دارد (و با I شروع نشده‌است):
 export class ProductListComponent implements OnInit {
پس از ذکر implements OnInit در انتهای تعریف کلاس، اکنون باید ماژول مرتبط با آن نیز جهت شناسایی این اینترفیس import شود:
 import { Component, OnInit } from 'angular2/core';
و دست آخر متد ngOnInit تعریف شده‌ی در این اینترفیس باید توسط کلاس پیاده سازی کننده‌ی آن تامین شود:
ngOnInit(): void {
    console.log('In OnInit');
}
نام این متدها عموما شروع شده با ng و ختم شده به نام اینترفیس hook متناظر هستند؛ مانند ngOnInit فوق.

به عنوان تمرین، فایل product-list.component.ts را گشوده و سه مرحله‌ی implements سپس import و در آخر تعریف متد ngOnInit فوق را به آن اضافه کنید.
در ادامه برنامه را اجرا کرده و به کنسول developer tools مرورگر خود جهت مشاهده‌ی console.log فوق مراجعه کنید:



ساخت یک Pipe سفارشی جهت فعال سازی textbox فیلتر کردن محصولات

همانطور که در قسمت قبل نیز عنوان شد، کار pipes، تغییر اطلاعات حاصل از data binding، پیش از نمایش آن‌ها در رابط کاربری است و AngularJS 2.0 به همراه تعدادی pipe توکار است؛ مانند currency، percent و غیره. در ادامه قصد داریم یک pipe سفارشی را ایجاد کنیم تا بر روی حلقه‌ی ngFor* نمایش لیست محصولات تاثیرگذار شود و همچنین ورودی خود را از مقدار وارد شده‌ی توسط کاربر دریافت کند.
برای این منظور، یک فایل جدید را به نام product-filter.pipe.ts به پوشه‌ی products اضافه کنید. سپس کدهای آن‌را به نحو ذیل تغییر دهید:
import { PipeTransform, Pipe } from 'angular2/core';
import { IProduct } from './product';
 
@Pipe({
    name: 'productFilter'
})
export class ProductFilterPipe implements PipeTransform {
 
    transform(value: IProduct[], args: string[]): IProduct[] {
        let filter: string = args[0] ? args[0].toLocaleLowerCase() : null;
        return filter ? value.filter((product: IProduct) =>
            product.productName.toLocaleLowerCase().indexOf(filter) != -1) : value;
    }
}
برای تعریف یک pipe سفارشی جدید، کار با پیاده سازی اینترفیس PipeTransform شروع می‌شود. این اینترفیس دارای متدی است به نام transform که امضای آن‌را در کدهای فوق ملاحظه می‌کنید. کار آن اعمال تغییرات بر روی value دریافتی و سپس بازگشت آن است. بنابراین اولین پارامتر آن، مقادیر اصلی را که قرار است تغییر کنند، مشخص می‌کند. در اینجا نوع آن‌را از نوع اینترفیسی که در ابتدای بحث تعریف کردیم، تعیین کرده‌ایم. پارامتر دوم آن، لیست پارامترها و آرگومان‌های اختیاری این فیلتر را مشخص می‌کند.
برای مثال در اینجا می‌خواهیم شرایط فیلتر محصولات وارد شده‌ی توسط کاربر را دریافت کنیم.
خروجی این متد نیز از نوع آرایه‌ای از IProduct تعریف شده‌است؛ از این جهت که نتیجه نهایی فیلتر اطلاعات نیز آرایه‌ای از همین نوع است. کار این pipe پیاده سازی متد contains به صورت غیرحساس به کوچکی و بزرگی حروف است.
سپس بلافاصله بالای نام این کلاس، از یک decorator جدید به نام Pipe استفاده شده‌است تا به AngularJS 2.0 اعلام شود، این کلاس، صرفا یک کلاس معمولی نیست و یک Pipe است.
در ابتدای فایل هم importهای لازم جهت تعریف اینترفیس‌های مورد استفاده‌ی در این ماژول، ذکر شده‌اند.

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

پس از تعریف این pipe سفارشی، برای استفاده‌ی از آن در یک template، به فایل product-list.component.html مراجعه کرده و سپس ngFor* آن‌را به نحو ذیل تغییر می‌دهیم:
 <tr *ngFor='#product of products | productFilter:listFilter'>
همانطور که ملاحظه می‌کنید، نام این pipe جدید که در decorator مرتبط با آن، توسط خاصیت name مشخص گردیده‌است، ذکر شده‌است. پس از آن یک : قرار گرفته‌است که مشخص کننده‌ی پارامتر اول این pipe است که در اینجا خاصیت listFilter تعریف شده‌ی در قسمت قبل را به آن انتساب داده‌ایم.
اگر از قسمت قبل به خاطر داشته باشید، این خاصیت را توسط two-way binding به روز می‌کنیم (اطلاعات وارد شده‌ی در textbox، بلافاصله به این خاصیت منعکس می‌شوند و برعکس):
 <input type='text'  [(ngModel)]='listFilter' />
تا اینجا این pipe را در قالب لیست محصولات بکار بردیم؛ اما کامپوننت آن نمی‌داند که این pipe را باید از کجا تامین کند. به همین جهت فایل product-list.component.ts را گشوده و خاصیت pipes را به نحو ذیل مقدار دهی کنید:
import { Component, OnInit } from 'angular2/core';
import { IProduct } from './product';
import { ProductFilterPipe } from './product-filter.pipe';
 
@Component({
    selector: 'pm-products',
    templateUrl: 'app/products/product-list.component.html',
    styleUrls: ['app/products/product-list.component.css'],
    pipes: [ProductFilterPipe]
})
export class ProductListComponent implements OnInit {
   //...
در اینجا دو کار صورت گرفته‌است. ابتدا افزودن pipe جدید ProductFilterPipe به لیست خاصیت pipes کامپوننت و سپس import ماژول آن درابتدای فایل.

اکنون اگر برنامه را اجرا کنید، خروجی ذیل را مشاهده خواهید کرد:


در اینجا چون مقدار فیلتر وارد شده‌ی پیش فرض، cart است، فقط ردیف Garden Cart نمایش داده شده‌است. اگر این مقدار را خالی کنیم، تمام ردیف‌ها نمایش داده می‌شوند و اگر برای مثال ham را جستجو کنیم، فقط ردیف Hammer نمایش داده می‌شود.


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


خلاصه‌ی بحث

- اینترفیس‌ها یکی از روش‌های بهبود strong typing برنامه‌های AngularJS 2.0 هستند.
- جهت مدیریت بهتر شیوه‌نامه‌های هر کامپوننت بهتر است از روش styleUrls استفاده شود تا از نشتی‌های تعاریف شیوه‌نامه‌ها جلوگیری گردد.
- از life cycle hooks برای مدیریت رخدادهای مرتبط با طول عمر یک کامپوننت استفاده می‌شود؛ برای مثال دریافت اطلاعات از سرور و یا پاکسازی منابع مصرفی.
- تعریف یک pipe سفارشی با پیاده سازی اینترفیس PipeTransform انجام می‌شود. سپس نام این Pipe، به قالب مدنظر اضافه شده و در ادامه نیاز است کامپوننت استفاده کننده‌ی از این قالب را نیز از وجود این Pipe مطلع کرد.
مطالب دوره‌ها
بررسی قسمت‌های مختلف قالب پروژه WPF Framework تهیه شده
پس از ایجاد یک Solution جدید توسط قالب WPF Framework، هشت پروژه به صورت خودکار اضافه خواهند شد:



1) پروژه ریشه که بسته به نامی که در ابتدای کار انتخاب می‌کنید، تغییر نام خواهد یافت.
برای مثال اگر نام وارد شده در ابتدای کار MyWpfFramework باشد، این پروژه ریشه نیز، MyWpfFramework نام خواهد داشت. از آن صرفا جهت افزودن Viewهای برنامه استفاده می‌کنیم. کلیه Viewها در پوشه View قرار خواهند گرفت و با توجه به ساختار خاصی که در اینجا انتخاب شده، این Viewها باید از نوع Page انتخاب شوند تا با سیستم راهبری فریم ورک هماهنگ کار کنند.
در داخل پوشه Views، هر بخش از برنامه را می‌توان داخل یک زیر پوشه قرار داد. برای مثال قسمت Login سیستم، دارای سه صفحه ورود، نمایش پیام خوش آمد و نمایش صفحه عدم دسترسی است.
متناظر با هر Page اضافه شده، در پروژه MyWpfFramework.Infrastructure یک ViewModel در صورت نیاز اضافه خواهد شد. قرار داد ما در اینجا ترکیب نام View به علاوه کلمه ViewModel است. برای مثال اگر نام View اضافه شده به پروژه ریشه برنامه، LoginPage است، نام ViewModel متناظر با آن باید LoginPageViewModel باشد تا به صورت خودکار توسط برنامه ردیابی و وهله سازی گردد.
این پروژه از کتابخانه MahApps.Metro استفاده می‌کند و اگر به فایل MainWindow.xaml.cs آن مراجعه کنید، ارث بری پنجره اصلی برنامه را از کلاس MetroWindow مشاهده خواهید نمود. این فایل‌ها نیازی به تغییر خاصی نداشته و به همین نحو در این قالب قابل استفاده هستند.
و در پوشه Resources آن یک سری قلم و آیکون را می‌توانید مشاهده نمائید.

2) پروژه MyWpfFramework.Common
در این پروژه کلاس‌هایی قرار می‌گیرند که قابلیت استفاده در انواع و اقسام پروژه‌های WPF را دارند و الزاما وابسته به پروژه جاری نیستند. یک سری کلاس‌های کمکی در این پروژه Common قرار گرفته‌اند و قسمت‌های مختلف سیستم را تغذیه می‌کنند؛ مانند خواندن اطلاعات از فایل کانفیگ، هش کردن کلمه عبور، یک سری متد عمومی برای کار با EF، کلاس‌های عمومی مورد نیاز در حین استفاده از الگوی MVVM، اعتبارسنجی و امثال آن.
در این پروژه از کلاس PageAuthorizationAttribute آن جهت مشخص سازی وضعیت دسترسی به صفحات تعریف شده در پروژه ریشه استفاده خواهد شد.
نمونه‌ای از آن‌را برای مثال با مراجعه به سورس صفحه About.xaml.cs می‌توانید مشاهده کنید که در آن AuthorizationType.AllowAnonymous تنظیم شده و به این ترتیب تمام کاربران اعتبارسنجی نشده می‌توانند این صفحه را مشاهده کنند.
همچنین در این پروژه کلاس BaseViewModel قرار دارد که جهت مشخص سازی کلیه کلاس‌های ViewModel برنامه باید مورد استفاده قرار گیرد. سیستم طراحی شده، به کمک این کلاس پایه است که می‌تواند به صورت خودکار ViewModelهای متناظر با Viewها را یافته و وهله سازی کند (به همراه تمام وابستگی‌های تزریق شده به آن‌ها).
به علاوه کلاس DataErrorInfoBase آن برای یکپارچه سازی اعتبارسنجی با EF طراحی شده است. اگر به کلاس BaseEntity.cs مراجعه کنید که در پروژه MyWpfFramework.DomainClasses قرار دارد، نحوه استفاده آن‌را ملاحظه خواهید نمود. به این ترتیب حجم بالایی از کدهای تکرای، کپسوله شده و قابلیت استفاده مجدد را پیدا می‌کنند.
قسمت‌های دیگر پروژه Common، برای ثبت وقایع برنامه مورد استفاده قرار می‌گیرند. استفاده از آن‌ها را در فایل App.xaml.cs پروژه ریشه برنامه ملاحظه می‌کنید و نیاز به تنظیم خاص دیگری در اینجا وجود ندارد.

3) پروژه MyWpfFramework.DataLayer
کار تنظیمات EF در اینجا انجام می‌شود (و قسمت عمده‌ای از آن انجام شده است). تنها کاری که در آینده برای استفاده از آن نیاز است انجام شود، مراجعه به کلاس MyWpfFrameworkContext.cs و افزودن DbSetهای لازم است. همچنین اگر نیاز به تعریف نگاشت‌های اضافه‌تری وجود داشت، می‌توان از پوشه Mappings آن استفاده کرد.
در این پروژه الگوی واحد کار پیاده سازی شده است و همچنین سعی شده تمام کلاس‌های آن دارای کامنت‌های کافی جهت توضیح قسمت‌های مختلف باشند.
کلاس MyDbContextBase به اندازه کافی غنی سازی شده‌است، تا در وقت شما، در زمینه تنظیم مباحثی مانند اعتبارسنجی و نمایش پیغام‌های لازم به کاربر، یک دست سازی ی و ک ورودی در برنامه و بسیاری از نکات ریز دیگر صرفه جویی شود.
در اینجا از خاصیت ContextHasChanges جهت بررسی وضعیت Context جاری و نمایش پیغامی به کاربر در مورد اینکه آیا مایل هستید تغییرات را ذخیره کنید یا خیر استفاده می‌شود.
در متد auditFields آن یک سری خاصیت کلاس BaseEntity که پایه تمامی کلاس‌های Domain model برنامه خواهد بود به صورت خودکار مقدار دهی می‌شوند. مثلا این رکورد را چه کسی ثبت کرده یا چه کسی ویرایش و در چه زمانی. به این ترتیب دیگر نیازی نیست تا در برنامه نگران تنظیم و مقدار دهی آن‌ها بود.
کلاس MyWpfFrameworkMigrations به حالت AutomaticMigrationsEnabled تنظیم شده است و ... برای یک برنامه دسکتاپ WPF کافی و مطلوب است و ما را از عذاب به روز رسانی دستی ساختار بانک اطلاعاتی برنامه با تغییرات مدل‌ها، رها خواهد ساخت. عموما برنامه‌های دسکتاپ پس از طراحی، آنچنان تغییرات گسترده‌ای ندارند و انتخاب حالت Automatic در اینجا می‌تواند کار توزیع آن‌را نیز بسیار ساده کند. از این جهت که بانک اطلاعاتی انتخابی از نوع SQL Server CE نیز عمدا این هدف را دنبال می‌کند: عدم نیاز به نگهداری و وارد شدن به جزئیات نصب یک بانک اطلاعاتی بسیار پیشرفته مانند نگارش‌های کامل SQL Server. هرچند زمانیکه با EF کار می‌کنیم، سوئیچ به بانک‌های اطلاعاتی صرفا با تغییر رشته اتصالی فایل app.config برنامه اصلی و مشخص سازی پروایدر مناسب قابل انجام خواهد بود.
در فایل MyWpfFrameworkMigrations، توسط متد addRolesAndAdmin کاربر مدیر سیستم در آغاز کار ساخت بانک اطلاعاتی به صورت خودکار افزوده خواهد شد.


4) پروژه MyWpfFramework.DomainClasses
کلیه کلاس‌های متناظر با جداول بانک اطلاعاتی در پروژه MyWpfFramework.DomainClasses قرار خواهند گرفت. نکته مهمی که در اینجا باید رعایت شود، مزین کردن این کلاس‌ها به کلاس پایه BaseEntity می‌باشد که نمونه‌ای از آن‌را در کلاس User پروژه می‌توانید ملاحظه کنید.
BaseEntity چند کار را با هم انجام می‌دهد:
- اعمال خودکار DataErrorInfoBase جهت یکپارچه سازی سیستم اعتبارسنجی EF با WPF (برای مثال به این ترتیب خطاهای ذکر شده در ویژگی‌های خواص کلاس‌ها توسط WPF نیز خوانده خواهند شد)
- اعمال ImplementPropertyChanged به کلاس‌های دومین برنامه. به این ترتیب برنامه کمکی Fody که کار Aspect oriented programming را انجام می‌دهد، اسمبلی برنامه را ویرایش کرده و متدها و تغییرات لازم جهت پیاده سازی INotifyPropertyChanged را اضافه می‌کند. به این ترتیب به کلاس‌های دومین بسیار تمیزی خواهیم رسید با حداقل نیاز به تغییرات و نگهداری ثانویه.
- فراهم آوردن فیلدهای مورد نیاز جهت بازرسی سیستم؛ مانند اینکه چه کسی یک رکورد را ثبت کرده یا ویرایش و در چه زمانی

نقش‌های سیستم در کلاس SystemRole تعریف می‌شوند. به ازای هر نقش جدیدی که نیاز بود، تنها کافی است یک خاصیت bool را در اینجا اضافه کنید. سپس نام این خاصیت در ویژگی PageAuthorizationAttribute به صورت خودکار قابل استفاده خواهد بود. برای مثال به پروژه ریشه مراجعه و به فایل AddNewUser.xaml.cs دقت کنید؛ چنین تعریفی را در بالای کلاس مرتبط مشاهده خواهید کرد:
 [PageAuthorization(AuthorizationType.ApplyRequiredRoles, "IsAdmin, CanAddNewUser")]
در اینجا AuthorizationType سه حالت را می‌تواند داشته باشد:
    /// <summary>
    /// وضعیت اعتبار سنجی صفحه را مشخص می‌کند
    /// </summary>
    public enum AuthorizationType
    {
        /// <summary>
        /// همه می‌توانند بدون اعتبار سنجی، دسترسی به این صفحات داشته باشند
        /// </summary>
        AllowAnonymous,

        /// <summary>
        /// کاربران وارد شده به سیستم بدون محدودیت به این صفحات دسترسی خواهند داشت
        /// </summary>
        FreeForAuthenticatedUsers,

        /// <summary>
        /// بر اساس نام نقش‌هایی که مشخص می‌شوند تصمیم گیری خواهد شد
        /// </summary>
        ApplyRequiredRoles
    }
اگر حالت ApplyRequiredRoles را انتخاب کردید، در پارامتر اختیاری دوم ویژگی PageAuthorization نیاز است نام یک یا چند خاصیت کلاس SystemRole را قید کنید. بدیهی است کاربر متناظر نیز باید دارای این نقش‌ها باشد تا بتواند به این صفحه دسترسی پیدا کند، یا خیر.


5) پروژه MyWpfFramework.Models
در پروژه MyWpfFramework.Models کلیه Modelهای مورد استفاده در UI که الزاما قرار نیست در بانک اطلاعاتی قرارگیرند، تعریف خواهند شد. برای نمونه مدل صفحه لاگین در آن قرار دارد و ذکر دو نکته در آن حائز اهمیت است:
 [ImplementPropertyChanged] // AOP
public class LoginPageModel : DataErrorInfoBase
- ویژگی ImplementPropertyChanged کار پیاده سازی INotifyPropertyChanged را به صورت خودکار سبب خواهد شد.
- کلاس پایه DataErrorInfoBase سبب می‌شود تا مثلا در اینجا اگر از ویژگی Required استفاده کردید، اطلاعات آن توسط برنامه خوانده شود و با WPF یکپارچه گردد.


6) پروژه MyWpfFramework.Infrastructure.csproj
در پروژه MyWpfFramework.Infrastructure.csproj تعاریف ViewModelهای برنامه اضافه خواهند شد.
این پروژه دارای یک سری کلاس پایه است که تنظیمات IoC برنامه را انجام می‌دهد. برای مثال FrameFactory.cs آن یک کنترل Frame جدید را ایجاد کرده است که کار تزریق وابستگی‌ها را به صورت خودکار انجام خواهد داد. فایل IocConfig آن جایی است که کار سیم کشی کلاس‌های لایه سرویس و اینترفیس‌های متناظر با آن‌ها انجام می‌شود. البته پیش فرض‌های آن را اگر رعایت کنید، نیازی به تغییری در آن نخواهید داشت. برای مثال در آن scan.TheCallingAssembly قید شده است. در این حالت اگر نام کلاس لایه سرویس شما Test و نام اینترفیس متناظر با آن ITest باشد، به صورت خودکار به هم متصل خواهند شد.
همانطور که پیشتر نیز عنوان شد، در پوشه ViewModels آن، به ازای هر View یک ViewModel خواهیم داشت که نام آن مطابق قرار داد، نام View مدنظر به همراه کلمه ViewModel باید درنظر گرفته شود تا توسط برنامه شناخته شده و مورد استفاده قرار گیرد. همچنین هر ViewModel نیز باید دارای کلاس پایه BaseViewModel باشد تا توسط IoC Container برنامه جهت تزریق وابستگی‌های خودکار در سازنده‌های کلاس‌ها شناسایی شده و وهله سازی گردد.


7) پروژه MyWpfFramework.ServiceLayer
کلیه کلاس‌های لایه سرویس که منطق تجاری برنامه را پیاده سازی می‌کنند (خصوصا توسط EF) در این لایه قرار خواهند گرفت. در اینجا دو نمونه سرویس کاربران و سرویس عمومی AppContextService را ملاحظه می‌کنید.
سرویس AppContextService قلب سیستم اعتبارسنجی سیستم است و در IocConfig برنامه به صورت سینگلتون تعریف شده است. چون در برنامه‌های دسکتاپ در هر لحظه فقط یک نفر وارد سیستم می‌شود و نیاز است تا پایان طول عمر برنامه، اطلاعات لاگین و نقش‌های او را در حافظه نگه داری کرد.


8) پروژه MyWpfFramework.Tests
یک پروژه خالی Class library هم در اینجا جهت تعریف آزمون‌های واحد سیستم درنظر گرفته شده است.