مطالب
آشنایی با Feature Toggle - بخش دوم
در بخش اول آشنایی با Feature Toggle، با مفهوم Feature Toggle آشنا شدیم و در بخش پایانی مقاله، به معرفی یکی از کتابخانه‌های نوشته شده توسط مایکروسافت پرداختیم.
در این مقاله به صورت کاربردی‌تر به استفاده از کتابخانه‌ی مورد استفاده می‌پردازیم. برای ادامه نیاز هست بسته‌ی زیر را که مخصوص برنامه‌های مبتنی بر ASP.NET CORE است نصب نمایید :
Install-package Microsoft.FeatureManagement.AspNetCore

فرض کنید یک قابلیت را تحت عنوان Chat پیاده سازی کرده و با توجه به تکنولوژی‌هایی که استفاده کرده‌اید، فقط با مرورگر کروم سازگار هست و شما باید این قابلیت را فقط برای کاربرانی که مروگر کروم دارند، فعال نمایید؛ در غیر اینصورت غیرفعال و در دسترس کاربران نباشد. برای این منظور فرض میکنیم کنترلر زیر مسئول تمام کارهای مربوط به قابلیت چت می‌باشد :
[FeatureGate("chat")]
public class ChatController : Controller
{
      public IActionResult Index()
      {
          // do sth
      }
}
همانطور که در کد بالا قابل مشاهده می‌باشد ، کنترلر با یک Attribute مزین شده‌است که از Attribute‌های توکار کتابخانه می‌باشد. با استفاده از این ویژگی می‌توانیم یک کنترلر و یا اکشن متد را کلا از دسترس خارج کنیم (اگر مقدار این قابلیت در appsetting.json غیرفعال باشد).
اگر درخواستی به کنترلر Chat ارسال شود و قابلیت چت در فایل appsetting.json غیرفعال باشد (طبق روش‌هایی که در مقاله قبل توضیح داده شد) کاربر با خطای 404 مواجه خواهد شد.
میتوان به FeatuteGate اسم چندین قابلیت را داد و اگر همه‌ی آنها فعال باشند، کنترلر/اکشن در دسترس خواهد بود؛ در غیر اینصورت خطای 404 دریافت می‌شود.
[FeatureGate("feature1", "feature2")]
public class ChatController : Controller
 {
        public IActionResult Index()
        {
            // do sth
        }
 }
  "FeatureManagement": {
    "feature1": true,
    "feature2": false
  },

 برای حالتیکه نیاز هست اسم چندین قابلیت را به FeatureGate بدهیم، میتوانیم تعیین کنیم که آیا همه‌ی قابلیت‌ها باید فعال باشند تا کنترلر/ اکشن در دسترس باشد یا خیر؟ برای این منظور یک Enum توکار، به اسم RequirementType به همراه این کتابخانه وجود دارد که کار آن And/OR است:
public enum RequirementType
    {
        //
        // Summary:
        //     The enabled state will be attained if any feature in the set is enabled.
        Any = 0,
        //
        // Summary:
        //     The enabled state will be attained if all features in the set are enabled.
        All = 1
    }
همانطور که از توضیحات آن قابل تشخیص است، در زمان استفاده از FeatureGate میتوانیم با استفاده از این enum مشخص کنیم که اگر فقط یکی از قابلیت‌ها فعال بود، کنترلر/اکشن موردنظر فعال و در دسترس باشد، در غیر اینصورت از دسترس خارج شود و تمامی درخواست‌ها را با خطای 404 پاسخ دهد.
نمونه‌ای از استفاده از این enum به صورت زیر است:
 [FeatureGate(RequirementType.Any,"feature1", "feature2","feature3")]
 public class ChatController : Controller
 {
        public IActionResult Index()
        {
            // do sth
        }
 }

تگ <feature>
تا اینجا موفق شدیم یک کنترلر و یا اکشن متد را غیرفعال و از دسترس خارج نماییم. فرض کنید قابلیت چت بنا بر تنظیمات انجام شده، غیرفعال می‌باشد، منتها در منوی سایت همچنان لینک آن در حال نمایش است و کاربران میتوانند لینک را کیک کنند (و در نتیجه با خطای 404 مواجه می‌شوند). برای غیر فعال کردن المان‌هایی (تگ) مربوط به یک قابلیت، می‌توانیم از tag helper مربوطه به صورت زیر استفاده نماییم :
@addTagHelper *, Microsoft.FeatureManagement.AspNetCore // put this line in _ViewImports

<feature name="feature1,feature2,feature3">
  <li>
        <a asp-area="" asp-controller="Chat" asp-action="index">Stay in contact</a>
    </li>
</feature>
 لازم به ذکر هست اینجا هم می‌توان با مقداردهی خصویت requirement با یکی از مقدارهای Any و یا All، مشخص نماییم به صورت And اجرا شود یا خیر.

نوشتن Handler سفارشی
همانطور که در بالا هم بیان شد، اگر یک قابلیت به هر دلیلی غیرفعال باشد، کاربران با خطای 404 مواجه خواهند شد. اگر نیاز داشتید کاربر را به صفحه‌ی دیگری هدایت کنید و یا Status Code بهتری را برگردانید، میتوانید این‌کار را با پیاده سازی یک هندلر سفارشی که اینترفیس IDisabledFeaturesHandler را پیاده سازی میکند، انجام دهید. در زیر یک نمونه پیاده سازی شده را مشاهده می‌کنید:
public class RedirectDisabledFeatureHandler : IDisabledFeaturesHandler
    {
        public Task HandleDisabledFeatures(IEnumerable<string> features, ActionExecutingContext context)
        {
            context.Result = new RedirectResult("url");
            return Task.CompletedTask;
        }
    }
و سپس نیاز هست تا این هندلر را به صورت زیر ثبت نماییم :
  public void ConfigureServices(IServiceCollection services)
   {
            services.AddFeatureManagement().UseDisabledFeaturesHandler(new RedirectDisabledFeatureHandler()); ;
    }
مطالب
مدیریت سراسری خطاها در یک برنامه‌ی 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 مشاهده و اجرا کنید.
مطالب
پیاده سازی Option یا Maybe در #C

Options یا Maybe در یک زبان تابعی مثل #F، نشان دهنده‌ی این است که شیء (Object) ممکن است وجود نداشته باشد(Null Reference) که یکی از مهمترین ویژگی‌های یک زبان شیءگرا مثل #C و یا Java محسوب می‌شودما برنامه نویس‌ها (اغلب) از هرچیزی که باعث کرش برنامه می‌شود، بیزاریم و برای اینکه برنامه کرش نکند، مجبور میشویم تمام کد‌های خود  را از Null Reference محافظت کنیم. تمام این مشکلات توسط Tony Hoare مخترع ALOGL است که تنها دلیل وجود Null References را سادگی پیاده سازی آن می‌داند و او این مورد را یک «خطای  میلیون دلاری» نامیده‌است. 

به این مثال توجه بفرمایید: 

public class User
    {
        public int Id { get; set; }
        public string Name { get; set; }
    }

public class UserService : IUserService
    {
        private IList<User> _userData;

        public UserService()
        {
            _userData = new List<User>
            {
                new User {Id = 1,Name = "ali"},
                new User {Id = 2,Name = "Karim"}
            };
        }

        public User GetById(int id)
        {
            return _userData.FirstOrDefault(x => x.Id == id);
        }
    }  

public class UserController : Controller
    {
        private readonly IUserService _userService;

        public UserController(IUserService  userService)
        {
            _userService = userService;
        }
        public ActionResult Details(int id)
        {
            var user=_userService.GetById(3); // این متد ممکن است مقداری برگرداند و یا مقدار نال برگرداند                           
            if( user == null)
                 return HttpNotFound();    
            return View(user);  
        }
    }

این کدی است که ما برنامه نویسان به صورت متداولی با آن سروکار داریم. اما چه چیزی درباره این کد اشکال دارد؟

مشکل از آن جایی هست که ما نمی‌دانیم متد GetById مقداری را برمیگرداند و یا Null را بر می‌گرداند. این متد هرگاه که امکان برگرداندن Null وجود داشته باشد، خطای  NullReferenceException را در زمان اجرا بر می‌گرداند و همان طور که میدانید، به ازای هر شرطی که به برنامه اضافه میکنیم، پیچیدگی برنامه هم افزایش می‌یابد و کد خوانایی خود را از دست می‌دهد. تصور کنید دنیایی بدون NullReferenceException چه دنیایی زیبایی می‌بود؛ ولی متاسفانه این مورد از ویژگی‌های زبان #C است. خوشبختانه راه‌حل‌های برای حل NRE ارائه شده‌اند که در ادامه به آن‌ها می‌پردازیم.

ما می‌خواهیم متد GetById همیشه چیزی غیر از نال را برگرداند و یکی از راه‌هایی که ما را به این هدف می‌رساند این است که این متد یک توالی را برگرداند.

به نگاری جدید کد توجه بفرمایید:
public class UserService : IUserService
    {
        private IList<User> _userData;

        public UserService()
        {
            _userData = new List<User>
            {
                new User {Id = 1,Name = "ali"},
                new User {Id = 2,Name = "Karim"}
            };
        }

        public IEnumerable<User> GetById(int id)
        {
            var user = _userData.FirstOrDefault(x => x.Id == id);
            if (user == null) return new User[0];
            return new[] { user };
        }
    } 

اگر به امضای متد GetById توجه کنید، به جای اینکه User را برگرداند، این متد یک توالی از User را بر می‌گرداند و اگر در اینجا کاربری یافت شد، این توالی دارای یک المان خواهد بود و در غیر این صورت اگر User یافت نشد، این متد یک توالی را بر می‌گرداند که دارای هیچ المانی نیست. در ادامه اگر کلاینت بخواهد از متد GetById استفاده کند، به صورت زیر خواهد بود:

 public ActionResult Details(int id)
        {
            var user = _userService
                            .GetById(3)
                            .DefaultIfEmpty(new User())
                            .Single();
            return View(user);
        }

 متد GetById دارای دو وجه است و وجه مثبت آن این است که اگر مجموعه دارای مقداری باشد، هیچ مشکلی نیست؛ ولی اگر مجموعه دارای المانی نباشد، باید یک شیء را به صورت پیش فرض به آن اختصاص دهیم که این کار را با استفاده از متد DefualtIfEmpty انجام داده‌ایم. 

 در اول مقاله هم اشاره کردیم که  Maybe یا Options، مجموعه‌ای است که دارای یک المان و یا هیچ المانی است. اگر به امضای متد GetById توجه کنید، متوجه خواهید شد که این متد می‌تواند مجموعه‌ای را برگرداند و نمی‌تواند گارانتی کند که حتما مجموعه‌ای را بر می‌گرداند که دارای یک المان و یا هیچ باشد. برای حل این مشکل می‌توانیم از کلاس Option استفاده کنیم:

public class Option<T> : IEnumerable<T>
    {
        private readonly T[] _data;

        private Option(T[] data)
        {
            _data = data;
        }

        public static Option<T> Create(T element) => new Option<T>(new[] { element });

        public static Option<T> CreateEmpty() => new Option<T>(new T[0]);

        public IEnumerator<T> GetEnumerator() => ((IEnumerable<T>) _data).GetEnumerator();

        IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
    }

تنها دلیل استفاده از متد‌های Create و CreateEmpty این است که به خوانایی برنامه کمک کنیم؛ نه بیشتر. در ادامه اگر بخواهیم از کلاس option استفاده کنیم، به صورت زیر خواهد بود:

 public class UserService : IUserService
    {
       ...
       ...
       public Option<User> GetById(int id)
        {
            var user = _userData.FirstOrDefault(x => x.Id == id);
            return user == null ? Option<User>.CreateEmpty() : Option<User>.Create(user);
        }
    }

 public class UserController : Controller
    {
       ...
       ...
       public ActionResult Details(int id)
        {
            var user = _userService
                            .GetById(3)
                            .DefaultIfEmpty(new User())
                            .Single();
            return View(user);
        }
    }


چکیده:

مدیریت کردن References کار بسیار پیچیده‌ای است. قبل از آن که تلاش کنیم مقداری را برگردانیم و یا عملیاتی را بر روی آن انجام دهیم، اول باید مطمئن شویم که این شیء به جایی اشاره می‌کند. نمونه‌های متفاوتی از Option و یا Maybe را می‌توانید در اینترنت پیدا کنید که هدف نهایی آن‌ها، حذف NullReferenceException است و آشنایی با این ایده، شما را به دنیای برنامه نویسی تابعی در#C هدایت می‌کند.

اشتراک‌ها
قسمت چهارم از سری بررسی معماری نرم افزار Architecture Characteristics and Design Principles - Part

در قسمت 4 ام به یه جمع بندی در مورد تعریف معماری رسیدیم و چهار بعد اصلی یه معماری رو بررسی کردیم و از جلسه بعدی میریم توی بحث الگوهای معماری مثل کلین و اون هارو کامل بررسی میکنیم.


01:00 Previous Session
02:35 Frozen Caveman anti-pattern
08:00 Architecture Characteristics
09:00 NFR
11:35 Architecture Decisions
15:14 Design Principles

#design #architecture

👀 مدت زمان ویدیو : 23 دقیقه 

قسمت چهارم از سری بررسی معماری نرم افزار Architecture Characteristics and Design Principles - Part
نظرات مطالب
افزونه نویسی برای مرورگرها : قسمت دوم : فایرفاکس
سلام مقاله جالبی بود. ممنون. برای ایجاد منوها برای کلیدهایی که ایجاد میکنیم چطوری میشه عمل کرد؟ مثلا فایرباگ و یا ColorZilla کلیدهایی هستن که یک آیکن کنارشون دارن و زیر منو تعریف شده براشون. ممنون میشم این مورد با ایجاد پنجره‌های بازشو آموزش بدید. البته منظورم پنجره‌های ویندوزی برای داخل فایرفاکس هستش که باز میشن نه پاپ آپی که تو آموزشهای اکستنشن برای فایرفاکس آموزش داده شده.

ممنون
نظرات مطالب
معرفی Kendo UI
بله امکان استفاده از wrapper در نسخه asp.net.mvc.commercial وجود دارد
استفاده از اون هم خیلی ساده‌تر و خواناتر از جاوا اسکریپت هست. نظر آفای نصیری هم محترم است ولی در مواقع خواص میتونید همزمان هم از جاوااسکریپت استفاده کنید هم از wrapper یک نمونه رو در زیر با هم مقایسه میکنیم
با استفاده از جاوا اسکریپت
<input id="pickDate" type="text"/>

<script type="text/javascript">
  $(function() {
        $("#pickDate").kendoDatePicker();
  });
</script>
با استفاده از wrapper
@(Html.Kendo().DatePicker().Name("pickDate"))
در ضمن اینکه توی  wrapper امکان استفاده از Intellisense و امکان تعریف ارتباط اغلب کامپوننت‌های وب به مدل با استفاده از for‌های نمونه معادل کامپوننت فراهم شده است مانند wrapper زیر
@(Html.Kendo().DatePickerFor(m => m.HireDate).Name("pickDate1"))

نظرات مطالب
انجام کارهای زمانبندی شده در برنامه‌های ASP.NET توسط DNT Scheduler
خیلی خیلی ممنون ... کتابخانه‌ی ساده و مفیدی است. 
سوال من اینه که میشه کاری کرد که آدرس روت تعریف شده در فایل کانفیگ جهت بیدار شدن IIS را در کد نوشت مثل کد زیر : 
var urlToWakeup = Request.Url.Scheme + Uri.SchemeDelimiter + Request.Url.Host +
            (Request.Url.IsDefaultPort ? "" : ":" + Request.Url.Port);
که مجبور نباشی در هر سایتی که طراحی میکنیم  آدرس رو در فایل کانفیگ ست کنیم !
پی نوشت : من این کار رو در متد Application_End نوشتم و خطای در دسترس نبودن Request رو دریافت کردم ! (هر چند که میدونستم این خطا رو میده !)
اشتراک‌ها
پلاگینی برای ساخت DropDownList های مرتبط
A multi level dependent dropdown JQuery plugin that allows nested dependencies. The plugin allows you to convert normal select inputs, whose options are derived based on value selected in another input/or a group of inputs. It works both with normal select options and select with optgroups as well. 
پلاگینی برای ساخت DropDownList های مرتبط
مطالب
بازسازی کد: جایگزینی متد با شیء (Replace method with method object)
زیبایی متدهای کوچک، در قابل درک بودن آنها نهفته است. هدف نهایی بیشتر روش‌های بازسازی کد، تغییر تکه کدی بزرگ و در هم تنیده، به تکه‌های کوچک قابل درک‌تر است. مهمترین مانع در شکستن تکه کدهای طولانی، وجود متغیرهای موقتی در آن است. چند روش بازسازی کد مطرح شده به طور خاص به این موضوع توجه دارند. 
زمانیکه متد طولانی ای در کد وجود داشته باشد که نتوان (به راحتی) آن را به چند متد کوچکتر تقسیم کرد، معمولا از این روش بازسازی کد استفاده می‌شود. 
در این روش می‌توان کلاسی را تعریف کرد و متد مربوطه را به آن کلاس منتقل کرد. در نتیجه متغیرهای حاضر در متد را می‌توان در کلاس، به فیلدهای اطلاعاتی تبدیل کرد. پس از این تغییر حتی می‌توان فراتر رفت و متد ابتدایی را به راحتی به چند متد معنی دار در کلاس مقصد تبدیل کرد. 
فرض کنید در کلاس زیر متدی نسبتا طولانی برای محاسبه قیمت سفارش وجود دارد.  
public class Order { 
    public decimal Price() { 
        var primaryBasePrice = 10000; 
        var branchDiscountBasePrice = 9000; 
        var taxPercentage = 9; 
        // calculate price 
    } 
}
متدی مانند متد Price معمولا پیچیده‌تر از موردی است که در این مثال آورده شده است. علارغم این، متد ذکر شده با توجه به متغیرهای محلی موقتی ای که دارد و روالی که در آن نیاز به پیاده سازی است، ممکن است به اندازه‌ای به متغیرهای مربوطه وابسته باشد که نتوان آن را به متدهای کوچکتر در همین کلاس تقسیم کرد و یا تقسیم کردن آن در همین کلاس کمک چندانی به ما نکند. در چنین شرایطی بازسازی کد مورد نظر این نوشتار انجام می‌شود و کد، شکلی شبیه به تکه کد زیر را به خود می‌گیرد. 
public class Order 
{ 
    public decimal Price() { 
        return new PriceCalculator(this).Compute(); 
    } 
} 
public class PriceCalculator 
{ 
    private Order orderInstance ;
    public PriceCalculator(Order order) 
    { 
        orderInstance = order;
    } 
    public decimal Compute() 
    { 
        var primaryBasePrice = 10000; 
        var branchDiscountBasePrice = 9000; 
        var taxPercentage = 9; 
        // calculate price 
    } 
}

در بازسازی ذکر شده، کلاسی با نام PriceCalculator برای روال محاسبه قیمت ایجاد شده و متدی با نام Compute در آن ساخته شده است که محاسبه قیمت را انجام می‌دهد. به دلیل این که ممکن است در کد مربوط به محاسبه قیمت، از خصوصیات موجود در کلاس Order استفاده شود، شیء مربوطه نیز به صورت پارامتر به کلاس محاسبه قیمت پاس داده می‌شود. 
همانطور که در ابتدای مطلب ذکر شد، بعد از این بازسازی کد می‌توان متغیرهای محلی موقتی را به صورت فیلدهای کلاس PriceCalculator تغییر داد و حتی متد را با سادگی بیشتری به چند متد تقسیم کرد. به طور مثال همچنین می‌توان روالهایی مانند محاسبه تخفیف و مالیات را نیز با استفاده از الگوی طراحی decorator در PriceCalculator پیاده سازی نمود.  

مراحل انجام این بازسازی کد  

  1. کلاس جدیدی بسازید و نام آن را بر اساس نام متد مورد نظر خود انتخاب نمایید. 
  2. به کلاس ساخته شده فیلدی برای نگه داری شیء کلاس مرجع (در مثال ما Order) متد اصلی ایجاد کنید.
  3. به کلاس جدید سازنده‌ای برای پاس دادن شیء کلاس مرجع (در مثال ما Order) ایجاد کنید. 
  4. در کلاس جدید متدی با نام مناسب (در مثال ما Compute) ایجاد کنید. 
  5. بدنه متد مورد نظر را به بدنه متد ایجاد شده در کلاس جدید کپی کنید و تمامی رفرنسها به اعضای کلاس مرجع را با رفرنس به شیء موجود آن تغییر دهید. 
  6. کد را کامپایل کنید. 
  7. فراخوانی متد اصلی را با فراخوانی متد شیء کلاس جدید ایجاد شده تغییر دهید.  
یکی از فواید مهم این بازسازی کد، جداسازی منطقی از کد که به صورت متد بوده و تبدیل آن به کلاس است. زمانیکه روالی به کلاس مستقلی تبدیل می‌شود، می‌توان از الگوهای طراحی دیگری برای بهبود بیشتر آن استفاده نمود. همچنین انتقال و استفاده دوباره آن نیز بسیار آسان‌تر خواهد شد. 
نظرات مطالب
نکاتی در مورد ELMAH
در همان مطلب پیشنیاز در مورد location بحث شده، یا حداقل اشاره شده (و اگر کدهای آن ذکر نشده، چون یک مبحث مقدماتی است و نه تکمیلی). در یک مبحث تکمیلی فرض بر این است که شما حداقل یک فصل از مباحث ابتدایی Forms Authentication رو مطالعه کردید. مثلا می‌دونید تگ location را باید به چه صورتی تعریف کرد، یا چطور باید Role به آن اضافه کرد:
<location path=".............">
  <system.web>
    <authorization>
      <allow roles="Administrators" />
      <deny users="*" />
    </authorization>
  </system.web>
</location>