مطالب
آشنایی با Defensive programming - قسمت دوم

در ادامه یک سری از خط مشی‌های متداول در defensive programming را با هم مرور خواهیم کرد:

1- بررسی نال بودن اشیاء
سعی در استفاده از اشیاء نال، به یک NullReferenceException منتهی خواهد شد. اگر به هر دلیلی امکان نال بودن یک شیء وجود داشت، پیش از استفاده از آن، حتما این وضعیت ‌را بررسی نمائید.
بهترین ابزاری هم که برای این منظور می‌توان استفاده کرد، نگارش جدید افزونه‌ی ReSharper است که زیر شیء‌ایی را که احتمال نال بودن آن می‌رود یک خط آبی رنگ می‌کشد.



2- بررسی آرگومان‌های دریافتی
برای نمونه اگر متد شما تاریخی را بر اساس DateTime دریافت می‌کند، حتما حدود آن‌را بررسی نمائید. برای مثال دریافت تاریخ 31 اسفند از کاربر، به یک ArgumentOutOfRangeException منتهی خواهد شد. بنابراین آرگومان‌های دریافت شده باید انتظارات مربوطه را برآورده کنند و پیش از استفاده حتما بررسی گردند تا بتوان مشکلات را به کاربر به صورت واضحی گوشزد کرد. (خطای ArgumentOutOfRangeException برای کاربر نهایی بی‌معنی است)

3- وضعیت اشیاء را بررسی کنید
برای مثال بستن یک کانکشن به دیتابیس در صورت بسته بودن آن،‌ به یک InvalidOperationException منتهی می‌شود. بنابراین بهتر است ابتدا وضعیت این شیء بررسی شده و سپس عملیات خاصی بر روی آن صورت گیرد.

4- هنگام کار با آرایه‌ها دقت کنید
اگر اندیس فراخوانی شده کمتر از صفر یا بیشتر از اندازه‌ی آرایه باشد به یک IndexOutOfRangeException بر خواهید خورد. بنابراین همواره بهتر است که این بررسی پیش از بروز واقعه انجام شود.

5- مراقب الگوریتم‌های بازگشتی باشید
هر چند متدهای بازگشتی در بعضی از موارد کار راه انداز هستند اما اگر بدون دقت از آن‌ها استفاده شود ممکن است سبب ایجاد یک حلقه‌ی بی نهایت شده و نهایتا برنامه با یک StackOverFlowException خاتمه می‌یابد (این مورد در دات نت فریم ورک تنها حالتی است که با try و catch قابل مهار نیست).

6- هنگام تبدیل اشیایی از نوع object‌ مراقب باشید
اگر قصد تبدیل یک شیء را به نوعی داشته باشید که با مقدار ذخیره شده در آن مطابقت ندارد، یک InvalidCastException حاصل خواهد شد. بنابراین در اینگونه موارد بهتر است از اپراتورهای as و یا is استفاده کنید. هنگام استفاده از as اگر عملیات تبدیل با موفقیت صورت نگیرد، حاصل عملیات تنها یک شیء نال خواهد بود و استثنایی رخ نخواهد داد.

7- بجای متد Parse از TryParse استفاده کنید
برای مثال در دات نت جهت تبدیل یک رشته به مقداری عددی می‌توان از int.Parse و یا int.TryParse استفاده کرد. در حالت اول اگر عملیات تبدیل میسر نباشد حتما یک FormatException رخ خواهد داد اما در حالت دوم در صورت موفقیت آمیز بودن عملیات تبدیل، خروجی true خواهد بود و حاصل اصلی را با یک پارامتر از نوع out در اختیار شما قرار می‌دهد.


و به صورت خلاصه
- ورودی‌های کاربر را محدود کرده (مثلا اگر قرار است عددی را وارد کند، از یک تکست باکس عددی (masked edit controls) استفاده کنید) و یا آن‌ها را دقیقا بررسی نمائید تا احتمال بروز خطاهای بعدی را کاهش دهید.
- زمانیکه می‌توان از بروز یک exception جلوگیری کرد، بهتر است مدیریت آن‌را به قسمت catch بلاک try/catch واگذار نکرد.
- و هنگامیکه با برنامه نویسی نمی‌توانید تمامی خطاهای ممکن را پیش بینی کرده و آن‌ها را مدیریت کنید، برای مدیریت استثناء‌ها برنامه داشته باشید.

مطالب
مروری بر قابلیت‌های جدید ES10
از زمان ارائه‌ی نگارش 72 مرورگر chrome، قابلیت‌های استفاده از ES10، میسر شده‌است. برای اینکه از شماره نگارش مرورگر خود مطلع شوید، کافیست به منوی Help و سپس بر روی گزینه‌ی About Google Chrome کلیک کنید تا شماره‌ی نسخه‌ی نصبی بر روی سیستم شما مشخص شود:


تابع ()flat : امکان یکی شدن همه آرایه‌های زیرمجموعه (sub-array) مجموعه را در یک آرایه جدید فراهم میکند و با استفاده از depth، سطح ادغام آرایه را مشخص میکنیم (عملکرد این تابع بصورت بازگشتی میباشد) و سینتکس استفاده از این تابع بشکل زیر است:
var newArray=Array.flat([depth])
بطور مثال:
var arr1 = [1, 2, [3, 4]];
arr1.flat(); 
// [1, 2, 3, 4]

var arr2 = [1, 2, [3, 4, [5, 6]]];
arr2.flat();
// [1, 2, 3, 4, [5, 6]]

var arr3 = [1, 2, [3, 4, [5, 6]]];
arr3.flat(2);
// [1, 2, 3, 4, 5, 6]


Array.flatMap  : سبب نگاشت المنت‌ها با تابع تعریف شده‌ی برای این متد میشود. سپس نتیجه در آرایه‌ای جدید برگشت داده میشود. این تابع عملکردی شبیه به انجام تابع map و سپس اجرای تابع  ()flat با عمق 1 را دارد:
// Let's say we want to remove all the negative numbers and split the odd numbers into an even number and a 1
let a = [5, 4, -3, 20, 17, -33, -4, 18]
//       |\  \  x   |  | \   x   x   |
//      [4,1, 4,   20, 16, 1,       18]

a.flatMap( (n) =>
  (n < 0) ?      [] :
  (n % 2 == 0) ? [n] :
                 [n-1, 1]
)

// expected output: [4, 1, 4, 20, 16, 1, 18]

()Object.fromEntries : یک لیست را که بصورت  key-value تعریف شده باشد، به یک آبجکت تبدیل میکند. شیء دریافتی این تابع باید از نوع Array و یا map باشد:
const entries = new Map([
  ['foo', 'bar'],
  ['baz', 42]
]);

