مطالب
C# 12.0 - Collection Expressions & Spread Operator
C# 12 به همراه روش جدیدی برای آغاز مجموعه‌ها است که با آرایه‌ها، Spanها و هر نوعی که آغازگرهای مجموعه‌ها را بپذیرد، کار می‌کند. همچنین اپراتور جدیدی را هم به نام spread operator به صورت .. به زبان #C اضافه کرده‌است که امکان ساده‌تر ترکیب مجموعه‌ها را میسر می‌کند.


آغاز ساده‌تر مجموعه‌ها با کمک Collection Expressions

تا پیش از C# 12 برای آغاز یک آرایه می‌توان از روش زیر استفاده کرد که در آن نوع آرایه از طریق نوع اعضای آن حدس زده می‌شود:
var numbers1_CS11 = new[] { 1, 2, 3 };
که در حقیقت ساده شده‌ی تعریف اصلی زیر است:
var numbers1_CS_11 = new int[] { 1, 2, 3 };
در C# 12، می‌توان این تعاریف را به کمک collection expressions، خلاصه‌تر هم کرد:
int[] numbers1_CS12 = [ 1, 2, 3 ];
که در اینجا، {}‌ها به [] تبدیل شده‌اند و ذکر نوع آرایه، ضروری است (یعنی نمی‌توان از var جهت تعریف آن‌ها استفاده کرد)؛ در غیراینصورت با خطای زیر متوقف می‌شویم:
error CS9176: There is no target type for the collection expression.

یک collection expression و یا collection literals، به مجموعه‌ای از عناصر گفته می‌شود که بین دو براکت [] قرار می‌گیرند.

نمونه‌ی دیگر آن کار با Spanها است که نمونه کد C# 11 آن:
Span<string> span1_CS11 = new string[] { "AC", "AL" };
در C# 12 به صورت زیر خلاصه می‌شود:
Span<string> span1_CS12 = [ "AC", "AL" ];
و در اینجا امکان کار با ReadOnlySpan‌ها هم وجود دارد:
ReadOnlySpan<string> readOnlySpan_CS12 = [ "Africa",  "Asia", "Europa"];

مثال دیگر، نحوه‌ی آغاز آرایه‌های چندبعدی است:
int[][] array2D_CS11 =
  {
    new int[] { 2002, 2006, 2010},
    new int[] { 2014, 2018},
    new int[] { 2022, 2026, 2030}
  };
که در C# 12 به صورت خلاصه‌ی زیر قابل بیان است:
int[][] array2D_CS12 =
  [
     [2002, 2006, 2010],
     [2014, 2018],
     [2022, 2026, 2030]
  ];

و یا حتی این مورد را در مورد نحوه‌ی آغاز Listهای پیش از C#12
List<string> list_CS11 = new List<string> { "Item 1", "Item 2" };
نیز می‌توان بکار گرفت:
List<string> list_CS12 = [ "Item 1", "Item 2" ];

در کل همانطور که مشاهده می‌کنید، این تغییر، تغییر مثبتی است و حجم قابل ملاحظه‌ای از کدها را کاهش داده و خواندن آن‌ها را نیز ساده‌تر می‌کند.

یک نکته: روش ساده شده‌ی آغاز یک لیست با مجموعه‌ای خالی در C# 12 به صورت زیر است:
// Before C#12
List<User> users = new List<User>();
// or
var users = new List<User>();
// or
List<User> user = new();

// C#12
List<User> users = [];


اضافه شدن spread operator به زبان #C

اگر پیشتر با زبان JavaScript کار کرده باشید، با spread operator هم آشنایی دارید. کار آن ساده سازی یکی کردن مجموعه‌ها و یا افزودن ساده‌تر عناصری به آن‌ها است و .. بالاخره به زبان #C هم راه پیدا کرده‌است! برای مثال دو آرایه‌ی زیر را درنظر بگیرید:
int[] numbers1_CS12 = [ 1, 2, 3 ];
int[] numbers2_CS12 = [ 4, 5, 6 ];
در C# 12 برای یکی کردن آن‌ها می‌توان از spread operator به صورت زیر استفاده کرد:
int[] allItems = [ ..numbers1_CS12, ..numbers2_CS12 ];
Spread به معنای «پخش کردن»/«گسترده کردن»/«باز کردن» هست. برای مثال در اینجا، اعضای دو آرایه را داخل یک آرایه‌ی جدید، پخش کرده‌ایم!

اگر در نگارش‌های قبلی #C بخواهیم چنین کاری را انجام دهیم، یک روش آن به صورت زیر است:
int[] allItems_CS11 = numbers1_CS12.Concat(numbers2_CS12).ToArray();
که ... نگارش C# 12 آن کارآیی بیشتری دارد؛ چون تعداد بار اختصاص حافظه‌ی آن کمتر است. در C# 12، هنگام استفاده از spread operator، کار کپی کردن اطلاعات صورت نمی‌گیرد و همچنین طول نهایی مجموعه‌ی حاصل دقیقا مشخص می‌شود که این مورد از چندین بار تخصیص حافظه برای چسباندن آرایه‌های مختلف به هم جلوگیری می‌کند.

همچنین اپراتور پخش کردن، قابلیت قرارگرفتن در کنار سایر اعضای یک آرایه را هم به سادگی و با خوانایی بیشتری به همراه دارد:
int[] join = [..a, ..b, ..c, 6, 5];

به علاوه محدودیتی در مورد نوع مجموعه‌ی بکار گرفته شده نیز در اینجا وجود ندارد. برای نمونه در مثال زیر، یک آرایه، یک Span و یک لیست، با هم یکی شده‌اند:
int[] a =[1, 2, 3];
Span<int> b = [2, 4, 5, 4, 4];
List<int> c = [4, 6, 6, 5];

List<int> join = [..a, ..b, ..c, 6, 5];

و مثالی دیگر، نحوه‌ی ساده‌ی تعریف لیستی از tuples است:
List<(string, int)> otherScores = [("Dave", 90), ("Bob", 80)];
و سپس باز کردن آن داخل آرایه‌ای از tuples:
(string name, int score)[] scores = [("Alice", 90), ..otherScores, ("Charlie", 70)];
مطالب
بازسازی کد: جابجایی متد (Move method)
معمولا زمانیکه متدی از امکانات کلاس دیگری غیر از کلاسی که در آن تعریف شده است استفاده می‌کند، نیاز به چنین بازسازی کدی داریم. روش کلی این بازسازی کد، انتقال متد به کلاسی است که بیشترین تعلق را به آن دارد! 
جابجایی متد یکی از موارد پر تکرار و مهم در امر بازسازی کد است. این بازسازی در مراحل انجام دیگر بازسازی‌های کد، مانند شکستن کلاس نیز استفاده می‌شود. با این روش ساده می‌توان کلاس‌هایی با مسئولیت‌های محدود و مشخص را توسعه داد.  

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

  1. تمامی امکانات کلاس مبدا را که متد مورد نظر از آنها استفاده می‌کند، بررسی نمایید که آیا آنها نیز نیاز به انتقال دارند یا خیر. 
  2. کلاس‌های پدر و فرزند کلاس مبدا را برای یافتن تعاریف دیگری از متد مورد نظر بررسی نمایید. اگر تعاریف دیگری وجود داشتند به راحتی نمی‌توان متد را جابجا کرد. در این صورت اگر قصد جابجایی داشتید، باید به فکر جابجایی رابطه چند ریختی موجود نیز باشید. 
  3. متد را در کلاس مقصد ایجاد نمایید. 
  4. بدنه متد را به متد مقصد منتقل نمایید و تمامی امکانات استفاده شده در آن را طوری تغییر دهید که در کلاس جدید کار کند. اگر متد، نیاز به اشاره‌ای به کلاس مبدا داشت، باید تعیین نمایید که به چه صورت این اشاره انجام شود. اگر مکانیزم مدیریت خطایی (exception handling) در متد مبدا پیاده سازی شده بود، تعیین کنید که آیا متد مبدا نیز کماکان امر مدیریت خطا را انجام خواهد داد، یا به متد مقصد انتقال خواهد یافت. 
  5. کد کلاس مقصد را کامپایل و تست نمایید. 
  6. اگر متد مبدا را به عنوان فراخوان متد مقصد نگه داشتید، باید تصمیم بگیرید که کلاس مقصد در آن متد به چه صورت استفاده خواهد شد. 
  7. فراخوانی متد مقصد را به بدنه متد مبدا اضافه کنید. 
  8. کد را کامپایل و تست نمایید.
  9. در مورد سرنوشت متد مبدا تصمیم گیری نمایید که آیا نیازی به وجود آن هست یا خیر. در صورتیکه از متد مبدا در مکان‌های زیادی استفاده شده یا متد در کتابخانه یا فریم ورکی است که کنترلی بر روی استفاده کنندگان آن وجود ندارد، احتمالا باقی ماندن متد به عنوان صرفا فراخوان، ایده خوبی باشد. 
  10. اگر متد مبدا را حذف کردید تمامی استفاده از آن را باید به متد مقصد تغییر دهید. توجه داشته باشید ممکن است سناریو ساختن کلاس جدید با کلاس قدیمی متفاوت باشد. 
  11. مجددا کد را کامپایل و تست نمایید.  

مثال: فرض کنید نرم افزاری برای مدیریت رویدادها و شرکت کنندگان آن‌ها تهیه کرده‌ایم. در این نرم افزار، کلاسی با نام Event وجود دارد و کلاسی نیز با نام Person که نام آنها کاملا نمایانگر استفاده آنها است.
بخشی از بدنه این کلاس‌ها به صورت زیر است:  
public class Event 
{ 
    public List<Person> Participants { get; internal set; } 
} 
public class Person 
{ 
    public int Id { get; private set; } 
    public void Participate(Event ev) 
    { 
        var isParticipatedAlready = ev.Participants.Any(ff => ff.Id == Id); 
        if (isParticipatedAlready) 
            return; 
        ev.Participants.Add(this); 
    } 
}
در کد مربوط به کلاس Person، شاهد متدی هستیم که عمل ثبت‌نام فرد را در یک رویداد انجام می‌دهد. اما با دقت به این متد مشاهده می‌کنیم که بدنه این متد بیشتر از اعضای کلاس Event استفاده می‌کند. حتی این استفاده باعث شده است که خصوصیت Participants از کلاس Event به صورت public تعریف شود که خود مشکل دیگری در این طراحی است.
در چنین شرایطی، بازسازی کد جابجایی متد می‌تواند در راستای انتقال مسئولیت‌های مناسب هر کلاس به بدنه آن و بهبود طراحی کمک کند. بعد از بازسازی کد شاهد چنین طراحی‌ای هستیم:  
public class Event 
{ 
    protected List<Person> Participants { get; set; } 
    public void Participate(Person person) 
    { 
        var isParticipatedAlready = Participants.Any(ff => ff.Id == person.Id); 
        if (isParticipatedAlready) 
            return; 
        Participants.Add(person); 
    } 
} 
public class Person 
{ 
    public int Id { get; private set; } 
}
بازسازی‌ای که انجام شد، دو تاثیر را بر روی طراحی این کلاس‌ها داشته است:
   اول: جایگذاری بهتر و منطقی‌تر مسئولیت‌های یک کلاس 
   دوم: کپسوله سازی آسان‌تر کلاس ها 

بازسازی کد جابجایی متد، سنگ بنای بیشتر بازسازی‌های مورد نیاز در فعالیت‌های روزمره تولید یا نگهداری نرم افزار است. علارغم این که این بازسازی ساده به نظر می‌رسد، در مجموعه کدهای پیچیده، انجام این بازسازی ممکن است امری طاقت فرسا شود.  
مطالب
نوشتن پرس و جو در Entity Framework‌ با استفاده از LINQ To Entity قسمت دوم
در قسمت قبل با نحوه اجرای پرس و جو آشنا شدید و همچنین به بررسی متدهای Find و Single و First و تفاوت‌های آنها پرداختیم. در این قسمت با خصوصیت Local و متد Load آشنا خواهیم شد. همانطور که در قسمت قبل دیدید، مقادیر اولیه‌ای برای Database و جداولمان مشخص کردیم. برای جدول Customer این داده‌ها را داشتیم:
 FamilyName  ID
 Nasiri Vahid یک مقدار Guid
 Akbari Mohsen یک مقدار Guid 
 Jamshidi Mohsen یک مقدار Guid 
ID توسط Database تولید می‌شوند به همین دلیل از ذکر مقداری مشخص خودداری شده است.
به کد زیر دقت کنید:
private static void Query7()
{
    using (var context = new StoreDbContext())
    {
        // Add
        context.Customers.Add(new Customer { Name = "Ali", Family = "Jamshidi" });

        // change
        var customer1 = context.Customers.Single(c => c.Family == "Jamshidi");
        customer1.Name = "Mohammad";

        // Remove
        var customer2 = context.Customers.Single(c => c.Family == "Akbari");
        context.Customers.Remove(customer2);

        var customers = context.Customers.Where(c => c.Name != "Vahid");

        foreach (var cust in customers)
        {
            Console.WriteLine("Customer Name: {0}, Customer Family: {1}", cust.Name, cust.Family)
        }
    }
}
همانطور که مشاهده می‌کنید عمل اضافه، تغییر و حذف روی Customer انجام شده ولی هنوز هیچ تغییری در Database ذخیره نشده است. آخرین پرس و جو چه نتیجه ای را دربر خواهد داشت؟
 Customer Name: Mohammad, Customer Family: Jamshidi
Customer Name: Mohsen, Customer Family: Akbari
بله، فقط تغییر یک موجودیت در نظر گرفته شده است ولی اضافه و حذف نه! 
نتیجه مهمی که حاصل می‌شود این است که در پرس و جوهایی که روی Database اجرا می‌شوند سه مورد را باید در نظر داشت:
  • داده هایی که اخیرا به DbContext اضافه شده‌اند ولی هنوز در Database ذخیره نشده‌اند، درنظر گرفته نخواهند شد.
  • داده هایی که در DbContext حذف شده‌اند ولی در Database  هستند، در نتیجه پرس و جو خواهند بود.
  • داده هایی که قبلا از database توسط پرس و جوی دیگری گرفته شده و تغییر کرده‌اند، آن تغییرات در نتیجه پرس و جو موثر خواهند بود.
پس پرس و جو‌های LINQ ابتدا روی database انجام می‌شوند و id‌های بازگشت داده شده با id‌های موجود در DbContext مطابقت داده می‌شوند یا در DbContext وجود دارند که در این صورت آن موجودیت بازگشت داده می‌شود یا وجود ندارند که در این صورت موجودیتی که از Database خوانده شده، بازگشت داده می‌شوند.
برای درک بیشتر کد زیر را در نظر بگیرید:
private static void Query7_1()
{
    using (var context = new StoreDbContext())
    {
        // Add
        context.Customers.Add(new Customer { Name = "Ali", Family = "Jamshidi" });

        // change
        var customer1 = context.Customers.Single(c => c.Family == "Jamshidi");
        customer1.Name = "Vahid";

        // Remove
        var customer2 = context.Customers.Single(c => c.Family == "Akbari");
        context.Customers.Remove(customer2);

        var customers = context.Customers.Where(c => c.Name != "Vahid");

        foreach (var cust in customers)
        {
            Console.WriteLine("Customer Name: {0}, Customer Family: {1}", cust.Name, cust.Family)
        }
    }
}
این کد همان کد قبلی است اما نام customer1 در DbContext (که Mohsen بوده در Database) به Vahid تغییر کرده و پرس و جو روی نام هایی زده شده است که Vahid نباشند خروجی به صورت زیر خواهد بود:
 Customer Name: Vahid, Customer Family: Jamshidi
Customer Name: Mohsen, Customer Family: Akbari
 Vahid در خروجی آمده در صورتیکه در شرط صدق نمی‌کند چراکه پرس و جو روی Database زده شده، جاییکه نام این مشتری Mohsen بوده اما موجودیتی بازگشت داده شده که دارای همان Id هست اما در DbContext دستخوش تغییر شده است.

Local: همانطور که قبلا اشاره شد خصوصیتی از DbSet می‌باشد که شامل تمام داده هایی هست که:
  • اخیرا از database پرس و جو شده است (می‌تواند تغییر کرده یا نکرده باشد)
  • اخیرا به Context اضافه شده است (توسط متد Add)
دقت شود که Local شامل داده‌هایی که از database خوانده شده و از Context، حذف (Remove) شده‌اند، نمی‌باشد.
نوع این خصوصیت ObservableCollection می‌باشد که می‌توان از آن برای Binding در پروژه‌های ویندوزی استفاده کرد.
به کد زیر دقت کنید:
private static void Query8()
{
    using (var context = new StoreDbContext())
    {
        // Add
        context.Customers.Add(new Customer { Name = "Ali", Family = "Jamshidi" });

        // change
        var customer1 = context.Customers.Single(c => c.Family == "Jamshidi");
        customer1.Name = "Mohammad";

        // Remove
        var customer2 = context.Customers.Single(c => c.Family == "Akbari");
        context.Customers.Remove(customer2);

        var customers = context.Customers.Local;

        foreach (var cust in customers)
        {
            Console.WriteLine("Customer Name: {0}, Customer Family: {1}", cust.Name, cust.Family);
        }
    }
}
کد بالا شبیه به کد قبلی می‌باشد با این تفاوت که در انتها foreach روی Local زده شده است. خروجی به صورت زیر خواهد بود:
Customer Name: Ali, Customer Family: Jamshidi
Customer Name: Mohammad, Customer Family: Jamshidi
همانطور که ملاحظه می‌کنید Local شامل Ali Jamshidi که اخیرا اضافه شده (ولی در Database ذخیره نشده) و Mohammad Jamshidi که از Database خوانده شده و تغییر کرده، می‌باشد اما شامل Mohsen Akbari که از Database خوانده شده اما در Context حذف شده است، نمی‌باشد.
می‌توان روی Local نیز پرس و جوی اجرا کرد. در این صورت از پروایدر LINQ To Object استفاده خواهد شد و درنتیجه دست بازتر هست و تمام امکانات این پروایدر می‌توان استفاده کرد.

Load: یکی دیگر از مواردی که باعث اجرای پرس و جو می‌شود متد Load می‌باشد که یک Extension Method می‌باشد. این متد در حقیقت یک پیمایش روی پرس و جو انجام می‌دهد و باعث بارگذاری داده‌ها در Context می‌شود. مانند استفاده از ToList البته بدون ساختن List که سربار ایجاد می‌کند.
private static void Query9()
{
    using (var context = new StoreDbContext())
    {
        var customers = context.Customers.Where(c => c.Name == "Mohsen");
        customers.Load();

        foreach (var cust in context.Customers.Local)
        {
            Console.WriteLine("Customer Name: {0}, Customer Family: {1}", cust.Name, cust.Family);
        }
    }
    // Output:
    // Customer Name: Mohsen, Customer Family: Akbari
    // Customer Name: Mohsen, Customer Family: Jamshidi
}

مطالب
کارهایی جهت بالابردن کارآیی Entity Framework #1
امروزه اهمیت استفاده از  Entity Framework بر هیچ کسی پوشیده نیست؛ اما در صورتی که به مفاهیم ابتدایی آن آشنایی نداشته باشید ممکن است در دام هایی بیفتید که استفاده از آن کم رنگ شود. در زیر به توصیه‌هایی جهت بالابردن کارآیی برنامه‌های مبتنی بر EF اشاره خواهیم کرد.
  • تنها دریافت رکوردهای مورد نیاز

EF راهی برای کار با اشیاء POCO، بدون آگاهی از مقادیرشان می‌باشد. اما هنگام فرآیند دریافت و یا به روزرسانی مقادیر این اشیاء از بانک اطلاعاتی، رفت و برگشت هایی انجام می‌شود که اطلاع از آنها بسیار حیاتی و ضروری است. به این فرآیند materiallization می‌گویند.
string city = "New York";
List<School> schools = db.Schools.ToList();
List<School> newYorkSchools = schools.Where(s => s.City == city).ToList();

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

List<School> newYorkSchools = db.Schools.Where(s => s.City == city).ToList();
یا
IQueryable<School> schools = db.Schools;
List<School> newYorkSchools = schools.Where(s => s.City == city).ToList();
  • حداقل رفت و برگشت به دیتابیس

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

string city = "New York";
List<School> schools = db.Schools.Where(s => s.City == city).ToList();
var sb = new StringBuilder();
foreach(var school in schools)
{
    sb.Append(school.Name);
    sb.Append(": ");
    sb.Append(school.Pupils.Count);
    sb.Append(Environment.NewLine);
}

هدف تکه کد بالا این است که تعداد دانش آموزان مدرسه‌های واقع در شهر New York را بدست آورد.

توجه داشته باشید:

  • یک مدرسه می‌تواند چندین دانش آموز داشته باشد (وجود رابطه یک به چند)
  • LazyLoading فعال است
  • تعداد مدرسه‌های شهر نیویورک 200 عدد می‌باشد

اگر کوئری بالا را به‌وسیله‌ی یک پروفایلر بررسی نمایید، متوجه خواهید شد 1 + 200 رفت و برگشت به دیتابیس صورت گرفته است که به "N+1 select problem" معروف است. 1 مرتبه جهت دریافت لیست مدرسه‌های شهر نیویورک و 200 مرتبه جهت دریافت تعداد دانش آموزان هر مدرسه.

بدلیل فعال بودن Lazy Loading، زمانیکه موجودیتی فراخوانی می‌شود، سایر موجودیت‌های وابسته به آن، زمانی از دیتابیس فراخوانی خواهند شد که به آن‌ها دسترسی پیدا کنید. در حلقه‌ی foreach هم به ازای هر مدرسه (200 مدرسه) شهر نیویورک یک رفت و برگشت انجام می‌شود.

اما راه حل در این مورد خاص استفاده از Eager Loading است. خط دوم کد را بصورت زیر تغییر دهید:

List<School> schools = db.Schools
    .Where(s => s.City == city)
    .Include(x => x.Pupils)
    .ToList();

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

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

فرض کنید قصد دارید نام و نام خانوادگی دانش آموزان یک مدرسه را بدست آورید.

int schoolId = 1;

List<Pupil> pupils = db.Pupils
    .Where(p => p.SchoolId == schoolId)
    .ToList();

foreach(var pupil in pupils)
{
    textBox_Output.Text += pupil.FirstName + " " + pupil.LastName;
    textBox_Output.Text += Environment.NewLine;
}
کد بالا تمام ستون‌های یک جدول را همراه با ستون‌های نام و نام خانوادگی جدول مربوطه را از دیتابیس فراخوانی می‌کند که باعث بروز 2 مشکل زیر می‌گردد:
  1. انتقال اطلاعات بلا استفاده که ممکن است باعث کاهش کارآیی Sql Server I/O و شبکه و اشغال حافظه‌ی کلاینت گردد.
  2. کاهش کارآیی ایندکس گذاری. فرض کنید برروی جدول دانش آموزان ایندکسی شامل 2 ستون نام و نام خانوادگی تعریف کرده‌اید. با انتخاب تمام ستونهای جدول توسط خط دوم (select * from...) به کارآیی ایندکس گذاری برروی این جدول آسیب زده‌اید. توضیح بیشتر در اینجا مطرح شده است.

اما راه حل:

var pupils = db.Pupils
    .Where(p => p.SchoolId == schoolId)
    .Select(x => new { x.FirstName, x.LastName })
    .ToList();
  • عدم تطابق نوع ستون با نوع خصیصه مدل

فرض کنید نوع ستون جدول دانش آموزان (VARCHAR(20 است و خصیصه کدپستی مدل دانش آموز مانند زیر تعریف شده است:

public string PostalZipCode { get; set; }

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

string zipCode = "90210";
var pupils = db.Pupils
    .Where(p => p.PostalZipCode == zipCode)
    .Select(x => new {x.FirstName, x.LastName})
    .ToList();
کوئری بالا منجر به تولید SQL زیر می‌گردد: (نوع پارامتر ارسالی NVARCHAR است در حالی که ستون از نوع VARCHAR)

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

همانطور که در شکل بالا مشخص است عملیات index scan از سایر عملیات‌ها پرهزینه‌تر است. حال به بررسی علت به‌وجود آمدن این عملیات پرهزینه خواهیم پرداخت.

Index Scan زمانی رخ می‌دهد که اس کیو ال سرور مجبور است هر صفحه‌ی از ایندکس را بخواند و شرایط را (کدپستی برابر 90210) اعمال نماید و نتیجه را برگرداند. Index Scan بسیار هزینه بر است، چون اس کیو ال سرور، کل ایندکس را بررسی می‌نماید. نقطه‌ی مقابل و بهینه‌ی آن، Index Seek است که اس کیو ال سرور به صفحه‌ی مورد نظر ایندکسی که به شرایط نزدیک‌تر است، منتقل می‌گردد.

خب چرا اس کیو ال سرور Index Scan را بجای Index Seek انتخاب کرده است؟!

اشکالی در قسمت سمت چپ شکل بالا که به رنگ قرمز نمایش داده شده است، وجود دارد:

Type conversion: Seek Plan for CONVERT_IMPLICIT(nvarchar(20), [Extent1].[PostalZipCode],0)=[@p__linq__0]
[Extent1].[PostalZipCode]  بصورت غیر صریح به (NVARCHAR(20 تبدیل شده است. اما چرا؟
پارامتر کوئری تولید شده‌ی توسط EF از نوع NVARCHAR است و تبدیل نوع NVARCHAR پارامتر کدپستی، که محدوده‌ی اطلاعات بیشتری (Unicode Strings) را نسبت به نوع VARCHAR ستون دارد، به‌دلیل از دست رفتن اطلاعات امکان پذیر نیست. به‌همین جهت برای مقایسه‌ی پارامتر کدپستی با ستون VARCHAR ، اس کیو ال سرور باید هر ردیف ایندکس را از VARCHAR به NVARCHAR تبدیل نماید که منجر به Index Scan می‌شود. اما راه حل بسیار ساده این است که فقط نوع خصیصه را با ستون جدول یکسان کنید.
[Column(TypeName = "varchar")]
public string PostalZipCode { get; set; }

نظرات اشتراک‌ها
تبدیلگر ایران سیستم به یونیکد
برای ایجاد بانک اطلاعاتی جدید فاکس پرو نیاز است فقط مسیر پوشه را داد و نه مسیر فایل را. نام جدول مشخص شده، تبدیل به نام فایل تولیدی می‌شود:
//ایجاد بانک اطلاعاتی خالی
var dir = @"D:\Prog\"; // این مسیر پوشه است و نه مسیر فایل
using (OleDbConnection connection = new OleDbConnection(@"Provider=VFPOLEDB.1;Data Source=" + dir))
{
    using (var command = new OleDbCommand())
    {
        command.Connection = connection;
        command.Connection.Open();
        command.CommandText = "Create Table myDBF (dsw_id1 int, dsw_fname v(250), dsw_lname v(250) )";
        command.CommandType = CommandType.Text;
        command.ExecuteNonQuery();
    }
}
برای ثبت اطلاعات پارامتری در بانک اطلاعاتی فاکس پرو فقط و فقط پارامترهایی با نام ? مجاز هستند (و نه هیچ حالت دیگری):
//ثبت اطلاعات در بانک ایجاد شده
var dbPath = Path.Combine(dir, "myDBF.dbf");
using (OleDbConnection connection = new OleDbConnection(@"Provider=VFPOLEDB.1;Data Source=" + dbPath))
{
    using (var command = new OleDbCommand())
    {
        command.Connection = connection;
        command.Connection.Open();
        command.CommandText = "Insert Into myDBF Values (1, ?, ?)";
        command.Parameters.AddWithValue("?", "B"); // فقط علامت سؤال در اینجا کار می‌کند و نه هیچ چیز دیگری
        command.Parameters.AddWithValue("?", "A");
        command.CommandType = CommandType.Text;
        command.ExecuteNonQuery();
    }
}
- متد اصلی آن مطلب «public List» است که این لیست را باید تبدیل به رشته کنید.
مطالب
کار با Visual Studio در ASP.NET Core
پیش نویس: این مقاله ترجمه شده فصل 6 کتاب Pro Asp.Net Core MVC2 می‌باشد.

کار با Visual Studio

در این مقاله، یکسری توضیحاتی در مورد ویژگی‌های کلیدی ویژوال استودیو به برنامه نویس‌های (توسعه دهنده‌های) پروژه‌های Asp.net Core MVC ارائه می‌دهیم.

 
ایجاد یک پروژه

در ابتدا یک پروژه‌ی وب جدید Asp.net core را به نام Working و بر اساس قالب Empty ایجاد می‌کنیم. سپس در کلاس startup، قابلیت MVC را فعال میکنیم (کدهای این قسمت، در فصل 5 کامل شرح داده شده‌است)
 

namespace Working
{
    public class Startup
    {
        // This method gets called by the runtime. Use this method to add services to the container.
        // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            //app.Run(async (context) =>
            //{
            //    await context.Response.WriteAsync("Hello World!");
            //});
        }
    }
}

ایجاد مدل:

یک پوشه جدید را به نام Models ایجاد می‌کنیم و بعد در این پوشه یک کلاس جدید را به نام Product ایجاد می‌کنیم و کدهای زیر را در کلاس ایجاد شده قرار میدهیم (این قسمت در فصل 5 نیز شرح داده شده‌است):
namespace Working.Models
{
    public class Product
    {
        public string Name { get; set; }
        public decimal Price { get; set; }
    }
}
برای ایجاد یک فروشگاه ساده از اشیاء محصول، من یک فایل کلاس را به نام SimpleRepository.cs به پوشه Models اضافه و از آن برای تعریف کلاس استفاده کردم.
 

namespace WorkingWithVisualStudio.Models
{
    public class SimpleRepository
    {
        private static SimpleRepository sharedRepository = new SimpleRepository();
        private Dictionary<string, Product> products = new Dictionary<string, Product>();
        public static SimpleRepository SharedRepository => sharedRepository;

        public SimpleRepository()
        {
            var initialItems = new[] { new Product { Name = "Kayak", Price = 275M }, new Product { Name = "Lifejacket", Price = 48.95M }, new Product { Name = "Soccer ball", Price = 19.50M }, new Product { Name = "Corner flag", Price = 34.95M } };
            foreach (var p in initialItems)
            {
                AddProduct(p);
            }
        }
        public IEnumerable<Product> Products => products.Values;
        public void AddProduct(Product p) => products.Add(p.Name, p);

    }
}
کلاس Stores، اشیا مدل را در حافظه ذخیره می‌کند. یعنی هر تغییری را که در Model داده باشید، زمانیکه نرم افزار متوقف یا Restart شود، از بین می‌رود. یک فروشگاه ناپیوسته برای مثال در این فصل کافی است. اما این رویکردی نیست که در بسیاری از پروژه‌های واقعی استفاده شود. در فصل 8 یک مثال را پیاده سازی می‌کنیم تا اطلاعات مدل Store را به صورت مداوم در بانک اطلاعاتی ذخیره کند.

نکته: من یک مشخصه (Property) استاتیک را به نام SharedRepository تعریف کردم که دسترسی به SimpleRepository را فراهم می‌کند و می‌تواند در طول برنامه از آن استفاده شود. این بهترین کار نیست، ولی میخواهم یک مشکل رایج را که در توسعه MVC روبرو میشوید، نشان دهم. من راه بهتری را برای کار با اجزای مشترک، در فصل 18 توضیح می‌دهم.


ایجاد Controller و View

در پوشه Controllers، یک فایل جدید را به نام HomeController.cs ایجاد می‌کنیم و کدهای زیر را در آن قرار میدهیم:
namespace WorkingWithVisualStudio.Controllers
{
    public class HomeController : Controller
    {
        public IActionResult Index() => View(SimpleRepository.SharedRepository.Products);
    }
}
این یکی Action Method ایی به نام Index است که اطلاعات مدل را دریافت می‌کند و برای View پیشفرض، جهت نمایش ارسال می‌کند. برای ایجاد View هم بر روی پوشه Views/Home راست کلیک کرده و یک View را به نام index.cshtml ایجاد کنید؛ با کدهای زیر:
@{
    Layout = null;
}

<!DOCTYPE html>

<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>>Working with Visual Studio</title>
</head>
<body>
    <table>
    <thead>
        <tr>
        <td>Name</td>
        <td>Price</td>
        </tr>
        </thead>
    <tbody>            @foreach (var p in Model)
    {<tr>
        <td>@p.Name</td>
        <td>@p.Price</td>
        </tr>}
        </tbody>
    </table>
</body>
</html>
این View شامل یک جدول است که از حلقه foreach Razor، برای ایجاد ردیف‌هایی برای هر شیء مدل استفاده می‌کند. جایی که هر ردیف، حاوی سلول‌هایی برای خواص نام و قیمت است. اگر شما برنامه کاربردی را اجرا کنید، نتایج حاصل را در شکل خواهید دید:

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

دو نوع مختلف از بسته‌های نرم افزاری مورد نیاز برای Asp.Net Core MVC وجود دارند.

معرفی NuGet 

ویژوال استودیو به همراه یک ابزار گرافیکی برای مدیریت بسته‌های NET. است که در یک پروژه گنجانده شده‌است. برای باز کردن این ابزار، گزینه Management NuGet Packages for Solution را از منوی Tools ➤ NuGet Package Manager انتخاب کنید. به این ترتیب ابزار NuGet باز می‌شود و لیستی از بسته‌هایی که قبلا نصب شده‌اند، نمایش داده می‌شود؛ همانطور که در شکل زیر نشان داده شده‌است:
 


برگه‌ی Installed، خلاصه‌ای از بسته‌هایی را که قبلا در پروژه نصب شده‌اند، نشان می‌دهد. از برگه‌ی Browse، برای یافتن و نصب بسته‌های جدید می‌توان استفاده کرد و برگه‌ی Updates، فهرست package هایی را که نسخه‌های اخیر آن‌ها منتشر شده‌اند، نمایش می‌دهد.

 
معرفی بسته‌ی MICROSOFT.ASPNETCORE.ALL

اگر شما از نسخه‌های قبلی Asp.Net Core استفاده کرده باشید، باید یک لیست طولانی از بسته‌های NuGet را به پروژه جدید خود اضافه نمایید. Asp.Net Core2 یک بسته‌ی متفاوت را به نام Microsoft.AspNetCore.All معرفی می‌کند.

پکیچ Microsoft.AspNetCore.All یک meta-package است که شامل تمام بسته‌های Nuget مورد نیاز Asp.net Core و MVC Framework است. یعنی شما دیگر نیازی به نصب تک به تک این نوع بسته‌ها ندارید و هنگامیکه برنامه خود را منتشر می‌کنید، هر بسته‌ای از بسته‌های Meta-package که مورداستفاده قرار نمی‌گیرند، حذف خواهند شد. البته این بسته در نگارش 2.1، قسمت All آن به App تغییر نام یافته‌است.
 
معرفی بسته‌های Nuget و موقعیت ذخیره سازی آن‌ها

ابزار NuGet لیست بسته‌های پروژه را در فایل projectname.csproj نگهداری می‌کند. در اینجا <projectname> با نام پروژه جایگزین میشود. برای مثال در پروژه فوق اطلاعات Nuget، در فایل WorkingWithVisualStudio.csproj ذخیره می‌شوند. ویژوال استودیو محتویات فایل csproj را در پنجره‌ی Solution Explorer نمایش نمی‌دهد. برای ویرایش این فایل، روی پروژه در پنجره‌ی Solution Explorer راست کلیک کنید و گزینه‌ی Edit WorkWithVisualStudio.csproj را از منوی باز شده، انتخاب کنید. ویژوال استودیو فایل را برای ویرایش باز می‌کند. فایل csproj یک فایل XML است و شما در آن عنصری را مانند قطعه کد زیر در آن می‌بینید که Asp.net Core Meta package را به پروژه اضافه می‌کند:
<ItemGroup>
    <PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.0" />
</ItemGroup>
در اینجا یک بسته با نام و شماره نسخه مورد نیاز مشخص شده‌است. اگرچه بسته Meta-Package شامل تمام ویژگی‌های مورنیاز Asp.Net Core MVC می‌باشد، اما شما هنوز هم باید بسته‌های دیگری را به پروژه اضافه کنید تا بتوانید از ویژگی‌های اضافی خاص آن‌ها استفاده کنید. این بسته‌ها را می‌توان توسط رابط‌های خط فرمان و یا ابزار گرافیکی آن اضافه کرد. حتی شما می‌توانید فایل Csproj را به صورت مستقیم ویرایش کنید و ویژوال استودیو میتواند تغییرات بسته‌ها را شناسایی کرده، دانلود و نصب کند.
 


هنگامیکه از NuGet برای اضافه کردن یک بسته به پروژه‌ی خود استفاده می‌کنید، به صورت خودکار به همراه هر بسته‌ای که به آن وابستگی دارد، نصب می‌شود. شما می‌توانید بسته‌های Nuget و وابستگی‌های آن‌ها را در SolutionExpolrer از طریق گزینه‌ی Dependencies -> Nuget مشاهده کنید که هر یک از بسته‌های موجود در فایل csproj و وابستگی‌های آن‌ها را نشان می‌دهد. برای نمونه بسته Meta-Package ASP.Net Core دارای تعداد زیادی وابستگی است؛ برخی از آنها در شکل زیر دیده میشوند:


 
معرفی Bower

یک بسته Client-Side، شامل محتوایی است که به مشتری ارسال می‌شود؛ مانند فایل‌های جاوا اسکریپت، Css Stylesheets و یا تصاویر. از Nuget برای مدیریت این نوع فایل‌ها در پروژه نیز استفاده میشود. اما اکنون Asp.Net Core MVC پشتیبانی توکاری را از یک ابزار مدیریت بسته‌های سمت کاربر، به نام Bower نیز ارائه می‌دهد. Bower یک ابزار منبع باز ( Open Source ) است که در خارج از مایکروسافت و دنیای NET. توسعه داده شده و نگهداری می‌شود.

نکته: Bower به تازگی منسوخ شده اعلام گردیده‌است. ممکن است هشدارهایی را که ابزارهای جایگزین را پیشنهاد می‌کنند نیز مشاهده کنید. با این حال پشتیبانی از Bower با ویژوال استودیو یکپارچه شده‌است و در نگارش 2.1 ابزار مدیریت سمت کلاینت جدید دیگری را نیز بجای آن معرفی کرده‌اند.
 

معرفی لیست بسته‌های Bower

بسته‌های Bower از طریق فایل ویژه‌ی bower.json مشخص می‌شوند. برای ایجاد این فایل در پنجره Solution Explorer روی پروژه WorkingWithVisualStudio راست کلیک کنید و Add -> New Item را از منوی باز شده انتخاب کنید. سپس قالب مورد نظر Bower Configuration File را از Asp.net Core -> Web -> General  Category انتخاب نمائید؛ مانند تصویر زیر:
 


ویژوال استودیو نام bower.json را برای آن قرار میدهد. برروی ok کلیک می‌کنیم و یک فایل جدید، با محتویات پیشفرض زیر به پروژه اضافه میشود:
{
  "name": "asp.net",
  "private": true,
  "dependencies": {}
}
به علاوه برای فایل Bower.json، تصویر زیر بسته‌های Client Side وابسته به Bower را نشان میدهد. از این قسمت برای اضافه کردن وابستگی‌های برنامه نیز استفاده میشود.


نکته: منبع بسته‌های Bower در لینک http://bower.io/search وجود دارد. شما می‌توانید بسته‌ها مورنظر را در اینجا جستجو و به پروژه اضافه کنید.

بعد از اینکه بسته‌ها نصب شدند، محتویات فایل bower.json به صورت زیر می‌باشد:
{
  "name": "asp.net",
  "private": true,
  "resolutions": {
    "jquery": "3.3.1"
  }
}

در ادامه بسته Bootstrap CSS به پروژه اضافه شده‌است. زمانیکه شما فایل Bower.json را ویرایش می‌کنید، ویژوال استادیو لیستی از نام بسته‌ها و نسخه‌های بسته‌های موجود را نمایش می‌دهد؛ مانند تصویر زیر:


در زمان نوشتن این مطلب، آخرین نسخه‌ی پایدار بسته بوت استرپ، 3،3،7 است. البته اگر در دقت کنید، در اینجا سه گزینه‌ی ارائه شده‌ی توسط ویژوال استودیو وجود دارند: 3.3.7 و 3.3.7^ و 3.3.7~. شماره نسخه می‌تواند در طیف وسیعی از روش‌های مختلف در فایل bower.json مشخص شود. مفیدترین آنها در جدول زیر شرح داده شده‌اند. استفاده از شماره نسخه صریح یک بسته، امن‌ترین راه برای مشخص کردن یک بسته است. این تضمین می‌کند که شما همیشه با همان نسخه کار می‌کنید؛ مگر اینکه عمدا فایل bower.json را برای پاسخ گویی به درخواست‌های دیگری به روز رسانی کنید:
  فرمت    توضیحات 
  3.3.7  بیان شماره مستقیم بسته نصب شده و تطبیق دقیق آن با شمار نسخه ، e.g ، 3.3.7 
  *  با استفاده از یک ستاره به Bower اجازه نصب آخرین نسخه را می‌دهد
3.3.7 =<3.3.7<
پیشوند یک شماره نسخه با < یا =< به Bower اجازه می‌دهد تا هر نسخه از بسته‌ای که بزرگتر یا بزرگتر مساوی آن نسخه‌ی معین است، نصب شود 
3.3.7 =>3.3.7
پیشوند یک شماره نسخه با > یا => به Bower اجازه می‌دهد تا هر نسخه از بسته‌ای را که کوچکتر یا کوچکتر و مساوی نسخه‌ی معین است، نصب شود 
  3.3.7~  پیشوند یک شماره نسخه با یک tilde (با کاراکتر ~ ) به نسخه‌هایی که دو شماره اولیه آن‌ها مشابه باشند، اجازه نصب میدهد؛ حتی اگر شماره آخر آن نسخه متفاوت باشد. مانند نسخه‌های 3.3.9 و 3.3.8 و اجازه نصب نسخه 3.4.0 را نمیدهد؛ چون شماره دوم آن متفاوت است.
  3.3.7^  پیشوند یک شماره نسخه با یک قلم (کاراکتر ^) به نسخه‌هایی که شماره اول آنها مشابه باشند، اجازه نصب می‌دهد؛ حتی اگر شماره دوم آن‌ها متفاوت باشد. مانند نسخه‌های 3.3.1 و 3.4.1 و 3.5.1 اما نسخه 4.0.0 اجازه نصب ندارد 
 
نکته: برای مثال در این کتاب، من فایل bower.json را مستقیما ایجاد و ویرایش می‌کنم. ویرایش این فایل ساده است و به شما کمک می‌کند تا اطمینان حاصل کنید که نتایج مورد انتظار را در صورت پیگیری به همراه داشته باشد. همچنین ویژوال استودیو ابزار گرافیکی را نیز برای مدیریت بسته‌های bower فراهم می‌کند. شما می‌توانید با کلیک راست بر روی فایل bower.json و انتخاب Manage Bower packages به منوی باز شده دسترسی داشته باشید. ویژوال استادیو فایلهای bower.json را برای تغییرات نظارت می‌کند و به صورت خودکار از ابزار Bower برای دانلود و نصب بسته‌ها استفاده می‌کند. هنگامیکه شما تغییرات فایل را ذخیره می‌کنید، ویژوال استودیو بسته‌ی BootStrap را دانلود می‌کند و در پوشه‌ی wwwroot/lib ذخیره می‌کند.


مانند Nuget نیز Bower وابستگی‌های مرتبط با بسته‌های اضافه شده‌ی به یک پروژه را مدیریت می‌کند. BootStrap برای دسترسی به برخی از ویژگی‌های پیشرفته، به JQuery که یک کتابخانه‌ی جاوا اسکریپتی است، تکیه می‌کند. به همین دلیل است که دو بسته را در شکل فوق نشان داده است. شما می‌توانید لیست بسته‌ها و وابستگی‌های آنها را به صورت باز شده در بخش مورد نظر در Solution Explorer مشاهده کنید.

به روزرسانی بسته Bootstrap

در ادامه کتاب، من از نسخه قبلی Bootstrap CSS framework استفاده می‌کنم. هنگامی که دارم این را می‌نویسم، تیم Bootstrap در حال توسعه‌ی نسخه‌ی 4 bootStrap است و چندین بار منتشر شده‌است. این نسخه‌ها به عنوان "آلفا" برچسب گذاری شده‌اند، اما کیفیت آن‌ها بالا است و برای استفاده در نمونه‌های این کتاب به اندازه کافی پایدار است. با توجه به انتخاب نوشتن این کتاب با استفاده از Bootstrap 3 و نسخه پیش از نسخه بوت استرپ 4 و به زودی بایگانی شدن آن، تصمیم گرفتم از نسخه جدید استفاده کنم؛ حتی اگر برخی از نام‌های کلاس‌ها که برای شیوه نامه‌های عناصر HTML استفاده می‌شوند، احتمالا قبل از انتشار نهایی تغییر یابند. این مورد به این معنا است که شما باید همان نسخه از Bootstrap را که برای گرفتن نتایج موردنظر از خروجی نیاز دارید، استفاده  کنید.

برای به روزرسانی بسته Bootstrap، شماره نسخه را در فایل bower.json تغییر دهید. مانند کد زیر:
{
  "name": "asp.net",
  "private": true,
  "dependencies": {
    "bootstrap": "4.0.0-alpha.6"
  }
}
زمانی که شما تغییرات فایل bower.json را ذخیره می‌کنید، ویژوال استودیو نسخه جدید BootStrap را دانلود می‌کند.
معرفی توسعه و کامپایل مداوم

توسعه نرم افزار وب اغلب می‌تواند یک فرآیند تکراری باشد، جایی که تغییرات کوچکی را به ویووها یا کلاس‌ها می‌دهید و برنامه را اجرا می‌کنید تا اثرات آن را آزمایش کنید. MVC و ویژوال استودیو همکاری می‌کنند تا از این رویکرد مداوم استفاده کنند تا تغییرات را سریع‌تر و آسان‌تر ببینید.

 اعمال تغییرات در Razor Views  
در زمان توسعه، تغییراتی که به Razor View اعمال میشوند، به محض رسیدن درخواست‌های HTTP، از مرورگر دریافت میشوند. برای اینکه ببینید چطور کار می‌کند، برنامه را با انتخاب گزینه Start Debugging از منوی Debug اجرا کنید و هنگامیکه یک برگه‌ی مرورگر باز شد و اطلاعات نمایش داده شد، تغییراتی را که در زیر نمایش میدهم در فایل Index.cshtml اعمال کنید.

@model IEnumerable<WorkingWithVisualStudio.Models.Product>
@{
    Layout = null;
}

<!DOCTYPE html>

<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>>Working with Visual Studio</title>
</head>
<body>
    <h3>Products</h3>
    <table>
        <thead>
            <tr>
                <td>Name</td>
                <td>Price</td>
            </tr>
        </thead>
        <tbody>
            @foreach (var p in Model)
            {
            <tr>
                <td>@p.Name</td>
                <td>@($"{p.Price:C2}")</td>
            </tr>}
        </tbody>
    </table>
</body>
</html>
تغییرات را در فایل Index ذخیره می‌کنیم و صفحه وب را با استفاده از دکمه browser Reload مجددا بارگذاری می‌کنیم. تغییرات در View (یک عنوان و فرمت را برای مشخصه Price به عنوان ارز وارد کردیم) در مرورگر هم اعمال شده است؛ مانند تصویر زیر:



اعمال تغییرات در کلاس‌های #C

برای کلاس‌های #C، از جمله کنترلرها و مدل‌ها، دو رویکرد موجود را که از طریق آیتم‌های مختلف در منوی Debug انتخاب می‌شوند، شرح می‌دهم:

Start Without Debugging
تغییرات در کلاس‌ها در پروژه به صورت خودکار زمانیکه یک درخواست HTTP دریافت می‌شود، برای مشاهده‌ی یک تجربه‌ی توسعه‌ی پویا، کامپایل می‌شوند. در این حالت برنامه بدون امکانات دیباگ و اشکال‌زادیی اجرا می‌شود.

Start Debugging
به شما اجزا میدهد صریح تغییرات را کامپایل کنید و برنامه را اجرا کنید ، بررسی مشکلات هم در زمان اجرا پروژه انجام میگیرد.به شما اجرا بررسی و تجزیه و تحلیل هر گونه مشکل در کد را میدهد.

 
کامپایل خودکار کلاس ها

در طول توسعه عادی، این چرخه کامپایل سریع به شما اجازه می‌دهد تا فورا تاثیر تغییرات خود را ببینید؛ حالا می‌تواند این تغییر اضافه نمودن یک اکشن جدید و یا ویرایش نمایش اطلاعات یک Model باشد. برای ارائه‌ی این نوع از توسعه، ویژوال استودیو به محض رسیدن درخواست HTTP از مرورگر، تغییرات را دریافت و کلاس‌ها را به صورت خودکار کامپایل می‌کند. برای دیدن اینکه چگونه کار می‌کند، گزینه Start Without Debugging را از منوی Debug در ویژوال استودیو انتخاب کنید. هنگامیکه مرورگر داده‌های برنامه را نمایش می‌دهد، تغییرات زیر را در فایل Home controller ایجاد کنید:
namespace WorkingWithVisualStudio.Controllers
{
    public class HomeController : Controller
    {
        public IActionResult Index() => View(SimpleRepository.SharedRepository.Products
            .Where(p => p.Price < 50));
    }
}
در این تغییرات با استفاده از LINQ محصولات را فیلتر می‌کنیم به طوری که فقط کالاهایی را که price آنها کمتر از 50 است، نمایش داده می‌شوند. تغییرات را در فایل کلاس controller ذخیره کنید و پنجره مرورگر را دوباره باز کنید. بدون توقف و یا راه اندازی مجدد برنامه در ویژوال استادیو، درخواست HTTP از مرورگر باعث عملیات کامپایل میشود و برنامه با استفاده از تغییرات کلاس Controller، دوباره راه اندازی خواهد شد و نتیجه را در زیر میتوانید ببینیدکه محصولات Kayak را از جدول حذف می‌کند.

ویژگی کامپایل خودکار زمانی مفید است که همه چیز برنامه ریزی شود. مشکل این است که خطاهای کامپایلر، در زمان اجرا و در مرورگر بجای ویژوال استودیو نمایش داده می‌شوند. در این حالت زمانیکه یک مشکل وجود دارد، سخت می‌توان متوجه شد که چه مشکلی ایجاد شده است. برای مثال، کدهای زیر اضافه کردن یک مقدار Null را به مجموعه نمایش میدهد. 
namespace WorkingWithVisualStudio.Models
{
    public class SimpleRepository
    {
        private static SimpleRepository sharedRepository = new SimpleRepository();
        private Dictionary<string, Product> products = new Dictionary<string, Product>();
        public static SimpleRepository SharedRepository => sharedRepository;

        public SimpleRepository()
        {
            var initialItems = new[] { new Product { Name = "Kayak", Price = 275M }, new Product { Name = "Lifejacket", Price = 48.95M }, new Product { Name = "Soccer ball", Price = 19.50M }, new Product { Name = "Corner flag", Price = 34.95M } };
            foreach (var p in initialItems)
            {
                AddProduct(p);
            }
            products.Add("Error", null);
        }
        public IEnumerable<Product> Products => products.Values;
        public void AddProduct(Product p) => products.Add(p.Name, p);

    }
}
مشکلی مانند ورودی Null تا زمانیکه برنامه اجرا نشود، نمایش داده نمیشود. بارگذاری صفحه مرورگر باعث می‌شود کلاس SimpleRepository به صورت خودکار کامپایل شود و برنامه دوباره راه اندازی خواهد شد. هنگامیکه MVC نمونه‌ای از کلاس Controller را برای پردازش درخواست HTTP از مرورگر ایجاد می‌کند، سازنده HomeController کلاس SimpleRepository را ایجاد خواهد کرد که به نوبه خود سعی می‌کند مقدار Null اضافه شده در لیست را پردازش کند. مقدار Null باعث بروز یک مشکل می‌شود، اما مشخص نیست مشکل چیست. مرورگر یک پیام مفید را نمایش نمی‌دهد.
توانایی نمایش صفحات خطاها  
زمانیکه مشکلی در پنجره‌ی مرورگر ایجاد شد، می‌توان یک راهنمای با اطلاعات مفید را نمایش داد. این قابلیت را می‌توانید با فعال کردن نمایش صفحات انجام داد که باید در تنظیمات کلاس Startup تغییرات زیر را اعمال کنید.

namespace WorkingWithVisualStudio
{
    public class Startup
    {
        // This method gets called by the runtime. Use this method to add services to the container.
        // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
                app.UseDeveloperExceptionPage();
            }
        }
    }
}
اگر پنجره مرورگر را دوباره بارگذاری کنید، فرآیند کامپایل خودکار به صورت خودکار برنامه را بازسازی می‌کند و یک پیام خطای مفید‌تری را در مرورگر ایجاد می‌کند. مانند تصویر زیر:

پیام خطایی که توسط مرورگر نشان داده می‌شود، می‌تواند برای کشف مشکلات ساده، کافی باشد. اما برای مشکلات پیچیده‌تر و برای مشکلاتی که بلافاصله آشکار نمی‌شوند، ویژوال استودیو برای پیگیری خطا مورد نیاز است.

 
استفاده از Debugger

ویژوال استادیو از اجرای یک برنامه MVC با استفاده از Debugger نیز پشتیبانی می‌کند که اجازه می‌دهد برنامه برای بررسی وضعیت نرم افزار و دنبال کردن درخواستی که به برنامه ارسال میشود، متوقف و از این طریق، پیگیری شود. این مورد نیاز به یک سبک متفاوت از توسعه را دارد. زیرا تغییراتی را در کلاس‌های #C میدهیم، تا زمانیکه برنامه مجددا راه اندازی نشود، اعمال نمی‌شوند ( هرچند تغییرات Razor View هنوز هم به صورت خودکار اعمال میشوند). این سبک توسعه به همراه استفاده‌ی از ویژگی کامپایل خودکار نیست؛ اما Debugger ویژوال استودیو عالی است و می‌تواند بینش عمیق‌تری را در مورد نحوه‌ی کارکرد برنامه داشته باشد. برای اجرای برنامه با استفاده Debugger، در ویژوال استودیو از منوی Debug گزینه‌ی Start Debugging را انتخاب کنید. ویژوال استودیو کلاسهای #C در پروژه را قبل از اجرای برنامه کامپایل می‌کند. اما شما همچنان می‌توانید با استفاده از موارد موجود در منوی Build، کد خود را به صورت دستی نیز کامپایل کنید.

مثال فوق حاوی مقدار NULL است که سبب می‌شود یک NullReferenceException توسط کلاس SimpleRepository پرتاب شود. این حالت برنامه را قطع و کنترل اجرا را به توسعه دهنده منتقل می‌کند؛ همانطور که در شکل زیر نشان داده شده است



نکته: اگر Debugger خطا را نفهمد، گزینه‌ی Windows ➤ exception settings را از منوی Debugger ویژوال استودیو انتخاب کنید و اطمینان حاصل کنید که تمام انواع خطاهای در لیست خطاهای زمان اجرای زبان مشترک، تایید شده‌است.
تنظیم یک Break-point

Debugger عامل اصلی خطا را نمایش نمی‌دهد؛ تنها مکان آن‌را آشکار می‌کند. عبارتیکه ویژوال استودیو برجسته می‌کند نشان می‌دهد که این مشکل زمانی رخ می‌دهد که فیلتر کردن اشیاء با استفاده از LINQ انجام شود، اما یک کار کوچک لازم است تا از جزئیات کاسته شود و به علت اصلی برسد.
Breakpoint عبارتی است که به Debugger می‌گوید تا برنامه را متوقف کند و کنترل دستی برنامه را به برنامه نویس میدهد. شما می‌توانید وضعیت برنامه را بازبینی کنید و ببینید چه اتفاقی می‌افتد و به صورت اختیاری روند کاری را دوباره ادامه دهید.
برای ایجاد Breakpoint، روی عبارت راست کلیک کنید و در منوی باز شده، گزینه Breakpoint -> Insert Breakpoint را انتخاب کنید.

به عنوان مثال: یک Breakpoint به خط کد AddProduct در کلاس SimpleRepository اعمال کنید. همانطور که در شکل زیر نمایش داده میشود:
 


برنامه را اجرا کنید؛ با استفاده از Debug -> Start Debugging و یا با استفاده از Debug -> Restart برنامه را Restart می‌کنیم. در طی درخواست اولیه HTTP، برنامه اجرا میشود تا به نقطه‌ای که Break Point دارد برسد و در آنجا برنامه متوقف میشود. در این نقطه، شما می‌توانید از آیتم‌های منوی Debug ویژوال استودیو یا کنترل‌ها در بالای پنجره، برای کنترل اجرای برنامه استفاده کنید؛ یا از نمایش‌های مختلف Debugger موجود از طریق Debug -> Windows برای بررسی وضعیت برنامه استفاده می‌کنیم.
مشاهده مقادیر داده در ویرایشگر کد
رایج‌ترین استفاده Break Point، ردیابی مشکلات در کد شماست. قبل از اینکه بتوانید یک مشکل را رفع کنید، باید بدانید چه اتفاقی در حال رخ دادن است و یکی از ویژگیهای مفید ویژوال استودیو این است که توانایی مشاهده و کنترل ارزش متغیرها را درست در ویرایشگر کد، میدهد.
اگر اشاره‌گر ماوس را بر روی پارامتر p به متد AddProduct که توسط Debugger برجسته شده‌است، حرکت دهید، یک فرم ظاهر خواهد شد که ارزش فعلی p را نشان می‌دهد؛ همانطور که در شکل زیر نشان داده شده‌است. من یک نمونه بزرگ شده از محتویات فرم ظاهر شده را نمایش میدهم تا به راحتی بتوانید متن در آن را بخوانید.
 


این مورد ممکن است مؤثر به نظر نرسد، چون شیء داده در یک سازنده همانند BreakPoint تعریف شده‌است. اما این ویژگی‌ها برای هر متغیری کار می‌کند. شما می‌توانید مقادیر را مشاهده کنید تا مقادیر خود و فیلد آنها را ببینید. هر مقدار دارای یک دکمه پین​​ کوچک به سمت راست است. برای زمانیکه کد در حال اجراست، برای نظارت بر مقدار، از آن استفاده کنید.
اشاره‌گر ماوس را بر روی متغیر P قرار دهید و مرجع محصول را پین کنید. مرجع پیوست شده را باز کنید تا بتوانید نام و قیمت را نیز ببینید؛ مانند شکل زیر:
 


گزینه Continue را از منوی Debug در ویژوال استادیو انتخاب کنید تا برنامه ادامه پیدا کند. از آنجا که در برنامه حلقه Foreach وجود دارد، برنامه که دوباره اجرا میشود، وقتی مجددا به BreakPoint رسید، برنامه متوقف میشود. مقادیر پین شده در شکل زیر نشان میدهند که چگونه متغیر P و خواص آن تغییر می‌کنند.
 


استفاده از پنجره متغیرهای محلی ( Local Windows )

یکی از ویژگی‌های مرتبط، پنجره Locals است که با انتخاب گزینه‌ی منوی Debug ➤ Windows ➤ Locals باز می‌شود. پنجره‌ی Locals، مقدار متغیرها را به شکلی مشابه پنل پین شده نمایش می‌دهد، اما در اینجا تمام اشیاء محلی را نسبت به Break Point نمایش می‌دهد؛ همانطور که در شکل زیر نشان داده شده‌است:
 


هربار که Continue را انتخاب می‌کنید، اجرای برنامه ادامه یافته و یک شیء دیگر توسط حلقه foreach پردازش می‌شود.
اگر ادامه دهید، در زمان ویرایش کد، در هر دو پنجره Locals و در مقادیر پنل پین شده، شما مرجع Null را می‌بینید. برای کنترل اجرای برنامه، می‌توانید جریان را از طریق کد خود در دیباگر دنبال کنید و احساس کنید که چه اتفاقی می‌افتد.

برای غیرفعال کردن BreakPoint، روی  عبارت راست کلیک کنید و از منوی باز شده گزینه Delete BreakPoint را انتخاب کنید. برنامه را دوباره راه اندازی کنید و جدول داده ساده‌ای را که در شکل نشان داده شده، مشاهده خواهید کرد.


 
استفاده از Browser Link

ویژگی Browser Link می‌تواند روند توسعه را با قرار دادن یک یا چند مرورگر تحت کنترل ویژوال استودیو، ساده سازی کند. این ویژگی مخصوصا مفید است اگر شما نیاز به دیدن اثر تغییرات را در طیف وسیعی از مرورگرها دارید. قابلیت Browser Link با و یا بدون Debugger کار می‌کند و به این معنا است که می‌توانیم هر فایلی را در پروژه تغییر دهیم و تاثیر تغییر را بدون نیاز به تغییری در مرورگر مشاهده کنیم.

 
راه اندازی BrowserLink

برای فعال کردن Browser Link باید در کلاس Startup، تنظیمات را تغییر دهید. مانند کد زیر:

namespace WorkingWithVisualStudio
{
    public class Startup
    {
        // This method gets called by the runtime. Use this method to add services to the container.
        // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
                app.UseBrowserLink();
                app.UseDeveloperExceptionPage();
            }

            app.Run(async (context) =>
            {
                await context.Response.WriteAsync("Hello World!");
            });
        }
    }
}


استفاده از Browser Link

برای درک اینکه Browser Link چگونه کار می‌کند، در ویژوال استودیو گزینه Start Without Debugging را از منوی Debug انتخاب می‌کنیم. ویژوال استودیو برنامه را اجرا می‌کند و یک برگه جدید مرورگر را برای نمایش نتیجه باز می‌کند. با بازبینی HTML ارسال شده به مرورگر، شما خواهید دید که حاوی بخش دیگری مانند این است:
 

<!DOCTYPE html>

<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>>Working with Visual Studio</title>
</head>
<body>
    <h3>Products</h3>
    <table>
        <thead>
            <tr><td>Name</td><td>Price</td></tr>
        </thead>
        <tbody>
            <tr><td>Lifejacket</td><td>&#xA3;48.95</td></tr>
            <tr><td>Soccer ball</td><td>&#xA3;19.50</td></tr>
            <tr><td>Corner flag</td><td>&#xA3;34.95</td></tr>
        </tbody>
    </table>
    <!-- Visual Studio Browser Link -->
    <script type="application/json" id="__browserLink_initializationData">
        {"requestId":"968949d8affc47c4a9c6326de21dfa03","requestMappingFromServer":false}
    </script>
    <script type="text/javascript" src="http://localhost:55356/d1a038413c804e178ef009a3be07b262/browserLink" async="async"></script> <!-- End Browser Link -->
</body>
</html>
نکته: اگر قسمت اضافی را نمی‌بینید، لینک مرورگر را از منوی نشان داده شده‌ی در شکل زیر فعال کنید و مرورگر را دوباره بارگذاری کنید.


ویژوال استادیو یک جفت عناصر اسکریپت را به HTML فرستاده شده‌ی به مرورگر اضافه می‌کند که برای بازکردن یک اتصال طولانی مدت HTTP با سرور برنامه کاربردی است؛ تا زمانیکه ویژوال استودیو مجددا برنامه را ری‌استارت کند. کد زیر تغییر در فایل Index و تاثیر استفاده از Browser Link را نشان میدهد.
 

@model IEnumerable<WorkingWithVisualStudio.Models.Product>
@{
    Layout = null;
}

<!DOCTYPE html>

<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>>Working with Visual Studio</title>
</head>
<body>
    <h3>Products</h3>
    <p>Request Time: @DateTime.Now.ToString("HH:mm:ss")</p>
    <table>
        <thead>
            <tr>
                <td>Name</td>
                <td>Price</td>
            </tr>
        </thead>
        <tbody>
            @foreach (var p in Model)
            {
                <tr>
                    <td>@p.Name</td>
                    <td>@($"{p.Price:C2}")</td>
                </tr>}
        </tbody>
    </table>
</body>
</html>
تغییر در فایل View را ذخیره کنید و Refresh Linked Browsers را از منوی Browser Link در نوار ابزار ویژوال استودیو انتخاب کنید؛ همانطور که در شکل نشان داده شده است.  اگر Browser Link کار نمی‌کند، بارگیری مجدد مرورگر یا راه اندازی مجدد ویژوال استادیو را امتحان کنید).
 


کد جاوا اسکریپتی که در HTML ارسال شده به مرورگر جاسازی شده، صحفه را دوباره بارگذاری می‌کند؛ برای دیدن تاثیرات کد اضافه شده که اضافه کردن  یک timestamp ساده است.
 
نکته: عناصر اسکریپت Browser Link فقط در پاسخ‌های موفق جاسازی شده است. به این معنا که اگر یک خطا هنگام کامپایل در هنگام اجرا کردن یک Razor View یا مدیریت یک درخواست ایجاد شود، اتصال بین مرورگر و ویژوال استودیو از بین میرود و شما بعد از حل مشکل باید صفحه را مجدد بارگذاری کنید.

 
استفاده از مرورگرهای متعدد

Browser Link می‌تواند برای نمایش یک برنامه در مرورگرهای متعددی به طور همزمان استفاده شود و می‌تواند زمانی مفید باشد که شما می‌خواهید تفاوت‌های پیاده سازی را بین مرورگرهای مختلف کنترل کنید و یا ببینید که چگونه یک برنامه بر روی ترکیبی از مرورگرهای دسکتاپ و تلفن همراه ارائه می‌شود.
برای انتخاب مرورگرهایی که استفاده می‌شوند، مرورگر را با استفاده از دکمه IIS Express در نوار ابزار ویژوال استودیو، انتخاب کنید؛ همانطور که در شکل زیر نشان داده شده است.
 


ویژوال استودیو لیستی از مرورگرهایی را که در مورد آنها اطلاعاتی دارد، نمایش میدهد. در عکس زیر مرورگرهایی را که من در سیستم خود نصب کرده‌ام، نشان می‌دهد. برخی از آنها با ویندوز مانند Internet Explorer و Edge نصب می‌شوند.

 
ویژوال استادیو معمولا مرورگرهای رایجی را که نصب میشوند، نمایش میدهد. اما شما می‌توانید با استفاده از دکمه‌ی Add، برای اضافه کردن مرورگری که به صورت خودکار لیست نشده نیز استفاده کنید. همچنین می‌توانید ابزار تست شخص ثالث مانند Browser Stack را نیز راه اندازی کنید که مرورگرها را بر روی سرویس‌های ابری میزبان ( cloud-hosted ) و ماشین‌های مجازی اجرا می‌کند.

من سه مرورگر را در شکل انتخاب کردم: Chrome ، Internet Explorer و Edge. با کلیک بر روی دکمه Browse، فعالیت هر سه مرورگر شروع می‌شود و باعث می‌شود URL مثال برنامه را بارگذاری کند؛ همانطور که در شکل نشان داده شده است.
 


با استفاده از منوی Browser Link Dashboard، شما می‌توانید ببینید که چه مرورگرهایی در Browser Link انتخاب شده‌اند. داشبورد آن نشانی اینترنتی نمایش داده شده توسط هر مرورگر را نشان می‌دهد و در اینجا هر مرورگر را می‌توان به صورت جداگانه رفرش کرد.
 


آماده سازی جاوا اسکریپت و CSS برای استقرار

هنگامی که Client-Side بخشی از یک برنامه وب را ایجاد می‌کنید، معمولا تعدادی از فایل‌های جاوا اسکریپت و CSS سفارشی را تهیه می‌کنید که برای تکمیل آن‌ها، از بسته‌های نصب شده‌ی توسط Bower استفاده می‌شود. این فایل‌ها نیاز به پردازش دارند تا آنها را برای تحویل در یک محیط تولید، بهینه سازی کنند تا تعداد درخواستهای HTTP و میزان پهنای باند شبکه مورد نیاز برای ارسال آنها به مشتری، به حداقل برسد. این فرآیند به عنوان بسته بندی شناخته می‌شود.
 

فعال کردن تحویل محتوای استاتیک

ASP.Net Core شامل پشتیبانی از ارائه فایل‌های استاتیک از پوشه wwwroot به مشتریان است. اما این امکان به صورت پیشفرض در زمان ایجاد یک پروژه‌ی خالی جدید فعال نیست و شما باید با قرار دادن عبارتی در فایل StartUp آن را فعال کنید؛ مانند کد زیر:
 

namespace WorkingWithVisualStudio
{
    public class Startup
    {
        // This method gets called by the runtime. Use this method to add services to the container.
        // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
                app.UseBrowserLink();
                app.UseStaticFiles();
                app.UseDeveloperExceptionPage();
            }

            app.Run(async (context) =>
            {
                await context.Response.WriteAsync("Hello World!");
            });
        }
    }
}


اضافه کردن محتوای استاتیک به پروژه

برای نشان دادن فرآیند بسته بندی، من نیاز به اضافه کردن تعدادی محتوای استاتیک به پروژه و یکی کردن آن‌ها با برنامه‌ی نمونه را دارم. برای این منظور ابتدا یک پوشه‌ی جدید را به نام wwwroot/css ایجاد کنید که محل متداولی برای فایل‌های سفارشی CSS است. من فایلی را به نام First.css با استفاده از قالب آیتم Style Sheet اضافه کردم؛ همانطور که در شکل زیر نشان داده شده است. قالب Style Sheet در مسیر Asp.Net Core -> Web -> Content Section وجود دارد.
 


فایل First.Css را ویرایش کنید و محتوای زیر را در آن قرار دهید.
h3 {
}

table, td {
    border: 2px solid black;
    border-collapse: collapse;
    padding: 5px;
}
من این روند را تکرار کردم و یک فایل دیگر را نیز به نام second.css در پوشه wwwroot/css ایجاد کردم.

فایل‌های جاوا اسکریپت معمولا در پوشه wwwroot/js قرار میگیرند. من این پوشه را ایجاد کردم. فایل‌های جاوا اسکریپت را می‌توانید در مسیر Asp.Net Core -> Web -> Script انتخاب کنید. همانطور که در شکل زیر نشان داده شده است.


من کد جاوا اسکریپتی ساده زیر را به این فایل جدید اضافه کردم؛ همانطور که در لیست نشان داده شده است.
document.addEventListener("DOMContentLoaded", function ()
{
    var element = document.createElement("p");
    element.textContent = "This is the element from the third.js file";
    document.querySelector("body").appendChild(element);
});

من به بیش از یک فایل جاوا اسکریپت نیاز دارم. بنابراین فایل دیگری را به نام fourth.js نیز در پوشه wwwroot ایجاد می‌کنم و محتوای زیر را در آن قرار میدهم.
document.addEventListener("DOMContentLoaded", function ()
{
    var element = document.createElement("p");
    element.textContent = "This is the element from the fourth.js file";
    document.querySelector("body").appendChild(element);
});


به روز رسانی View

گام نهایی، به روز رسانی فایل Index.cshtml برای استفاده از Css و فایل جاوا اسکریپت است. کد‌های آن در زیر نشان داده شده است:
@model IEnumerable<WorkingWithVisualStudio.Models.Product>
@{
    Layout = null;
}

<!DOCTYPE html>

<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>>Working with Visual Studio</title>
    <link rel="stylesheet" href="css/first.css" />
    <link rel="stylesheet" href="css/second.css" />
    <script src="js/third.js"></script>
    <script src="js/fourth.js"></script>
</head>
<body>
    <h3>Products</h3>
    <p>Request Time: @DateTime.Now.ToString("HH:mm:ss")</p>
    <table>
        <thead>
            <tr>
                <td>Name</td>
                <td>Price</td>
            </tr>
        </thead>
        <tbody>
            @foreach (var p in Model)
            {
                <tr>
                    <td>@p.Name</td>
                    <td>@($"{p.Price:C2}")</td>
                </tr>}
        </tbody>
    </table>
</body>
</html>
اگر برنامه کاربردی را اجرا کنید، محتویات نشان داده شده‌ی در شکل زیر را مشاهده خواهید کرد. محتوای موجود توسط شیوه نامه‌های CSS شبیه سازی شده است و کد جاوا اسکریپتی جدیدی را اضافه کرده است.


یکی کردن فایل‌های سمت کلاینت در برنامه‌های MVC

در حال حاضر چهار فایل استاتیک وجود دارند و مرورگر باید چهار درخواست را برای دریافت فایل‌های استاتیک ایجاد کند و هر یک از این فایل‌ها نیازمند پهنای باند بیشتری است که باید به مشتری تحویل داده شود؛ زیرا آنها حاوی فضای سفید و نام متغیرها هستند که برای توسعه دهنده‌ها معنا دار هستند؛ اما برای مرورگرها اهمیتی ندارند.
ترکیب فایل‌هایی هم نوع، تلفیق نامیده می‌شود و در آن کار ساختن فایل‌ها به صورتی کوچکتر انجام می‌شود. هر دوی این کارها در برنامه Asp.Net Core MVC توسط  Bundler & Minifier مخصوص ویژوال استودیو انجام میشود.


نصب افزونه‌های ویژوال استودیو

اولین قدم برای نصب افزونه، انتخاب از منوی Tools -> Extensions and Update و کلیک بر روی مجموعه Online است تا افزونه‌های ویژوال استودیو را در مجموعه نمایش بدهد. نام افزونه را در جعبه جستجوی در گوشه‌ی سمت راست بالای پنجره وارد کنید؛ همانطور که در شکل زیر نشان داده شده است. محل نصب افزونه را مشخص می‌کنیم و بر روی دانلود کلیک می‌کنیم تا آن را به ویژوال استودیو اضافه کند. ویژوال استودیو را مجدد راه اندازی کنید تا فرآیند نصب تکمیل شود.


دسته بندی و یکی کردن فایل‌ها

پس از نصب افزونه، ویژوال استودیو را مجددا راه اندازی کنید و پروژه نمونه را باز کنید. با افزودن افزونه، می‌توانید چندین فایل هم نوع را در Solution Explorer انتخاب کنید. آنها را با یکدیگر ترکیب کرده و محتویات آنها را کوچکتر کنید. به عنوان مثال فایل‌های First.css و Second.css را در Solution Explorer را انتخاب و کلیک راست کرده و سپس Bundler & Minifier -> Bundle and Minify Files را از منوی باز شده انتخاب کنید . همانطور که در شکل زیر نشان داده شده است.
 


فایل خروجی را با عنوان bundle.css ذخیره کنید. در Solution Explorer یک بسته جدید ایجاد میشود. اگر شما این فایل را باز کنید، خواهید دید که محتویات هر دو فایل CSS جداگانه ترکیب شده‌اند و تمام فضای سفید آن‌ها حذف شده‌است. البته شما نمی‌خواهید به طور مستقیم با این فایل کار کنید؛ اما این فایل کوچکتر است و فقط یک اتصال HTTP را برای ارائه CSS styles به مشتری نیاز دارد.

مراحل قبل را برای فایل‌های third.js و fourth.js تکرار کنید تا فایل‌های جدید bundle.js و bundle.min.js در پوشه wwwroot ایجاد شوند.

احتیاط: اطمینان حاصل کنید که فایل‌ها را به ترتیبی که توسط مرورگر بارگیری می‌شوند، انتخاب کنید تا ترتیب دستورات Style‌ها یا دستورات کد را در فایل‌های خروجی حفظ کنید. به عنوان مثال دقت کنید که فایل third.js قبل از فایل fourth.js انتخاب شده باشد تا مطمئن باشید دستورات به ترتیب و به درستی اجرا می‌شوند.
کد زیر، عناصر پیوند فایل‌های جداگانه‌ای را که باید در فایل Index.cshtml قرار گیرند، نمایش میدهد:
@model IEnumerable<WorkingWithVisualStudio.Models.Product>
@{
    Layout = null;
}

<!DOCTYPE html>

<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>>Working with Visual Studio</title>
    <link rel="stylesheet" href="css/bundle.min.css" />
    <script src="js/bundle.min.js"></script>
</head>
<body>
    <h3>Products</h3>
    <p>Request Time: @DateTime.Now.ToString("HH:mm:ss")</p>
    <table>
        <thead>
            <tr>
                <td>Name</td>
                <td>Price</td>
            </tr>
        </thead>
        <tbody>
            @foreach (var p in Model)
            {
                <tr>
                    <td>@p.Name</td>
                    <td>@($"{p.Price:C2}")</td>
                </tr>}
        </tbody>
    </table>
</body>
</html>
اگر برنامه را اجرا کنید، هیچ تغییر بصری وجود نخواهد داشت؛ اما فایلهای آن یکی شده‌اند و با حجم کمتر و با تعداد اتصالات کمتری از سرور دریافت می‌شوند.

همان زمان که عملیات جمع آوری و یکی کردن را انجام می‌دهید، رکورد عملیات انجام شده را در فایلی به نام bundleconfig.json در پوشه‌ی wwwroot پروژه نگهداری می‌کند. در اینجا یک نمونه از فایل تولیدی را مشاهده می‌کنید:
[
  {
    "outputFileName": "Views/wwwroot/css/bundle.css",
    "inputFiles": [
      "Views/wwwroot/css/First.css",
      "Views/wwwroot/css/second.css"
    ]
  },
  {
    "outputFileName": "Views/wwwroot/js/bundle.js",
    "inputFiles": [
      "Views/wwwroot/js/fourth.js",
      "Views/wwwroot/js/third.js"
    ]
  }
]
 

خلاصه
در این بخش من توضیحاتی را در مورد ویژگی‌هایی که ویژوال استودیو برای طراحی برنامه‌های وب به توسعه دهنده‌ها ارائه میدهد، شرح دادم که شامل کامپایل خودکار کلاس‌ها، Browser Link و یکی کردن فایل‌های سمت کلاینت ( bundling and minification ) بود. 
مطالب
Blazor 5x - قسمت 27 - برنامه‌ی Blazor WASM - کار با سرویس‌های Web API
در قسمت‌های Blazor Server مثال این سری، با روش کار با سرویس‌های سمت سرور برنامه، آشنا شدیم. در این نوع برنامه‌ها، فقط کافی است اصل سرویس مدنظر را مستقیما در کامپوننت‌های Razor تزریق کرد و سپس می‌توان به نحو متداولی با آن‌ها کار کرد؛ اما در برنامه‌های Blazor WASM خیر! به این نوع برنامه‌های سمت کلاینت باید همانند برنامه‌های React ، Angular ، Vue و یا حتی برنامه‌های مبتنی بر jQuery نگاه کرد. در تمام فناوری‌های سمت کلاینت، این درخواست‌های Ajax ای هستند که با سرویس‌های یک Web API سمت سرور، ارتباط برقرار کرده، اطلاعاتی را به آن‌ها ارسال و یا دریافت می‌کنند. در برنامه‌های Blazor WASM نیز باید به همین ترتیب عمل کرد و در اینجا HttpClient دات نت، جایگزین برای مثال jQuery Ajax ، Fetch API و یا XMLHttpRequest استاندارد می‌شود (البته jQuery Ajax در اصل یک محصور کننده‌ی استاندارد XMLHttpRequest است که برای اولین بار توسط مایکروسافت در برنامه‌ی Outlook web access معرفی شد).


ایجاد سرویس سمت کلاینت دریافت اطلاعات اتاق‌ها از Web API

در قسمت 24، HotelRoomController را تکمیل کردیم که کار آن، بازگشت اطلاعات تمام اتاق‌ها و یا یک اتاق مشخص به کلاینت است. اکنون می‌خواهیم در ادامه‌ی قسمت قبل، اگر کاربری بر روی دکمه‌ی Go صفحه‌ی اول رزرو اتاقی کلیک کرد، لیست تمام اتاق‌های تعریف شده را به او نمایش دهیم. به همین جهت نیاز به سرویس سمت کلاینتی داریم که بتواند با این Web API endpoint کار کند:
namespace BlazorWasm.Client.Services
{
    public interface IClientHotelRoomService
    {
        public Task<IEnumerable<HotelRoomDTO>> GetHotelRoomsAsync(DateTime checkInDate, DateTime checkOutDate);
        public Task<HotelRoomDTO> GetHotelRoomDetailsAsync(int roomId, DateTime checkInDate, DateTime checkOutDate);
    }
}
این سرویس را در پوشه‌ی Services پروژه‌ی BlazorWasm.Client ایجاد کرده‌ایم که HotelRoomDTO خود را از پروژه‌ی BlazorServer.Models دریافت می‌کند. به این ترتیب می‌توان مدلی را بین یک Web API سمت سرور و یک سرویس سمت کلاینت، به اشتراک گذاشت. بنابراین پروژه‌ی کلاینت، باید ارجاعی را به پروژه‌ی BlazorServer.Models.csproj نیز داشته باشد.

در ادامه اینترفیس فوق را به صورت زیر پیاده سازی می‌کنیم:
namespace BlazorWasm.Client.Services
{
    public class ClientHotelRoomService : IClientHotelRoomService
    {
        private readonly HttpClient _httpClient;

        public ClientHotelRoomService(HttpClient httpClient)
        {
            _httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient));
        }

        public Task<HotelRoomDTO> GetHotelRoomDetailsAsync(int roomId, DateTime checkInDate, DateTime checkOutDate)
        {
            throw new NotImplementedException();
        }

        public Task<IEnumerable<HotelRoomDTO>> GetHotelRoomsAsync(DateTime checkInDate, DateTime checkOutDate)
        {
            // How to url-encode query-string parameters properly
            var uri = new UriBuilderExt(new Uri(_httpClient.BaseAddress, "/api/hotelroom"))
                            .AddParameter("checkInDate", $"{checkInDate:yyyy'-'MM'-'dd}")
                            .AddParameter("checkOutDate", $"{checkOutDate:yyyy'-'MM'-'dd}")
                            .Uri;
            return _httpClient.GetFromJsonAsync<IEnumerable<HotelRoomDTO>>(uri);
        }
    }
}
توضیحات:
- HttpClient یکی از سرویس‌های تنظیم شده‌ی در فایل Program.cs پروژه‌های سمت کلاینت است. بنابراین می‌توان آن‌را از طریق تزریق به سازنده‌ی این سرویس، به دست آورد.
- در اینجا برای دریافت اطلاعات JSON دریافتی از سمت سرور و سپس Deserialize خودکار آن به لیستی از DTO تعریف شده، از متد جدید GetFromJsonAsync استفاده شده‌است. این مورد جزو تازه‌های NET 5x. است.
- در اینجا استفاده از کلاس UriBuilderExt را نیز جهت تشکیل یک URL دارای کوئری استرینگ، مشاهده می‌کنید. هیچگاه نباید URL نهایی را از طریق جمع زدن اجزای آن به سمت سرور ارسال کرد؛ از این جهت که اجزای آن باید URL-encoded شوند؛ وگرنه در سمت سرور قابلیت پردازش نخواهند داشت. در ادامه تعریف کلاس جدید UriBuilderExt را مشاهده می‌کنید:
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Net.Http.Json;
using System.Threading.Tasks;
using BlazorServer.Models;
using BlazorWasm.Client.Utils;

using System;
using System.Collections.Specialized;
using System.Web;

namespace BlazorWasm.Client.Utils
{
    public class UriBuilderExt
    {
        private readonly NameValueCollection _collection;
        private readonly UriBuilder _builder;

        public UriBuilderExt(Uri uri)
        {
            _builder = new UriBuilder(uri);
            _collection = HttpUtility.ParseQueryString(string.Empty);
        }

        public UriBuilderExt AddParameter(string key, string value)
        {
            _collection.Add(key, value);
            return this;
        }

        public Uri Uri
        {
            get
            {
                _builder.Query = _collection.ToString();
                return _builder.Uri;
            }
        }
    }
}
- در اینجا توسط متد AddParameter، کار افزودن کوئری استرینگ‌ها به یک Url از پیش مشخص، انجام می‌شود. کار encoding نهایی به صورت خودکار توسط HttpUtility استاندارد دات نت، انجام خواهد شد.
- تاریخ‌های ارسالی به سمت سرور را با فرمت yyyy'-'MM'-'dd تبدیل رشته کردیم. این قالب، یکی از قالب‌های پذیرفته شده‌است.
- جهت سهولت استفاده‌ی از سرویس فوق و همچنین مدل‌های برنامه، فضای نام آن‌ها را به فایل BlazorWasm.Client\_Imports.razor اضافه می‌کنیم تا در تمام کامپوننت‌های برنامه‌ی سمت کلاینت، قابل دسترسی شوند:
@using BlazorWasm.Client.Services
@using BlazorServer.Models
- در آخر این سرویس جدید را باید به لیست سرویس‌های برنامه معرفی کرد تا قابلیت تزریق در کامپوننت‌ها را پیدا کند:
namespace BlazorWasm.Client
{
    public class Program
    {
        public static async Task Main(string[] args)
        {
            var builder = WebAssemblyHostBuilder.CreateDefault(args);
            // ...

            builder.Services.AddScoped<IClientHotelRoomService, ClientHotelRoomService>();

            // ...
        }
    }
}

چند اصلاح جزئی در کنترلرها و سرویس‌های سمت سرور

در Url نهایی فوق، دو پارامتر جدید checkInDate و checkOutDate هم وجود دارند. به همین جهت این دو را به اکشن متدهای کنترلر HotelRoom:
namespace BlazorWasm.WebApi.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class HotelRoomController : ControllerBase
    {
        // ...

        [HttpGet]
        public async Task<IActionResult> GetHotelRooms(DateTime? checkInDate, DateTime? checkOutDate)
        {
          // ...
        }

        [HttpGet("{roomId}")]
        public async Task<IActionResult> GetHotelRoom(int? roomId, DateTime? checkInDate, DateTime? checkOutDate)
        {
           // ...
        }
    }
}
و همچنین سرویس سمت سرور IHotelRoomService نیز اضافه می‌کنیم:
namespace BlazorServer.Services
{
    public interface IHotelRoomService : IDisposable
    {
        Task<List<HotelRoomDTO>> GetAllHotelRoomsAsync(DateTime? checkInDate, DateTime? checkOutDate);
        Task<HotelRoomDTO> GetHotelRoomAsync(int roomId, DateTime? checkInDate, DateTime? checkOutDate);
        // ...
    }
}
البته فعلا پیاده سازی خاصی ندارند و آن‌ها را در قسمت‌های بعد مورد استفاده قرار خواهیم داد.


تنظیمات ویژه‌ی HttpClient برنامه‌ی سمت کلاینت

سرویس ClientHotelRoomService فوق، از HttpClient تزریق شده‌ی در سازنده‌ی خود استفاده می‌کند که BaseAddress خود را مطابق تنظیمات ابتدایی برنامه، از HostEnvironment دریافت می‌کند. در اینجا علاقمندیم تا بجای این تنظیم پیش‌فرض، فایل جدید appsettings.json را به پوشه‌ی BlazorWasm.Client\wwwroot\appsettings.json کلاینت اضافه کرده (محل قرارگیری آن در برنامه‌های سمت کلاینت، داخل پوشه‌ی wwwroot است و نه در داخل پوشه‌ی ریشه‌ی اصلی پروژه):
{
    "BaseAPIUrl": "https://localhost:5001/"
}
و از این تنظیم جدید به عنوان BaseAddress برنامه‌ی Web API استفاده کنیم که روش آن‌را در کدهای ذیل مشاهده می‌کنید:
namespace BlazorWasm.Client
{
    public class Program
    {
        public static async Task Main(string[] args)
        {
            var builder = WebAssemblyHostBuilder.CreateDefault(args);
            // ... 

            // builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
            builder.Services.AddScoped(sp => new HttpClient
            {
                BaseAddress = new Uri(builder.Configuration.GetValue<string>("BaseAPIUrl"))
            });

            // ... 
        }
    }
}

تکمیل کامپوننت دریافت لیست تمام اتاق‌ها

در قسمت قبل، کامپوننت خالی HotelRooms.razor را تعریف کردیم. کاربران پس از کلیک بر روی دکمه‌ی Go صفحه‌ی اول، به این کامپوننت هدایت می‌شوند. اکنون می‌خواهیم، لیست تمام اتاق‌ها را در این کامپوننت، از Web API برنامه دریافت کنیم:
@page "/hotel/rooms"

@inject ILocalStorageService LocalStorage
@inject IJSRuntime JsRuntime
@inject IClientHotelRoomService HotelRoomService

<h3>HotelRooms</h3>

@code {
    HomeVM HomeModel = new HomeVM();
    IEnumerable<HotelRoomDTO> Rooms = new List<HotelRoomDTO>();

    protected override async Task OnInitializedAsync()
    {
        try
        {
            var model = await LocalStorage.GetItemAsync<HomeVM>(ConstantKeys.LocalInitialBooking);
            if (model is not null)
            {
                HomeModel = model;
            }
            else
            {
                HomeModel.NoOfNights = 1;
            }
            await LoadRooms();
        }
        catch (Exception e)
        {
            await JsRuntime.ToastrError(e.Message);
        }
    }

    private async Task LoadRooms()
    {
        Rooms = await HotelRoomService.GetHotelRoomsAsync(HomeModel.StartDate, HomeModel.EndDate);
    }
}
در اینجا در ابتدا سعی می‌شود تا HomeModel، از Local Storage که در قسمت قبل آن‌را تنظیم کردیم، خوانده شود. سپس با استفاده از متد GetHotelRoomsAsync، لیست اتاق‌ها را از Web API دریافت می‌کنیم. تمام این عملیات آغازین نیز باید در روال رویدادگران OnInitializedAsync صورت گیرند.


روش اجرای پروژه‌های Blazor WASM

تا اینجا اگر برنامه‌ی سمت کلاینت را توسط دستور dotnet watch run اجرا کنیم، هرچند صفحه‌ی خالی نمایش لیست اتاق‌ها ظاهر می‌شود، اما یک خطای fetch error را هم دریافت خواهیم کرد؛ چون نیاز است ابتدا پروژه‌ی Web API را اجرا کرد و سپس پروژه‌ی WASM را.
برای ساده سازی اجرای همزمان این دو پروژه، اگر از ویژوال استودیوی کامل استفاده می‌کنید، بر روی نام Solution کلیک راست کرده و از منوی ظاهر شده، گزینه‌ی «Set Startup projects» را انتخاب کنید. در صفحه دیالوگ ظاهر شده، گزینه‌ی «multiple startup projects» را انتخاب کرده و از لیست پروژه‌های موجود، دو پروژه‌ی Web API و WASM را انتخاب کنید و Action مقابل آن‌ها را به Start تنظیم کنید. در اینجا حتی می‌توان ترتیب اجرای این پروژه‌ها را هم تغییر داد. در این حالت زمانیکه بر روی دکمه‌ی Run، در ویژوال استودیو کلیک می‌کنید، هر دو پروژه را با هم برای شما اجرا خواهد کرد.

نکته‌ی مهم! در این حالت هم برنامه‌ی کلاینت نمی‌تواند با برنامه‌ی Web API ارتباط برقرار کند! چون شماره پورت iisExpress درج شده‌ی در فایل appsettings.json آن، باید به شماره sslPort مندرج در فایل Properties\launchSettings.json پروژه‌ی Web API تغییر داده شود که برای نمونه در اینجا این عدد 44314 است:
{
  "iisSettings": {
    "iisExpress": {
      "applicationUrl": "http://localhost:62930",
      "sslPort": 44314
    }
  }
}
و یا اگر می‌خواهید پروژه را از طریق NET Core CLI. با اجرای دستور dotnet watch run اجرا کنید ... به صورت پیش‌فرض نمی‌شود! چون برای اینکار باید به پوشه‌ی ریشه‌ی پروژه‌های Web API و WASM وارد شد و دوبار دستور یاد شده را به صورت مجزا اجرا کرد. در این حالت، هر دو پروژه، بر روی پورت پیش‌فرض 5001 اجرا می‌شوند. روش تغییر این پورت، مراجعه به فایل Properties\launchSettings.json این پروژه‌ها است. برای مثال همان پورت پیش‌فرض 5001 را که در فایل appsettings.json انتخاب کردیم، ثابت نگه می‌داریم. یعنی فایل launchSettings.json پروژه‌ی Web API را ویرایش نمی‌کنیم. اما این پورت را در پروژه‌ی کلاینت برای مثال به عدد 5002 تغییر می‌دهیم تا برنامه‌ی کلاینت، بر روی پورت پیش‌فرض برنامه‌ی Web API اجرا نشود:
{
    "BlazorWasm.Client": {
      "applicationUrl": "https://localhost:5002;http://localhost:5003",
    }  
}


کدهای کامل این مطلب را از اینجا می‌توانید دریافت کنید: Blazor-5x-Part-27.zip
مطالب
اعتبارسنجی در Angular 2 توسط JWT
در مقالاتی که در سایت منتشر شده‌است، آشنایی و همچنین نحوه پیاده سازی Json Web Token را فرا گرفتیم. در اینجا میخواهیم با استفاده از توکن تولید شده، برنامه‌های Angular2 یا هر نوع فریمورک spa را با آن ارتباط دهیم. در سایت جاری قبلا در مورد نحوه پیاده سازی آن صحبت شده‌است و میخواهیم از آن در یک پروژه Angular 2 صحبت کنیم.
پروژه دات نت را از طریق این آدرس دریافت کرده  و آن را در حالت اجرا بگذارید.

سپس یک سرویس جدید را در پروژه Angular خود اجرا کنید و متد زیر را به آن اضافه کنید:
login():any{
    let urlSearchParams = new URLSearchParams();
    urlSearchParams.append('username', 'Vahid');
    urlSearchParams.append('password', '1234');
    urlSearchParams.append('grant_type', 'password');
    let body = urlSearchParams.toString();

    let headers = new Headers();
    headers.append('Content-Type', 'application/x-www-form-urlencoded');
    return this._http.post('http://localhost:9577/login', body, { headers: headers })
        .map(res => res.json());
}
در متد بالا ابتدا از کلاس  URLSearchParams  یک شیء میسازیم. این کلاس در ماژول Http قرار دارد و وظیفه آن تبدیل پارامترهای داده شده به شکل زیر میباشد:
username=vahid&password=1234
دلیل اینکه ما در اینجا همانند jquery از JSON.stringify استفاده نکردیم این است که در حالتیکه خصوصیت Content-Type هدر را بر روی application/x-wwww-form-urlencoded قرار میدهیم، شیء ایجاد شده از کلاس Http در اینجا، کل عبارت را تبدیل به کلید بدون مقدار میکند و باعث میشود که پارامترها به درستی به سمت Owin هدایت نشوند. بعد از آن هدری که ذکر شد را در درخواست قرار داده و آن را ارسال میکنیم.
از آنجاکه پروژه انگیولار ساخته شده در آدرسی دیگر و جدا از پروژه‌ی دات نت قرار دارد و همینطور که می‌بینید آدرس کامل آن را به این دلیل قرار دادم، ممکن است خطای CORS را دریافت کنید که میتوانید آن را با نصب یک بسته نیوگتی حل کنید.
 
همچنین برای تست و انجام یک عمل مرتبط با توکن، متد زیر را هم به آن اضافه می‌کنیم:
ApiAdmin(token:any):any{
    let headers = new Headers();
    headers.append('Authorization', 'bearer ' + token);
    return this._http.get('http://localhost:9577/api/MyProtectedApi', { headers: headers })
        .map(res => res.json());
}
در اینجا با استفاده از روش Http Bearer که در اعتبارسنجی در سطح OAuth کاربرد زیادی دارد، توکن تولید شده را که در پارامتر ورودی متد دریافت کرده‌ایم، به هدر اضافه کرده و آن را ارسال میکنیم.

کد کل سرویس،  الان به شکل زیر شده است:
import { Http, Headers, URLSearchParams } from '@angular/http';
import { Injectable } from '@angular/core';
import { Observable } from "rxjs/Observable";
import "rxjs/Rx";

@Injectable()
export class MemberShipService {
    constructor(private _http: Http) { }

    ApiAdmin(token: any): any {
        let headers = new Headers();
        headers.append('Authorization', 'bearer ' + token);
        return this._http.get('http://localhost:9577/api/MyProtectedApi', { headers: headers })
            .map(res => res.json());
    }

    login(): any {
        let urlSearchParams = new URLSearchParams();
        urlSearchParams.append('username', 'Vahid');
        urlSearchParams.append('password', '1234');
        urlSearchParams.append('grant_type', 'password');
        let body = urlSearchParams.toString();

        let headers = new Headers();
        headers.append('Content-Type', 'application/x-www-form-urlencoded');
        return this._http.post('http://localhost:9577/login', body, { headers: headers })
            .map(res => res.json());
    }
}
سپس سرویس ساخته شده را در ngModule معرفی میکنیم. در کامپوننت هدف دو دکمه را قرار میدهیم؛ یکی برای لاگین و دیگری برای دریافت اطلاعاتی که نیاز به اعتبار سنجی دارد. رویداد کلیک دکمه‌ها را به متدهای زیر متصل میکنیم:
Login(){
    this._service.login()
        .subscribe(res => {
            localStorage.setItem('access_token', res.access_token);
            localStorage.setItem('refresh_token', res.refresh_token);
        }
        , error => console.log(error));
}
در اینجا ما اطلاعات لاگین را به سمت سرور فرستاده و در صورتیکه پاسخ صحیحی را دریافت کردیم، متد Subscribe اجرا خواهد شد و مقادیر دریافتی را باید به نحوی در سیستم و بین کامپوننت‌های مختلف، ذخیره و نگهداری کنیم. در اینجا من از روش Local Storage که در سایت جاری قبلا به آن پرداخته شده‌است، استفاده میکنم. access_token و refresh_token جاری و اطلاعات دیگری را چون رول‌ها و ... هر یک را با یک کلید ذخیره میکنیم تا بعدا به آن دسترسی داشته باشیم.
در متد بعدی که قرار است توسط آن صحت اعتبارسنجی مورد آزامایش قرار بگیرد، کدهای زیر را مینویسیم:
CallApi()
{
    let item = localStorage.getItem("access_token");
    if (item == null) {
        alert('please Login First.');
        return;
    }
    this._service.ApiAdmin(item)
        .subscribe(res => {
            alert(res);
        }
        , error => console.log(error));
}
در اینجا ابتدا توکن ذخیره شده را از Local Storage درخواست میکنیم. اگر نتیجه این درخواست نال باشد، به این معنی است که کاربر قبلا لاگین نکرده است؛ در غیر این صورت درخواست را با توکن دریافتی میفرستیم و پاسخ موفق آن را در یک alert می‌بینیم. در صورتیکه توکن ما اعتبار نداشته باشد، خطای بازگشتی در کنسول نمایش خواهد یافت.


اعتبارسنجی در مسیریابی


یکی از روش‌هایی که انگیولار باید بررسی کند این است که کاربر جاری با توجه به نقش‌هایی که ممکن است داشته باشد، یا اعتبار سنجی شده است یا خیر و میزان دسترسی او به کامپوننت‌ها، باید مشخص گردد. برای این مورد خصوصیتی به مسیریابی اضافه شده است به نام CanActivate که از شما یک کلاس را که در آن اینترفیس CanActivate پیاده سازی شده است، درخواست میکند. در اینجا ما یک Guard را با نام LoginGuard ایجاد میکنیم، تا تنها کاربرانی که لاگین کرده‌اند، به این صفحه دسترسی داشته باشند:
import { CanActivate } from '@angular/router';
import { Injectable } from '@angular/core';

@Injectable()
export class LoginGuard implements CanActivate {
    constructor() { }

    canActivate() {
        let item = localStorage.getItem('access_token');
        if (item == null)
            return false;
        return true;
    }
}
در متد Activate باید خروجی boolean بازگردد. در صورتیکه true بازگشت داده شود، عملیات هدایت به کامپوننت مقصد با موفقیت انجام خواهد شد. در صورتیکه خلاف این موضوع اتفاق بیفتد، کامپوننت هدف نمایش داده نمیشود. در اینجا بررسی کرده‌ایم که اگر توکن مورد نظر در localStorage  قرار داشت، به معنی این است که لاگین شده‌است. ولی این موضوع روشن است که در یک مثال واقعی باید صحت این توکن بررسی شود. این Guard در واقع یک سرویس است که باید در فایل ngModule معرفی شده و آن را در فایل مسیریابی به شکل زیر استفاده کنیم:
 { path: 'component-one/:id', component: Component1Component,canActivate:[LoginGuard]}
در معرفی یک مسیر به فایل مسیریابی، خصوصیاتی چون مسیری که نوشته میشود و کامپوننت مربوط به آن مسیر ذکر می‌شود. خصوصیت دیگر، CanActivate است که به آن کلاس LoginGuard را معرفی مکنیم. در اینجا شما میتواند به هر تعداد گارد را که دوست دارید، معرفی کنید ولی به یاد داشته باشید که اگر یکی از آن‌ها در اعتبارسنجی ناموفق باشد، دیگر کامپوننت جاری در دسترس نخواهد بود. به معنای دیگر تمامی گاردها باید نتیجه‌ی true را بازگردانند تا دسترسی به کامپوننت هدف ممکن شود.
 { path: 'component-one/:id', component: Component1Component,canActivate:[LoginGuard,SecondGuard]}

در یک گارد ممکن است کاربر لاگین نکرده باشد و شما نیاز دارید او را به صفحه لاگین هدایت کنید. در این صورت گارد لاگین را به شکل زیر بازنویسی میکنیم:
import { Router } from '@angular/router';
import { CanActivate } from '@angular/router';
import { Injectable } from '@angular/core';

@Injectable()
export class LoginGuard implements CanActivate {

    constructor(public router: Router) { }

    canActivate() {
        let item = localStorage.getItem('access_token');
        if (item == null) {
            this.router.navigate(['/app']);
            return false;
        }
        return true;
    }
}
در اینجا ما از سازنده کلاس، شیءایی از نوع Router را ساختیم که امکانات و متدهای خوبی را در باب مسیریابی به همراه دارد و از آن برای انتقال به مسیری دیگر که میتواند صفحه لاگین باشد، استفاده کردیم.

همچنین گارد میتواند اطلاعات مسیر درخواست شده را خوانده و بر اساس آن به شما پاسخ بدهد. به عنوان مثال پارامترهایی را که به سمت مسیر مورد نظر هدایت میشود، بخواند و بر اساس آن، تصمیم گیری کند. به عنوان نمونه کاربر به مسیر ذکرشده دسترسی دارد، ولی با Id که در مسیر ارسال کرده است، دسترسی ندارد:
import { Router } from '@angular/router';
import { CanActivate, ActivatedRouteSnapshot } from '@angular/router';
import { Injectable } from '@angular/core';

@Injectable()
export class SecondGuard implements CanActivate {

    constructor(public router: Router) { }

    canActivate(route: ActivatedRouteSnapshot) {
        let id = route.params['id'];
        if (id == 1) {
            return false;
        }
        return true;
    }
}

متد CanActivate میتواند پارامترهای زیادی را به عنوان ورودی دریافت کند که یکی از آن‌ها ActivatedRouteSnapshot است که اطلاعات خوب و مفیدی را در مورد مسیر ارسال شده از طرف کاربر دارد و با استفاده از آن در اینجا میتوانیم پارامترهای ارسالی را دریافت کنیم. در اینجا ذکر کرده‌ایم که اگر پارامتر Id که بر مبنای مسیر زیر است، برابر 1 بود، مقدار برگشتی برابر false خواهد بود و دسترسی به کامپوننت در اینجا ممکن نخواهد بود.
 { path: 'component-one/:id', component: Component1Component,canActivate:[LoginGuard,SecondGuard] }
مطالب
تزریق وابستگی (dependency injection) به زبان ساده

این مطلب در ادامه‌ی "آشنایی با الگوی IOC یا Inversion of Control (واگذاری مسئولیت)" می‌باشد که هر از چندگاهی یک قسمت جدید و یا کاملتر از آن ارائه خواهد شد.

==============
به صورت خلاصه ترزیق وابستگی و یا dependency injection ، الگویی است جهت تزریق وابستگی‌های خارجی یک کلاس به آن، بجای استفاده مستقیم از آن‌ها در درون کلاس.
برای مثال شخصی را در نظر بگیرید که قصد خرید دارد. این شخص می‌تواند به سادگی با کمک یک خودرو خود را به اولین محل خرید مورد نظر برساند. حال تصور کنید که 7 نفر عضو یک گروه، با هم قصد خرید دارند. خوشبختانه چون تمام خودروها یک اینترفیس مشخصی داشته و کار کردن با آن‌ها تقریبا شبیه به یکدیگر است، حتی اگر از یک ون هم جهت رسیدن به مقصد استفاده شود، امکان استفاده و راندن آن همانند سایر خودروها می‌باشد و این دقیقا همان مطلبی است که هدف غایی الگوی تزریق وابستگی‌ها است. بجای این‌که همیشه محدود به یک خودرو برای استفاده باشیم، بنابر شرایط، خودروی متناسبی را نیز می‌توان مورد استفاده قرار داد.
در دنیای نرم افزار، وابستگی کلاس Driver ، کلاس Car است. اگر موارد ذکر شده را بدون استفاده از تزریق وابستگی‌ها پیاده سازی کنیم به کلاس‌های زیر خواهیم رسید:

//Person.cs
namespace DependencyInjectionForDummies
{
class Person
{
public string Name { get; set; }
}
}

//Car.cs
using System;
using System.Collections.Generic;

namespace DependencyInjectionForDummies
{
class Car
{
List<Person> _passengers = new List<Person>();

public void AddPassenger(Person p)
{
_passengers.Add(p);
Console.WriteLine("{0} added!", p.Name);
}

public void Drive()
{
foreach (var passenger in _passengers)
Console.WriteLine("Driving {0} ...!", passenger.Name);
}
}
}

//Driver.cs
using System.Collections.Generic;

namespace DependencyInjectionForDummies
{
class Driver
{
private Car _myCar = new Car();

public void DriveToMarket(IList<Person> passengers)
{
foreach (var passenger in passengers)
_myCar.AddPassenger(passenger);

_myCar.Drive();
}
}
}

//Program.cs
using System.Collections.Generic;
using System;

namespace DependencyInjectionForDummies
{
class Program
{
static void Main(string[] args)
{
new Driver().DriveToMarket(
new List<Person>
{
new Person{ Name="Ali" },
new Person{ Name="Vahid" }
});

Console.WriteLine("Press a key ...");
Console.ReadKey();
}
}
}

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

خوب! اکنون اگر این کلاس‌ها را بر اساس الگوی تزریق وابستگی‌ها (روش تزریق در سازنده که در قسمت قبل بحث شد) بازنویسی کنیم به کلاس‌های زیر خواهیم رسید:

//ICar.cs
using System;

namespace DependencyInjectionForDummies
{
interface ICar
{
void AddPassenger(Person p);
void Drive();
}
}

//Car.cs
using System;
using System.Collections.Generic;

namespace DependencyInjectionForDummies
{
class Car : ICar
{
//همانند قسمت قبل
}
}

//Van.cs
using System;
using System.Collections.Generic;

namespace DependencyInjectionForDummies
{
class Van : ICar
{
List<Person> _passengers = new List<Person>();

public void AddPassenger(Person p)
{
_passengers.Add(p);
Console.WriteLine("{0} added!", p.Name);
}

public void Drive()
{
foreach (var passenger in _passengers)
Console.WriteLine("Driving {0} ...!", passenger.Name);
}
}
}

//Driver.cs
using System.Collections.Generic;

namespace DependencyInjectionForDummies
{
class Driver
{
private ICar _myCar;

public Driver(ICar myCar)
{
_myCar = myCar;
}

public void DriveToMarket(IList<Person> passengers)
{
foreach (var passenger in passengers)
_myCar.AddPassenger(passenger);

_myCar.Drive();
}
}
}

//Program.cs
using System.Collections.Generic;
using System;

namespace DependencyInjectionForDummies
{
class Program
{
static void Main(string[] args)
{
Driver driver = new Driver(new Van());
driver.DriveToMarket(
new List<Person>
{
new Person{ Name="Ali" },
new Person{ Name="Vahid" }
});

Console.WriteLine("Press a key ...");
Console.ReadKey();
}
}
}

توضیحات:
در اینجا یک اینترفیس جدید به نام ICar اضافه شده است و بر اساس آن می‌توان خودروهای مختلفی را با نحوه‌ی بکارگیری یکسان اما با جزئیات پیاده سازی متفاوت تعریف کرد. برای مثال در ادامه، یک کلاس ون با پیاده سازی این اینترفیس تشکیل شده است. سپس کلاس راننده‌ی ما بر اساس ترزیق این اینترفیس در سازنده‌ی آن بازنویسی شده است. اکنون این کلاس دیگر نمی‌داند که دقیقا چه خودرویی را باید مورد استفاده قرار دهد و از وابستگی مستقیم به نوعی خاص از آن‌ها رها شده است؛ اما می‌داند که تمام خودروها، اینترفیس مشخص و یکسانی دارند. به تمام آن‌ها می‌توان مسافرانی را افزود و سپس به مقصد رساند. در پایان نیز یک راننده جدید بر اساس خودروی ون تعریف شده، سپس یک سری مسافر نیز تعریف گردیده و نهایتا متد DriveToMarket فراخوانی شده است.
به این صورت به یک سری کلاس اصطلاحا loosely coupled رسیده‌ایم. دیگر راننده‌ی ما وابسته‌ی به یک خودروی خاص نیست و هر زمانی که لازم بود می‌توان خودروی مورد استفاده‌ی او را تغییر داد بدون اینکه کلاس راننده را بازنویسی کنیم.
یکی دیگر از مزایای تزریق وابستگی‌ها ساده سازی unit testing کلاس‌های برنامه توسط mocking frameworks است. به این صورت توسط این نوع فریم‌ورک‌ها می‌توان رفتار یک خودرو را تقلید کرد بجای اینکه واقعا با تمام ریز جرئیات آن‌ها بخواهیم سروکار داشته باشیم (وابستگی‌ها را به صورت مستقل می‌توان آزمایش کرد).

مطالب
PowerShell 7.x - قسمت پنجم - اسکریپت بلاک و توابع
همانطور که در قسمت قبل اشاره شد، توابع نیز یکی از ویژگی‌های اصلی PowerShell هستند. قبل از بررسی بیشتر توابع بهتر است ابتدا با مفهوم script block آشنا شویم. script blocks به مجموعه‌ایی از دستورات گفته میشود که داخل یک بلاک قرار میگیرند. در واقع هر چیزی داخل {} یک script block محسوب میشود (البته به جز hash tables). به عنوان مثال در کد زیر از یک script block مخصوص، با نام فیلتر استفاده شده است که یک ورودی برای پارامتر FilterScript مربوط به دستور Where-Object میباشد. چیزی که این script block را متمایز میکند، خروجی آن است. به این معنا که خروجی آن باید یک مقدار بولین باشد: 
Get-Process | Where-Object { $_.Name -eq 'Dropbox' }
script blocks را به صورت مستقیم درون command line هم میتوانیم استفاده کنیم. به محض تایپ کردن } و زدن کلید enter، امکان نوشتن اسکریپت‌های چندخطی را درون ترمینال خواهیم داشت. در نهایت با بستن script block و زدن کلید enter، از بلاک خارج خواهیم شد: 
PS /Users/sirwanafifi/Desktop> $block = {
>> $newVar = 10
>> Write-Host $newVar
>> }
با اینکار یک بلاک از کد را داخل متغیری با اسم block ذخیره کرده‌ایم. برای فراخوانی این قطعه کد میتوانیم از یک عملگر مخصوص با نام invocation operator یا call operator استفاده کنیم: 
PS /Users/sirwanafifi/Desktop> & $block
یا حتی میتوانیم از Invoke-Command نیز برای اجرای بلاک استفاده کنیم. همچنین از عملگر & برای فراخوانی یک expression رشته‌ایی نیز میتوان استفاده کرد: 
PS /Users/sirwanafifi/Desktop> & "Get-Process"
البته این نکته را در نظر داشته باشید که & قادر به پارز کردن (parse) یک expression نیست. به عنوان مثال اجرای کد زیر با خطا مواجه خواهد شد (برای حل این مشکل میتوانید بجای آن از Invoke-Expression استفاده کنید که امکان پارز کردن پارامترها را نیز دارد):
PS /Users/sirwanafifi/Desktop> & "1 + 1"
or
PS /Users/sirwanafifi/Desktop> & "Get-Process -Name Slack"

توابع
در قسمت قبل با نحوه ایجاد توابع آشنا شدیم. به این نوع توابع، basic functions گفته میشود و ساده‌ترین نوع توابع در PowerShell هستند. همچنین خیلی محدود نیز میباشند؛ یکسری ورودی/خروجی دارند. برای کنترل بیشتر روی نحوه فراخوانی توابع (به عنوان مثال دریافت ورودی از pipeline و…) باید از advanced functions یا توابع پیشرفته استفاده کنیم. در واقع به محض استفاده از اتریبیوتی با نام [()CmdletBinding] تابع ما تبدیل به یک advanced function خواهد شد. منظور از دریافت ورودی از pipeline این است که بتوانیم خروجی دستورات را به تابع‌مان pipe کنیم اینکار در basic function امکانپذیر نیست: 
Function Add-Something {
    Write-Host "$_ World"
}

"Hello" | Add-Something
اما با کمک advanced functions میتوانیم چنین قابلیتی را داشته باشیم: 
Function Add-Something {
    [CmdletBinding()]
    Param(
        [Parameter(ValueFromPipeline = $true)]
        [string]$Name
    )

    Write-Host "$Name World"
}

"Hello" | Add-Something
یکی دیگر از ویژگی‌های advanced functions امکان استفاده فلگ Verbose حین فراخوانی دستورات میباشد. به عنوان مثال قطعه کد زیر را در نظر بگیرید: 
$API_KEY = "...."

Function Read-WeatherData {
    [CmdletBinding()]
    Param(
        [Parameter(ValueFromPipeline = $true)]
        [string]$CityName
    )

    $Url = "https://api.openweathermap.org/data/2.5/forecast?q=$CityName&cnt=40&appid=$API_KEY&units=metric"
    Try {
        Write-Verbose "Reading weather data for $CityName"
        $Response = Invoke-RestMethod -Uri $Url
        $Response.list | ForEach-Object {
            Write-Verbose "Processing $($_.dt_txt)"
            [PSCustomObject]@{
                City               = $Response.city.name
                DateTime           = [DateTime]::Parse($_.dt_txt)
                Temperature        = $_.main.temp
                Humidity           = $_.main.humidity
                Pressure           = $_.main.pressure
                WindSpeed          = $_.wind.speed
                WindDirection      = $_.wind.deg
                Cloudiness         = $_.clouds.all
                Weather            = $_.weather.main
                WeatherDescription = $_.weather.description
            }
        } | Where-Object { $_.DateTime.Date -eq (Get-Date).Date }
        Write-Verbose "Done processing $CityName"
    }
    Catch {
        Write-Error $_.Exception.Message
    }
}
کاری که تابع فوق انجام میدهد، دریافت دیتای پیش‌بینی وضعیت آب‌وهوای یک شهر است. در حالت عادی فراخوانی تابع فوق پیام‌های Verbose را نمایش نمیدهد. از آنجائیکه تابع فوق یک advanced function است، میتوانیم فلگ Verbose را نیز وارد کنیم. با اینکار به صورت صریح گفته‌ایم که پیام‌های از نوع Verbose را نیز نمایش دهد: 
Read-WeatherData -CityName "London" -Verbose
هر چند این مقدار را همانطور که در قسمت‌های قبلی عنوان شد میتوانیم تغییر دهیم که دیگر مجبور نباشیم با فراخوانی هر تابع، این فلگ را نیز ارسال کنیم. بیشتر دستورات native نیز قابلیت نمایش پیام‌های Verbose را با ارسال همین فلگ در اختیارمان قرار میدهند. بنابراین بهتر است برای امکان مشاهده جزئیات بیشتر حین فراخوانی توابع‌مان از Write-Verbose استفاده کنیم. در ادامه اجزای دیگر توابع را بررسی خواهیم کرد (بیشتر این اجزا درون یک script block نیز قابل استفاده هستند)

کنترل کامل بر روی ورودی‌های توابع
بر روی ورودی‌های یک تابع میتوانیم کنترل نسبتاً کاملی داشتیم باشیم. PowerShell یک مجموعه وسیع از قابلیت‌ها را برای هندل کردن پارامترها و همچنین اعتبارسنجی ورودی‌ها ارائه میدهد. به عنوان مثال میتوانیم یک پارامتر را mandatory کنیم یا اینکه امکان positional binding و غیره را تعیین کنیم. اتریبیوت Parameter در واقع یک وهله از System.Management.Automation.ParameterAttribute میباشد. میتوانید با نوشتن دستور زیر لیستی از خواصی را که میتوانید همراه با این اتریبیوت تعیین کنید، مشاهده کنید: 
PS /> [Parameter]::new()

ExperimentName                  :
ExperimentAction                : None
Position                        : -2147483648
ParameterSetName                : __AllParameterSets
Mandatory                       : False
ValueFromPipeline               : False
ValueFromPipelineByPropertyName : False
ValueFromRemainingArguments     : False
HelpMessage                     :
HelpMessageBaseName             :
HelpMessageResourceId           :
DontShow                        : False
TypeId                          : System.Management.Automation.ParameterAttribute
در ادامه یک مثال از نحوه هندل کردن ورودی‌های یک تابع را بررسی خواهیم کرد. تابع زیر یک لیست از URLها را از کاربر دریافت کرده و یک health check توسط دستور Test-Connection انجام میدهد. در کد زیر پارامتر Websites را با تعدادی اتریبیوت مزین کرده‌ایم. توسط اتریبیوت Parameter تعیین کرده‌ایم که ورودی الزامی است و همچنین مقدار آن میتواند از pipeline نیز دریافت شود. در ادامه توسط ValidatePattern یک عبارت باقاعده را برای بررسی صحیح بودن URL دریافتی نوشته‌ایم. از آنجائیکه ورودی از نوع آرایه‌ایی از string تعریف شده است، این تست برای هر آیتم از آرایه بررسی خواهد شد. برای پارامتر دوم یعنی Count نیز رنج مقداری را که کاربر وارد میکند، حداقل ۳ و حداکثر ۳ انتخاب کرده‌ایم: 
Function Ping-Website {
    [CmdletBinding()]
    Param(
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [ValidatePattern('^www\..*')]
        [string[]]$Websites,
        [ValidateRange(1, 3)]
        [int]$Count = 3
    )
    $Results = @()
    $Websites | ForEach-Object {
        $Website = $_
        $Result = Test-Connection -ComputerName $Website -Count $Count -Quiet
        $ResultText = $Result ? 'Success' : 'Failed'
        $Results += @{
            Website = $Website
            Result  = $ResultText
        }
        Write-Verbose "The result of pinging $Website is $ResultText"
    }
    $Results | ForEach-Object { 
        $_ | Select-Object @{ Name = "Website"; Expression = { $_.Website }; }, @{ Name = "Result"; Expression = { $_.Result }; }, @{ Name = "Number Of Attempts"; Expression = { $Count }; } 
    }
}
یکی دیگر از اعتبارسنجی‌هایی که میتوانیم برای پارامترهای یک تابع انتخاب کنیم، ValidateScript است. توسط این اتریبیوت میتوانیم یک منطق سفارشی برای اعتبارسنجی مقادیر پارامترها بنویسیم. به عنوان مثال تابع فوق را به گونه‌ایی تغییر خواهیم داد که لیست وب‌سایت‌ها را از طریق یک فایل JSON دریافت کند. میخواهیم قبل از دریافت فایل مطمئن شویم که فایل، به صورت فیزیکی روی دیسک وجود دارد، در غیراینصورت باید یک خطا را به کاربر نمایش دهیم: 
Function Ping-Website {
    [CmdletBinding()]
    Param(
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [ValidateScript({
                If (-Not ($_ | Test-Path) ) {
                    Throw "File or folder does not exist" 
                }
                If (-Not ($_ | Test-Path -PathType Leaf) ) {
                    Throw "The Path argument must be a file. Folder paths are not allowed."
                }
                If ($_ -NotMatch "(\.json)$") {
                    throw "The file specified in the path argument must be either of type json"
                }
                Return $true
            })]
        [Alias("src", "source", "file")]
        [System.IO.FileInfo]$Path,
        [int]$Count = 1
    )
    $Results = [System.Collections.ArrayList]@()
    $Urls = Get-Content -Path $Path | ConvertFrom-Json
    $Urls | ForEach-Object -Parallel {
        $Website = $_.url
        $Result = Test-Connection -ComputerName $Website -Count $using:Count -Quiet
        $ResultText = $Result ? 'Success' : 'Failed'
        $Item = @{
            Website = $Website
            Result  = $ResultText
        }
        $null = ($using:Results).Add($Item)
    }
    
    $Results | ForEach-Object -Parallel { 
        $_ | Select-Object @{ Name = "Website"; Expression = { $_.Website }; }, @{ Name = "Result"; Expression = { $_.Result }; }, @{ Name = "Number Of Attempts"; Expression = { $using:Count }; } 
    }
}
تابع Ping-Website را جهت بررسی فیچر جدیدی که همراه با دستور ForEach-Object استفاده میشود، تغییر داده‌ایم تا به صورت Parallel عمل کند؛ این قابلیت از نسخه ۷ به بعد به PowerShell اضافه شده است. از آنجائیکه این قابلیت باعث میشود script block مربوط به ForEach-Object درون یک context دیگر با نام runspace اجرا شود. در نتیجه برای دسترسی به متغیرهای بیرون از script block نیاز خواهیم داشت از یک متغیر خودکار تحت‌عنوان using قبل از نام متغیر و بعد از علامت $ استفاده کنیم. همچنین آرایه مثال قبل را نیز به ArrayList تغییر داده‌ایم. زیرا در حالت قبلی امکان تغییر سایز یک آرایه با سایز ثابت را نخواهیم داشت. نکته دیگری که در مورد کد فوق میتوان به آن توجه کرد، نال کردن خروجی متد Add مربوط به آرایه‌ی Results است. همانطور که در قسمت قبل توضیح دادیم، از این تکنیک برای suppress کردن خروجی استفاده میکنیم و چون در اینجا خروجی متد Add یک عدد میباشد، با تکنیک فوق، خروجی را دیگر درون کنسول مشاهده نخواهیم کرد. توسط اتریبیوت Alias نیز نام‌های دیگری را که میتوان برای پارامتر Path حین فراخوانی تابع استفاده کرد، تعیین کرده‌ایم. لیست کامل اتریبیوت‌هایی را که میتوان برای پارامترهای یک تابع تعیین کرد، میتوانید در مستندات PowerShell ببینید. 
نکته: اگر تابع فوق را همراه با فلگ Verbose فراخوانی کنیم، لاگ‌های موردنظر را درون کنسول مشاهده نخواهیم کرد؛ زیرا همانطور که اشاره شد script block درون یک context جدا اجرا میشود و باید متغیرهای خودکار مربوط به Output را مجدداً مقداردهی کنیم:
Function Ping-Website {
    [CmdletBinding()]
    Param(
        # As before
    )
    # As before
    $Urls | ForEach-Object -Parallel {
        $DebugPreference = $using:DebugPreference 
        $VerbosePreference = $using:VerbosePreference 
        $InformationPreference = $using:InformationPreference 
        
        # As before
    }
    
    # As before
}

قابلیت تعریف بلاک‌ها/توابع، به صورت تودرتو  
درون توابع و script block امکان نوشتن بلاک‌های تودرتو را نیز داریم:
$scriptBlock = {
    $logOutput = {
        param($message)
        Write-Host $message
    }

    [int]$someVariable = 10
    $doSomeWork = {
        & $logOutput -message "Some variable value: $someVariable"
    }
    $someVariable = 20

    & $doSomeWork
}
خروجی بلاک فوق  Some variable value: 20 خواهد بود؛ زیرا قبل از فراخوانی doSomeWork مقدار متغیر عددی someVariable را به ۲۰ تغییر داده‌ایم. برای script blocks این امکان را داریم که دقیقاً در همان جایی که بلاک را تعریف میکنیم، یک snapshot تهیه کنیم. در اینحالت خروجی، مقدار Some variable value: 10 خواهد شد: 
$scriptBlock = {
    $logOutput = {
        param($message)
        Write-Host $message
    }

    [int]$someVariable = 10
    $doSomeWork = {
        & $logOutput -message "Some variable value: $someVariable"
    }.GetNewClosure()
    $someVariable = 20

    & $doSomeWork
}
یکسری بلاک‌های ویژه نیز درون توابع و script blockها میتوانیم بنویسیم که اصطلاحاً به name blocks معروف هستند:
begin
process
end
dynamicparam
درون یک تابع اگر هیچکدام از بلاک‌های فوق استفاده نشود، به صورت پیش‌فرض بدنه تابع، درون بلاک end قرار خواهد گرفت. بلاک begin قبل از شروع pipeline اجرا میشود. process به ازای هر آیتم pipe شده اجرا خواهد شد. end نیز در پایان اجرا میشود. به عنوان مثال تابع زیر را در نظر بگیرید:
function Show-Pipeline {
    begin { 
        Write-Host "Pipeline start" 
    }
    process { 
        Write-Host  "Pipeline process $_" 
    }
    end { 
        Write-Host  "Pipeline end $_" 
    }
}
در ادامه یکسری آیتم را به ورودی این تابع pipe خواهیم کرد:
PS /> 1..2 | Show-Pipeline                                   
Pipeline start 
Pipeline process 1
Pipeline process 2
Pipeline end 2
همانطور که مشاهده میکنید، به ازای هر آیتم pipe شده، یکبار بلاک process اجرا شده است. همچنین برای دسترسی به مقدار آیتم pipe شده نیز از متغیر خودکار _$ استفاده کرده‌ایم (PSItem$ نیز به همین متغیر اشاره دارد).

با توجه به توضیحات named blockهای فوق، اکنون اگر بخواهیم نسخه اول تابع Ping-Website را با pipe کردن یک آرایه فراخوانی کنیم، خروجی که در کنسول نمایش داده خواهد شد، تنها آیتم آخر از آرایه خواهد بود:
PS /> "www.google.com", "www.yahoo.com" | Ping-Website                 

Website       Result  Number Of Attempts
-------       ------  ------------------
www.yahoo.com Success                  3
دلیل آن نیز این است که به صورت صریح کدها را درون بلاک process ننوشته بودیم. همانطور که عنوان شد، در حالت پیش‌فرض، بدنه توابع درون بلاک end قرار خواهند گرفت و تنها یکبار اجرا خواهند شد. بنابراین:
Function Ping-Website {
    [CmdletBinding()]
    Param(
        # As before
    )
    process {
        # As before
    }
}
اینبار اگر تابع را مجدداً فراخوانی کنیم، خروجی مطلوب را نمایش خواهد داد:
PS /> "www.google.com", "www.yahoo.com" | Ping-Website

Website        Result  Number Of Attempts
-------        ------  ------------------
www.google.com Success                  3
www.yahoo.com  Success                  3

بلاک dynamicparam
از این بلاک برای تعریف پارامترهای داینامیک که به صورت on the fly نیاز هست ایجاد شوند، استفاده میشود. برای درک بهتر آن فرض کنید میخواهیم تابعی را بنویسیم که امکان خواندن یک فایل CSV را به ما میدهد. تا اینجای کار توسط Import-CSV به یک خط دستور قابل انجام است. اما فرض کنید میخواهیم به کاربر این امکان را بدهیم که یک ستون موردنظر از فایل را مشاهده کند. همچنین میخواهیم یک اعتبارسنجی هم روی نام ستونی که کاربر قرار است وارد کند نیز داشته باشیم. به عنوان مثال یک فایل CSV با ستون‌های name, lname, age داریم و کاربر میخواهد تنها ستون اول یک name را واکشی کند:
PS /> Read-Csv ./users.csv -Columns name
برای اینکار میتوانیم با کمک dynamic param یک پارامتر را در زمان اجرا ایجاده کرده و مقادیری را که کاربر برای ستون‌ها مجاز است وارد کند، براساس هدر فایل CSV تنظیم کنیم:
using namespace System.Management.Automation
Function Read-Csv {
    Param (
        [Parameter(Mandatory = $true, Position = 0)]
        [string]$Path
    )
    DynamicParam {
        $firstLine = Get-Content $Path | Select-Object -First 1
        [String[]]$headers = $firstLine -split ', '
        $parameters = [RuntimeDefinedParameterDictionary]::new()
        $parameter = [RuntimeDefinedParameter]::new(
            'Columns', [String[]], [Attribute[]]@(
                [Parameter]@{ Mandatory = $false; Position = 1 }
                [ValidateSet]::new($headers)
            )
        )
        $parameters.Add($parameter.Name, $parameter) 
        Return $parameters
    }
    Begin {
        $csvContent = Import-Csv $Path
        If ($PSBoundParameters.ContainsKey('Columns')) {
            $columns = $PSBoundParameters['Columns']
            $csvContent | Select-Object -Property $columns
        }
        Else {
            $csvContent
        }
    }
}
درون کنسول PowerShell هم یک IntelliSense برای مقادیر مجاز نمایش داده خواهد شد: