مطالب
امکان تعریف اعضای static abstract در اینترفیس‌های C# 11
امکان داشتن اعضای static abstract در اینترفیس‌ها شاید عجیب به‌نظر برسد یا حتی غیرضروری؛ اما در C# 11، پایه‌ی قابلیت جدیدی به نام «ریاضیات جنریک» شده‌است. به همین جهت در ابتدا نیاز است با اعضای static abstract آشنا شد و در قسمتی دیگر به «ریاضیات جنریک» پرداخت.


مثالی جهت توضیح علت نیاز به اعضای static abstract در اینترفیس‌ها

فرض کنید قصد داریم حاصل جمع اعضای یک آرایه‌ی int را محاسبه کنیم:
namespace CS11Tests;

public class StaticAbstractMembers
{
    public static void Test()
    {
        var sum = AddAll(new[] { 1, 2, 3, 4 });
        Console.WriteLine(sum);
    }

    private static int AddAll(int[] values)
    {
        int result = 0;
        foreach (var value in values)
        {
            result += value;
        }
        return result;
    }
}
روش متداول اینکار را در اینجا ملاحظه می‌کنید که حلقه‌ای بر روی عناصر آرایه، جهت یافتن حاصل جمع آن‌ها تشکیل شده‌است. اکنون فرض کنید بجای آرایه‌ای که در متد Test استفاده شده، از آرایه‌ی زیر استفاده شود:
var sum = AddAll(new[] { 1, 2, 3, 4, 0.68 });
اینبار با خطای زیر متوقف می‌شویم:
Argument 1: cannot convert from 'double[]' to 'int[]' [CS11Tests]csharp(CS1503)
عنوان می‌کند که آرایه‌ی مدنظر از نوع []double تشخیص داده شده‌است و متد AddAll، تنها آرایه‌های از نوع int را قبول می‌کند. در جهت رفع این مشکل شاید بهتر باشد نمونه‌ی جنریک متد AddAll را ایجاد کنیم، تا بتوان انواع و اقسام نوع‌های ممکن را به آن ارسال کرد:
private static T AddAll<T>(T[] values)
    {
        T result = 0;
        foreach (var value in values)
        {
            result += value;
        }
        return result;
    }
اما اینکار میسر نیست. چون زمانیکه از T استفاده می‌شود، مفهوم و امکان وجود «عدد صفر» در آن نوع، مشخص نیست. یک روش حل این مشکل، مقید و محدود کردن نوع T است. برای مثال عنوان کنیم که T، عددی است و از نوع INumber (فرضی/خیالی) است و این INumber فرضی، به همراه مفهوم عدد صفر هم هست. یعنی اولین سطر بدنه‌ی متد AddAll را باید بتوان به صورت زیر بازنویسی کرد:
T result = T.Zero;
یعنی باید بتوان از طریق یک «نوع» عمومی مانند T (نه وهله‌ای/نمونه‌ای/instance ای از آن نوع؛ دقیقا خود آن نوع) به خاصیت Zero آن نوع، دسترسی یافت و آن خاصیت هم باید از نوع استاتیک باشد و چون تا C# 10 و دات نت 6، چنین امکانی مهیا نشده بود (البته در حالت preview قرار داشت)، تنها راه ممکن، تهیه‌ی یک نمونه‌ی جدید double متد AddAll است/بود.
در C# 11 و دات نت 7، با معرفی اینترفیس جدید INumber، می‌توان قید <where T : INumber<T را به T اعمال کرد (مانند نمونه‌ی زیر) و همچنین با استفاده از اعضای static abstract این اینترفیس، به مقدار T.Zero هم دسترسی یافت و اینبار قطعه کد زیر، بدون مشکل در C# 11 کامپایل می‌شود:
using System.Numerics;

namespace CS11Tests;

public class StaticAbstractMembers
{
    public static void Test()
    {
        //var sum = AddAll(new[] { 1, 2, 3, 4 });
        var sum = AddAll(new[] { 1, 2, 3, 4, 0.68 });
        Console.WriteLine(sum);
    }

    private static T AddAll<T>(T[] values) where T : INumber<T>
    {
        T result = T.Zero;
        foreach (var value in values)
        {
            result += value;
        }
        return result;
    }
}
اگر به تعاریف INumber جدید مراجعه کنیم، نه فقط به خواص abstract static جدیدی می‌رسیم (که امکان دسترسی به T.Zero را میسر کرده‌اند)،
abstract static TSelf One { get; }
abstract static TSelf Zero { get; }
بلکه امکان تعریف اپراتورهای abstract static هم میسر شده‌اند (به همین جهت است که در کدهای فوق سطر result += value، هنوز هم کار می‌کند):
abstract static TResult operator +(TSelf left, TOther right);


مثال دیگری از کاربرد اعضای abstract static در اینترفیس‌ها

فرض کنید اینترفیس ISport را به همراه دو پیاده سازی از آن، به صورت زیر تعریف کرده‌ایم:
public interface ISport
{
    bool IsTeamSport();
}

public class Swimming : ISport
{
    public bool IsTeamSport() => false;
}

public class Football : ISport
{
    public bool IsTeamSport() => true;
}
اکنون جهت کار با متد IsTeamSport و تعریف جنریک این متد، می‌توان به صورت متداول زیر عمل کرد که در آن T، مقید به ISport شده‌است:
public class StaticAbstractMembers
{
    public static void Display<T>(T sport) where T : ISport
    {
        Console.WriteLine("Is Team Sport:" + sport.IsTeamSport());
    }
}
برای کار با آن هم باید حتما نمونه‌ای از ()new Football و یا ()new Swimming را به آن ارسال کرد:
Display(new Football());
سؤال: آیا با توجه به مشخص بودن و محدود بودن نوع T، می‌توان با حذف پارامتر T sport، به متد IsTeamSport اینترفیس ISport دسترسی یافت؟ یعنی تعریف متد Display را طوری تغییر داد تا دیگر نیاز به نمونه سازی ()new Football نداشته باشد. همینقدر که نوع Football مشخص بود، بتوان متد IsTeamSport آن‌را فراخوانی کرد.
پاسخ: تا پیش‌از C# 11 یکی از روش‌‌های انجام اینکار، استفاده از reflection بود. اما در C# 11 با کمک static abstractها می‌توان تعاریف این اینترفیس و پیاده سازی‌های آن‌را به صورت زیر تغییر داد:
public interface ISport
{
    static abstract bool IsTeamSport();
}

public class Swimming : ISport
{
    public static bool IsTeamSport() => false;
}

public class Football : ISport
{
    public static bool IsTeamSport() => true;
}
تا اینبار جهت دسترسی به متد IsTeamSport،‌مستقیما بتوان به خود «نوع»، «بدون نیاز به نمونه سازی آن» مراجعه کرد و قطعه کد زیر در C# 11 معتبر است:
public class StaticAbstractMembers
{
    public static void Display<T>() where T : ISport
    {
        Console.WriteLine("Is Team Sport:" + T.IsTeamSport());
    }
}
مطالب
آموزش TypeScript #5
در ادامه مباحث شی گرایی در TypeScript قصد داریم به مباحث مربوط به interface و طریقه استفاده از آن بپردازیم. همانند زبان‌های دات نتی در TypeScript نیز به راحتی می‌توانید interface تعریف کنید. در یک اینترفیس اجازه پیاده سازی هیچ تابعی را ندارید و فقط باید عنوان و پارامتر‌های ورودی و نوع خروجی آن را تعیین کنید. برای تعریف اینترفیس از کلمه کلیدی interface به صورت زیر استفاده خواهیم کرد.
export interface ILogger {
  log(message: string): void;
}
همان طور در پست‌های قبلی مشاهده شد از کلمه کلیدی export برای عمومی کردن اینترفیس استفاده می‌کنیم. یعنی این اینترفیس از بیرون ماژول خود نیز قابل دسترسی است.
حال نیاز به کلاسی داریم که این اینترفیس را پیاده سازی کند. این پیاده سازی به صورت زیر انجام می‌گیرد:
export class Logger implements ILogger
{
}
یا:
export class AnnoyingLogger implements ILogger {
   log(message: string): void{
         alert(message);
     } 
}
همانند دات نت یک کلاس می‌تواند چندین اینترفیس را پیاده سازی کند.(اصطلاحا به این روش explicit implementation یا پیاده سازی صریح می‌گویند)
export class MyClass implements IFirstInterface, ISecondInterface
{

}
*یکی از قابلیت جالب و کارآمد زبان TypeScript این است که در هنگام کار با اینترفیس‌ها حتما نیازی به پیاده سازی صریح نیست. اگر یک object تمام متغیر‌ها و توابع مورد نیاز یک اینترفیس را پیاده سازی کند به راحتی همانند روش explicit emplementation می‌توان از آن object استفاده کرد.  به این قابلیت Duck Typing  می‌گویند. مثال:
IPerson {
   firstName: string;
   lastName: string;
} 
class Person implements IPerson {
  constructor(public firstName: string, public lastName: string) {
  }
}