const obj = Object.fromEntries(entries);

console.log(obj);
// expected output: Object { foo: "bar", baz: 42 }

()String.trimStart : تابعی برای حذف کردن فضای خالی سمت چپ یک رشته می‌باشد. نام مستعار دیگر این تابع () trimStart است:
var greeting = '   Hello world!   ';

console.log(greeting);
// expected output: "   Hello world!   ";

console.log(greeting.trimStart());
// expected output: "Hello world!   ";

()String.trimEnd : تابعی برای حذف کردن فضای خالی سمت راست یک رشته می‌باشد و نام مستعار دیگر این تابع () trimRight است:
var greeting = '   Hello world!   ';

console.log(greeting);
// expected output: "   Hello world!   ";

console.log(greeting.trimEnd());
// expected output: "   Hello world!";

Optional Catch Binding : توسط آن توانایی تعریف بلاک try/catch را بدون نیاز به قرار دادن پارامتری برای catch داریم (گاهی نیاز به استفاده‌ی از متغیری که برای خطای رخ داده شده، ارائه می‌شود نیست؛ چرا باید آن را تعریف کنیم ؟!)
try {
  throw new Error('Error');
} catch (error) {
  // do stuff
}
تبدیل می‌شود به:
try {
    throw new Error('Error');
} catch { // removed parameter to catch block
    console.log('Got an error!');
}

Symbol.prototype.description: بوسیله ساختن یک متغیر از نوع  Symbol میتوانیم توضیحاتی را برای استفاده‌ی خطایابی آینده کد استفاده کنیم:
const symbol = Symbol('My Symbol!'); 

console.log(symbol.toString()); // Symbol(My Symbol!)
console.log(symbol.description); // My Symbol!

Function.prototype.toString() Revision : پیاده سازی جدیدی از تابع ()toString می‌باشد. در صورتیکه توسط یک تابع فراخوانی شود کد آن را برگشت میدهد:

// User-defined function object
// This prints "function () { console.log('My Function!'); }"
console.log(function () { console.log('My Function!'); }.toString());

// Build-in function object
// This prints "function parseInt() { [native code] }"
console.log(Number.parseInt.toString());

// Bound function object
// This prints "function () { [native code] }"
console.log(function () { }.bind(0).toString());

// Built-in callable function object
// This prints "function Symbol() { [native code] }"
console.log(Symbol.toString());

// Dynamically generated function object #1
// This prints "function anonymous() {}" (using V8 engine)
console.log(Function().toString());

// Dynamically generated function object #2
// This prints the followng (using V8 engine):
// function () { return __generator(this, function (_a) {
//     return [2 /*return*/];
// }); }
console.log(function* () { }.toString());

// This throws a TypeError: "Function.prototype.toString requires that 'this' be a Function"
Function.prototype.toString.call({});

()Array.sort : مرتب کردن عناصر یک آرایه را انجام میدهد. پیش‌تر برای مرتب سازی یک آرایه، کدی را مانند زیر داشتیم:
var numbers = [4, 2, 5, 1, 3];
numbers.sort(function(a, b) {
  return a - b;
});
console.log(numbers);

// [1, 2, 3, 4, 5]

 ES2015یا در

let numbers = [4, 2, 5, 1, 3];
numbers.sort((a, b) => a - b);
console.log(numbers);

// [1, 2, 3, 4, 5]
و اکنون میتوانیم با فراخوانی این تابع بدون نیاز به تابعی برای compare نمودن، مرتب سازی المنتها را انجام دهیم:
var months = ['March', 'Jan', 'Feb', 'Dec'];
months.sort();
console.log(months);
// expected output: Array ["Dec", "Feb", "Jan", "March"]

var array1 = [1, 30, 4, 21, 100000];
array1.sort();
console.log(array1);
// expected output: Array [1, 100000, 21, 30, 4]
مطالب
مدیریت سراسری خطاها در یک برنامه‌ی Angular
در این مطلب قصد داریم پیام‌ها و اخطارهای برنامه را توسط کامپوننت Angular2 Toasty نمایش داده و همچنین برای کاهش میزان تکرار قسمت‌های نمایش خطا در برنامه، کار مدیریت متمرکز و سراسری آن‌ها را نیز انجام دهیم.


نمایش پیام‌ها و اخطارهای یک برنامه‌ی Angular توسط ng2-toasty

در مطلب «ایجاد Drop Down List‌های آبشاری در Angular» در قسمت دریافت اطلاعات drop down دوم از سرور، اگر کاربر مجددا گروه را بر روی حالت «لطفا گروهی را انتخاب کنید ...» قرار دهد، مقدار categoryId به undefined تغییر می‌کند:
  fetchProducts(categoryId?: number) {
    console.log(categoryId);

    this.products = [];

    if (categoryId === undefined || categoryId.toString() === "undefined") {
      return;
    }
در اینجا می‌خواهیم توسط کامپوننت Angular2 Toasty، پیام متناسبی را نمایش دهیم:



پیشنیازهای کار با کامپوننت Angular2 Toasty توسط یک برنامه‌ی Angular CLI

برای کار با کامپوننت Angular2 Toasty، ابتدا از طریق خط فرمان به پوشه‌ی ریشه‌ی برنامه وارد شده و سپس دستور ذیل را صادر می‌کنیم:
> npm install ng2-toasty --save
اینکار سبب خواهد شد تا این کامپوننت در پوشه‌ی node_modules\ng2-toasty نصب شده و همچنین فایل package.json نیز جهت درج مدخل آن به روز رسانی شود:


یک نکته: اگر در حین اجرای این دستور به خطای ذیل برخوردید:
 npm ERR! Error: EPERM: operation not permitted, rename
چون VSCode پوشه‌ی node_modules را تحت نظر قرار می‌دهد، ممکن است یک سری اعمال npm مجوز اجرا را پیدا نکنند. بنابراین ابتدا VSCode را بسته و مجددا دستور npm را اجرا کنید.

پس از آن نیاز است یکی از شیوه‌نامه‌هایی را که در تصویر فوق ملاحظه می‌کنید، در فایل angular-cli.json. مشخص کنیم:
"styles": [
    "../node_modules/bootstrap/dist/css/bootstrap.min.css",
    "../node_modules/ng2-toasty/bundles/style-bootstrap.css",
    "styles.css"
],
که برای نمونه در اینجا، شیوه‌نامه‌ی بوت استرپ آن انتخاب شده‌است.

سپس باید به فایل src\app\app.module.ts مراجعه کرد و ماژول این کامپوننت را معرفی نمود:
import { ToastyModule } from "ng2-toasty";

@NgModule({
  imports: [
    BrowserModule,
    ToastyModule.forRoot(),

همچنین در همین قسمت، به فایل قالب src\app\app.component.html مراجعه کرده و selector tag این کامپوننت را در ابتدای آن تعریف می‌کنیم:
 <ng2-toasty [position]="'top-right'"></ng2-toasty>
در اینجا با استفاده از property binding و تعیین مقدار رشته‌ای top-right، محل نمایش اعلانات برنامه را مشخص می‌کنیم. مقدارهای ممکن آن شامل bottom-right، bottom-left، top-right، top-left، top-center، bottom-center، center-center هستند. برای مثال اگر می‌خواهید آن‌را در میانه‌ی صفحه نمایش دهید، مقدار center-center را انتخاب کنید. همچنین باید دقت داشت که این مقدار باید درون '' قرار گیرد تا مشخص شود که رشته‌ای به خاصیت position انتساب داده شده‌است و این مقدار یک خاصیت عمومی تعریف شده‌ی در کامپوننت متناظر با قالب، نیست.


نمایش یک پیام خطا توسط ToastyService

اکنون که کار برپایی کامپوننت Angular2 Toasty به پایان رسید، کار کردن با آن به سادگی تزریق سرویس آن به سازنده‌ی یک کامپوننت و فراخوانی متدهای info، success ، wait ، error و warning آن است:
import { ToastyService, ToastOptions } from "ng2-toasty";

export class ProductGroupComponent implements OnInit {

  constructor(
    private productItemsService: ProductItemsService,
    private toastyService: ToastyService) { }

  fetchProducts(categoryId?: number) {
    console.log(categoryId);

    this.products = [];

    if (categoryId === undefined || categoryId.toString() === "undefined") {
      this.toastyService.error(<ToastOptions>{
        title: "Error!",
        msg: "Please select a category.",
        theme: "bootstrap",
        showClose: true,
        timeout: 5000
      });
      return;
    }
- در اینجا در ابتدا ماژول‌های مورد نیاز import شده‌اند.
- سپس ToastyService به سازنده‌ی کلاس کامپوننت مدنظر تزریق شده‌است تا بتوان از امکانات آن استفاده کرد.
- در ادامه، فراخوانی متد this.toastyService.error سبب نمایش اخطار قرمز رنگی می‌شود که تصویر آن‌را در ابتدای مطلب جاری مشاهده کردید.
- علت ذکر <ToastOptions> در اینجا این است که وجود آن سبب خواهد شد تا intellisense در VSCode فعال شود و پس از آن بتوان تمام گزینه‌های این متد و تنظیمات را بدون مراجعه‌ی به مستندات آن از طریق intellisense یافت و درج کرد:



مدیریت سراسری خطاهای مدیریت نشده، در یک برنامه‌ی Angular

در برنامه‌های Angular از این دست کدها بسیار مشاهده می‌شوند:
    this.productItemsService.getCategories().subscribe(
      data => {
        this.categories = data;
      },
      err => console.log("get error: ", err)
    );
تا اینجا قسمت err یا بروز خطا را با console.log مدیریت کرده‌ایم. در این حالت کاربر ممکن است 10 بار بر روی دکمه‌ای کلیک کند یا صفحه‌ای را بارگذاری کند و دست آخر متوجه نشود که مشکل کار چیست. به همین جهت می‌توان خطاها را نیز توسط ToastyService نمایش داد تا کاربران دقیقا متوجه بروز مشکل رخ داده شوند. اما ... به این ترتیب تکرار کد زیادی را خواهیم داشت و باید به ازای تمام این موارد، یکبار this.toastyService.error را فراخوانی کنیم. برای مدیریت بهتر یک چنین سناریویی در Angular، کلاس و سرویس توکاری به نام ErrorHandler وجود دارد. در هر قسمتی از برنامه‌ی Angular که استثنایی مدیریت نشده رخ دهد، ابتدا از این کلاس رد شده و سپس به برنامه انتشار پیدا می‌کند. بنابراین می‌توان یک ErrorHandler سفارشی را با ارث بری از آن تهیه کرد و سپس بجای سرویس توکار اصلی، به برنامه معرفی و از آن استفاده نمود. به این ترتیب می‌توان یک Global Error Interceptor را طراحی نمود.
به همین منظور کلاس جدیدی را به صورت ذیل در پوشه‌ی src\app اضافه می‌کنیم:
> ng g cl app.error-handler
با این خروجی
 installing class
  create src\app\app.error-handler.ts
سپس این کلاس را به نحو ذیل تکمیل خواهیم کرد:
import { ErrorHandler } from "@angular/core";

export class AppErrorHandler implements ErrorHandler {

  handleError(error: any): void {
    console.log("Error:", error);
  }
}
کلاس جدید AppErrorHandler از کلاس پایه ErrorHandler ارث بری می‌کند. بنابراین import آن‌را در ابتدای کار مشاهده می‌کنید. سپس باید متد handleError آن‌را با امضایی که مشاهده می‌کنید، پیاده سازی کنیم. فعلا با استفاده از console.log این خطا را در کنسول developer tools نمایش می‌دهیم.

اکنون نیاز است این ErrorHandler سفارشی را بجای نمونه‌ی اصلی به برنامه معرفی کنیم. برای این منظور به فایل src\app\app.module.ts مراجعه کرده و تغییرات ذیل را اعمال می‌کنیم:
import { NgModule, ErrorHandler } from "@angular/core";
import { AppErrorHandler } from "./app.error-handler";

@NgModule({
  providers: [
    { provide: ErrorHandler, useClass: AppErrorHandler }
  ]
ابتدا ErrorHandler به لیست imports اضافه شده‌است و همچنین محل تامین AppErrorHandler نیز مشخص گردیده‌است. سپس در قسمت providers ماژول جاری، از تعریف خاصی که ملاحظه می‌کنید، استفاده خواهد شد. به این ترتیب به Angular اعلام می‌کنیم، هرگاه نیازی به وهله‌ای از کلاس توکار ErrorHandler بود، وهله‌ای از کلاس سفارشی AppErrorHandler را مورد استفاده قرار بده.

اکنون برای آزمایش آن، در کدهای سمت سرور مطلب «ایجاد Drop Down List‌های آبشاری در Angular»، یک استثنای عمدی را قرار می‌دهیم:
[HttpGet("[action]/{categoryId:int}")]
public async Task<IActionResult> GetProducts(int categoryId)
{
   throw new Exception();
به این ترتیب هر زمانیکه گروهی انتخاب شد، دریافت محصولات آن گروه با خطا مواجه می‌شود.
برای اینکه AppErrorHandler، مورد استفاده قرار گیرد، قسمت err دریافت لیست محصولات را نیز حذف می‌کنیم (تا تبدیل به یک استثنای مدیریت نشده شود):
    this.productItemsService.getProducts(categoryId).subscribe(
      data => {
        this.products = data;
        this.isLoadingProducts = false;
      }// ,
      // err => {
      //   console.log("get error: ", err);
      //   this.isLoadingProducts = false;
      // }
    );
اکنون اگر برنامه را اجرا کنیم، چنین پیامی، در کنسول developer tools ظاهر می‌شود و مشخص است از فایل AppErrorHandler صادر شده‌است:



افزودن ToastyService به AppErrorHandler

در ادامه می‌خواهیم بجای console.log از ToastyService برای نمایش خطاهای مدیریت نشده‌ی برنامه در کلاس AppErrorHandler استفاده کنیم:
import { ToastyService, ToastOptions } from "ng2-toasty";
import { ErrorHandler } from "@angular/core";

export class AppErrorHandler implements ErrorHandler {

  constructor(private toastyService: ToastyService) {
  }

  handleError(error: any): void {
    // console.log("Error:", error);
    this.toastyService.error(<ToastOptions>{
      title: "Error!",
      msg: "Fatal error!",
      theme: "bootstrap",
      showClose: true,
      timeout: 5000
    });
  }
}
به همین منظور سرویس آن‌را به سازنده‌ی کلاس AppErrorHandler تزریق کرده و سپس از آن به نحو متداولی در متد handleError استفاده می‌کنیم. به این ترتیب بجای ده‌ها و یا صدها قسمت مدیریت err=>this.toastyService.error در برنامه، تنها یک مورد مدیریت مرکزی را خواهیم داشت.

مشکل اول! اکنون اگر برنامه را اجرا کنیم، در کنسول developer tools چنین خطایی ظاهر می‌شود:
 Uncaught Error: Can't resolve all parameters for AppErrorHandler: (?).
به این معنا که Angular قادر نیست وهله‌ای از AppErrorHandler را ایجاد کند؛ چون نمی‌داند که چگونه باید پارامتر سازنده‌ی ToastyService را وهله سازی و تزریق نماید. علت اینجا است که کار آغاز کلاس ویژه‌ی ErrorHandler سراسری، پیش از کار بارگذاری ماژول مرتبط با ToastyService انجام می‌شود. به همین جهت، این مورد جزو معدود مواردی است که باید به صورت دستی تزریق شود:
import { ErrorHandler, Inject } from "@angular/core";

export class AppErrorHandler implements ErrorHandler {

  constructor(
    @Inject(ToastyService) private toastyService: ToastyService
  ) {
  }
در اینجا توسط Inject decorator، کار تزریق دستی ToastyService انجام خواهد شد. اکنون اگر برنامه را مجدد اجرا کنیم، خطای قبلی برطرف شده‌؛ یعنی کلاس AppErrorHandler با موفقیت وهله سازی شده‌است.

مشکل دوم! اینبار برنامه را اجرا کنید. سپس گروهی را انتخاب نمائید. مشاهده می‌کنید که خطایی نمایش داده نشد؛ هرچند در کنسول developer tools می‌توان اثری از آن را مشاهده کرد. مجددا گروه دیگری را انتخاب کنید، در این بار دوم است که خطای ارائه شده‌ی توسط this.toastyService.error ظاهر می‌شود. توضیح آن نیاز به بررسی مفهومی به نام Zones در Angular دارد.


مفهوم Zones در Angular

زمانیکه متد this.toastyService.error در یک کامپوننت برنامه مورد استفاده قرار گرفت، به خوبی کار می‌کرد و در همان بار اول فراخوانی، پیام را نمایش می‌داد. اما با انتقال آن به کلاسAppErrorHandler ، این قابلیت از کار افتاد. علت اینجا است که زمینه‌ی اجرایی این قطعه کد، اکنون خارج از Zone یا ناحیه‌ی Angular است و به همین دلیل متوجه تغییرات آن نمی‌شود. Zone زمینه‌ی اجرایی اعمال async است و اگر به فایل package.json یک برنامه‌ی Angular دقت کنید، بسته‌ی zone.js، یکی از وابستگی‌های همراه آن است.
تغییرات حالت برنامه، توسط یکی از اعمال ذیل رخ می‌دهند:
الف) بروز رخ‌دادهایی مانند کلیک، ورود اطلاعات و یا ارسال فرم
ب) اعمال Ajax ایی
ج) استفاده از Timers مانند استفاده از setTimeout و  setInterval

هر سه مورد یاد شده از نوع async بوده و زمانیکه رخ می‌دهند، حالت برنامه را تغییر خواهند داد. Angular نیز تنها به این موارد علاقمند بوده و به آ‌ن‌ها در جهت به روز رسانی رابط کاربری برنامه واکنش نشان می‌دهد.
برای مثال this.toastyService.error دارای خاصیتی است به نام timeout: 5000 که در آن، مورد «ج» فوق رخ می‌دهد؛ یعنی یک Timer پس از 5 ثانیه سبب بسته شدن آن خواهد شد. به همین جهت است که اگر پیش از پایان این 5 ثانیه مجددا درخواست واکشی لیست محصولات یک گروه را بدهیم، خطای مربوطه مشاهده می‌شود. چون Angular زمینه‌ی اجرایی لازم را فراهم کرده (یا همان Zone در اینجا) و مجبور به واکنش به عملیات async از نوع Timer است.

برای دسترسی به امکانات کتابخانه‌ی zone.js، می‌توان از طریق تزریق سرویس آن به نام NgZone به سازنده‌ی کلاس شروع کرد:
import { ToastyService, ToastOptions } from "ng2-toasty";
import { ErrorHandler, Inject, NgZone } from "@angular/core";
import { LocationStrategy, PathLocationStrategy } from "@angular/common";

export class AppErrorHandler implements ErrorHandler {

  constructor(
    @Inject(NgZone) private ngZone: NgZone,
    @Inject(ToastyService) private toastyService: ToastyService,
    @Inject(LocationStrategy) private locationProvider: LocationStrategy
  ) {
  }

  handleError(error: any): void {
    // console.log("Error:", error);

    const url = this.locationProvider instanceof PathLocationStrategy ? this.locationProvider.path() : "";
    const message = error.message ? error.message : error.toString();
    this.ngZone.run(() => {
      this.toastyService.error(<ToastOptions>{
        title: "Error!",
        msg: `URL:${url} \n ERROR:${message}`,
        theme: "bootstrap",
        showClose: true,
        timeout: 5000
      });
    });

    // IMPORTANT: Rethrow the error otherwise it gets swallowed
    // throw error;
  }
}
در اینجا فراخوانی this.ngZone.run سبب می‌شود تا درخواست نمایش خطای رخ‌داده وارد Angular Zone شده و بلافاصله سبب نمایش آن گردد:
 


چند نکته
1- اگر می‌خواهید علاوه بر رخ‌دادگردانی سراسری خطاها، این خطاها را به محل اصلی آن‌ها نیز انتشار دهید، نیاز است سطر throw error را در انتهای متد handleError نیز ذکر کنید. در غیر اینصورت، کار در همینجا به پایان خواهد رسید و این خطاها دیگر منتشر نمی‌شوند.
2- روش دریافت URL جاری صفحه را نیز در اینجا مشاهده می‌کنید. این اطلاعات می‌توانند جهت ارسال به سرور برای ثبت و بررسی‌های بعدی مفید باشند.
3- مقدار new Error().stack معادل stack trace جاری است و تقریبا در تمام مرورگرهای جدید پشتیبانی می‌شود.


کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: angular-template-driven-forms-lab-07.zip
برای اجرای آن فرض بر این است که پیشتر Angular CLI را نصب کرده‌اید. سپس به ریشه‌ی پروژه وارد شده و دو پنجره‌ی کنسول مجزا را باز کنید. در اولی دستورات
>npm install
>ng build --watch
و در دومی دستورات ذیل را اجرا کنید:
>dotnet restore
>dotnet watch run
اکنون می‌توانید برنامه را در آدرس http://localhost:5000 مشاهده و اجرا کنید.
مطالب
عبارات باقاعده و نیاز به Timeout

یکبار سعی کنید مثال ساده زیر را اجرا کنید:

using System;
using System.Text.RegularExpressions;

namespace RegexLoop
{
class Program
{
static void Main(string[] args)
{
var emailAddressRegex = new Regex(@"^[A-Za-z0-9]([_\.\-]?[A-Za-z0-9]+)*\@[A-Za-z0-9]([_\.\-]?[A-Za-z0-9]+)*\.[A-Za-z0-9]([_\.\-]?[A-Za-z0-9]+)*$|^$");
if (emailAddressRegex.IsMatch("an.infinite.loop.sample.just_for.test"))
{
Console.WriteLine("Matched!");
}

var input = "The quick brown fox jumps";
var pattern = @"([a-z ]+)*!";
if (Regex.IsMatch(input, pattern))
{
Console.WriteLine("Matched!");
}
}
}
}


پس از اجرا، برنامه هنگ خواهد کرد یا به عبارتی برنامه در یک حلقه بی‌نهایت قرار می‌گیرد (در هر دو مثال؛ اطلاعات بیشتر و آنالیز کامل در اینجا). بنابراین نیاز به مکانیزمی امنیتی جهت محافظت در برابر این نوع ورودی‌ها وجود خواهد داشت؛ مثلا یک Timeout . اگر تا 2 ثانیه به جواب نرسیدیم، اجرای Regex متوقف شود. تا دات نت 4، چنین timeout ایی پیش بینی نشده؛ اما در دات نت 4 و نیم آرگومانی جهت تعریف حداکثر مدت زمان قابل قبول اجرای یک عبارت باقاعده در نظر گرفته شده است (^) و اگر در طی مدت زمان مشخص شده، کار انجام محاسبات به پایان نرسد، استثنای RegexMatchTimeoutException صادر خواهد شد.
خیلی هم خوب. به این ترتیب کسی نمی‌تونه با یک ورودی ویژه، CPU Usage سیستم رو تا مدت زمان نامحدودی به 100 درصد برساند و عملا استفاده از سیستم رو غیرممکن کنه.
اما تا قبل از دات نت 4 و نیم چکار باید کرد؟ روش کلی حل این مساله به این ترتیب است که باید اجرای Regex را به یک ترد دیگر منتقل کرد؛ اگر مدت اجرای عملیات، از زمان تعیین شده بیشتر گردید، آنگاه می‌شود ترد را Abort کرد و به عملیات خاتمه داد. روش پیاده سازی و نحوه استفاده از آن‌را در ادامه ملاحظه خواهید نمود:

using System;
using System.Text.RegularExpressions;
using System.Threading;

namespace RegexLoop
{
public static class TimedRunner
{
public static R RunWithTimeout<R>(Func<R> proc, TimeSpan duration)
{
using (var waitHandle = new AutoResetEvent(false))
{
var ret = default(R);
var thread = new Thread(() =>
{
ret = proc();
waitHandle.Set();
}) { IsBackground = true };
thread.Start();

bool timedOut = !waitHandle.WaitOne(duration, false);
waitHandle.Close();

if (timedOut)
{
try
{
thread.Abort();
}
catch { }
return default(R);
}
return ret;
}
}
}

class Program
{
static void Main(string[] args)
{
var emailAddressRegex = new Regex(@"^[A-Za-z0-9]([_\.\-]?[A-Za-z0-9]+)*\@[A-Za-z0-9]([_\.\-]?[A-Za-z0-9]+)*\.[A-Za-z0-9]([_\.\-]?[A-Za-z0-9]+)*$|^$");
if (TimedRunner.RunWithTimeout(
() => emailAddressRegex.IsMatch("an.infinite.loop.sample.just_for.test"),
TimeSpan.FromSeconds(2)))
{
Console.WriteLine("Matched!");
}

var input = "The quick brown fox jumps";
var pattern = @"([a-z ]+)*!";
if (TimedRunner.RunWithTimeout(() => Regex.IsMatch(input, pattern), TimeSpan.FromSeconds(2)))
{
Console.WriteLine("Matched!");
}
}
}
}

اینبار به هر کدام از عبارات باقاعده 2 ثانیه زمان برای اتمام کار داده شده است. در غیراینصورت مقدار پیش فرض خروجی متد فراخوانی شده، بازگشت داده می‌شود که در اینجا false است.

اشتراک‌ها
صدور Exception یا بازگشت Result به عنوان خروجی متد، برای رسیدگی به خطاها

Summary

It’s pretty easy to differentiate use cases for Result and exceptions. Whenever the failure is something you expect and know how to deal with – catch it at the lowest level possible and convert into a Result instance. If you don’t know how to deal with it – let it propagate and interrupt the current business operation. Don’t catch exceptions you don’t know what to do about. 

صدور Exception یا بازگشت Result به عنوان خروجی متد، برای رسیدگی به خطاها
اشتراک‌ها
تاثیر بروز استثناءها بر روی کارآیی برنامه

In order to cleanse the data as we parse it, we thought using a try/catch would be ok. If we don’t catch the exceptions, we’re good, right?
Turns out it kills our performance when we throw a lot of exceptions, even if we don’t catch them. Each exception has some costs . We needed to find a way to handle this data without involving exceptions.
TryParse turns out to be a method designed to solve our problem. We ran some benchmarks to prove it. 

تاثیر بروز استثناءها بر روی کارآیی برنامه
نظرات مطالب
EF Code First #12
ببخشید آقای نصیری من این کدا رو دیدم،یه سوالم پیش اومد،اینکه همونطور که اینجا عنوان شد ما باید پیاده سازی اینترفیس IUnitOfWork رو تو کلاس MyDbContextBase انجام بدیم،حالا پیاده سازی متد SaveChanges اینترفیس IUnitOfWork تو اونجا باید به صورت زیر باشه؟

try
{
applyCorrectYeKe();
auditFields();
//and another methods ...
Return base.SaveChanges(); } catch (DbEntityValidationException validationException) { //... } catch (DbUpdateConcurrencyException concurrencyException) { //... } catch (DbUpdateException updateException) { //... }
مطالب
پر کردن مقادیر Enum در کلاینت

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

میتوان در این گونه موارد از قابلیت‌های گوناگونی استفاده کرد که در اینجا ما با استفاده از یک فیلد مخفی و json مقدار را به کلاینت پاس میدهیم و در این مثال  در سمت کلاینت نیز دراپ دان را با این مقادیر پر میکنیم: 

    public enum PersistType
    {
        Persistable = 1,
        NotPersist = 2,
        AlwaysPersist = 3
    }

لیست را باید قبل از پر کردن در فیلد مخفی به json بصورت serialize شده تبدیل کرد، برای این منظور از JavaScriptSerializer موجود در اسمبلی‌های دات نت در متد زیر استفاده شده:

        public static string ConvertEnumToJavascript(Type t)
        {
            if (!t.IsEnum) throw new Exception("Type must be an enumeration");

            var values = System.Enum.GetValues(t);
            var dict = new Dictionary<int, string>();

            foreach (object obj in values)
            {
                string name = System.Enum.GetName(t, obj);
                dict.Add(Convert.ToInt32(System.Enum.Format(t, obj, "D")), name);
            }

            return new JavaScriptSerializer().Serialize(dict);
        }

با توجه به اینکه در سمت کلاینت مقدار json ذخیره شده در فیلد مخفی را میتوان به صورت آبجکت برخورد کرد پس یک متد در سمت کلاینت این آبجکت را در loop قراد داده و درمتغییری در فایل جاوا اسکریپت نگهداری میکنیم:

var Enum_PersistType = null;

function SetEnumTypes() {
    Enum_PersistType = JSON.parse($('#hfJsonEnum_PersistType').val());
}

و در هر قسمت که نیاز به مقدار enum بود با توجه به ایندکس مقدار را برای نمایش ازاین متغییر بیرون میکشیم:

function GetPersistTypeTitle_Concept(enumId) {
    return Enum_PersistType[enumId];
}

برای مثال در dropdown در سمت کلاینت این نوع استفاده شده و در حالتی از صفحه فقط برای نمایش عنوان آن احتیاج به دریافت آن از سمت سرور باشد میتوان از این روش کمک گرفت

و یا در سمت javascript میتوان با استفاده از jQuery مقادیر متغییر را در dropdown پر کرد.

function FillDropdown() {
    $("#ddlPersistType").html("");
    $.each(Enum_PersistType, function (key, value) {
        $("#ddlPersistType").append($("<option></option>").val(key).html(value));
    });
}

FillDropdownListOnClient.zip 

مطالب
INPC استاندارد با بهره گیری از صفت CallerMemberName
یکی از Attribute‌های بسیار کاربردی که در سی شارپ 5 اضافه شد CallerMemberNameAttribute بود. این صفت به یک متد اجازه میدهد که از فراخواننده‌ی خود مطلع شود. این صفت را می‌توان بر روی یک پارامتر انتخابی که مقدار پیش‌فرضی دارد اعمال نمود.

استفاده از این صفت هم بسیار ساده است:

private void A ( [CallerMemberName] string callerName = "") 
{
  Console.WriteLine("Caller is " + callerName);
}

private static void B()
{
        // let's call A
        A();
}
در کد فوق، متد A به راحتی می‌تواند بفهمد چه کسی آن را فراخوانی کرده است. از جمله کاربردهای این صفت در ردیابی و خطایابی است.

ولی یک استفاده‌ی بسیار کاربردی از این صفت، در پیاده سازی رابط INotifyPropertyChanged می‌باشد.

معمولا هنگام پیاده سازی INotifyPropertyChanged کدی شبیه به این را می‌نویسیم:

    public class PersonViewModel : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        private void OnPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }

        private string name;
        public string Name
        {
            get { return name; }
            set
            {
                this.name = value;
                OnPropertyChanged("Name");
            }
        }
    }

یعنی در Setter معمولا نام ویژگی ای را که تغییر کرده است، به متد OnPropertyChanged می‌فرستیم تا اطلاع رسانی‌های لازم انجام پذیرد. تا اینجای کار همه چیز خوب و آرام است. اما به محضی که کد شما کمی طولانی شود و شما به دلایلی نیاز به Refactor کردن کد و احیانا تغییر نام ویژگی‌ها را پیدا کنید، آن موقع مسائل جدیدی بروز پیدا می‌کند.

برای مثال فرض کنید پس از نوشتن کلاس PersonViewModel تصمیم می‌گیرد نام ویژگی Name را به FirstName تغییر دهید؛ چرا که می‌خواهید اجزای نام یک شخص را به صورت مجزا نگهداری و پردازش کنید. پس احتمالا با زدن کلید F2 روی فیلد name آن را به firstName و ویژگی Name را به FirstName تغییر نام می‌دهید. همانند کد زیر:

private string firstName;
public string FirstName
{
            get { return firstName; }
            set
            {
                this.firstName = value;
                OnPropertyChanged("Name");
            }
}

برنامه را کامپایل کرده و در کمال تعجب می‌بینید که بخشی از برنامه درست رفتار نمی‌کند و تغییراتی که در نام کوچک شخص توسط کاربر ایجاد می‌شود به درستی بروزرسانی نمی‌شوند. علت ساده است: ما کد را به صورت اتوماتیک Refactor کرده ایم و گزینه‌ی Include String را در حین Refactor، در حالت پیشفرض غیرفعال رها کرده‌ایم. پس جای تعجبی ندارد که در هر جای کد که رشته‌ای به نام "Name" با ماهیت نام شخص داشته ایم، دست نخورده باقی مانده است. در واقع در کد تغییر یافته، هنگام تغییر FirstName، ما به سیستم گزارش می‌کنیم که ویژگی Name (که اصلا وجود ندارد) تغییر یافته است و این یعنی خطا.

حال احتمال بروز این خطا را در ViewModel هایی با ده‌ها ویژگی و ترکیب‌های مختلف در نظر بگیرید. پس کاملا محتمل است و برای خیلی از دوستان این اتفاق رخ داده است.

و اما راه حل چیست؟ به کارگیری صفت CallerMemberName

بهتر است که یک کلاس انتزاعی برای تمام ViewModel‌های خود داشته باشیم و پیاده سازی جدید INPC را در درون آن قرار دهیم تا براحتی VM‌های ما از آن مشتق شوند:

public abstract class ViewModelBase : INotifyPropertyChanged
{
        public event PropertyChangedEventHandler PropertyChanged;

        protected void OnPropertyChanged([CallerMemberName] string propertyName = "")
        {
            OnPropertyChangedExplicit(propertyName);
        }

        protected void OnPropertyChanged<TProperty>(Expression<Func<TProperty>> projection)
        {
            var memberExpression = (MemberExpression)projection.Body;
            OnPropertyChangedExplicit(memberExpression.Member.Name);
        }

        void OnPropertyChangedExplicit(string propertyName)
        {
            this.CheckPropertyName(propertyName);

            PropertyChangedEventHandler handler = this.PropertyChanged;

            if (handler != null)
            {
                var e = new PropertyChangedEventArgs(propertyName);
                handler(this, e);
            }
        }

        #region Check property name

        [Conditional("DEBUG")]
        [DebuggerStepThrough]
        public void CheckPropertyName(string propertyName)
        {
            if (TypeDescriptor.GetProperties(this)[propertyName] == null)
                throw new Exception(String.Format("Could not find property \"{0}\"", propertyName));
        }

        #endregion // Check property name
}

در این کلاس، ما پارامتر propertyName را از متد OnPropertyChanged، توسط صفت CallerMemberName حاشیه نویسی کرده‌ایم. این کار باعث می‌شود در Setter‌های ویژگی‌ها، به راحتی بدون نوشتن نام ویژگی، عملیات اطلاع رسانی تغییرات را انجام دهیم. بدین صورت که کافیست متد OnPropertyChanged بدون هیچ آرگومانی در Setter فراخوانی شود و صفت CallerMemberName به صورت اتوماتیک نام ویژگی ای که فراخوانی از درون آن انجام شده است را درون پارامتر propertyName قرار می‌دهد.

پس کلاس PersonViewModel را به صورت زیر می‌توانیم اصلاح و تکمیل کنیم:

public class PersonViewModel : ViewModelBase
{
        private string firstName;
        public string FirstName
        {
            get { return firstName; }
            set
            {
                this.firstName = value;

                OnPropertyChanged();
                OnPropertyChanged(() => this.FullName);
            }
        }

        private string lastName;
        public string LastName
        {
            get { return lastName; }
            set
            {
                this.lastName = value;

                OnPropertyChanged();
                OnPropertyChanged(() => this.FullName);
            }
        }

        public string FullName
        {
            get { return string.Format("{0} {1}", FirstName, LastName); }
        }
}
همانطور که می‌بینید متد OnPropertyChanged بدون آرگومان فراخوانی میشود. اکنون اگر شما اقدام به Refactor کردن کد خود بکنید دیگر نگرانی از بابت تغییر نکردن رشته‌ها و کامنت‌ها نخواهید داشت و مطمئن هستید، نام ویژگی هر چیزی که باشد، به صورت خودکار به متد ارسال خواهد شد.

کلاس ViewModelBase یک پیاده سازی دیگر از OnPropetyChanged هم دارد که به شما اجازه می‌دهد با استفاده دستورات لامبدا، OnPropertyChanged را برای هر یک از اعضای دلخواه کلاس نیز فراخوانی کنید. همانطور که در مثال فوق می‌بینید، تغییرات نام خانوادگی در نام کامل شخص نیز اثرگذار است. در نتیجه به وسیله‌ی یک Func به راحتی بیان می‌کنیم که FullName هم تغییر کرده است و اطلاع رسانی برای آن نیز باید صورت پذیرد.

برای استفاده از صفت CallerMemberName باید دات نت هدف خود را 4.5 یا 4.6 قرار دهید.

ارجاع:
Raise INPC witout string name
مطالب
دریافت اطلاعات از پایگاه داده بواسطه Stored Procedure در EF Core 2.0
همواره در تکنولوژی  EF CodeFirst، چه در ASP.NET MVC و چه در ASP.NET Core، استفاده از امکانات بومی پایگاه‌های داده با محدودیت‌هایی مواجه بوده‌است. یکی از این اشکالات، عدم توانایی این تکنولوژی در گرفتن لیستی از اطلاعات که منطبق بر بیشتر از یک مدل می‌باشد، هست. در این مقاله تمرکز بر روی رفع این اشکال، بدون نیاز به اضافه کردن مدخل جدیدی به پروژه می‌باشد. بنابراین پیشنیاز ضروری این مبحث، مطالعه «شروع به کار با EF Core 1.0» ، مخصوصا «استفاده از امکانات بومی بانک‌های اطلاعاتی» است.

Stored Procedure چیست ؟

Stored Procedure  یا  SP  یا به زبان فارسی «رویه‌های ذخیره شده» اشیایی اجرا پذیر در بانک اطلاعاتی SQL Server هستند که شامل یک یا چندین دستور SQL می‌شوند. این رویه‌ها می‌توانند پارامتر‌های ورودی و خروجی داشته باشند؛ همچنین می‌توانند لیستی از موجودیت‌ها را نیز برگردانند و یا می‌توان داخل این رویه‌ها به زبان T-SQL برنامه نویسی کرد.
مهم‌ترین کاربر این رویه‌ها، ذخیره کردن دستورات Select , Insert , Update , Delete هست و یا ترکیبی از این‌ها .


اشکال راه حل‌های پیش فرض مبتنی بر Context

برای استفاده از راه حل‌های پیش فرض  مبتنی بر Context، همانطور که در مقاله «استفاده از امکانات بومی بانک‌های اطلاعاتی» به آن پرداخته شده، سه روش کلی برای استفاده از Stored Procedure  پیشنهاد شده‌است:
- روش اول استفاده از متد fromsql است. اشکال این متد، محدودیت استفاده برای یک موجودیت برنامه  است و به زبان ساده نمی‌توان در کوئری پایگاه داده از join  استفاده کرد.
- روش دوم استفاده از متد ExecuteSqlCommand موجود در context برنامه است . اشکال این متد void بودن آن است که باعث می‌شود بازگشتی از پایگاه داده حاصل نشود.
- روش سوم استفاده از متد ExecuteScalar  موجود در Context برنامه است. اشکالی که به این متد گرفته می‌شود، Scalar  بودن مقدار بازگشتی از آن است که باعث می‌شود نتوانیم لیستی از موجودیت‌ها را به ViewModel مورد نظر نگاشت کنیم.

راه حل این مشکل

برای حل این مشکلات که بسیار هم مهم هستند، اول باید قطعه کد زیر را به Context برنامه اضافه نمود:
public void OpenConnection()
{
   Database.OpenConnection();
}

public DbCommand Command()
{
   DbCommand cmd = Database.GetDbConnection().CreateCommand();
   return cmd;
}
سپس در اینترفیس IUnitOfWork  که در مطلب «لایه بندی و تزریق وابستگی‌ها» در مورد آن بحث شده، متد OpenConnection و Command را اضافه می‌کنیم:
void OpenConnection();
DbCommand Command();
حال کلاس و اینترفیس جدیدی را برای پیاده سازی سرویس اتصال به Stored Procedure ایجاد کرده و  در کلاس آغازین برنامه، به‌صورت AddScopped این سرویس را برای استفاده از تزریق وابستگی توکار ASP.NET Core  معرفی می‌کنیم:
public void ConfigureServices(IServiceCollection services)
{
     services.AddScoped<IUnitOfWork, ApplicationDbContext>();
     services.AddScoped<ISpReader, SpReader>();
}
سپس در سازنده کلاس این سرویس، اینترفیس IUnitOfWork را تزریق کرده تا بتوانیم از متد‌های نوشته شده در Context استفاده کنیم. حال اقدام به پیاده سازی متد GetFromSp بصورت زیر می‌کنیم :
public List<ViewModel> GetFromSp <ViewModel>(string[,] Parametr, string NameSp) where ViewModel  : new()
        {
            _uow.OpenConnection();
            DbCommand cmd = _uow.Command();
            cmd.CommandText = NameSp;
            cmd.CommandType = CommandType.StoredProcedure;
            var countParametr = Parametr.GetLength(0);

            for (int i = 0; i < countParame tr; i++)
            {
                cmd.Parameters.Add(new SqlParameter { ParameterName = Parametr[i, 0], Value = Parametr[i, 1] });
            }

            List<ViewModel> list = new List<ViewModel >();
            using (var reader = cmd.ExecuteReader())
            {
                if (reader != null && reader.HasRows)
                {
                    var entity = typeof(ViewModel);
                    var propDict = new Dictionary<string, PropertyInfo>();
                    var props = entity.GetProperties
           (BindingFlags.Instance | BindingFlags.Public);
                    propDict = props.ToDictionary(p => p.Name.ToUpper(), p => p);
                    while (reader.Read())
                    {
                        ViewModel  newobject = new ViewModel ();

                        for (int index = 0; index < reader.FieldCount; index++)
                        {
                            if (propDict.ContainsKey(reader.GetName(index).ToUpper()))
                            {
                                var info = propDict[reader.GetName(index).ToUpper()];
                                if ((info != null) && info.CanWrite)
                                {
                                    var val = reader.GetValue(index);
                                    info.SetValue(newobject, (val == DBNull.Value) ? null : val, null);
                                }
                            }
                        }
                        list.Add(newobject);
                    }

                }
                return list;
            }
در این متد، اول با استفاده از OpenConnection، اتصالی را به پایگاه داده، باز کرده سپس شیئ از DbCommand را می‌سازیم و نام Stored Procedure و نوع کوئری ارسالی را معین می‌کنیم. حال با استفاده از  حلقه for، نام و مقدار پارامتر‌های ارسال شده به متد را به شیئ cmd اضافه می‌کنیم. در مرحله بعد، لیستی را از کلاس مدلی که باید مقادیر بازگشتی به آن نگاشت شوند و بعنوان کلاس جنریک به متد ارسال شده است، می‌سازیم. با متد ExecuteReader که در شیئ ساخته شده از Command موجود می‌باشد، اقدام به خواندن اطلاعات از Stored Procedure کرده و در شیئ Reader نگه داری می‌کنیم و سپس اطلاعات خوانده شده را با استفاده از Dictionary و متد Add به لیست ساخته شده اضافه می‌کنیم. در آخر لیست ساخته شده در حلقه While را بعنوان نتیجه متد باز می‌گردانیم.

همچنین می‌توان برای استفاده این متد برای رویه‌های بدون پارامتر ورودی، از OverLoad این متد، با حذف قطعات کد زیر:
var countParametr = Parametr.GetLength(0);
for (int i = 0; i < countParametr; i++)
{
     cmd.Parameters.Add(new SqlParameter { ParameterName = Parametr[i, 0], Value = Parametr[i, 1] });
}
و حذف آرایه string[,] Parameter  از ورودی متد، استفاده نمود .

روش استفاده از این متد

برای استفاده از این متد، لازم است چند نکته رعایت شوند:
1- خروجی Stored Procedure دقیقا منطبق بر ViewModel ارسالی به متد جهت تشکیل لیست باشد.
2- لیست پارامتر‌ها باید بصورت آرایه دوبعدی باشد که اندازه بعد اول، تعداد پارامتر‌ها و اندازه بعد دوم 2 باشد.
3- در ماتریسی که از این پارامتر‌ها ساخته می‌شود، ستون اول نام پارامتر و ستون دوم مقدار پارامتر ست می‌شود.

بطور مثال Stored Procedure  زیر حاوی سه پارامتر است :
CREATE PROCEDURE [dbo].[isRelation](
@TableName as varchar(50),
@FieldOfRelation as varchar(70),
@ValueOfField as int)
برای دسترسی به این رویه ابتدا در سرویس استفاده کننده، ISpReader را تزریق می‌کنیم و سپس بصورت زیر مقدمات استفاده از این سرویس را فراهم می‌کنیم:

public class EntityServices : IEntityService
    {
        private ISpreader _Reader;
        public EntityServices( ISpreader reader)
        {
            _Reader = reader;
        }

        public List<StoreProcedureResultViewModel>  IsRelation(string tableName , int keyValue, string keyFieldName)
        {
            List<StoreProcedureResultViewModel> IsContact;
            try
            {
                string[,] Parametr = new string[3, 2];
                Parametr[0, 0] = "@TableName";
                Parametr[0, 1] = tableName ;
                Parametr[1, 0] = "@ValueOfField";
                Parametr[1, 1] = keyValue.ToString().Trim();
                Parametr[2, 0] = "@FieldOfRelation";
                Parametr[2, 1] = keyFieldName.Trim();
                IsContact = _Reader.GetSp<StoreProcedureResultViewModel>(Parametr, "IsRelation");
                return IsContact;
            }
            catch (Exception ex)
            {
            }
        }
    }
بدین ترتیب با استفاده از این متد توانستیم لیستی از ViewModel منطبق بر خروجی Stored Procedure  را بدست آوریم.