نظرات مطالب
اهمیت code review
در ANSI C ،‌ متغیرها فقط ابتدای تابع (scope block) مجاز به تعریف هستند؛ و هر زمان که به دنبال تعریف متغیرها در ابتدای متدی گشتید یعنی هنوز حال و هوای ANSI C برقرار است.  اما از زمان سی++ به بعد خیر؛ هر چند کامپایلر با هر دو حالت مشکلی ندارد.
بحث تکمیلی در اینجا : (^)
به صورت خلاصه: از سی++ به بعد متغیرها را در نزدیکترین مکانی که اولین استفاده از آ‌نها صورت می‌گیرد تعریف کنید تا بهتر بدانید که به چه علتی تعریف شده. همچنین خیالتان هم راحت می‌شود که به اشتباه جای دیگری استفاده نشده یا نمی‌شود.
مطالب
نمایش Breadcrumbs در برنامه‌های Angular
داشتن Breadcrumbs یکی از گزینه‌های مفید بهبود کاربری هر سایتی است و در برنامه‌های Angular با کوئری گرفتن از سیستم مسیریابی آن می‌توان به سادگی آن‌ها را تولید کرد.



ایجاد ساختاری برای نگهداری آرایه‌ی breadcrumbs

کامپوننت نمایش breadcrumbs را در مسیر src\app\core\bread-crumb ایجاد می‌کنیم. یعنی قصد داریم آن‌را به CoreModule برنامه اضافه کنیم؛ از این جهت که کامپوننت آن، تکمیل کننده‌ی app.component است و هر کامپوننتی که تنها در این کامپوننت ویژه بکار رود، محل تعریف آن در CoreModule خواهد بود.
به همین جهت کامپوننت bread-crumb را به صورت ذیل ایجاد می‌کنیم:
 ng g c core/bread-crumb
که تعاریف آن در فایل src\app\core\core.module.ts در قسمت exports و declarations درج خواهد شد:
import { BreadCrumbComponent } from "./bread-crumb/bread-crumb.component";

@NgModule({
  imports: [CommonModule, RouterModule],
  exports: [
    // components that are used in app.component.ts will be listed here.
    BreadCrumbComponent
  ],
  declarations: [
    // components that are used in app.component.ts will be listed here.
    BreadCrumbComponent
  ]
})
export class CoreModule {}
و سپس اینترفیس bread-crumb.ts را در مسیر src\app\core\bread-crumb\bread-crumb.ts به نحو زیر تعریف می‌کنیم:
export interface BreadCrumb {
  label: string;
  url: string;
};
برچسب و url، دو حداقل پیش‌فرض نمایش قطعات منوی breadcrumbs هستند.


تکمیل کامپوننت Breadcrumb

کدهای کامل کامپوننت Breadcrumb را در ذیل مشاهده می‌کنید:
import { Component, OnInit, ViewEncapsulation } from "@angular/core";
import { ActivatedRoute, NavigationEnd, Router } from "@angular/router";
import { BreadCrumb } from "./bread-crumb";
import { Observable } from "rxjs/Observable";

@Component({
  selector: "app-bread-crumb",
  templateUrl: "./bread-crumb.component.html",
  styleUrls: ["./bread-crumb.component.css"],
  encapsulation: ViewEncapsulation.None
})
export class BreadCrumbComponent implements OnInit {

  breadcrumbs$: Observable<BreadCrumb[]>;

  constructor(private activatedRoute: ActivatedRoute, private router: Router) { }

  ngOnInit() {
    this.breadcrumbs$ = this.router.events
      .filter(event => event instanceof NavigationEnd)
      .distinctUntilChanged()
      .map(event => event ? this.buildBreadCrumbs(this.activatedRoute.root) : []);
  }

  buildBreadCrumbs(route: ActivatedRoute, url: string = "", breadcrumbs: Array<BreadCrumb> = []): Array<BreadCrumb> {
    const routeDataBreadCrumbKey = "breadcrumb";
    const routeConfig = route.routeConfig;
    const path = routeConfig && routeConfig.path !== undefined ? routeConfig.path : "";

    let label = path;
    if (url === "") {
      label = "Home";
    } else if (routeConfig && routeConfig.data !== undefined) {
      label = routeConfig.data[routeDataBreadCrumbKey];
    }

    const nextUrl = `${url}${path}/`;
    const breadcrumb: BreadCrumb = {
      label: label,
      url: nextUrl
    };
    console.log("breadcrumb", { path: path, label: label, url: nextUrl, route: route });
    const newBreadcrumbs = [...breadcrumbs, breadcrumb];
    if (route.firstChild) {
      return this.buildBreadCrumbs(route.firstChild, nextUrl, newBreadcrumbs);
    }
    return newBreadcrumbs;
  }

}
توضیحات:
در اینجا در ابتدای کار، مشترک رخ‌داد NavigationEnd سیستم مسیریابی خواهیم شد:
  ngOnInit() {
    this.breadcrumbs$ = this.router.events
      .filter(event => event instanceof NavigationEnd)
      .distinctUntilChanged()
      .map(event => event ? this.buildBreadCrumbs(this.activatedRoute.root) : []);
  }
هر زمانیکه رخ‌داد مرور صفحه‌ی جاری به پایان رسید، بر اساس مسیر ریشه‌ی آن، متد buildBreadCrumbs فراخوانی می‌شود. این متد، یک متد بازگشتی است. از این جهت که مسیر جاری می‌تواند حاصل مرور یک مسیر والد و سپس چندین مسیر تو در توی فرزند و والد آن باشد. برای نمونه ممکن است مانند تصویر ابتدای بحث، مسیریابی شما به صورت ذیل تعریف شده باشد:
const routes: Routes = [
  {
    path: "breadCrumbTest",
    data: { breadcrumb: "Parent1" },
    children: [
      {
        path: "", component: Parent1Component
      },
      {
        path: "Parent1Child1",
        data: { breadcrumb: "Parent1-Child1" },
        children: [
          {
            path: "", component: Parent1Child1Component
          },
          {
            path: "Parent1Child1Child1", component: Parent1Child1Child1Component,
            data: { breadcrumb: "Parent1-Child1 Child1" }
          }
        ]
      }
    ]
  }
];
در اینجا چندین مسیر والد و فرزند تو در تو را مشاهده می‌کنید.
بر اساس قراردادی که در کامپوننت نمایش bread-crumbs درنظر گرفته‌ایم، عنوان هر مسیر در خاصیت data آن با کلید breadcrumb درج می‌شود:
 data: { breadcrumb: "Parent1-Child1 Child1" }
مقدار این خاصیت را به صورت ذیل در متد buildBreadCrumbs می‌خوانیم:
  buildBreadCrumbs(route: ActivatedRoute, url: string = "", breadcrumbs: Array<BreadCrumb> = []): Array<BreadCrumb> {
    const routeDataBreadCrumbKey = "breadcrumb";
    const routeConfig = route.routeConfig;
    const path = routeConfig && routeConfig.path !== undefined ? routeConfig.path : "";

    let label = path;
    if (url === "") {
      label = "Home";
    } else if (routeConfig && routeConfig.data !== undefined) {
      label = routeConfig.data[routeDataBreadCrumbKey];
    }
برای بار اول فراخوانی متد بازگشتی buildBreadCrumbs، مقدار url خالی است. به همین جهت برچسب home را به آن نسبت خواهیم داد. در بار بعدی، اطلاعات path و data مسیر فعال شده را از شیء route.routeConfig استخراج کرده و توسط آن یک bread-crumb جدید را ایجاد می‌کنیم:
    const nextUrl = `${url}${path}/`;
    const breadcrumb: BreadCrumb = {
      label: label,
      url: nextUrl
    };
سپس اگر این مسیر فرزندی را داشته باشد، مقدار خاصیت route.firstChild آن نال نبوده و می‌توان متد بازگشتی را در همینجا با فراخوانی متد جاری تشکیل داد:
    const newBreadcrumbs = [...breadcrumbs, breadcrumb];
    if (route.firstChild) {
      return this.buildBreadCrumbs(route.firstChild, nextUrl, newBreadcrumbs);
    }
    return newBreadcrumbs;
علت استفاده‌ی از روش [breadcrumb, breadcrumbs...] بجای استفاده از متد push آرایه، مربوط است به
 encapsulation: ViewEncapsulation.None
در مورد ViewEncapsulation در مطلب «بررسی استراتژی‌های تشخیص تغییرات در برنامه‌های Angular» بیشتر بحث کرده‌ایم و تنظیم آن به None، کارآیی برنامه را با کاهش کار سیستم ردیابی تغییرات Angular، بهبود می‌بخشد.


تکمیل قالب نمایش Breadcrumbs

پس از تکمیل BreadCrumbComponent، اکنون قالب آن به صورت ذیل تعریف می‌شود:
<ol class="breadcrumb">
  <li *ngFor="let breadcrumb of breadcrumbs$ | async" class="breadcrumb-item">
    <a [routerLink]="[breadcrumb.url]">{{ breadcrumb.label }}</a>
  </li>
</ol>
در اینجا بر روی آرایه‌ی تشکیل شده‌ی توسط متد buildBreadCrumbs، یک حلقه ایجاد شده و عناصر آن نمایش داده می‌شوند. چون خاصیت عمومی breadcrumbs به صورت یک Observable تعریف شده‌است، در اینجا استفاده از async pipe را نیز در این حلقه مشاهده می‌کنید:
   breadcrumbs$: Observable<BreadCrumb[]>;


نمایش نهایی BreadCrumbs

در پایان کافی است به فایل app.component.html مراجعه کرده و selector کامپوننت نمایش bread crumbs را در آن درج کنیم:
<div class="container">
  <app-bread-crumb></app-bread-crumb>
  <router-outlet></router-outlet>
</div>


کدهای کامل این مطلب را از اینجا می‌توانید دریافت کنید.
نظرات مطالب
آشنایی با ساختار ViewBag
کلمه dynamic به کامپایلر اعلام میکنه که شی که تعریف کردیم میتونه تغییر کنه یعنی انواع مختلفی به خودش بگیره . شما وقتی یک شی رو با var تعریف میکنید و یک عدد صحیح به اون اختصاص میدید،  در دفعه بعد نمیتونید چیزی غیر از عدد صحیح به اون اختصاص بدین. اما با استفاده از dynamic این کار امکان پذیره.
var x = 12; //OK
x = "String" //خطای کامپایل

dynamic y = 12; //OK
y="String"; //OK
y=12.7; //OK

مطالب
اعتبارسنجی در فرم‌های ASP.NET MVC با Remote Validation

بعد از آمدن نسخه‌ی سوم ASP.NET MVC مکانیسمی به نام Remote Validation به آن اضافه شد که کارش اعتبارسنجی از راه دور بود. فرض کنید نیاز است در یک فرم، قبل از اینکه کل فرم به سمت سرور ارسال شود، مقداری بررسی شده و اعتبارسنجی آن انجام گیرد و این اعتبارسنجی چیزی نیست که بتوان سمت کاربر و بدون فرستاده شدن مقداری به سمت سرور صورت گیرد. نمونه بارز این مسئله صفحه عضویت اکثر سایت‌هایی هست که روزانه داریم با آن‌ها کار می‌کنیم. فیلد نام کاربری توسط شما پر شده و بعد از بیرون آمدن از آن فیلد، سریعا مشخص می‌شود که آیا این نام کاربری قابل استفاده برای شما هست یا خیر. به‌صورت معمول برای انجام این کار باید با جاوا اسکریپت، مدیریتی روی فیلد مربوطه انجام دهیم. مثلا با بیرون آمدن فوکوس از روی فیلد، با Ajax نام کاربری وارد شده را به سمت سرور بفرستیم، چک کنیم و بعد از اینکه جواب برگشت بررسی کنیم که الان آیا این نام کاربری قبلا گرفته شده یا نه.
انجام این کار به‌راحتی با مزین‌کردن خصوصیت (Property) مربوطه موجود در مدل برنامه به Attribute یا ویژگی Remote و داشتن یک Action در Controller مربوطه که کارش بررسی وجود یوزرنیم هست امکان پذیر است. ادامه بحث را با مثال همراه می‌کنم.
به عنوان مثال در سیستمی که قرار هست محصولات ما را ثبت کند، باید بیایم و قبل از اینکه محصول جدید به ثبت برسد این عملیات چک‌کردن را انجام دهیم تا کالای تکراری وارد سیستم نشود. شناسه اصلی که برای هر محصول وجود دارد بارکد هست و ما آن را میخواهیم مورد بررسی قرار دهیم.


مدل برنامه

    public class ProductModel
    {
        public int Id { get; set; }

        [Display(Name = "نام کالا")]
        [Required(ErrorMessage = "{0} یک فیلد اجباری است و باید آن را وارد کنید.")]
        [StringLength(50, ErrorMessage = "طول {0} باید کمتر از {1} کاراکتر باشد.")]
        public string Name { get; set; }

        [Display(Name = "قیمت")]
        [Required(ErrorMessage = "{0} یک فیلد اجباری است و باید آن را وارد کنید.")]
        [DataType(DataType.Currency)]
        public double Price { get; set; }

        [Display(Name = "بارکد")]
        [Required(ErrorMessage = "{0} یک فیلد اجباری است و باید آن را وارد کنید.")]
        [StringLength(50, ErrorMessage = "طول {0} باید کمتر از {1} کاراکتر باشد.")]
        [Remote("IsProductExist", "Product", HttpMethod = "POST", ErrorMessage = "این بارکد از قبل در سیستم وجود دارد.")]
        public string Barcode { get; set; }
    }

همونطور که می‌بینید خصوصیت Barcode را مزین کردیم به ویژگی Remote. این ویژگی دارای ورودی‌های خاص خودش هست. وارد کردن نام اکشن و کنترلر مربوطه برای انجام این چک‌کردن از مهم‌ترین قسمت‌های اصلی هست. چیزهایی دیگه‌ای هم هست که می‌توانیم آن‌ها را مقداردهی کنیم. مثل HttpMethod، ErrorMessage و یا AdditionFields. HttpMethod که همان طریقه‌ی ارسال درخواست به سرور هست. ErrorMessage هم همان خطایی هست که در زمان رخ‌داد قرار است نشان داده شود. AdditionFields هم خصوصیتی را مشخص می‌کند که ما می‌خوایم به‌همراه فیلد مربوطه به سمت سرور بفرستیم. مثلا می‌تونیم به‌همراه بارکد، نام کالا را هم برای بررسی‌های مورد نیازمان بفرستیم.


کنترلر برنامه

        [HttpPost]
        [OutputCache(Location = OutputCacheLocation.None, NoStore = true)]
        public ActionResult IsProductExist(string barcode)
        {
            if (barcode == "123456789") return Json(false); // اگر محصول وجود داشت
            return Json(true);
        }

در اینجا به نمایش قسمتی از کنترلر برنامه می‌پردازیم. اکشنی که مربوط می‌شود به چک‌کردن مقدارهای لازم و در پایان آن یک خروجی Json را برمی‌گردانیم که مقدار true یا false دارد. در حقیقت مقدار را به این صورت برمی‌گردانیم که اگر مقدار ورودی در پایگاه داده وجود دارد، false را برمی‌گرداند و اگر وجود نداشت true. همین‌طور آمدیم از کش شدن درخواست‌هایی که با Ajax آمده با ویژگی OutputCache جلوگیری کردیم.

مطالب
تشخیص نوع فایل با استفاده از محتوای فایل
بی‌شک اگر در سایت خود بخشی را برای دریافت فایل‌های کاربر قرار داده باشید یکی از دغدغه‌های شما اعمال فیلتر و محدودیت روی نوع فایل‌های آپلود شده توسط کاربران خواهد بود. ممکن است سیاست شما پذیرای فایل هایی با پسوند خاص (برای مثال فقط عکس) باشد ولی هیچ تضمینی وجود ندارد که فایلی با پسوند مورد نظر شما محتوایی مشابه با پسوند خود داشته باشد
اگر بخواهیم دقیق‌تر به این موضوع نگاه کنیم فرض می‌کنیم شما در وب سایت خود قسمتی برای آپلود عکس‌های کاربر قرار داده باشید و با مکانیزمی مانند این روش صحت نوع فایل‌های آپلود شده توسط کاربر را بررسی کنید.
اگر شخصی به قصد تخریب و هدر دادن فضای ذخیره سازی شما فایلی با محتوایی غیر از عکس (برای مثال یک فایل اجرایی) را تغییر پسوند داده و به جای عکس آپلود کند چه اتفاقی می‌افتد؟!
دو دلیل مهم برای چک کردن محتوای فایل خواهیم داشت:
1- جلوگیری از اتلاف حافظه ذخیره سازی (ممکن است شخص مهاجم هزاران فایل چند مگابایتی با محتوایی غیر از پسوند فایل بر روی سرور قرار دهد)
2- جلوگیری از اختلال در نمایش فایل‌های آپلود شده (بی شک عدم نمایش عکس در سایت چهره خوبی نخواهد داشت و ممکن است اعتبار سایت را زیر سوال ببرد)

همیشه برای چک کردن نوع فایل باید به دونکته توجه داشت: یکی آنکه پسوند فایل را حتما چک کنیم تا مطابق فیلتر و سیاست مورد انتظار ما باشد ، دوم اینکه به روشی که در ادامه توضیح خواهیم داد، با استفاده از محتوای باینری فایل، MimeType آنرا تشخیص دهیم.
برای اینکار ما از یک تابع API استفاده میکنیم که با استفاده از 255 بایت ابتدایی محتوای فایل، نوع فایل را مشخص می‌کند. این تابع API که FindMimeFromData نام دارد، در فایل urlmon.dll قرار دارد و گویا توسط مرورگر IE برای چک کردن نوع فایل، بر اساس محتوای آن مورد استفاده قرار می‌گیرد. ما نیز از این تابع در دات نت بهره گرفته و با پاس دادن آرایه‌ای از بایتها به آن نوع فایل خود را مشخص می‌کنیم.
کلاسی برای اینکار تهیه شده که در زیر مشاهده میکنید:
using System;
using System.Runtime.InteropServices;
using System.Reflection;

namespace Parsnet.Core
{

    public class MimeTypeDetector
    {
        [DllImport(@"urlmon.dll", CharSet = CharSet.Auto)]
        private extern static System.UInt32 FindMimeFromData(
            System.UInt32 pBC,
            [MarshalAs(UnmanagedType.LPStr)] System.String pwzUrl,
            [MarshalAs(UnmanagedType.LPArray)] byte[] pBuffer,
            System.UInt32 cbSize,
            [MarshalAs(UnmanagedType.LPStr)] System.String pwzMimeProposed,
            System.UInt32 dwMimeFlags,
            out System.UInt32 ppwzMimeOut,
            System.UInt32 dwReserverd
        );

        public string GetMimeType(byte[] content)
        {
            var result = "unknown/unknown";

            try
            {
                byte[] buffer = new byte[256];
                var length = (content.Length > 256) ? 256 : content.Length;
                Array.Copy(content, buffer, length);

                System.UInt32 mimetype;
                FindMimeFromData(0, null, buffer, 256, null, 0, out mimetype, 0);
                System.IntPtr mimeTypePtr = new IntPtr(mimetype);
                result = Marshal.PtrToStringUni(mimeTypePtr);
                Marshal.FreeCoTaskMem(mimeTypePtr);
            }
            catch (Exception ex)
            {
                //Log.WriteError(MethodInfo.GetCurrentMethod(), ex);
            }
            

            return result;
        }

    }
}
تنها نکته قابل توجه در این کد این‌است که در بدنه‌ی متد GetMimeType اگر طول آرایه از 256 بایت بیشتر شود، فقط از 256 بایت ابتدایی آن استفاده میکنیم. در غیر اینصورت کل بایت‌های آرایه، برای چک کردن محتوای فایل به کار گرفته میشوند. برای افزایش کارآیی، بهتر است هنگام فراخوانی این تابع، فقط 256 بایت را به آن ارسال کنید. بخصوص اگر حجم فایلی که باید چک شود زیاد باشد.

نحوه‌ی استفاده از کلاس فوق هم بسیار ساده است. برای مثال هنگام آپلود فایل در MVC می‌توانیم از آن استفاده کنیم:
        [HttpPost]
        [ValidateAntiForgeryToken]
        public ActionResult Upload(HttpPostedFileBase file)
        {
            //ابتدا با مکانیزم مورد نظر خود پسوند فایل را چک میکنیم اگر پسوند معتبری بود بعد محتوای فایل را چک میکنیم

            var data = new byte[256];
            file.InputStream.Read(data, 0, 256);
            var detector = new Parsnet.Core.MimeTypeDetector();
            var mimeType = detector.GetMimeType(data);
            if (CheckWhiteList(mimeType) == true)
            {
                file.SaveAs("yourpath");
            }
            else
            {
                ModelState.AddModelError("InvalidFileContent", "فایل بارگزاری شده مورد پذیرش نیست.");
            }
            return View();
        }
همیشه از یک مکانیزم دیگر برای اعتبارسنجی پسوند فایل استفاده کنید. برای اینکار می‌توان با تلفیق روش فوق و این روش یک کنترل اعتبارسنجی خوب ایجاد کرد که در مطالب بعدی حتما به آن خواهم پرداخت.
نظرات مطالب
C# 7 - Out variables
یک نکته‌ی تکمیلی: صرفنظر کردن از مقدار out
در C# 7 اگر مقدار خروجی حاصل از out مهم نبود، می‌توان آن‌را با _ جایگزین کرد:
var intString = "123";
if (int.TryParse(intString, out _))
{
    // it's integer
}
در این مثال برای ما فقط تشخیص عدد صحیح بودن رشته‌ی دریافتی مهم است و نه مقدار بازگشت داده شده‌ی توسط out. به همین جهت آن‌را با _ مشخص کرده‌ایم.
البته کامپایلر در پشت صحنه کار تعریف یک متغیر را در اینجا انجام خواهد داد؛ اما در حین کدنویسی نیازی به ذکر آن نیست.
مطالب
Span در C# 7.2
C# 7.2 به همراه تعداد کوچکی از بهبودهای کامپایلر است و با Visual Studio 2017 نگارش 15.5 ارائه شده و روش فعالسازی آن با نگارش 7.1 آن یکی است (انتخاب گزینه‌ی «C# latest minor version (latest)» در تنظیمات پیشرفته‌ی Build خواص پروژه). همچنین اگر از VSCode استفاده می‌کنید، نگارش 1.14 افزونه‌ی #C آن، پشتیبانی کاملی را از C# 7.2 به همراه دارد؛ در اینجا، افزودن خاصیت <LangVersion>latest</LangVersion> به فایل csproj برنامه برای استفاده‌ی از آخرین نگارش کامپایلر نصب شده، کفایت می‌کند. البته باید دقت داشت کامپایلر C# 7.2 به همراه NET Core SDK 2.1.2. ارائه شده‌است. بنابراین تنها نصب آخرین نگارش افزونه‌ی #C مخصوص VSCode برای کامپایل آن کافی نیست و باید حداقل SDK یاد شده (یا نگارش جدیدتر آن) را هم نصب کنید.
 

نوع‌های جدید <Span<T و  <ReadOnlySpan<T در C# 7.2

نوع‌های جدید <Span<T و <ReadOnlySpan<T جهت ارائه‌ی ناحیه‌های اختیاری پیوسته‌ای از حافظه، شبیه به آرایه‌ها تدارک دیده شده‌اند و هدف استفاده‌ی از آن‌ها، تولید برنامه‌های سمت سرور با کارآیی بالا است.
برای کار با این نوع‌ها، هم نیاز به کامپایلر C# 7.2 است و هم نصب بسته‌ی نیوگت System.Memory:
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>netcoreapp2.0</TargetFramework>
    <LangVersion>latest</LangVersion>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="System.Memory" Version="4.4.0-preview1-25305-02" />
  </ItemGroup>
</Project>
این بسته از .NETStandard 1.0. به بعد را پشتیبانی می‌کند؛ یعنی با +NET 4.5+ ،Mono 4.6.  و +NET Core 1.0. سازگار است.


Spanها و امکان دسترسی به انواع حافظه

Spanها می‌توانند به حافظه‌ی مدیریت شده، حافظه‌ی بومی (native) و حافظه‌ی اختصاص داده شده‌ی در Stack اشاره کنند. به عبارتی Spanها یک لایه انتزاعی، برفراز تمام انواع و اقسام حافظه‌هایی هستند که می‌توانند در اختیار توسعه دهندگان NET. باشند.
- البته اکثر توسعه دهندگان دات نت از حافظه‌ی مدیریت شده استفاده می‌کنند. برای مثال Stack memory تنها از طریق کدهای unsafe و واژه‌ی کلیدی stackalloc قابل تخصیص است. این نوع حافظه بسیار سریع است و همچنین بسیار کوچک؛ کمتر از یک مگابایت که به خوبی در CPU cache جا می‌شود. اما اگر در این بین حجم حافظه‌ی تخصیصی بیشتر از یک مگابایت شود، بلافاصله استثنای StackOverflowException غیرقابل مدیریتی را به همراه خاتمه‌ی فوری برنامه به همراه خواهد داشت. برای نمونه از این نوع حافظه در جهت مدیریت رخ‌دادهای داخلی corefx زیاد استفاده می‌شود.
- حافظه‌ی مدیریت شده، همان حافظه‌ای است که توسط واژه‌ی کلیدی new در برنامه، جهت ایجاد اشیاء، تخصیص داده می‌شود و طول عمر آن تحت مدیریت GC است.
- حافظه‌ی مدیریت نشده یا بومی از دید GC مخفی است و توسط متدهایی مانند Marshal.AllocHGlobal و Marshal.AllocCoTaskMem در اختیار برنامه قرار می‌گیرند. این حافظه باید به صورت صریحی توسط توسعه دهنده به کمک متدهایی مانند Marshal.FreeHGlobal و Marshal.FreeCoTaskMem آزاد شود. وب سرور Kestrel مخصوص ASP.NET Core، از این روش جهت کار با آرایه‌های حجیم، جهت کاهش بار GC استفاده می‌کند.

مزیت کار با Spanها این است که دسترسی امن و type safeایی را به انواع حافظه‌های مهیا، جهت توسعه دهندگانی که عموما کدهای unsafe ایی را نمی‌نویسند و با اشاره‌گرها به صورت مستقیم کار نمی‌کنند، میسر می‌کند. برای مثال تا پیش از معرفی Spanها، برای دسترسی به 1000 عنصر یک آرایه‌ی 10 هزار عنصری و ارسال آن به یک متد، نیاز بود تا ابتدا یک کپی از این 1000 عنصر را تهیه کرد. این عملیات از لحاظ میزان مصرف حافظه و همچنین زمان انجام آن، بسیار هزینه‌بر است. با استفاده از <Span<T می‌توان یک دید مجازی از آن آرایه را بدون اختصاص آرایه‌ای و یا آرایه‌هایی جدید، ارائه کرد.


مثالی از کاربرد Spanها جهت کاهش تعداد بار تخصیص‌های حافظه

برای نمونه، متد IsValidName زیر، بررسی می‌کند که طول رشته‌ی دریافتی حداقل 2 باشد و حتما با یک حرف شروع شده باشد:
    static class NameValidatorUsingString
    {
        public static bool IsValidName(string name)
        {
            if (name.Length < 2)
                return false;

            if (char.IsLetter(name[0]))
                return true;

            return false;
        }
    }
در این حالت یک نمونه مثال از استفاده‌ی آن می‌تواند به صورت زیر باشد:
string fullName = "User 1";
string firstName = fullName.Substring(0, 4);
NameValidatorUsingString.IsValidName(firstName);
در اینجا زمانیکه از متد Substring استفاده می‌شود، در حقیقت تخصیص حافظه‌ی دومی جهت تولید firstName رخ می‌دهد.

همچنین اگر این اطلاعات را از طریق شبکه دریافت کرده باشیم، ممکن است به صورت آرایه‌ای از حروف دریافت شوند:
char[] anotherFullName = { 'A', 'B' };
که به صورت مستقیم در متد IsValidName قابل استفاده نیست و خطای عدم امکان تبدیل []char به string، از طرف کامپایلر صادر می‌شود:
NameValidatorUsingString.IsValidName(anotherFullName);
در این حالت برای استفاده‌ی از این آرایه، نیاز است یک تخصیص حافظه‌ی دیگر نیز صورت گیرد:
NameValidatorUsingString.IsValidName(new string(anotherFullName));

اکنون در C# 7.2، بازنویسی این متد توسط ReadOnlySpan، به صورت ذیل است:
    static class NameValidatorUsingSpan
    {
        public static bool IsValidName(ReadOnlySpan<char> name)
        {
            if (name.Length < 2)
                return false;

            if (char.IsLetter(name[0]))
                return true;

            return false;

        }
    }
که این مزایا را به همراه دارد:
ReadOnlySpan<char> fullName = "User 1".AsSpan();
ReadOnlySpan<char> firstName = fullName.Slice(0, 4);
NameValidatorUsingSpan.IsValidName(firstName);
کار با API مربوط به Spanها به همراه تخصیص حافظه‌ی جدیدی نیست. برای نمونه در اینجا متد Slice این API، سبب تخصیص حافظه‌ی جدیدی نمی‌شود (برخلاف متد Substring) و فقط به قسمتی از حافظه‌ی موجود اشاره می‌کند (بدون نیاز به کار مستقیم با اشاره‌گرها و کدهای unsafe).

و یا اینبار امکان استفاده‌ی از آرایه‌ای از کاراکترها، بدون نیاز به تخصیص حافظه‌ای جدید، برای بررسی اعتبار مقادیر دریافتی میسر است:
char[] anotherFullName = { 'A', 'B' };
NameValidatorUsingSpan.IsValidName(anotherFullName);

برای نمونه از یک چنین APIایی در پشت صحنه‌ی کتابخانه‌هایی مانند SignalR و یا Roslyn، برای بالا بردن کارآیی برنامه، با کاهش تعداد بار تخصیص‌های حافظه‌ی مورد نیاز، بسیار استفاده شده‌است. برای نمونه در NET Core 2.1.، حجم رشته‌های تخصیص داده شده‌ی در فریم ورک‌های وابسته، به این ترتیب به شدت کاهش یافته‌است.


مثال‌هایی از کار با API نوع Span

امکان ایجاد یک Span از یک array
var arr = new byte[10];
Span<byte> bytes = arr; // Implicit cast from T[] to Span<T>
پس از آن کار با این span همانند کار با آرایه‌های معمولی است؛ با این تفاوت که این span تنها یک دید مجازی از قسمتی از این آرایه را ارائه می‌دهد؛ بدون سربار تخصیص حافظه‌ی اضافی و کپی اطلاعات:
Span<byte> slicedBytes = bytes.Slice(start: 5, length: 2);
slicedBytes[0] = 42;
slicedBytes[1] = 43;
slicedBytes[2] = 44; // Throws IndexOutOfRangeException
bytes[2] = 45; // OK
در اینجا slicedBytes یک دید مجازی از ایندکس 5 تا 7 آرایه‌ی arr را ارائه می‌دهد. کار کردن با آن نیز همانند آرایه‌ها، توسط ایندکس‌ها میسر است.
همچنین تغییرات بر روی Span (غیر read only) بر روی آرایه‌ی اصلی نیز تاثیر گذار است. برای مثال در اینجا با تغییر bytes[2]، مقدار arr[2] نیز تغییر می‌کند.

و یا روش دیگر ایجاد Span استفاده از متد AsSpan است:
var array = new byte[100];
Span<byte> interiorRef1 = array.AsSpan().Slice(start: 20);
همین عملیات را توسط new Span نیز می‌توان به صورت ساده‌تری ارائه داد:
Span<byte> interiorRef2 = new Span<byte>(array: array, start: 20, length: array.Length - 20);


محدودیت‌های کار با Spanها

- Span تنها یک نوع stack-only است.
- Spanها در بین تردها به اشتراک گذاشته نمی‌شوند. هر استک در یک زمان تنها توسط یک ترد قابل دسترسی است. بنابراین Spanها thread-safe هستند.
- طول عمر Spanها کوتاه است و قابلیت قرارگیری بر روی heap با طول عمر بیشتر را ندارند؛ یعنی:
  • به صورت فیلد در یک نوع non-stackonly قابل تعریف نیستند:
class Impossible
{
   Span<byte> field;
}
فیلدهای یک کلاس در heap ذخیره می‌شوند. بنابراین محل ذخیره سازی spanها نیستند.
  • به عنوان پارامترهای متدهای async قابل استفاده نیستند. چون در این بین در پشت صحنه یک AsyncMethodBuilder تشکیل می‌شود که در قسمتی از آن، پارامترها بر روی heap قرار می‌گیرند.
  • هرجائیکه عملیات boxing صورت گیرد، نتیجه‌ی عملیات بر روی heap قرار می‌گیرد. بنابراین در یک چنین مواردی نمی‌توان از Spanها استفاده کرد. برای مثال تعریف Func<T> valueProvider و سپس فراخوانی ()valueProvider.Invoke به همراه یک boxing است. بنابراین نمی‌توان از spanها به عنوان نوع آرگومان جنریک استفاده کرد. این مورد هرچند کامپایل می‌شود، اما در زمان اجرا سبب خاتمه‌ی برنامه خواهد شد و یا نمونه‌ی دیگر، عدم امکان دسترسی به آن‌ها توسط reflection invoke APIs است که سبب boxing می‌شود.


معرفی نوع <Memory<T

با توجه به محدودیت‌های Span و خصوصا اینکه به عنوان پارامتر متدهای async قابل استفاده نیست (چون بر روی stack ذخیره می‌شوند)، نوع دیگری به نام <Memory<T نیز به همراه C# 7.2 ارائه شده‌است. البته این نوع هنوز به بسته‌ی نیوگت فوق اضافه نشده‌است و به همراه ارائه نهایی NET Core 2.1. ارائه خواهد شد.
این نوع، محدودیت <Span<T را نداشته و قابلیت ذخیره سازی بر روی heap را دارا است.
static async Task<int> ChecksumReadAsync(Memory<byte> buffer, Stream stream)
{
   int bytesRead = await stream.ReadAsync(buffer);
   return Checksum(buffer.Span.Slice(0, bytesRead));
   // Or buffer.Slice(0, bytesRead).Span
}
در اینجا نیز می‌توان از یک آرایه، یک <Memory<T را ایجاد و سپس یک <Span<T را از آن دریافت و با Sliceهای آن کار کرد.
نظرات مطالب
بررسی روش آپلود فایل‌ها در ASP.NET Core
اکثرا از base64 استفاده میکنم. برای برنامه نویس‌های موبایل و فرانت قابل قبول‌تر است :)
نمونه کد تبدیل base64 به iformfile:
public static async Task<ResponsePayload<string>> SaveBase64(this string imgBase64, string filePath, FileSizeType fileSizeType)
    {
        if (string.IsNullOrWhiteSpace(imgBase64))
            return new ResponsePayload<string>(false, "فایل را وارد کنید.", null);

        string data;
        if (imgBase64.StartsWith("data:"))
        {
            string[] base64Arr = imgBase64.Split(',');
            if (base64Arr.Length == 0)
                return new ResponsePayload<string>(false, "فایل را وارد کنید.", null);
            data = base64Arr[1];
        }
        else
        {
            data = imgBase64;
        }

        byte[] bytes = Convert.FromBase64String(data);
        var fileType = GetFileExtension(imgBase64);
        if (string.IsNullOrEmpty(fileType))
            return new ResponsePayload<string>(false, "فایل وارد شده صحیح نمی‌باشد.", null);

        using var stream = new MemoryStream(bytes);
        IFormFile file = new FormFile(stream, 0, bytes.Length, filePath, "." + fileType);

        string fileName = Guid.NewGuid().ToString().Replace("-", "");
        return await UploadFile(file, filePath + fileName, fileSizeType);
    }
private static string GetFileExtension(string base64String)
    {
        string data;

        if (base64String.StartsWith("data:"))
        {
            string[] base64Arr = base64String.Split(',');
            if (base64Arr.Length == 0)
                return "";
            data = base64Arr[1];
        }
        else
        {
            data = base64String;
        }
        return data.Substring(0, 5).ToUpper() switch
        {
            "IVBOR" => "png",
            "/9J/4" => "jpg",
            "AAAAF" => "mp4",
            "JVBER" => "pdf",
            "AAABA" => "ico",
            "UMFYI" => "rar",
            "E1XYD" => "rtf",
            "U1PKC" => "txt",
            "MQOWM" => "srt",
            "77U/M" => "srt",
            "UESDB" => "",
            "" => "docx",
            _ => string.Empty,
        };
    }
}

public class FileSizeType
{
    public int Size { get; set; }
}

نظرات مطالب
سفارشی سازی ASP.NET Core Identity - قسمت پنجم - سیاست‌های دسترسی پویا
سلام؛ در متد CanUserAccess کلاس SecurityTrimmingService:
 public bool CanUserAccess(ClaimsPrincipal user, string area, string controller, string action)
        {
            var currentClaimValue = $"{area}:{controller}:{action}";
            var securedControllerActions = _mvcActionsDiscoveryService.GetAllSecuredControllerActionsWithPolicy(ConstantPolicies.DynamicPermission);
            if (!securedControllerActions.SelectMany(x => x.MvcActions).Any(x => x.ActionId == currentClaimValue))
            {
                throw new KeyNotFoundException($@"The `secured` area={area}/controller={controller}/action={action} with `ConstantPolicies.DynamicPermission` policy not found. Please check you have entered the area/controller/action names correctly and also it's decorated with the correct security policy.");
            }

            if (!user.Identity.IsAuthenticated)
            {
                return false;
            }

            if (user.IsInRole(ConstantRoles.Admin))
            {
                // Admin users have access to all of the pages.
                return true;
            }

            // Check for dynamic permissions
            // A user gets its permissions claims from the `ApplicationClaimsPrincipalFactory` class automatically and it includes the role claims too.
            return user.HasClaim(claim => claim.Type == ConstantPolicies.DynamicPermissionClaimType &&
                                          claim.Value == currentClaimValue);
        }
ابتدا بررسی میشه که کاربر دارای دسترسی مورد نظر می‌باشد و سپس چک میشه که کاربر احراز هویت شده و دارای دسترسی admin می‌باشد.
آیا کاربری که دارای دسترسی Admin می‌باشد نیازی به چک کردن  HasClaim دارد؟
مثلا اگر این متد به این صورت نوشته شود مشکلی پیش میاد؟
public bool CanUserAccess(ClaimsPrincipal user, string area, string controller, string action)
        {
            if (!user.Identity.IsAuthenticated)
            {
                return false;
            }

            if (user.IsInRole(ConstantRoles.Admin))
            {
                // Admin users have access to all of the pages.
                return true;
            }

            var currentClaimValue = $"{area}:{controller}:{action}";
            var securedControllerActions = _mvcActionsDiscoveryService.GetAllSecuredControllerActionsWithPolicy(ConstantPolicies.DynamicPermission);
            if (!securedControllerActions.SelectMany(x => x.MvcActions).Any(x => x.ActionId == currentClaimValue))
            {
                throw new KeyNotFoundException($@"The `secured` area={area}/controller={controller}/action={action} with `ConstantPolicies.DynamicPermission` policy not found. Please check you have entered the area/controller/action names correctly and also it's decorated with the correct security policy.");
            }

            // Check for dynamic permissions
            // A user gets its permissions claims from the `ApplicationClaimsPrincipalFactory` class automatically and it includes the role claims too.
            return user.HasClaim(claim => claim.Type == ConstantPolicies.DynamicPermissionClaimType &&
                                          claim.Value == currentClaimValue);
        }
نظرات مطالب
واژه‌های کلیدی جدید and، or و not در C# 9.0
معادل‌های string.IsNullOrEmpty در C# 9.0 جهت اطلاع و آشنایی با Syntax جدید؛ اگر جائی آن‌ها را دیدید!
using System;

namespace CS9Features
{
    public static class ModernizingACodebase
    {
        public static void PropertyPatternToReplaceIsNullorEmpty1()
        {
            string hello = null;

            // Old approach
            if (!string.IsNullOrEmpty(hello))
            {
                Console.WriteLine($"{hello} has {hello.Length} letters.");
            }

            // New approach, with a property pattern
            if (hello is { Length: > 0 })
            {
                Console.WriteLine($"{hello} has {hello.Length} letters.");
            }
        }

        public static void PropertyPatternToReplaceIsNullorEmpty2()
        {
            // For arrays
            var greetings = new string[2];
            greetings[0] = "Hello world";
            greetings = null;

            // Old approach
            if (greetings != null && !string.IsNullOrEmpty(greetings[0]))
            {
                Console.WriteLine($"{greetings[0]} has {greetings[0].Length} letters.");
            }

            // New approach
            if (greetings?[0] is { Length: > 0 } hi)
            {
                Console.WriteLine($"{hi} has {hi.Length} letters.");
            }
        }
    }
}
برای مطالعه‌ی بیشتر