varpersonA: IPerson = newPerson('Masoud', 'Pakdel'); //expilict
varpersonB: IPerson = { firstName: 'Ashkan', lastName: 'Shahram'}; // duck typing
همان طور که می‌بینید object  دوم به نام personB تمام متغیر‌ها ی مورد نیاز اینترفیس IPerson را پیاده سازی کرده است در نتییجه کامپایلر همان رفتاری را که با object اول به نام personA دارد را با آن نیز خواهد داشت.

پیاده سازی چند اینترفیس به صورت همزمان
همانند دات نت که یک کلاس فقط می‌تواند از یک کلاس ارث ببرد ولی می‌تواند n  تا اینترفیس را پیاده سازی کند در TypeScript نیز چنین قوانینی وجود دارد. یعنی یک اینترفیس می‌تواند چندین اینترفیس دیگر را توسعه دهد(extend) و کلاسی که این اینترفیس را پیاده سازی می‌کند باید تمام توابع اینترفیس‌ها را پیاده سازی کند. مثال:
interface IMover {
 move() : void;
}

interface IShaker {
 shake() : void;
} 

interface IMoverShaker extends IMover, IShaker {
}
class MoverShaker implements IMoverShaker {
 move() {
 }
 shake() {
 }
}
*به کلمات کلیدی extends و implements و طریقه به کار گیری آن‌ها دقت کنید.

 instanceof

از instanceof زمانی استفاده می‌کنیم که قصد داشته باشیم که یک instance را با یک نوع مشخص مقایسه کنیم. اگر instance مربوطه از نوع مشخص باشد یا از این نوع ارث برده باشد مقدار true برگشت داده می‌شود در غیر این صورت مقدار false خواهد بود.
یک مثال:
var isLogger = logger instanceof Utilities.Logger; 
var isLogger = logger instanceof Utilities.AnnoyingLogger; 
var isLogger = logger instanceof Utilities.Formatter;
Method overriding
در TypeScript می‌توان مانند زبان‌های شی گرای دیگر Method overriding را پیاده سازی کرد. یعنی می‌توان متد‌های کلاس پایه را در کلاس مشتق شده تحریف کرد. با یک مثال به شرح این مورد خواهم پرداخت.
فرض کنید یک کلاس پایه به صورت زیر داریم:
class BaseEmployee
{   
    constructor (public fname: string,public lname: string) 
    {  
    }  
    sayInfo() 
    {  
       alert('this is base class method');
    }  
}
کلاس دیگری به نام Employee می‌سازیم که کلاس بالا را توسعه می‌دهد(یا به اصطلاح از کلاس بالا ارث می‌برد).
class Employee extends BaseEmployee
{  
   sayInfo() 
     {  
        alert('this is derived class method');
     }  
}  

window.onload = () =>  
{   
    var first: BaseEmployee= new Employee();     
    first.sayInfo();  
    var second: BaseEmployee = new BaseEmployee(); 
    second.sayInfo(); 
}
نکته مهم این است که دیگر خبری از کلمه کلیدی virtual برای مشخص کردن توابعی که قصد overriding آن‌ها را داریم نیست. تمام توابع که عمومی هستند را می‌توان override کرد.
*اگر در کلاس مشتق شده قصد داشته باشیم که به توابع و فیلد‌های کلاس پایه اشاره کنیم باید از کلمه کلیدی super استفاده کنیم.(معادل base در #C).
مثال:
class Animal {
    constructor (public name: string) {
    }
}

class Dog extends Animal {    
      constructor (public name: string, public age:number)
      {
        super(name);
      }

    sayHello() {
alert(super.name);
 } }
اگر به سازنده کلاس مشتق شده دقت کنید خواهید دید که پارامتر name را به سازنده کلاس پایه پاس دادیم: کد معادل در #C به صورت زیر است:
public class Dog : Animal 
{    
      public Dog (string name, int age):base(name)
      {
      }
}
در تابع sayHello نیز با استفاده از کلمه کلیدی super به فیلد name در کلاس پایه دسترسی خواهیم داشت.

*دقت کنید که مباحث مربوط به interface و private modifier و Type safety که پیش‌تر در مورد آن‌ها بحث شد، فقط در فایل‌های TypeScript و در هنگام کد نویسی و طراحی معنی دار هستند، زیرا بعد از کامپایل فایل‌های ts این مفاهیم در Javascript پشتیبانی نمی‌شوند در نتیجه هیچ مورد استفاده هم نخواهد داشت.

ادامه دارد...
مطالب
بازسازی کد: گسترش امکانات کلاس های غریبه
هیچ کلاسی کامل نیست. در مواقع زیادی ممکن است یک کلاس نیاز به متدی داشته باشد که در آن وجود ندارد. در چنین شرایطی اگر سورس کلاس را در دست داشته باشیم به راحتی می‌توان رفتار مورد نظر را به آن اضافه کرد. اما اگر از کلاسهایی استفاده می‌کنیم که سورس آنها در دست نیست، حل این مورد کمی مشکل خواهد بود. برای مدیریت و رفع این مورد، دو بازسازی کد وجود دارند که به جهت همسویی این دو، آنها را در یک نوشتار پوشش می‌دهیم.
نیاز به متد جدید در یک کلاس خاص، نیازی مکرر در توسعه نرم افزار است. معمولا اگر این نیاز فقط در یک مورد بوجود بیاید موضوع خیلی سخت نیست و می‌توان در بدنه متد استفاده کننده، منطق را پیاده سازی کرد. اما زمانیکه تعداد استفاده‌ها از متد مورد نیاز بیشتر از یک بار باشد، کپی کردن روال آن کار درستی نیست و باید متدی برای این روال ایجاد کرد؛ اما کجا؟ 

ایجاد متد بیرونی  

در این روش در کلاس استفاده کننده، متدی برای نیاز جدید ساخته می‌شود. به طور مثال به تکه کد زیر توجه نمایید. در این مثال نیاز داریم در شیء نشان دهنده تاریخ، به روز بعد برسیم. برای این منظور می‌توانیم تکه کدی به صورت زیر داشته باشیم:  
DateTime newStart = new DateTime(previousEnd.Year, previousEnd.Month, previousEnd.Day + 1);
زمانیکه بدست آوردن روز بعدی در جاهای زیادی از کد نیاز باشد، باید این روال را در متدی پیاده سازی کرد. این متد می‌تواند بدنه‌ای به صورت زیر داشته باشد:  
private static DateTime NextDay(DateTime now) 
{ 
      return new DateTime(now.Year, now.Month, now.Day + 1); 
}

ایجاد کلاس توسعه دهنده برای کلاس غریبه 

زمانیکه تعداد متدهای جدید مورد نیاز روی یک کلاس غریبه کم است، روش اول روش قابل قبولی است. اما در مواردی تعداد متدهای جدید مورد نیاز زیاد می‌شوند. در چنین شرایطی بهتر است کلاسی را برای توسعه امکانات کلاس غریبه ایجاد کنیم و متدهای مورد نظر را در آن بنویسیم.
برای ایجاد این کلاس نیز دو روش وجود دارد:
روش اول: ارث بری از کلاس غریبه. این روش در شرایطی که کلاس غریبه به صورت sealed باشد، جواب نخواهد داد. به طور مثال تکه کد زیر را در نظر بگیرید. در این تکه کد با فرض این که کد کلاس Person در دست نیست و نیاز است متدی برای دریافت سن فرد، بر اساس سال تولد او به کلاس اضافه شود.  
public class MyPerson : Person 
{ 
    public int GetAge() 
    { 
        return 0; 
    } 
}
یکی از اشکالات این روش این است که برای استفاده از این کلاس و متد تعریف شده در آن، باید تمامی موارد مورد نیاز متد جدید را از کلاس Person، به نوع کلاس جدید تغییر داد. این تغییر در مقاطعی نیاز به Cast یا ایجاد شیء جدیدی با نوع جدیدی منجر خواهد شد.
روش دوم: ساختن wrapper به دور شیء کلاس غریبه. در این روش کلاسی ایجاد می‌شود و شیء‌ای از کلاس غریبه در آن ساخت شده و تمامی متدهای کلاس غریبه در آن تعریف می‌شوند. این متدها صرفا امر هدایت فرخوانی به متدهای کلاس اصلی را انجام می‌دهند. سپس متدهای جدیدی در این کلاس تعریف و پیاده سازی می‌شوند. برای ایجاد امکان محاسبه سن فرد می‌توان کلاسی را مانند کلاس زیر نوشت:  
public class PersonWrapper 
{ 
    private readonly Person _person; 
    public PersonWrapper(Person person) 
    { 
        _person = person; 
    } 
    public int GetAge() 
    { 
        return 0; 
    } 
}
دقت کنید که در این روش پیاده سازی نیز تمامی خصوصیات و متدهای کلاس اصلی در کلاس wrapper وجود خواهند داشت. استفاده از این کلاس به صورت زیر خواهد بود:  
var person = new Person(); 
var wrapper = new PersonWrapper(person); 
wrapper.GetAge();
در زبان برنامه نویسی سی شارپ امکانی به نام extension method وجود دارد که هدف آن پیاده سازی همین طراحی از طریق امکانات زبان است. برای مطالعه بیشتر این مبحث در زبان سی شارپ می‌توانید به اینجا مراجعه نمایید.  
مطالب
یافتن آدرس نهایی یک Url پس از Redirect

برای مثال آدرس http://feedproxy.google.com/~r/nettuts/~3/tWCksueANyY/ را در نظر بگیرید. گوگل پس از خرید feedburner ، از feedproxy.google.com جهت ردیابی آدرس‌های فیدها استفاده می‌کند؛‌ مثلا فید شما چند نفر خواننده دارد، کدام موارد بیشتر خوانده شده، با چه مرورگرهایی، از چه مکان‌هایی و مواردی از این دست. البته می‌توان در تنظیمات فیدبرنر این نوع آدرس دهی را خاموش کرد ولی به صورت پیش فرض فعال است. مشکل feedproxy.google.com هم این است که در ایران فیلتر است. بنابراین یافتن آدرس اصلی این نوع لینک‌ها پس از Redirect نهایی می‌تواند جهت ارائه عمومی آن‌ها مفید باشد. با استفاده از قطعه کد زیر می‌توان این آدرس را یافت:

using System;
using System.Net;

namespace Linq2Rss
{
public class RedirectFinder
{
public CookieContainer Cookies { set; get; }
public string Url { get; set; }

public string GetRedirectUrl()
{
var hops = 1;
const int MaxRedirects = 20;

do
{
var request = (HttpWebRequest)WebRequest.Create(Url);
request.UserAgent = "MyUA";
request.KeepAlive = true;
request.Referer = Url;
if (Cookies == null) Cookies = new CookieContainer();
request.CookieContainer = Cookies;
request.AllowAutoRedirect = false;

using (var webResp = request.GetResponse() as HttpWebResponse)
{
if ((webResp.StatusCode == HttpStatusCode.Found) ||
(webResp.StatusCode == HttpStatusCode.Redirect) ||
(webResp.StatusCode == HttpStatusCode.Moved) ||
(webResp.StatusCode == HttpStatusCode.MovedPermanently))
{
var newLocation = webResp.Headers["Location"];
if (newLocation.StartsWith("/"))
{
var uri = new Uri(Url);
Url = string.Format("{0}://{1}:{2}{3}", uri.Scheme, uri.Host, uri.Port, newLocation);
}
else
Url = newLocation;
}
else
{
if (webResp.StatusCode == HttpStatusCode.OK)
return Url;
}
}

hops++;
} while (hops <= MaxRedirects);
return Url;
}
}
}


برای یافتن آدرس واقعی یک Url پس از Redirect راهی بجز درخواست آن از وب سرور اولیه وجود ندارد. سپس وضعیت پاسخ داده شده بررسی می‌شود؛ اگر حاوی Found ، Moved یا Redirect بود، به این معنا است که باید آدرس جدید را از هدر پاسخ دریافتی استخراج کنیم. این آدرس، در کلید Location ذخیره می‌شود. اکنون یکبار دیگر نیاز است تا این آدرس جدید بررسی شود، زیرا ممکن است این مورد هم به آدرس دیگری اشاره کند. در کل، کد فوق 20 بار این بررسی را انجام خواهد داد (هر چند عموما در دو یا سه سعی به جواب خواهیم رسید).
نظرات مطالب
VS Code برای توسعه دهندگان ASP.NET Core - قسمت دوم - ایجاد و اجرای اولین برنامه
افرایش سرعت در نوشتن صفت (Attribute)‌های ویوو مدل ها
در اکثر پروژه‌ها، متن خطای اکثر ویوو مدل‌ها شبیه هم است و تفاوت خاصی ندارند؛ مثلا اتریبیوت Required، متن خطایش معمولا با این مضمون است: "لطفا فیلد ... را وارد نمایید." ما میتوانیم تمام این متن‌های خطاها را در جایی دیگر تعریف و در متن خطای اتریبیوت‌هایمان از آن استفاده کنیم که باعث میشود بعدا اگر خواستیم متن خطا‌ها را تغییر دهیم (مثلا در مورد اتریبیوت Required متن "لطفا فیلد ... را وارد نمایید" را با * عوض کنیم) در تمام پروژه این تغییر اعمال میشود و دیگر نیازی نیست تمامی متن خطاها را یکی یکی تغییر دهیم و نگهداری کد‌ها برای بعد راحت‌تر میشود.
برای شروع یک کلاس را برای متن خطاهای اتریبیوت‌هایمان تعریف میکنیم:
public static class AttributesErrorMessages
{
    public const string RequiredMessage = "لطفا {0} را وارد نمایید";
    public const string MinLengthMessage = "{0} نباید کمتر از {1} کاراکتر باشد";
    public const string MaxLengthMessage = "{0} نباید بیشتر از {1} کاراکتر باشد";
    public const string RegularExpressionMessage = "{0} را به درستی وارد نمایید";
    public const string StringLengthMessage = "{0} باید بین {2} کاراکتر و {1} کاراکتر باشد";
    public const string RemoteMessage = "با این {0} قبلا ثبت نام شده است";
}
حال برای تعریف هر ویوو مدل تنها کافی است آن را تعریف و در بالای آن، از اتریبیوت دلخواه استفاده و متن ارور آن را مطابق کلاس فوق وارد میکنیم، مثلا:
[StringLength(110, MinimumLength = 5, ErrorMessage = AttributesErrorMessages.StringLengthMessage)]
public string TestProp { get; set; }
حال میتوان کار را ساده‌تر نیز کرد و تمام اتریبیوت‌ها را به یک قطعه‌کد (Snippet) تبدیل کرد. برای این کار از طریق File>Preferences وارد منوی User Snippet میشویم و بعد زبان سی شارپ را انتخاب و بعد Snippet‌های خود را اضافه میکنیم:
{
   "Required":{
      "prefix":"required",
      "body":[
         "[Required(ErrorMessage = AttributesErrorMessages.RequiredMessage)]"
      ],
      "description":"Required attribute"
   },
   "Max Length":{
      "prefix":"maxlength",
      "body":[
         "[MaxLength(${1:number}, ErrorMessage = AttributesErrorMessages.MaxLengthMessage)]"
      ],
      "description":"Max length attribute"
   },
   "Min Length":{
      "prefix":"minlength",
      "body":[
         "[MinLength(${1:number}, ErrorMessage = AttributesErrorMessages.MinLengthMessage)]"
      ],
      "description":"Min length attribute"
   },
   "String Length":{
      "prefix":"stringlength",
      "body":[
         "[StringLength(${1:maximumNumber}, MinimumLength = ${2:minmumNumber}, ErrorMessage = AttributesErrorMessages.StringLengthMessage)]"
      ],
      "description":"String length attribute"
   },
   "Email Address":{
      "prefix":"emailaddress",
      "body":[
         "[EmailAddress(ErrorMessage = AttributesErrorMessages.RegularExpressionMessage)]"
      ],
      "description":"Email address attribute"
   },
   "Regular Expression":{
      "prefix":"regularexpression",
      "body":[
         "[RegularExpression(\"${1:patternString}\", ErrorMessage = AttributesErrorMessages.RegularExpressionMessage)]"
      ],
      "description":"Regular expression attribute"
   },
   "Remote Expression":{
      "prefix":"remote",
      "body":[
         "[Remote(\"${1:action}\", \"${2:controller}\", ErrorMessage = AttributesErrorMessages.RemoteMessage)]"
      ],
      "description":"Remote attribute"
   }
}
حال تنها کافی است اسم اتریبیوت مورد نظر را تایپ کنیم و آن Snippet را از پنجره Intellisense انتخاب کنیم.
مطالب
Blazor 5x - قسمت دهم - مبانی Blazor - بخش 7 - مسیریابی
تا اینجا به صورت بسیار مختصری با نحوه‌ی مسیریابی برنامه‌های مبتنی بر Blazor توسط دایرکتیو page@ آشنا شدیم. برای مثال با داشتن تعریف زیر در ابتدای یک کامپوننت:
@page "/LearnRouting"

<h3>Learn Routing</h3>
این کامپوننت جدید، صرفنظر از محل قرارگیری فایل آن که برای مثال در پوشه‌ی Pages\LearnBlazor\LearnRouting.razor است، در مسیر https://localhost:5001/LearnRouting قابل دسترسی خواهد شد و برای تعریف مدخل منوی جدید آن، به کامپوننت Shared\NavMenu.razor مراجعه کرده و NavLink جدیدی را برای آن تعریف می‌کنیم:
<li class="nav-item px-3">
    <NavLink class="nav-link" href="LearnRouting">
        <span class="oi oi-list-rich" aria-hidden="true"></span> Learn Routing
    </NavLink>
</li>
در اینجا برچسب مدخل جدید تعریف شده، Learn Routing است و href لینک به آن، دقیقا به مسیریابی تعریف شده اشاره می‌کند.

یک نکته: مسیریابی‌های تعریف شده‌ی در Blazor، حساس به حروف کوچک و بزرگ نیستند.


امکان تعریف بیش از یک مسیریابی برای یک کامپوننت نیز وجود دارد

در کامپوننت‌های Blazor، محدودیتی از لحاظ تعداد بار تعریف دایرکتیو page@ وجود ندارد:
@page "/LearnRouting"
@page "/NewRouting"

<h3>Learn Routing</h3>
در این حالت می‌توان در حین تعریف یک مسیریابی جدید، مسیریابی قبلی را نیز حفظ کرد. در اینجا کامپوننت فوق، از طریق هر دو آدرس https://localhost:5001/LearnRouting و https://localhost:5001/NewRouting تعریف شده، قابل دسترسی‌است.


روش تعریف پارامترهای مسیریابی

تا اینجا اگر مسیر جدید https://localhost:5001/NewRouting/1/2 را درخواست کنیم چه اتفاقی رخ می‌دهد؟


در مورد نحوه‌ی تعریف قالب «یافت نشد» فوق، در قسمت دوم بیشتر بحث شد.
برای تعریف پارامترهای مسیریابی، می‌توان مسیریابی سومی را با پارامترهای مدنظر تعریف کرد که در مثال زیر، ذکر پارامتر دوم اختیاری است؛ چون سومین مسیریابی تعریف شده، امکان پردازش مسیرهایی با یک پارامتر را هم ممکن می‌کند:
@page "/LearnRouting"
@page "/NewRouting"
@page "/LearnRouting/{parameter1}"
@page "/LearnRouting/{parameter1}/{parameter2}"

<h3>Learn Routing</h3>

<p>Parameter1: @Parameter1</p>
<p>Parameter2: @Parameter2</p>

@code
{
    [Parameter]
    public string Parameter1 { set; get; }

    [Parameter]
    public string Parameter2 { set; get; }
}
سپس جهت دست‌یابی به مقادیر این پارامترها می‌توان در قسمت کدهای کامپوننت، از خواص عمومی مزین شده‌ی با ویژگی Parameter استفاده کرد. در اینجا هر خاصیت تعریف شده، باید هم نام پارامتر تعریف شده باشد (و این مورد نیز غیر حساس به حروف بزرگ و کوچک است).
پس از این تعاریف، مسیریابی مانند https://localhost:5001/LearnRouting/1 با یک پارامتر و یا https://localhost:5001/LearnRouting/1/2 که به همراه دو پارامتر است، قابل فراخوانی می‌شود.





روش تعریف لینک به سایر کامپوننت‌های Blazor

در ادامه کامپوننت جدید Pages\LearnBlazor\LearnAdvancedRouting.razor را اضافه می‌کنیم؛ با این محتوای آغازین:
@page "/LearnAdvancedRouting"

<h3>Learn Advanced Routing</h3>
در اینجا قصد نداریم لینک به این کامپوننت را به منوی اصلی برنامه اضافه کنیم؛ بلکه می‌خواهیم از طریق همان کامپوننت LearnRouting.razor ابتدای بحث، این مسیریابی را برقرار کنیم. برای اینکار یا می‌توان از یک anchor tag استاندارد استفاده کرد و یا همانند کامپوننت Shared\NavMenu.razor، از کامپوننت NavLink استفاده نمود. NavLink‌ها نیز همانند anchor tag‌های استاندارد HTML هستند، با این تفاوت که این کامپوننت، افزودن کلاس active مخصوص بوت استرپ را هم بر اساس فعال بودن مسیریابی مرتبط به آن، انجام می‌دهد ("class="nav-link active). به همین علت است که اگر گزینه‌ی منوی خاصی را انتخاب کنیم، این گزینه با رنگ متمایزی نشان داده می‌شود:


بنابراین یک روش تعریف لینک به کامپوننتی دیگر، استفاده از کامپوننت NavLink است که href آن به مسیریابی مقصد اشاره می‌کند:
<NavLink class="btn btn-secondary" href="LearnAdvancedRouting">
    <span class="oi oi-list-rich" aria-hidden="true"></span> Learn Advanced Routing
</NavLink>


ارسال کوئری استرینگ‌ها به کامپوننت‌های مختلف

پس از تعریف لینکی به کامپوننتی دیگر از درون یک کامپوننت، اکنون می‌خواهیم دو کوئری استرینگ param1 و param2 را نیز به آن ارسال کنیم:
<NavLink class="btn btn-secondary" href="LearnAdvancedRouting?param1=value1&param2=value2">
    <span class="oi oi-list-rich" aria-hidden="true"></span> Learn Advanced Routing
</NavLink>
در کامپوننت LearnAdvancedRouting برای دریافت این پارامترها، نیاز است آن‌ها را از URL جاری استخراج کرد:
@page "/LearnAdvancedRouting"
@inject NavigationManager NavigationManager

<h3>Learn Advanced Routing</h3>

<h4>Parameter 1 : @Param1</h4>
<h4>Parameter 2 : @Param2</h4>

@code
{
    string Param1;
    string Param2;

    protected override void OnInitialized()
    {
        base.OnInitialized();

        var absoluteUri = new Uri(NavigationManager.Uri);
        var queryParam = System.Web.HttpUtility.ParseQueryString(absoluteUri.Query);
        Param1 = queryParam["Param1"];
        Param2 = queryParam["Param2"];
    }
}
ابتدا سرویس توکار NavigationManager توسط دایرکتیو inject@ به کامپوننت جاری تزریق شده‌است که برای کار و دسترسی به آن، نیاز به تنظیمات ابتدایی خاصی نیست و پیشتر به مجموعه‌ی سرویس‌های برنامه افزوده شده‌است. برای نمونه توسط آن می‌توان به Uri در حال پردازش، دسترسی یافت. اکنون که این Uri را داریم، با استفاده از متد HttpUtility.ParseQueryString می‌توان به مجموعه‌ی کوئری استرینگ‌های ارسالی، به صورت key/value دسترسی یافت و برای مثال آن‌ها را در روال رویدادگردان OnInitialized، دریافت و با انتساب آن‌ها به دو فیلد تعریف شده، سبب نمایش مقادیر دریافتی شد:



هدایت به یک کامپوننت دیگر با کد نویسی

فرض کنید می‌خواهیم دکمه‌ای را اضافه کنیم که با کلیک بر روی آن، ما را به کامپوننت LearnRouting هدایت می‌کند:
@page "/LearnAdvancedRouting"
@inject NavigationManager NavigationManager

@*<NavLink href="/learnrouting" class="btn btn-secondary">Back to Routing</NavLink>*@
@*<a href="/learnrouting" class="btn btn-secondary">Back to Routing</a>*@
<button class="btn btn-secondary" @onclick="BackToRouting">Back to Routing</button>

@code
{
    private void BackToRouting()
    {
        NavigationManager.NavigateTo("learnrouting");
    }
}
در اینجا روش‌های مختلف تعریف لینک به کامپوننتی دیگر را مشاهده می‌کنید. یا می‌توان از کامپوننت NavLink استفاده کرد و یا از یک anchor tag استاندارد، که href هر دوی آن‌ها به مسیریابی مقصد اشاره می‌کنند و یا می‌توان با استفاده از سرویس NavigationManager و متد NavigateTo آن مانند کدهای فوق، سبب هدایت کاربر به صفحه‌ای دیگر شد.


کدهای کامل این مطلب را از اینجا می‌توانید دریافت کنید: Blazor-5x-Part-10.zip
مطالب
روش یافتن لیست تمام کنترلرها و اکشن‌ متدهای یک برنامه‌ی ASP.NET Core
یک نمونه روش یافتن لیست تمام کنترلرها و اکشن متدهای یک برنامه‌ی ASP.NET MVC 5.x را در مطلب «نحوه ایجاد یک نقشه‌ی سایت پویا با استفاده از قابلیت Reflection» می‌توانید ملاحظه کنید. استفاده‌ی از این روش با ASP.NET Core الزاما به پاسخ مناسبی نخواهد رسید؛ چون در اینجا POCO controllers هم اضافه شده‌اند. به علاوه می‌توان اسمبلی‌های دیگری را در زمان آغاز برنامه به تنظیمات AddMvc اضافه کرد و تمام آن‌ها هم می‌توانند حاوی کنترلرها و ویووها خاص خودشان باشند. روش بهتر این است که از خود ASP.NET Core سؤال کنیم چه مواردی را به عنوان کنترلر تشخیص داده‌ای؟ در ادامه این نکته را بیشتر بررسی خواهیم کرد.


معرفی سرویس IActionDescriptorCollectionProvider در ASP.NET Core

فرض کنید می‌خواهیم لیست تمام کنترلرهای یک برنامه‌ی ASP.NET Core را با ساختار ذیل تهیه کنیم که شامل نام کنترلر، نام اکشن متد و نام ناحیه‌ی متناظر با آن (در صورت تنظیم) می‌باشد:
public class MvcActionViewModel
{
    public string ControllerName { get; set; }
 
    public string ActionName { get; set; }
 
    public string AreaName { get; set; } 
}
یکی از سرویس‌های از پیش ثبت شده‌ی ASP.NET Core که لیست تمام کنترلرها و اکشن متدهای تشخیص داده شده‌ی توسط آن را به همراه دارد، سرویس IActionDescriptorCollectionProvider می‌باشد. برای شروع به کار با آن، ابتدا این سرویس را به سازنده‌ی یک کلاس دلخواه تزریق می‌کنیم:
public interface IMvcActionsDiscoveryService
{
    ICollection<MvcActionViewModel> MvcActions { get; }
}
 
public class MvcActionsDiscoveryService : IMvcActionsDiscoveryService
{
    public MvcActionsDiscoveryService(IActionDescriptorCollectionProvider actionDescriptorCollectionProvider)
    {
        var actionDescriptors = actionDescriptorCollectionProvider.ActionDescriptors.Items;
        foreach (var actionDescriptor in actionDescriptors)
        {
            var descriptor = actionDescriptor as ControllerActionDescriptor;
            if (descriptor == null)
            {
                continue;
            }
 
            var controllerTypeInfo = descriptor.ControllerTypeInfo;
            var actionMethodInfo = descriptor.MethodInfo;
            MvcActions.Add(new MvcActionViewModel
            {
                ControllerName = descriptor.ControllerName,
                ActionName = descriptor.ActionName,
                AreaName = controllerTypeInfo.GetCustomAttribute<AreaAttribute>()?.RouteValue
            });
        }
    }
 
    public ICollection<MvcActionViewModel> MvcActions { get; } = new HashSet<MvcActionViewModel>(); 
}
توضیحات:
- در کلاس آغازین برنامه نیازی به ثبت سرویس IActionDescriptorCollectionProvider نیست و اینکار پیشتر توسط خود ASP.NET Core انجام شده‌است.
- این provider حاوی لیست اطلاعات تمام اکشن متدهای ثبت شده‌ی توسط ASP.NET Core است. در اینجا تنها کافی است حلقه‌ای را بر روی لیست آیتم‌های آن تشکیل داده و سپس مقادیر ControllerName و یا ActionName را بدست بیاوریم.
- اگر نیاز به اطلاعات بیشتری از کنترلر و اکشن متد جاری در حال بررسی توسط حلقه‌ی تهیه شده بود، می‌توان از ControllerTypeInfo و MethodInfo آن استفاده کرد. این TypeInfoها با استفاده از Reflection، امکان دسترسی به اطلاعاتی مانند ویژگی‌های اعمال شده‌ی به کنترلر یا اکشنی خاص را میسر می‌کنند. برای مثال در اینجا توسط اطلاعات نوع یک کنترلر در حال بررسی توانسته‌ایم متد GetCustomAttribute را فراخوانی کرده و سپس بررسی کنیم که آیا دارای ویژگی جدید Area هست یا خیر؟ و اگر بله، مقدار RouteValue آن را که در حقیقت مقدار یا نام Area آن کنترلر است، بازگشت می‌دهیم.


نحوه‌ی استفاده از سرویس IMvcActionsDiscoveryService تهیه شده

اگر دقت کرده باشید اطلاعات لیست MvcActions، در سازنده‌ی این کلاس مقدار دهی شده‌اند. علت اینجا است که اگر این کلاس را به صورت singleton ثبت کنیم، تنها یکبار در طول عمر برنامه و در همان آغاز کار، این لیست پر شده و سپس کش خواهد شد. بنابراین دسترسی‌های بعدی به MvcActions، شامل فراخوانی سازنده‌ی این کلاس نخواهند بود:
public static class MvcActionsDiscoveryServiceExtensions
{
    public static IServiceCollection AddMvcActionsDiscoveryService(this IServiceCollection services)
    {
        services.AddSingleton<IMvcActionsDiscoveryService, MvcActionsDiscoveryService>();
        return services;
    }
}
پس از تعریف متد الحاقی کمکی فوق برای افزودن سرویس تهیه شده به صورت singleton، برای ثبت آن در برنامه و در کلاس آغازین آن، خواهیم داشت:
public void ConfigureServices(IServiceCollection services)
{
    services.AddMvcActionsDiscoveryService();
}
در ادامه هر کنترلری و یا سرویس دیگری که نیاز به اطلاعات تمامی اکشن متدهای برنامه داشت، می‌تواند سرویس IMvcActionsDiscoveryService را به سازنده‌ی خود تزریق کرده و سپس از اطلاعات لیست MvcActions استفاده کند. این کاملترین لیستی که می‌توان تهیه کرد؛ زیرا زیرساخت ASP.NET Core نیز از همین سرویس IActionDescriptorCollectionProvider استفاده می‌کند.
مطالب دوره‌ها
شی گرایی در #F
برنامه نویسی شی گرای سومین نسل از الگوهای اصلی برنامه نویسی است. در توضیحات فصل اول گفته شد که #F یک زبان تابع گرا است ولی این بدان معنی نیست که #F از مفاهیمی نظیر کلاس و یا interface پشتیبانی نکند. برعکس در #F امکان تعریف کلاس و interface و هم چنین پیاده سازی مفاهیم شی گرایی وجود دارد.

*با توجه به این موضوع که فرض است دوستان با مفاهیم شی گرایی آشنایی دارند از توضیح و تشریح این مفاهیم خودداری می‌کنم.

Classes
کلاس چارچوبی از اشیا است برای نگهداری خواص(Properties) و رفتار ها(Methods) و رخدادها(Events). کلاس پایه ای‌ترین مفهوم در برنامه نویسی شی گراست. ساختار کلی تعربف کلاس در #F به صورت زیر است:
type [access-modifier] type-name [type-params] [access-modifier] ( parameter-list ) [ as identifier ] =
   [ class ]
     [ inherit base-type-name(base-constructor-args) ]
     [ let-bindings ]
     [ do-bindings ]
     member-list
      ...
   [ end ]

type [access-modifier] type-name1 ...
and [access-modifier] type-name2 ...
...
همان طور که در ساختار بالا می‌بینید مفاهیم access-modifier و inherit و constructor هم در #F وجود دارد.

انواع access-modifier در #F
  • public : دسترسی برای تمام فراخوان‌ها امکان پذیر است
  • internal : دسترسی برای تمام فراخوان هایی که در همین assembly هستند امکان پذیر است
  • private : دسترسی فقط برای فراخوان‌های موجود در همین ماژول امکان پذیر است

نکته : protected access modifier در #F پشتیبانی نمی‌شود.

مثالی از تعریف کلاس:

type Account(number : int, name : string) = class
    let mutable amount = 0m
   
end
کلاس بالا دارای یک سازنده است که دو پارامتر ورودی می‌گیرد. کلمه end به معنای انتهای کلاس است. برای استفاده کلاس باید به صورت زیر عمل کنید:
let myAccount = new Account(123456, "Masoud")
توابع و خواص در کلاس ها
برای تعریف خاصیت در #F باید از کلمه کلیدی member استفاده کنید. در مثال بعدی برای کلاس بالا تابع و خاصیت تعریف خواهیم کرد.
type Account(number : int, name: string) = class
    let mutable amount = 0m
 
    member x.Number = number
    member x.Name= name
    member x.Amount = amount
 
    member x.Deposit(value) = amount <- amount + value
    member x.Withdraw(value) = amount <- amount - value
end
کلاس بالا دارای سه خاصیت به نام‌های Number و Name و Amount است و دو تابع به نام‌های Deposit و Withdraw دارد. اما x استفاده شده قبل از هر member به معنی this در #C  است. در #F شما برای اشاره به شناسه‌های یک محدوده خودتون باید یک نام رو برای اشاره گر مربوطه تعیین کنید.
open System
 
type Account(number : int, name: string) = class
    let mutable amount = 0m
 
    member x.Number = number
    member x.Name= name
    member x.Amount = amount
 
    member x.Deposit(value) = amount <- amount + value
    member x.Withdraw(value) = amount <- amount - value
end
 let masoud= new Account(12345, "Masoud") let saeed = new Account(67890, "Saeed") let transfer amount (source : Account) (target : Account) = source.Withdraw amount target.Deposit amount let printAccount (x : Account) = printfn "x.Number: %i, x.Name: %s, x.Amount: %M" x.Number x.Name x.Amount let main() = let printAccounts() = [masoud; saeed] |> Seq.iter printAccount printfn "\nInializing account" homer.Deposit 50M marge.Deposit 100M printAccounts() printfn "\nTransferring $30 from Masoud to Saeed" transfer 30M masoud saeed
 printAccounts() printfn "\nTransferring $75 from Saeed to Masoud" transfer 75M saeed masoud printAccounts() main()
استفاده از کلمه do
در #F زمانی که قصد داشته باشیم در بعد از وهله سازی از کلاس و فراخوانی سازنده، عملیات خاصی انجام شود(مثل انجام برخی عملیات متداول در سازنده‌های کلاس‌های دات نت) باید از کلمه کلیدی do به همراه یک بلاک از کد استفاده کنیم.
open System
open System.Net
 
type Stock(symbol : string) = class

    let mutable _symbol = String.Empty
    do
     //کد مورد نظر در این جا نوشته  میشود
end
یک مثال در این زمینه:

open System

type MyType(a:int, b:int) as this =
    inherit Object()
    let x = 2*a
    let y = 2*b
    do printfn "Initializing object %d %d %d %d %d %d"
               a b x y (this.Prop1) (this.Prop2)
    static do printfn "Initializing MyType." 
    member this.Prop1 = 4*x
    member this.Prop2 = 4*y
    override this.ToString() = System.String.Format("{0} {1}", this.Prop1, this.Prop2)

let obj1 = new MyType(1, 2)
در مثال بالا دو عبارت do  یکی به صورت static و دیگری به صورت غیر static تعریف شده اند. استفاده از do  به صورت غیر static این امکان را به ما می‌دهد که بتوانیم به تمام شناسه‌ها و توابع تعریف شده در کلاس استفاده کنیم ولی do به صورت static فقط به خواص و توابع از نوع static در کلاس دسترسی دارد.
خروجی مثال بالا:
Initializing MyType.
Initializing object 1 2 2 4 8 16
خواص static:
برای تعریف خواص به صورت استاتیک مانند #C از کلمه کلیدی static استفاده کنید.مثالی در این زمینه:
type SomeClass(prop : int) = class
    member x.Prop = prop
    static member SomeStaticMethod = "This is a static method"
end
SomeStaticMethod به صورت استاتیک تعریف شده در حالی که x.Prop به صورت غیر استاتیک. دسترسی به متد‌ها یا خواص static باید بدون وهله سازی از کلاس انجام بگیرد در غیر این صورت با خطای کامپایلر روبرو خواهید شد.
let instance = new SomeClass(5);;
instance.SomeStaticMethod;; 

output:
stdin(81,1): error FS0191: property 'SomeStaticMethod' is static.
روش استفاده درست:
SomeClass.SomeStaticMethod;; (* invoking static method *)
متد‌های get , set در خاصیت ها:
همانند #C و سایر زبان‌های دات نت امکان تعریف متد‌های get و set برای خاصیت‌های یک کلاس وجود دارد.
ساختار کلی:
 member alias.PropertyName
        with get() = some-value
        and set(value) = some-assignment
مثالی در این زمینه:
type MyClass() = class
   let mutable num = 0 
    member x.Num
        with get() = num
        and set(value) = num <- value
end;;
کد متناظر در #C:
public int Num
{
   get{return num;}
   set{num=value;}
}
یا به صورت:
type MyClass() = class
    let mutable num = 0
 
    member x.Num
        with get() = num
        and set(value) =
            if value > 10 || value < 0 then
                raise (new Exception("Values must be between 0 and 10"))
            else
                num <- value
end

Interface ها
اینترفیس به تمامی خواص و توابع عمومی اشئایی که آن را پیاده سازی کرده اند اشاره می‌کند. (توضیحات بیشتر (^ ) و (^ ))ساختار کلی برای تعریف آن به صورت زیر است:
type type-name = 
   interface
       inherits-decl 
       member-defns 
   end
مثال:
type IPrintable =
   abstract member Print : unit -> unit
استفاده از حرف I برای شروع نام اینترفیس طبق قوانین تعریف شده (اختیاری) برای نام گذاری است.
نکته: در هنگام تعریف توابع و خاصیت در interface‌ها باید از کلمه abstract استفاده کنیم. هر کلاسی که از یک یا چند تا اینترفیس ارث ببرد باید تمام خواص و توابع اینتریس‌ها را پیاده سازی کند. در مثال بعدی کلاس SomeClass1 اینترفیس بالا را پیاده سازی می‌کند. دقت کنید که کلمه this توسط من به عنوان اشاره گر به اشیای کلاس تعیین شده و شما می‌تونید از هر کلمه یا حرف دیگری استفاده کنید.
type SomeClass1(x: int, y: float) =
   interface IPrintable with 
      member this.Print() = printfn "%d %f" x y
نکته مهم: اگر قصد فراخوانی متد Print را در کلاس بالا دارید نمی‌تونید به صورت مستقیم متد بالا را فراخوانی کنید. بلکه حتما باید کلاس به اینترفیس مربوطه cast شود.
روش نادرست:
let instance = new SomeClass1(10,20)
instance.Print//فراخوانی این متد باعث ایجاد خطای کامپایلری می‌شود.
روش درست:
let instance = new SomeClass1(10,20) 
let instanceCast = instance :> IPrintable// استفاده از (<:)  برای عملیات تبدیل کلاس به اینترفیس
instanceCast.Print
برای عملیات cast ازاستفاده کنید.
در مثال بعدی کلاسی خواهیم داشت که از سه اینترفیس ارث می‌برد. در نتیجه باید تمام متد‌های هر سه اینترفیس را پیاده سازی کند.
type Interface1 =
    abstract member Method1 : int -> int

type Interface2 =
    abstract member Method2 : int -> int

type Interface3 =
    inherit Interface1
    inherit Interface2
    abstract member Method3 : int -> int

type MyClass() =
    interface Interface3 with 
        member this.Method1(n) = 2 * n
        member this.Method2(n) = n + 100
        member this.Method3(n) = n / 10
فراخوانی این متد‌ها نیز به صورت زیر خواهد بود:
let instance = new MyClass()
let instanceToCast = instance :> Interface3
instanceToCast.Method3 10
کلاس‌های Abstract
#F از کلاس‌های abstract هم پشتیبانی می‌کند. اگر با کلاس‌های abstract در #C آشنایی ندارید می‌تونید مطالب مورد نظر رو در  (^ ) و (^ ) مطالعه کنید. به صورت خلاصه کلاس‌های abstract به عنوان کلاس‌های پایه در برنامه نویسی شی گرا استفاده می‌شوند. این کلاس‌ها دارای خواص و متد‌های پیاده سازی شده و نشده هستند. خواص و متد هایی که در کلاس پایه abstract پیاده سازی نشده اند باید توسط کلاس هایی که از این کلاس پایه ارث می‌برند حتما پیاده سازی شوند.
ساختار کلی تعریف کلاس‌های abstract:
[<AbstractClass>]
type [ accessibility-modifier ] abstract-class-name =
    [ inherit base-class-or-interface-name ]
    [ abstract-member-declarations-and-member-definitions ]

    abstract member member-name : type-signature
در #F برای این که مشخص کنیم که یک کلاس abstract است حتما باید [<AbstractClass>] در بالای کلاس تعریف شود.
[<AbstractClass>]
type Shape(x0 : float, y0 : float) =
    let mutable x, y = x0, y0
    let mutable rotAngle = 0.0

    abstract Area : float with get
    abstract Perimeter : float  with get
    abstract Name : string with get
کلاس بالا تعریفی از کلاس abstract است که سه خصوصیت abstract دارد (برای تعیین خصوصیت‌ها و متد هایی که در کلاس پایه پیاده سازی نمی‌شوند از کلمه کلیدی abstract در هنگام تعریف آن‌ها استفاده می‌کنیم). حال دو کلاس ایجاد می‌کنیم که این کلاس پایه را پیاده سازی کنند.

#1 کلاس اول
type Square(x, y,SideLength) =
    inherit Shape(x, y)
  override this.Area = this.SideLength * this.SideLength override this.Perimeter = this.SideLength * 4. override this.Name = "Square"
#2 کلاس دوم
type Circle(x, y, radius) =
    inherit Shape(x, y)
 let PI = 3.141592654 member this.Radius = radius override this.Area = PI * this.Radius * this.Radius override this.Perimeter = 2. * PI * this.Radius
Structures
structure‌ها در #F دقیقا معال struct در #C هستند. توضیحات بیشتر درباره struct در #C (^ ) و (^ )). اما به طور خلاصه باید ذکر کنم که strucure‌ها تقریبا دارای مفهوم کلاس هستند با اندکی تفاوت که شامل موارد زیر است:
  • structure‌ها از نوع مقداری هستند و این بدین معنی است مستقیما درون پشته ذخیره می‌شوند.
  • ارجاع به structure‌ها از نوع ارجاع با مقدار است بر خلاف کلاس‌ها که از نوع ارجاع به منبع هستند.(^ )
  • structure‌ها دارای خواص ارث بری نیستند.
  • عموما از structure برای ذخیره مجموعه ای از داده‌ها با حجم و اندازه کم استفاده می‌شود.

ساختار کلی تعریف structure

[ attributes ]
type [accessibility-modifier] type-name =
   struct
      type-definition-elements
   end

//یا به صورت زیر

[ attributes ]
[<StructAttribute>]
type [accessibility-modifier] type-name =
   type-definition-elements
یک نکته مهم هنگام کار با struct‌ها در #F این است که امکان استفاده از let و Binding در struct‌ها وجود ندارد. به جای آن باید از val استفاده کنید.
type Point3D =
   struct 
      val x: float
      val y: float
      val z: float
   end
تفاوت اصلی بین val و let در این است که هنگام تعریف شناسه با val امکان مقدار دهی اولیه به شناسه وجود ندارد. در مثال بالا مقادیر برای x و y و z برابر 0.0 است که توسط کامپایلر انجام می‌شود. در ادامه یک struct به همراه سازنده تعریف می‌کنیم:
type Point2D =
   struct 
      val X: float
      val Y: float
      new(x: float, y: float) = { X = x; Y = y }
   end
توسط سازنده struct بالا مقادیر اولیه x و y دریافت می‌شود به متغیر‌های متناظر انتساب می‌شود.

  در پایان یک مثال مشترک رو در #C و #F پیاده سازی می‌کنیم:


مطالب
ایجاد قابلیت قالب یا Theme در ASP.NET MVC
در این مقاله قصد داریم قابلیت ایجاد قالب را در پروژه‌های ASP.NET MVC، فراهم کنیم تا ظاهر سایت یا به اصطلاح  قالب سایت از طریق فایل کانفیگ تغییر کند. همانطور که می‌دانید معماری ASP.NET MVC براساس قراردادهای پیش فرض، قابل تعویض و تغییر طراحی شده است. یکی از این قراردادها، نحوه‌ی پیدا کردن یک view برای کنترلر و اکشن‌های آن است که به صورت زیر در ViewEngine تعریف شده‌است: 
ViewEngine.ViewLocationFormats= "~/Views/{controller}/{action}.cshtml"
در ادامه قصد داریم این مسیر یابی پیش فرض را طوری تغییر دهیم تا در پوشه‌ی themes پروژه و زیرپوشه‌ای با نام قالب که از فایل کانفیگ خوانده می‌شود نیز به دنبال view مرتبط با کنترلر و اکشن بگردد: 
"~/Themes/{ThemeName}/Views/{controller}/{action}.cshtml"
 برای راحتی کار، یک Extension Method برای اینترفیس پایه viewEngin  تعریف می‌کنیم: 
   public static void Themeable(this VirtualPathProviderViewEngine engine)
        {
            var ThemePath = "~/Themes";
            var ThemeName = WebConfigurationManager.AppSettings["MvcTheme"];
            if (string.IsNullOrEmpty(ThemeName))
                return;

            var themeFolder = HttpContext.Current.Server.MapPath(string.Format("{0}/{1}/", ThemePath, ThemeName));
            if (!Directory.Exists(themeFolder))
                throw new DirectoryNotFoundException(string.Format("Theme folder not exists: {0}/{1}}", ThemePath,
                    ThemeName));

            var newViewLocations = new[]
            {
                string.Format("{0}/{1}/Views/{2}/{3}.cshtml", ThemePath, ThemeName, "{1}", "{0}"),
                string.Format("{0}/{1}/Views/Shared/{2}.cshtml", ThemePath, ThemeName, "{0}"),
                 
                // vb.net :
                // string.Format("{0}/{1}/Views/{2}/{3}.vbhtml", ThemePath, ThemeName, "{1}", "{0}"),
                // string.Format("{0}/{1}/Views/Shared/{2}.vbhtml", ThemePath, ThemeName, "{0}"),
            };
            engine.ViewLocationFormats = newViewLocations;
            engine.PartialViewLocationFormats = newViewLocations;
        }
سپس در فایل کانفیگ، نام قالب را وارد کنید: 
  <appSettings>
 ...
    <add key="MvcTheme" value="Test1" />
  </appSettings>
و در فایل Global.asax، قابلیت فراخوانی قالب را فعال کنید:
ViewEngines.Engines.OfType<RazorViewEngine>().Single().Themeable();
حالا کافی است محتویات views اصلی را به پوشه‌ی views  قالب مورد نظر کپی کرده و فایل _ViewStart.cshtml را اصلاح کنید، بطوریکه که به فایل layout  داخل پوشه قالب اشاره کند: 
@{
    // Layout = "~/Views/Shared/_Layout.cshtml";
    Layout = "~/themes/test1/Views/Shared/_Layout.cshtml";
}
تا اینجای کار را اگر امتحان کنید، همه چیز درست است؛ مگر اینکه بخواهید از Bundling استفاده کنید. اگر بخواهید از css و اسکریپت‌های اصلی پروژه استفاده کنید، می‌توانید از همان  bundle‌های اصلی، در داخل layout و سایر viewهای قالب استفاده کنید. ولی اینکار نمی‌تواند کاربردی باشد؛ چون ساختار و اجزای هر قالب می‌تواند کاملا مجزا باشد. مثلا در یک قالب از  بوت استرپ استفاده می‌کنیم و در قالبی دیگر از UI Fabric مایکروسافت استفاده می‌کنیم. به همین دلیل، دست به کار می‌شویم و یک Bundling داینامیک را طراحی می‌کنیم:
ابتدا مدل زیر را تعریف کنید:
    public class ThemeBundle
    {
        public BundleType BundleType { get; set; }
        public string VirtualPath { get; set; }
        public string[] Urls { get; set; }
    }

    public enum BundleType
    {
        Style, Script
    }
در داخل هر پوشه‌ی قالب، به دنبال فایل ThemeBundle.json می‌گردیم تا تعاریف Bundling را از آن بخوانیم و در داخل پروژه استفاده کنیم.
توسط کد زیر bundleها را از محل پوشه‌ی قالب، فراخوانی می‌کنیم:
public static void RegisterThemeBundels(BundleCollection bundles)
        {
            var ThemePath = "~/Themes";
            var ThemeName = WebConfigurationManager.AppSettings["MvcTheme"];
            var ThemeBundleFileName = "ThemeBundle.json";

        List<ThemeBundle> list;

            try
            {
                JavaScriptSerializer jss = new JavaScriptSerializer();
                var jsonaddress =
                    System.Web.HttpContext.Current.Server.MapPath(string.Format("{0}/{1}/{2}", ThemePath, ThemeName, ThemeBundleFileName));
                var json = System.IO.File.ReadAllText(jsonaddress);
                  list = jss.Deserialize<List<ThemeBundle>>(json);
            }
            catch (Exception ex)
            {
                throw new Exception(string.Format("Cannot read {0}. see more error in inner exception.", ThemeBundleFileName), ex);
            }

            foreach (var themeBundle in list)
            {
                switch (themeBundle.BundleType)
                {
                    case BundleType.Script:
                        bundles.Add(new ScriptBundle(themeBundle.VirtualPath).Include(
                            themeBundle.Urls));
                        break;
                    case BundleType.Style:
                        bundles.Add(new StyleBundle(themeBundle.VirtualPath).Include(
                            themeBundle.Urls));
                        break;
                    default:
                        throw new ArgumentOutOfRangeException(nameof(themeBundle.BundleType));
                }
            }
        }
دستور فراخوانی bundle‌ها در صورتیکه نام قالب در فایل کانفیگ تعریف شده باشد:
    public class BundleConfig
    {        
        public static void RegisterBundles(BundleCollection bundles)
        {
            if (MvcTheme.ThemeName != null)
            {
                MvcTheme.RegisterThemeBundels(bundles); 
                return;
            }

            bundles.Add(new ScriptBundle("~/bundles/jquery").Include(
                        "~/Scripts/jquery-{version}.js"));

            bundles.Add(new ScriptBundle("~/bundles/jqueryval").Include(
                        "~/Scripts/jquery.validate*"));
...
}
}
فایل ThemeBundle.json قالب test1 را بصورت زیر تعریف می‌کنیم:
[
  {
    "BundleType": "Script",
    "VirtualPath": "~/themes/test1/js/jquery",
    "Urls": [ "~/themes/test1/js/jquery-1.10.2.js" ]
  },
  {
    "BundleType": "Script",
    "VirtualPath": "~/themes/test1/js/jqueryval",
    "Urls": [ "~/themes/test1/js/jquery.validate.js",
               "~/themes/test1/js/jquery.validate.unobtrusive.js" ]
  },
  {
    "BundleType": "Script",
    "VirtualPath": "~/themes/test1/js/modernizr",
    "Urls": [ "~/themes/test1/js/modernizr-2.6.2.js" ]
  },
  {
    "BundleType": "Script",
    "VirtualPath": "~/themes/test1/js/bootstrap",
    "Urls": [ "~/themes/test1/js/bootstrap.js",
              "~/themes/test1/js/respond.js" ]
  },
  {
    "BundleType": "Style",
    "VirtualPath": "~/themes/test1/css/css",
    "Urls": [ "~/themes/test1/css/bootstrap.css",
              "~/themes/test1/css/site.css" ]
  }
]
در نهایت ساختار پوشه‌ی قالب به صورت زیر می‌باشد:
 Themes
├───Test1
│    │ThemeBundle.json
│    ├───Css
│    ├───Fonts
│    ├───Images
│    ├───Js
│    └───Views
├───Test2
│    │ThemeBundle.json
│    ├───Css
│    ├───Fonts
│    ├───Images
│    ├───Js
│    └───Views
 کد کامل و نهایی :
  public static class MvcTheme
    {
 
        public static string ThemeName { get; }
        public static string ThemePath { get; set; }
        private const string AppSettingName = "MvcTheme";
        private const string ThemeBundleFileName = "ThemeBundle.json";

        static MvcTheme()
        {
            ThemePath = "~/Themes";
            ThemeName = WebConfigurationManager.AppSettings[AppSettingName];
        }

        public static void Themeable(this VirtualPathProviderViewEngine engine)
        {
            if (string.IsNullOrEmpty(ThemeName))
                return;

            var themeFolder = HttpContext.Current.Server.MapPath(string.Format("{0}/{1}/", ThemePath, ThemeName));
            if (!Directory.Exists(themeFolder))
                throw new DirectoryNotFoundException(string.Format("Theme folder not exists: {0}/{1}}", ThemePath,
                    ThemeName));

            var newViewLocations = new[]
            { 
                string.Format("{0}/{1}/Views/{2}/{3}.cshtml", ThemePath, ThemeName, "{1}", "{0}"), 
                string.Format("{0}/{1}/Views/Shared/{2}.cshtml", ThemePath, ThemeName, "{0}"),
                              
                // vb.net :
                // string.Format("{0}/{1}/Views/{2}/{3}.vbhtml", ThemePath, ThemeName, "{1}", "{0}"),
                // string.Format("{0}/{1}/Views/Shared/{2}.vbhtml", ThemePath, ThemeName, "{0}"),

            };
            engine.ViewLocationFormats = newViewLocations;
            engine.PartialViewLocationFormats = newViewLocations; 
        }

        public static void RegisterThemeBundels(BundleCollection bundles)
        {
            if(ThemeName == null)
                return;

            var list = ReadThemeBundles();

            foreach (var themeBundle in list)
            {
                switch (themeBundle.BundleType)
                {
                    case BundleType.Script:
                        bundles.Add(new ScriptBundle(themeBundle.VirtualPath).Include(
                            themeBundle.Urls));
                        break;
                    case BundleType.Style:
                        bundles.Add(new StyleBundle(themeBundle.VirtualPath).Include(
                            themeBundle.Urls));
                        break;
                    default:
                        throw new ArgumentOutOfRangeException(nameof(themeBundle.BundleType));
                }
            }
        }

        public static List<ThemeBundle> ReadThemeBundles()
        {
            try
            {
                JavaScriptSerializer jss = new JavaScriptSerializer();
                var jsonaddress =
                    System.Web.HttpContext.Current.Server.MapPath(string.Format("{0}/{1}/{2}", ThemePath, ThemeName, ThemeBundleFileName));
                var json = System.IO.File.ReadAllText(jsonaddress);
                var list = jss.Deserialize<List<ThemeBundle>>(json);

                return list;
            }
            catch (Exception ex)
            {
                throw new Exception(string.Format("Cannot read {0}. see more error in inner exception.", ThemeBundleFileName), ex);
            }
        }
    }

    public class ThemeBundle
    {
        public BundleType BundleType { get; set; }
        public string VirtualPath { get; set; }
        public string[] Urls { get; set; }
    }

    public enum BundleType
    {
        Style, Script
    }
- نکته یک: با حذف مقدار نام قالب در فایل کانفیگ، به راحتی به حالت پیش فرض asp.net mvc بر میگردید.
- نکته دو: نام bundle را حتما هم عمق با آدرس قالب تعریف کنید تا وقتی فایل css  به پوشه‌ی images  یا فونت مجاور خود اشاره می‌کند، آدرس دهی معتبر باشد.
- نکته سه: اگر از RazorGenerator استفاده می‌کنید، در فایل RazorGeneratorMvcStart متد ()Themeable را بر روی engine آن صدا بزنید. 
مطالب دوره‌ها
استفاده از IL Code Weaving برای تولید ویژگی‌های تکراری مورد نیاز در WCF
با استفاده از IL Code Weaving علاوه بر مدیریت اعمال تکراری پراکنده در سراسر برنامه مانند ثبت وقایع، مدیریت استثناءها، کش کردن داده‌ها و غیره، می‌توان قابلیتی را به کدهای موجود نیز افزود. برای مثال یک برنامه معمول WCF را درنظر بگیرید.
using System.Runtime.Serialization;

namespace AOP03.DataContracts
{
    [DataContract]
    public class User
    {
        [DataMember]
        public int Id { set; get; }

        [DataMember]
        public string Name { set; get; }
    }
}
نیاز است کلاس‌ها و خواص آن توسط ویژگی‌های DataContract و DataMember مزین شوند. در این بین نیز اگر یکی فراموش گردد، کار دیباگ برنامه مشکل خواهد شد و در کل حجم بالایی از کدهای تکراری در اینجا باید در مورد تمام کلاس‌های مورد نیاز انجام شود. در ادامه قصد داریم تولید این ویژگی‌ها را توسط PostSharp انجام دهیم. به عبارتی یک پوشه خاص به نام DataContracts را ایجاد کرده و کلاس‌های خود را به نحوی متداول و بدون اعمال ویژگی خاصی تعریف کنیم. در ادامه پس از کامپایل آن، به صورت خودکار با ویرایش کدهای IL توسط PostSharp، ویژگی‌های لازم را به اسمبلی نهایی اضافه نمائیم.


تهیه DataContractAspect جهت اعمال خودکار ویژگی‌های DataContract و DataMember

using System;
using System.Collections.Generic;
using System.Reflection;
using System.Runtime.Serialization;
using PostSharp.Aspects;
using PostSharp.Extensibility;
using PostSharp.Reflection;

namespace AOP03
{
    [Serializable]
    //این ویژگی تنها نیاز است به کلاس‌ها اعمال شود
    [MulticastAttributeUsage(MulticastTargets.Class)]
    public class DataContractAspect : TypeLevelAspect, IAspectProvider
    {
        public IEnumerable<AspectInstance> ProvideAspects(object targetElement)
        {
            var targetType = (Type)targetElement; //همان نوعی است که ویژگی جاری به آن اعمال خواهد شد

            //این سطر معادل است با درخواست تولید ویژگی دیتاکانترکت
            var introduceDataContractAspect = new CustomAttributeIntroductionAspect(
                new ObjectConstruction(typeof(DataContractAttribute).GetConstructor(Type.EmptyTypes)));

            //این سطر معادل است با درخواست تولید ویژگی دیتاممبر
            var introduceDataMemberAspect = new CustomAttributeIntroductionAspect(
                new ObjectConstruction(typeof(DataMemberAttribute).GetConstructor(Type.EmptyTypes)));

            //در اینجا کار اعمال ویژگی دیتاکانترکت به کلاسی که به عنوان پارامتر متد جاری
            //دریافت شده انجام خواهد شد
            yield return new AspectInstance(targetType, introduceDataContractAspect);

            //مرحله بعد کار اعمال ویژگی دیتاممبر به خواص کلاس است
            foreach (var property in targetType.GetProperties(BindingFlags.Public |
                                                          BindingFlags.DeclaredOnly |
                                                          BindingFlags.Instance))
            {
                if (property.CanWrite)
                    yield return new AspectInstance(property, introduceDataMemberAspect);
            }
        }
    }
}
توضیحات مرتبط با قسمت‌های مختلف این Aspect سفارشی، به صورت کامنت در کدهای فوق ارائه شده‌اند.
برای اعمال آن به سراسر برنامه تنها کافی است به فایل AssemblyInfo.cs پروژه مراجعه و سپس سطر زیر را به آن اضافه کنیم:
 [assembly: DataContractAspect(AttributeTargetTypes = "AOP03.DataContracts.*")]
به این ترتیب در زمان کامپایل پروژه، Aspect تعریف شده به تمام کلاس‌های موجود در فضای نام AOP03.DataContracts اعمال خواهند شد.

در این حالت اگر کلیه ویژگی‌های کلاس User فوق را حذف و برنامه را کامپایل کنیم، با مراجعه به برنامه ILSpy می‌توان صحت اعمال ویژگی‌ها را به کمک PostSharp بررسی کرد: