مطالب
امکان تعریف ساده‌تر کلاس‌های Immutable در C# 9.0 با معرفی نوع جدید record
در مطلب معرفی خواص init-only، با روش معرفی خواص immutable آشنا شدیم. نوع جدیدی که به C# 9.0 به نام record اضافه شده‌است، قسمتی از آن بر اساس همان خواص init-only کار می‌کند. به همین جهت مطالعه‌ی آن مطلب، پیش از ادامه‌ی بحث جاری، ضروری است.


چرا در C# 9.0 تا این اندازه بر روی سادگی ایجاد اشیاء Immutable تمرکز شده‌است؟

به شیءای Immutable گفته می‌شود که پس از وهله سازی ابتدایی آن، وضعیت آن دیگر قابل تغییر نباشد. همچنین به کلاسی Immutable گفته می‌شود که تمام وهله‌های ساخته شده‌ی از آن نیز Immutable باشند. نمونه‌ی یک چنین شیءای را از نگارش 1 دات نت در حال استفاده هستیم: رشته‌ها. رشته‌ها در دات نت غیرقابل تغییر هستند و هرگونه تغییری بر روی آن‌ها، سبب ایجاد یک رشته‌ی جدید (یک شیء جدید) می‌شود. نوع جدید record نیز به همین صورت عمل می‌کند.

مزایای وجود Immutability:

- اشیاء Immutable یا غیرقابل تغییر، thread-safe هستند که در نتیجه، برنامه نویسی همزمان و موازی را بسیار ساده می‌کنند؛ چون چندین thread می‌توانند با شیءای کار کنند که دسترسی به آن، تنها read-only است.
- اشیاء Immutable از اثرات جانبی، مانند تغییرات آن‌ها در متدهای مختلف در امان هستند. می‌توانید آن‌ها را به هر متدی ارسال کنید و مطمئن باشید که پس از پایان کار، این شیء تغییری نکرده‌است.
- کار با اشیاء Immutable، امکان بهینه سازی حافظه را میسر می‌کنند. برای مثال NET runtime.، هش رشته‌های تعریف شده‌ی در برنامه را در پشت صحنه نگهداری می‌کند تا مطمئن شود که تخصیص حافظه‌ی اضافی، برای رشته‌های تکراری صورت نمی‌گیرد. نمونه‌ی دیگر آن نمایش حرف "a" در یک ادیتور یا نمایشگر است. زمانیکه یک شیء Immutable حاوی اطلاعات حرف "a"، ایجاد شود، به سادگی می‌توان این تک وهله را جهت نمایش هزاران حرف "a" مورد استفاده‌ی مجدد قرار داد، بدون اینکه نگران مصرف حافظه‌ی بالای برنامه باشیم.
- کار با اشیاء Immutable به باگ‌های کمتری ختم می‌شود؛ چون همواره امکان تغییر حالت درونی یک شیء، توسط قسمت‌های مختلف برنامه، می‌تواند به باگ‌های ناخواسته‌ای منتهی شوند.
- Hash list‌ها که در جهت بهبود کارآیی برنامه‌ها بسیار مورد استفاده قرار می‌گیرند، بر اساس کلیدهایی Immutable قابل تشکیل هستند.


روش تعریف نوع‌های جدید record

کلاس ساده‌ی زیر را در نظر بگیرید:
public class User
{
   public string Name { set; get; }
}
برای تبدیل آن به یک نوع جدید record فقط کافی است واژه‌ی کلیدی class آن‌را با record جایگزین کنیم (به آن nominal record هم می‌گویند):
public record User
{
   public string Name { set; get; }
}
نحوه‌ی کار با آن و وهله سازی آن نیز دقیقا مانند کلاس‌ها است:
var user = new User();
user.Name = "User 1";
و ... در اینجا امکان انتساب مقداری به خاصیت Name وجود دارد؛ یعنی این خاصیت به صورت پیش‌فرض Immutable نیست.

روش تعریف دومی نیز در اینجا میسر است (به آن positional record هم می‌گویند):
public record User(string Name);
با این‌کار، به صورت خودکار یک record جدید تشکیل می‌شود که به همراه خاصیت Name است؛ چیزی شبیه به record قبلی که تعریف کردیم (به همین جهت نیاز است نام آن‌را شروع شده‌ی با حروف بزرگ درنظر بگیریم). با این تفاوت که این record، اینبار دارای سازنده است و همچنین خاصیت Name آن از نوع init-only است. در این حالت است که کل record به صورت immutable معرفی می‌شود؛ وگرنه روش تعریف یک خاصیت معمولی که از نوع init-only نیست (مانند مثال اول)، سبب بروز Immutability نخواهد شد.

برای کار با رکورد دومی که تعریف کردیم باید سازند‌ه‌ی این record را مقدار دهی کرد:
var user = new User("User 1");
// Error: Init-only property or indexer 'User.Name' can only be assigned
// in an object initializer, or on 'this' or 'base' in an instance constructor
// or an 'init' accessor. [CS9Features]csharp(CS8852)
user.Name = "User 1";
و همانطور که ملاحظه می‌کنید، چون خاصیت Name از نوع init-only است و در سازنده‌ی record تعریف شده مقدار دهی شده‌است، دیگر نمی‌توان آن‌را مقدار دهی مجدد کرد. همچنین در اینجا امکان استفاده‌ی از object initializers مانند new User { Name = "User 1" } نیز وجود ندارد؛ چون به همراه یک سازنده‌ی به صورت خودکار تولید شده‌است که خاصیتی init-only را مقدار دهی کرده‌است.


نوع جدید record چه اطلاعاتی را به صورت خودکار تولید می‌کند؟

روش دوم تعریف recordها اگر در نظر بگیریم:
public record User(string Name);
و در این حالت برنامه را کامپایل کنیم، به کدهای زیر که حاصل از دی‌کامپایل است، می‌رسیم:
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Text;
using CS9Features;

public class User : IEquatable<User>
{
 protected virtual Type EqualityContract
 {
  [System.Runtime.CompilerServices.NullableContext(1)]
  [CompilerGenerated]
  get
  {
   return typeof(User);
  }
 }

 public string Name
 {
  get;
  set/*init*/;
 }

 public User(string Name)
 {
  this.Name = Name;
  base..ctor();
 }

 public override string ToString()
 {
  StringBuilder stringBuilder = new StringBuilder();
  stringBuilder.Append("User");
  stringBuilder.Append(" { ");
  if (PrintMembers(stringBuilder))
  {
   stringBuilder.Append(" ");
  }
  stringBuilder.Append("}");
  return stringBuilder.ToString();
 }

 protected virtual bool PrintMembers(StringBuilder builder)
 {
  builder.Append("Name");
  builder.Append(" = ");
  builder.Append((object?)Name);
  return true;
 }

 [System.Runtime.CompilerServices.NullableContext(2)]
 public static bool operator !=(User? r1, User? r2)
 {
  return !(r1 == r2);
 }

 [System.Runtime.CompilerServices.NullableContext(2)]
 public static bool operator ==(User? r1, User? r2)
 {
  return (object)r1 == r2 || (r1?.Equals(r2) ?? false);
 }

 public override int GetHashCode()
 {
  return EqualityComparer<Type>.Default.GetHashCode(EqualityContract) * -1521134295 + EqualityComparer<string>.Default.GetHashCode(Name);
 }

 public override bool Equals(object? obj)
 {
  return Equals(obj as User);
 }

 public virtual bool Equals(User? other)
 {
  return (object)other != null && EqualityContract == other!.EqualityContract && EqualityComparer<string>.Default.Equals(Name, other!.Name);
 }

 public virtual User <Clone>$()
 {
  return new User(this);
 }

 protected User(User original)
 {
  Name = original.Name;
 }

 public void Deconstruct(out string Name)
 {
  Name = this.Name;
 }
}
این خروجی به صورت خودکار تولید شده‌ی توسط کامپایلر، چنین نکاتی را به همراه دارد:
- record‌ها هنوز هم در اصل همان class‌های استاندارد #C هستند (یعنی در اصل reference type هستند).
- این کلاس به همراه یک سازنده و یک خاصیت init-only است (بر اساس تعاریف ما).
- متد ToString آن بازنویسی شده‌است تا اگر آن‌را بر روی شیء حاصل، فراخوانی کردیم، به صورت خودکار نمایش زیبایی را از محتوای آن ارائه دهد.
- این کلاس از نوع  <IEquatable<User است که امکان مقایسه‌ی اشیاء record را به سادگی میسر می‌کند. برای این منظور متدهای GetHashCode و Equals آن به صورت خودکار بازنویسی و تکمیل شده‌اند (یعنی مقایسه‌ی آن شبیه به value-type است).
- این کلاس امکان clone کردن اطلاعات جاری را مهیا می‌کند.
- همچنین به همراه یک متد Deconstruct هم هست که جهت انتساب خواص تعریف شده‌ی در آن، به یک tuple مفید است.

بنابراین یک رکورد به همراه قابلیت‌هایی است که سال‌ها در زبان #C وجود داشته‌اند و شاید ما به سادگی حاضر به تشکیل و تکمیل آن‌ها نمی‌شدیم؛ اما اکنون کامپایلر زحمت کدنویسی خودکار آن‌ها را متقبل می‌شود!


ساخت یک وهله‌ی جدید از یک record با clone کردن آن

اگر به کدهای حاصل از دی‌کامپایل فوق دقت کنید، یک قسمت جدید clone هم با syntax خاصی در آن ظاهر شده‌است:
public virtual User <Clone>$()
{
  return new User(this);
}
زمانیکه یک شیء Immutable است، دیگر نمی‌توان مقادیر خواص آن‌را در ادامه تغییر داد. اما اگر نیاز به اینکار وجود داشت، باید چکار کنیم؟ در C# 9.0 برای ایجاد وهله‌ی جدید معادلی از یک record، واژه‌ی کلیدی جدیدی را به نام with، اضافه کرده‌اند. برای نمونه اگر record زیر را در نظر بگیریم که دارای دو خاصیت نام و سن است:
public record User(string Name, int Age);
وهله سازی متداول آن به صورت زیر خواهد بود:
var user1 = new User("User 1", 21);
اما اگر خواستیم خاصیت سن آن‌را تغییر دهیم، می‌توان با استفاده از واژه‌ی کلیدی with، به صورت زیر عمل کرد:
var user2 = user1 with { Age = 31 };
کاری که در اصل در اینجا انجام می‌شود، ابتدا clone کردن شیء user1 است (یعنی دقیقا یک وهله‌ی جدید از user1 را با تمام اطلاعات قبلی آن در اختیار ما قرار می‌دهد که این وهله، ارجاعی را به شیء قبلی ندارد و از آن منقطع است). بنابراین نام user2، دقیقا همان "User 1" است که پیشتر تنظیم کردیم؛ با این تفاوت که اینبار مقدار سن آن متفاوت است. با استفاده از cloning، هنوز شیء user1 که immutable است، دست نخورده باقی مانده‌است و توسط with می‌توان خواص آن‌را تغییر داد و حاصل کار، یک شیء کاملا جدید است که مکان آن در حافظه، با مکان شیء user1 در حافظه، یکی نیست.


مقایسه‌ی نوع‌های record

در کدهای حاصل از دی‌کامپایل فوق، قسمت عمده‌ای از آن به تکمیل اینترفیس <IEquatable<User پرداخته شده بود. به همین جهت اکنون دو رکورد با مقادیر خواص یکسانی را ایجاد می‌کنیم:
var user1 = new User("User 1", 21);
var user2 = new User("User 1", 21);
سپس یکبار آن‌ها را از طریق عملگر == و بار دیگر به کمک متد Equals، مقایسه می‌کنیم:
Console.WriteLine("user1.Equals(user2) -> {0}", user1.Equals(user2));
Console.WriteLine("user1 == user2 -> {0}", user1 == user2);
خروجی هر دو حالت، True است:
user1.Equals(user2) -> True
user1 == user2 -> True
این مورد، یکی از مهم‌ترین تفاوت‌های recordها با classها هستند.
- زمانیکه عملگر == را بر روی شیء user1 و user2 اعمال می‌کنیم، اگر User، از نوع کلاس معمولی باشد، حاصل آن false خواهد بود؛ چون این دو، به یک مکان از حافظه اشاره نمی‌کنند، حتی با اینکه مقادیر خواص هر دو شیء یکی است.
- اما اگر به قطعه کد دی‌کامپایل شده دقت کنید، در یک رکورد که هر چند در اصل یک کلاس است، حتی عملگر == نیز بازنویسی شده‌است تا در پشت صحنه همان متد Equals را فراخوانی کند و این متد با توجه به پیاده سازی اینترفیس <IEquatable<User، اینبار دقیقا مقادیر خواص رکورد را یک به یک مقایسه کرده و نتیجه‌ی حاصل را باز می‌گرداند:
public virtual bool Equals(User? other)
{
   return (object)other != null &&
 EqualityContract == other!.EqualityContract &&
 EqualityComparer<string>.Default.Equals(Name, other!.Name) && 
EqualityComparer<int>.Default.Equals(Age, other!.Age);
}
این متدی است که به صورت خودکار توسط کامپایلر جهت مقایسه‌ی مقادیر خواص رکورد جدید تعریف شده، تشکیل شده‌است. به عبارتی recordها از لحاظ مقایسه، شبیه به value objects عمل می‌کنند؛ هرچند در اصل یک کلاس هستند.

یک نکته: بازنویسی عملگر == در SDK نگارش rc2 فعلی رخ‌داده‌است و در نگارش‌های قبلی preview، اینگونه نبود.


امکان ارث‌بری در recordها

دو رکورد زیر را در نظر بگیرید که اولی به همراه Name است و نمونه‌ی مشتق شده‌ی از آن، خاصیت init-only سن را نیز به همراه دارد:
    public record User
    {
        public string Name { get; init; }

        public User(string name)
        {
            Name = name;
        }
    }

    public record UserWithAge : User
    {
        public int Age { get; init; }

        public UserWithAge(string name, int age) : base(name)
        {
            Age = age;
        }
    }
در اینجا روش دیگر تعریف recordها را ملاحظه می‌کنید که شبیه به کلاس‌ها است و خواص آن init-only هستند. در این حالت اگر مقایسه‌ی زیر را انجام دهیم:
var user1 = new User("User 1");
var user2 = new UserWithAge("User 1", 21);

Console.WriteLine("user1.Equals(user2) -> {0}", user1.Equals(user2));
Console.WriteLine("user1 == user2 -> {0}", user1 == user2);
به خروجی زیر خواهیم رسید:
user1.Equals(user2) -> False
user1 == user2 -> False
علت آن را هم پیشتر بررسی کردیم. تساوی رکوردها بر اساس مقایسه‌ی مقدار تک تک خواص آن‌ها صورت می‌گیرد و چون user1 به همراه سن نیست، مقایسه‌ی این دو، false را بر می‌گرداند.

امکان تعریف ارث‌بری رکوردها به صورت زیر نیز وجود دارد و الزاما نیازی به روش تعریف کلاس مانند آن‌ها، مانند مثال فوق نیست:
public abstract record Food(int Calories);
public record Milk(int C, double FatPercentage) : Food(C);


رکوردها متد ToString را بازنویسی می‌کنند

در مثال قبلی اگر یک ToString را بر روی اشیاء تشکیل شده فراخوانی کنیم:
Console.WriteLine(user1.ToString());
Console.WriteLine(user2.ToString());
به این خروجی‌ها می‌رسیم:
User { Name = User 1 }
UserWithAge { Name = User 1, Age = 21 }
که حاصل بازنویسی خودکار متد ToString در پشت صحنه است.


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

دو روش برای تعریف رکوردها وجود دارند؛ یکی شبیه به تعریف کلاس‌ها است و دیگری تعریف یک سطری، که positional record نیز نامیده می‌شود:
public record Person(string Name, int Age);
 فقط در حالت تعریف یک سطری positional record فوق است که خروجی خودکار نهایی تولیدی، به همراه public void Deconstruct نیز خواهد بود:
public void Deconstruct(out string Name, out int Age)
{
  Name = this.Name;
  Age = this.Age;
}
در این حالت می‌توان از tuples نیز برای کار با آن استفاده کرد:
var (name, age) = new Person("User 1", 21);
واژه‌ی «positional» نیز دقیقا به همین قابلیت اشاره می‌کند که بر اساس موقعیت خواص تعریف شده‌ی در رکورد، امکان Deconstruct آن‌ها به متغیرهای یک tuple وجود دارد. حالت تعریف کلاس مانند رکوردها، nominal نام دارد.


امکان استفاده‌ی از نوع‌های record در ASP.NET Core 5x

سیستم model binding در ASP.NET Core 5x، از نوع‌های record نیز پشتیبانی می‌کند؛ یک مثال:
 public record Person([Required] string Name, [Range(0, 150)] int Age);

 public class PersonController
 {
   public IActionResult Index() => View();

   [HttpPost]
   public IActionResult Index(Person person)
   {
    // ...
   }
 }


پرسش و پاسخ

آیا نوع‌های record به صورت value type معرفی می‌شوند؟
پاسخ: خیر. رکوردها در اصل reference type هستند؛ اما از لحاظ مقایسه، شبیه به value types عمل می‌کنند.

آیا می‌توان در یک کلاس، خاصیتی از نوع رکورد را تعریف کرد؟
پاسخ: بله. از این لحاظ محدودیتی وجود ندارد.

آیا می‌توان در رکوردها، از struct و یا کلاس‌ها جهت تعریف خواص استفاده کرد؟
پاسخ: بله. از این لحاظ محدودیتی وجود ندارد.

آیا می‌توان از واژه‌ی کلیدی with با کلاس‌ها و یا structها استفاده کرد؟
پاسخ: خیر. این واژه‌ی کلیدی در C# 9.0 مختص به رکوردها است.

آیا رکوردها به صورت پیش‌فرض Immutable هستند؟
پاسخ: اگر آن‌ها را به صورت positional records تعریف کنید، بله. چون در این حالت خواص تشکیل شده‌ی توسط آن‌ها از نوع init-only هستند. در غیراینصورت، می‌توان خواص غیر init-only را نیز به تعریف رکوردها اضافه کرد.
مطالب
React 16x - قسمت 7 - ترکیب کامپوننت‌ها - بخش 1 - ارسال داده‌ها، مدیریت رخ‌دادها
تا اینجا، تنها با یک تک کامپوننت کار کردیم؛ اما یک برنامه‌ی واقعی ترکیبی است از چندین کامپوننت که در نهایت درخت کامپوننت‌ها را در React تشکیل می‌دهند. به همین جهت در طی چند قسمت، نکات ترکیب کامپوننت‌ها را بررسی می‌کنیم.


ترکیب کامپوننت‌ها

در ادامه، همان برنامه‌ی تا قسمت 5 را که کار نمایش یک counter را انجام می‌دهد، تکمیل می‌کنیم. در این برنامه اگر به فایل index.js دقت کنید، کار رندر تک کامپوننت Counter را انجام می‌دهیم:
ReactDOM.render(<Counter />, document.getElementById("root"));
اما یک برنامه‌ی واقعی React، متشکل از درختی از کامپوننت‌ها است. به این ترتیب با ترکیب و در کنار هم قرار دادن کامپوننت‌های مختلف، می‌توان به UI ای کارآمد و پیچیده رسید.
برای نمایش این مفهوم، کامپوننت جدید src\components\counters.jsx را ایجاد می‌کنیم. قصد داریم در این کامپوننت، لیستی از کامپوننت‌های Counter را رندر کنیم. سپس در index.js، بجای رندر کامپوننت Counter، کامپوننت جدید Counters را رندر می‌کنیم. به این ترتیب درخت کامپوننت‌های برنامه، در سطح بالایی خودش از کامپوننت Counters شروع می‌شود و سپس فرزندان آن‌را کامپوننت‌های Counter تشکیل می‌دهند. به همین جهت فایل index.js را به صورت زیر ویرایش می‌کنیم تا به کامپوننت Counters اشاره کند:
import Counters from "./components/counters";

ReactDOM.render(<Counters />, document.getElementById("root"));
سپس به فایل جدید src\components\counters.jsx مراجعه کرده و با استفاده از قطعه کدهای کمکی imrc و cc که در قسمت‌های قبل با آن‌ها آشنا شدیم، ساختار بدنه‌ی کامپوننت جدید Counters را ایجاد می‌کنیم. اکنون در متد render آن، یک div را ایجاد کرده و داخل آن، چندین کامپوننت Counter را رندر می‌کنیم:
import React, { Component } from "react";

import Counter from "./counter";

class Counters extends Component {
  state = {};

  render() {
    return (
      <div>
        <Counter />
        <Counter />
        <Counter />
        <Counter />
      </div>
    );
  }
}

export default Counters;
در این حالت اگر به مرورگر مراجعه کنیم، مشاهده خواهیم کرد که هر کامپوننت، state خاص خودش را دارد و از سایر کامپوننت‌ها ایزوله است:


در مرحله‌ی بعد، بجای رندر و درج دستی این کامپوننت‌ها، آرایه‌ای از اشیاء counter را ایجاد کرده و سپس آن‌ها را توسط متد Array.map رندر می‌کنیم:
import React, { Component } from "react";
import Counter from "./counter";

class Counters extends Component {
  state = {
    counters: [
      { id: 1, value: 0 },
      { id: 2, value: 0 },
      { id: 3, value: 0 },
      { id: 4, value: 0 }
    ]
  };

  render() {
    return (
      <div>
        {this.state.counters.map(counter => (
          <Counter key={counter.id} />
        ))}
      </div>
    );
  }
}

export default Counters;
در اینجا یک خاصیت جدید را به شیء منتسب به خاصیت state به نام counters اضافه کرده‌ایم. این خاصیت حاوی آرایه‌ای از اشیاء counter است که هر کدام دارای یک id (که در قسمت key ذکر خواهد شد) و مقداری اولیه است. سپس آرایه‌ی this.state.counters را توسط متد map، رندر کرده‌ایم. تا اینجا پس از ذخیره‌ی فایل و بارگذاری مجدد برنامه، همان خروجی قبلی را مشاهده خواهیم کرد.


ارسال داده‌ها به کامپوننت‌ها

مشکل! مقدار value هر شیء شمارشگر تعریف شده، به کامپوننت‌های مرتبط رندر شده اعمال نشده‌است. برای مثال اگر value اولین شیء را به 4 تغییر دهیم، هنوز هم این کامپوننت با همان مقدار صفر شروع به کار می‌کند. برای رفع این مشکل، به همان روشی که ویژگی key کامپوننت Counter را مقدار دهی کردیم، می‌توان ویژگی‌های سفارشی دیگری را تعریف و مقدار دهی کرد:
  render() {
    return (
      <div>
        {this.state.counters.map(counter => (
          <Counter key={counter.id} value={counter.value} selected={true} />
        ))}
      </div>
    );
پس از تعریف ویژگی‌های دلخواه value و selected که یکی از آن‌ها به مقدار value شیء counter مرتبط متصل است، به خود کامپوننت Counter مراجعه کرده و سپس در ابتدای متد render آن، خاصیت props به ارث رسیده شده‌ی از کلاس پایه‌ی Component را جهت بررسی بیشتر لاگ می‌کنیم:
class Counter extends Component {
  state = {
    count: 0
  };

  render() {
    console.log("props", this.props);
    //...
پس از ذخیره‌ی فایل counter.jsx و بارگذاری مجدد برنامه، یک چنین خروجی در کنسول توسعه دهندگان مرورگر قابل مشاهده است:


خاصیت this.props، یک شیء ساده‌ی جاوا اسکریپتی است و شامل تمام ویژگی‌هایی می‌باشد که ما در کامپوننت Counters برای هر کدام از کامپوننت‌های Counter رندر شده‌ی توسط آن، تعریف کردیم. برای نمونه دو ویژگی جدید value و selected را که به تعاریف المان‌های Counter در کامپوننت Counters اضافه کردیم، در اینجا به همراه مقادیر منتسب به آن‌ها، قابل مشاهده هستند. البته در این خروجی، key را ملاحظه نمی‌کنید؛ چون هدف اصلی آن، معرفی یکتای المان‌ها در DOM مجازی React است.
بنابراین اکنون می‌توان به value تنظیم شده‌ی در کامپوننت Counters به صورت this.props.value در کامپوننت Counter دسترسی یافت و سپس از آن جهت مقدار دهی اولیه‌ی counter استفاده کرد.
class Counter extends Component {
  state = {
    count: this.props.value
  };
اکنون اگر تغییرات کامپوننت Counter را ذخیره کرده و به مرورگر مراجعه کنیم، در اولین بار نمایش برنامه و بدون اعمال هیچگونه تغییری، یک چنین خروجی حاصل می‌شود:


یک نکته: در اینجا selected={true} را داریم. اگر مقدار آن‌را حذف کنیم، یعنی selected تنها درج شود، مقدار آن، همان true دریافت خواهد شد.


تعریف فرزند برای المان‌های کامپوننت‌ها

ویژگی‌های اضافه شده‌ی به تعاریف المان‌های کامپوننت‌ها، توسط خاصیت this.props، به هر کدام از آن کامپوننت‌ها منتقل می‌شوند. این خاصیت props، یک خاصیت ویژه را به نام children، نیز دارا است و از آن برای دسترسی به المان‌های تعریف شده‌ی بین تگ‌های یک المان اصلی استفاده می‌شود:
  render() {
    return (
      <div>
        {this.state.counters.map(counter => (
          <Counter key={counter.id} value={counter.value} selected={true}>
            <h4>‍Counter #{counter.id}</h4>
          </Counter>
        ))}
      </div>
    );
  }
در اینجا بین تگ‌های ابتدا و انتهای تعریف المان Counter، یک محتوا نیز تعریف شده‌است. اکنون اگر به خروجی کنسول توسعه دهندگان مرورگر دقت کنیم، خاصیت جدید اضافه شده‌ی children را نیز می‌توان مشاهده کرد:


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


روش دیباگ برنامه‌های React

افزونه‌ی مفید React developer tools را می‌توانید برای مرورگرهای کروم و فایرفاکس، دریافت و نصب کنید. برای نمونه پس از نصب آن در مرورگر کروم، یک برگه‌ی جدید به لیست برگه‌های کنسول توسعه دهندگان آن اضافه می‌شود:


همانطور که مشاهده کنید، درخت کامپوننت‌های برنامه را در برگه‌ی جدید Components، می‌توان مشاهده کرد. در اینجا با انتخاب هر کدام از فرزندان این درخت، مشخصات آن نیز مانند props و state، در کنار صفحه ظاهر می‌شوند. همچنین در بالای همین قسمت، 4 آیکن مشاهده‌ی سورس، مشاهده‌ی DOM و یا لاگ کردن جزئیات شیء کامپوننت انتخابی در کنسول هم درج شده‌اند:


که برای نمونه چنین خروجی را لاگ می‌کند:



بررسی تفاوت‌های خواص props و state

در کامپوننت Counter، از props برای مقدار دهی اولیه‌ی state استفاده می‌کنیم:
class Counter extends Component {
  state = {
    count: this.props.value
  };
اکنون این سؤال مطرح می‌شود که چه تفاوتی بین props و state وجود دارد؟
- props حاوی اطلاعاتی است که به یک کامپوننت ارسال می‌کنیم؛ اما state حاوی اطلاعاتی است که مختص به آن کامپوننت بوده و private است. یعنی سایر کامپوننت‌ها نمی‌توانند به state کامپوننت دیگری دسترسی پیدا کنند. برای مثال در کامپوننت Counters، تمام attributes سفارشی تنظیم شده‌ی بر روی تعاریف المان‌های کامپوننت Counter، جزئی از اطلاعات props خواهند بود. در اینجا نمی‌توان به state کامپوننت مدنظری دسترسی یافت و آن‌را مقدار دهی کرد. به همین ترتیب state کامپوننت Counters نیز در سایر کامپوننت‌ها قابل دسترسی نیست.
- همچنین باید درنظر داشت که props، در مقایسه با state، فقط خواندنی است. به عبارتی مقدار ورودی به یک کامپوننت را داخل آن کامپوننت نمی‌توان تغییر داد. برای مثال سعی کنید در داخل متد رویدادگردان کلیک موجود در کامپوننت Counter، مقدار this.props.value را به صفر تنظیم کنید. در این حالت با کلیک بر روی دکمه‌ی Increment، بلافاصله خطای readonly بودن خواص شیء منتسب به props را دریافت می‌کنیم. در اینجا اگر نیاز است این مقدار را داخل کامپوننت تغییر دهیم، باید ابتدا این مقدار را دریافت کرده و سپس آن‌را داخل state قرار دهیم. پس از آن امکان ویرایش اطلاعات منتسب به state، داخل یک کامپوننت وجود خواهد داشت.


صدور و مدیریت رخ‌دادها

در ادامه می‌خواهیم در کنار هر دکمه‌ی Increment کامپوننت شمارشگر، یک دکمه‌ی Delete هم قرار دهیم:


مشکل! اگر کد مدیریتی handleDelete را در کامپوننت Counter قرار دهیم، چگونه باید به لیست آرایه‌ی اشیاء counters والد آن، یعنی کامپوننت Counters که سبب رندر شدن کامپوننت‌های شمارشگر شده (state = { counters: [ ] })، دسترسی یافت و شیء‌ای را از آن حذف کرد؟ در React، کامپوننتی که state ای را تعریف می‌کند، باید کامپوننتی باشد که قرار است آن‌را تغییر دهد و اطلاعات state هر کامپوننت، صرفا متعلق به آن کامپوننت بوده و جزو اطلاعات خصوصی آن است. بنابراین مدیریت حذف و یا افزودن کامپوننت‌ها در لیست نمایش داده شده، باید جزو وظایف کامپوننت Counters باشد و نه Counter.
برای حل این مشکل، کامپوننت Counter تعریف شده (کامپوننت فرزند) باید سبب بروز رخ‌داد onDelete شود تا کامپوننت Counters (کامپوننت والد)، آن‌را توسط متد handleDelete مدیریت کند. بنابراین ابتدا به کامپوننت Counters (کامپوننت والد) مراجعه کرده و متد رویدادگردان handleDelete را به آن اضافه می‌کنیم:
  handleDelete = () => {
    console.log("handleDelete called.");
  };
سپس ارجاعی از این متد را به صورت خاصیتی از props به کامپوننت Counter (کامپوننت فرزند) ارسال خواهیم کرد؛ برای این منظور در کامپوننت Counters (کامپوننت والد)، ویژگی onDelete را به تعریف المان Counter اضافه کرده و آن‌را با ارجاعی به متدhandleDelete  مقدار دهی می‌کنیم:
<Counter
     key={counter.id}
     value={counter.value}
     selected={true}
     onDelete={this.handleDelete}
/>
پس از آن به کامپوننت Counter مراجعه کرده و دکمه‌ی جدید Delete را به صورت زیر در کنار دکمه‌ی Increment تعریف می‌کنیم:
<button
  onClick={this.props.onDelete}
  className="btn btn-danger btn-sm m-2"
>
  Delete
</button>
در اینجا onClick، به خاصیت onDelete شیء props ارسالی به کامپوننت متصل شده‌است.
اکنون اگر برنامه را ذخیره کرده و پس از بارگذاری مجدد برنامه در مرورگر بر روی دکمه‌ی Delete کلیک کنیم، پیام «handleDelete called» در کنسول توسعه دهندگان مرورگر لاگ می‌شود. به این ترتیب کامپوننت فرزند سبب بروز رخ‌دادی شده و والد آن، این رخ‌داد را مدیریت می‌کند.


به روز رسانی state

تا اینجا دکمه‌ی Delete فرزند، به متد handleDelete والد متصل شده‌است. مرحله‌ی بعد، پیاده سازی واقعی حذف یک المان از DOM مجازی و به روز رسانی state است. برای اینکار ابتدا به رخ‌دادگردان onClick، در کامپوننت شمارشگر، مراجعه کرده و id دریافتی را به سمت والد ارسال می‌کنیم:
onClick={() => this.props.onDelete(this.props.id)}
البته در سمت والد نیز باید این id را به صورت یک خاصیت جدید به props اضافه کنیم (تا this.props.id فوق کار کند)؛ چون ویژگی key، مختص DOM مجازی بوده و به props اضافه نمی‌شود:
<Counter
  key={counter.id}
  value={counter.value}
  selected={true}
  onDelete={this.handleDelete}
  id={counter.id}
/>
اکنون این id را در کامپوننت والد دریافت و به آن واکنش نشان می‌دهیم:
  handleDelete = counterId => {
    console.log("handleDelete called.", counterId);
    const counters = this.state.counters.filter(
      counter => counter.id !== counterId
    );
    this.setState({ counters }); // = this.setState({ counters: counters });
  };
همانطور که پیشتر نیز در این سری عنوان شده، در React، مقدار state را به صورت مستقیم تغییر نمی‌دهیم و اینکار باید از طریق متد setState آن صورت گیرد. به عبارت دیگر مستقیما خاصیت counters شیء منتسب به خاصیت state را تغییر نمی‌دهیم. ابتدا یک آرایه‌ی جدید از المان‌ها را تولید کرده و به متد setState ارسال می‌کنیم. سپس React، هم خاصیت counters و هم UI را بر این اساس به روز رسانی خواهد کرد. در اینجا، لیست جدید counters، بر اساس id دریافتی از کامپوننت فرزند، تولید شده و به متد this.setState ارسال می‌شود. در این حالت اگر برنامه را ذخیره کرده و پس از بارگذاری مجدد آن در مرورگر، بر روی دکمه‌ی Delete هر ردیف کلیک کنیم، آن ردیف از UI حذف خواهد شد.

البته پیاده سازی ما تا به اینجا بدون مشکل کار می‌کند، اما به ازای هر خاصیت counter، یک ویژگی جدید را به تعریف المان مرتبط اضافه کرده‌ایم که در طول زمان بیش از اندازه طولانی خواهد شد. برای رفع این مشکل، خود شیء counter را به صورت یک ویژگی جدید به کامپوننت مرتبط با آن ارسال می‌کنیم. به این ترتیب اگر در آینده خاصیتی را به این شیء اضافه کردیم، دیگر نیازی نیست تا آن‌را به صورت دستی و مجزا تعریف کنیم. به همین جهت ابتدا تعریف المان Counter را به صورت زیر خلاصه می‌کنیم که در آن ویژگی جدید counter، حاوی کل شیء counter است:
<Counter
  key={counter.id}
  counter={counter}
  onDelete={this.handleDelete}
/>
سپس در سمت کامپوننت فرزند شمارشگر، دو تغییر this.props.counter.value و this.props.counter.id باید صورت گیرند تا مقادیر شیء counter به درستی خوانده شوند.


کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: sample-07.zip
مطالب
پلاگین DataTables کتابخانه jQuery - قسمت اول
DataTables پلاگینی برای کتابخانه jQuery است. این پلاگین امکانات پیشرفته ای برای یک جدول html که حاوی داده‌ها است اضافه می‌کند، و همچنین عملیات صفحه بندی، جستجو، مرتب سازی داده‌ها را در سمت کاربر انجام می‌دهد.

به طور خلاصه می‌توانید امکانات متعدد این پلاگین را در زیر مشاهده کنید:
  • صفحه بندی داده‌ها با تعداد رکوردهای قابل تغییر در هر صفحه (variable length pagination)
  • فیلتر کردن داده‌های بایند شده به جدول (on-the-fly filtering)
  • مرتب سازی داده‌ها بر اساس ستون‌های مختلف با قابلیت تشخیص نوع داده ستون (Multi-column sorting with data type detection)
  • تغییر اندازه ستون‌ها به صورت هوشمند (Smart handling of column widths)
  • نمایش داده‌ها در جدول از اکثر data source‌ها (DOM، یک آرایه جاوا اسکریپتی، یک فایل، یا با استفاده از پردازش سمت سروری (سی شارپ، php و غیره) )
  • قابلیت جهانی شدن یا منطبق شدن با زبان‌های مختلف دنیا (Fully Internationalisable)
  • قابلیت تعویض theme آن با استفاده از jQuery UI ThemeRoller
  • وجود داشتن 2900 آزمون واحد برای آن (backed by a suite of 2900 unit test)
  • وجود داشتن پلاگین‌های متعدد برای آن
  • و رایگان بودن آن
در این مقاله شما را به طور مقدماتی با این پلاگین آشنا خواهم کرد.
برای استفاده از این پلاگین ابتدا به اینجا مراجعه کرده و آنرا به همراه مثالهای آن که در یک فایل فشرده هستند را دانلود کنید. بعد از دانلود و خارج کردن فایل دانلودی از حالت فشرده، وارد پوشه examples از آن که بشوید می‌توانید مثالهای متعدد در رابطه با این پلاگین را مشاهده نمائید.

  مثال‌های این پلاگین یکی از بهترین منابع یادگیری آن هستند. در این سری از مقالات هم از روی همین مثالها پیش میرویم. برای این کار، بعد از مراجعه به پوشه examples فایل index.html را باز کنید و مثال اول را (Zero Configuration) کلیک کنید.
 

نتیجه حاصل از اجرای مثال Zero Configuration چیزی شبیه تصویر زیر است: 
تصویر را شماره گزاری کرده ام تا بتوانم راحت‌تر آنرا برایتان تشریح کنم.
  1. داده‌های درون جدول (10 تای اول) که در قسمت tbody جدول قرار دارند
  2. قسمت thead جدول
  3. قسمت tfoot جدول
  4. اندازه صفحه (page size)
  5. کادر جستجو که در کلیه ستون‌های جدول جستجویی را انجام می‌دهد و داده‌ها بر اساس آن فیلتر می‌شوند.
  6. قابلیت مرتب سازی رکوردها بر اساس یک ستون خاص به صورت صعودی یا نزولی
  7. اطلاعات مربوط به رکوردهای جاری و تعداد کل رکوردها
  8. قابلیت تغییر صفحه با دکمه‌های previous و next

تشریح مثال Zero Configuration :

برای استفاده از این پلاگین، باید ارجاعی به کتابخانه jquery و نیز فایل jquery.dataTables.js  وجود داشته باشد. این دو فایل در زیر پوشه media/js قرار گرفته اند.
<script type="text/javascript" language="javascript" src="../../media/js/jquery.js"></script>
<script type="text/javascript" language="javascript" src="../../media/js/jquery.dataTables.js"></script>
و همچنین css‌های مربوطه به این پلاگین بدین صورت معرفی شده اند:
<style type="text/css" title="currentStyle">
    @import "../../media/css/demo_page.css";
    @import "../../media/css/demo_table.css";
</style>
در این مثال که ساده‌ترین مثال مربوط به این پلاگین است داده‌ها به صورت دستی در جدول قرار گرفته اند و روش‌های دیگر را به قسمت‌های بعد موکول می‌کنیم. اگر به source این مثال مراجعه کنید (از روی فایل اصلی و نه از طریق مرورگر) مشاهده می‌کنید که یک جدول html با id برابر با example وجود دارد که حاوی 57 سطر است (در قسمت tbody) که حاوی داده‌های جدول هستند. اما با مراجعه به source مثال از طریق مرورگر مشاهده می‌کنید تعداد این سطرها 10 تا هست و این بدین معنیه که پلاگین فقط تعداد رکوردهای مورد نیاز رو در قسمت tbody قرار می‌ده و از بقیه فاکتور می‌گیره و هر بار که کاربر به صفحه رو با دکمه‌های Previous و Next تغییر می‌ده این پلاگین قسمت tbody رو تغییر میده

این نکته هم جا نمونه که برای اعمال شدن پلاگین DataTables به یک جدول که به طور مثال id جدول example هست، به صورت زیر عمل می‌کنیم:
$(document).ready(function() {
    $('#example').dataTable();
} );


مطالب
ارتقاء به ASP.NET Core 1.0 - قسمت 8 - فعال سازی ASP.NET MVC
پیشنیازهای بحث (از قسمت 8 به بعد این سری)
اگر پیشتر سابقه‌ی کار کردن با ASP.NET MVC را ندارید، نیاز است «15 مورد» ابتدایی مطالب ASP.NET MVC سایت را پیش از ادامه‌ی این سری مطالعه کنید؛ از این جهت که این سری از مطالب «ارتقاء» نام دارند و نه «بازنویسی مجدد». دراینجا بیشتر تفاوت‌ها و روش‌های تبدیل کدهای قدیمی، به جدید را بررسی خواهیم کرد؛ تا اینکه بخواهیم تمام مطالبی را که وجود دارند از صفر بازنویسی کنیم.


فعال سازی ASP.NET MVC

تا اینجا خروجی برنامه را صرفا توسط میان افزار app.Run نمایش دادیم. اما در نهایت می‌خواهیم یک برنامه‌ی ASP.NET MVC را برفراز ASP.NET Core 1.0 اجرا کنیم و این قابلیت نیز به صورت پیش فرض غیرفعال است. برای فعال سازی آن نیاز است ابتدا بسته‌ی نیوگت آن‌را نصب کرد. سپس سرویس‌های مرتبط با آن‌را ثبت و معرفی نمود و در آخر میان افزار خاص آن‌را فعال کرد.


نصب وابستگی‌های ASP.NET MVC

برای این منظور بر روی گره references کلیک راست کرده و گزینه‌ی manage nuget packages را انتخاب کنید. سپس در برگه‌ی browse آن Microsoft.AspNetCore.Mvc را جستجو کرده و نصب نمائید:


انجام این مراحل معادل هستند با افزودن یک سطر ذیل به فایل project.json برنامه:
 {
    "dependencies": {
      //same as before  
      "Microsoft.AspNetCore.Mvc": "1.0.0"
 },


تنظیم سرویس‌ها و میان افزارهای ASP.NET MVC

پس از نصب بسته‌ی نیوگت ASP.NET MVC، دو تنظیم ذیل در فایل آغازین برنامه، برای شروع به کار با ASP.NET MVC کفایت می‌کنند:
الف) ثبت یکجای سرویس‌های ASP.NET MVC
public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc();

ب) معرفی میان افزار ASP.NET MVC
public void Configure(IApplicationBuilder app)
{
   app.UseFileServer();
   app.UseMvcWithDefaultRoute();
در مورد متد UseFileServer در مطلب «ارتقاء به ASP.NET Core 1.0 - قسمت 4 - فعال سازی پردازش فایل‌های استاتیک» بیشتر بحث شد.
در اینجا دو متد UseMvc و UseMvcWithDefaultRoute را داریم. اولی، امکان تعریف مسیریابی‌های سفارشی را میسر می‌کند و دومی به همراه یک مسیریابی پیش فرض است.


افزودن اولین کنترلر برنامه و معرفی POCO Controllers


در ویژوال استودیو بر روی نام پروژه کلیک راست کرده و پوشه‌ی جدیدی را به نام کنترلر اضافه کنید (تصویر فوق). سپس به این پوشه کلاس جدید HomeController را با این محتوا اضافه کنید:
namespace Core1RtmEmptyTest.Controllers
{
    public class HomeController
    {
        public string Index()
        {
            return "Running a POCO controller!";
        }
    }
}
در ادامه برای اینکه فایل index.html موجود در پوشه‌ی wwwroot بجای محتوای اکشن متد Index ما نمایش داده نشود (با توجه به تقدم و تاخر میان افزارهای ثبت شده‌ی در کلاس آغازین برنامه)، این فایل را حذف کره و یا تغییر نام دهید.
سپس برنامه را اجرا کنید. این خروجی باید قابل مشاهده باشد:


اگر با نگارش‌های قبلی ASP.NET MVC کار کرده باشید، تفاوت این کنترلر با آن‌ها، در عدم ارث بری آن از کلاس پایه‌ی Controller است. به همین جهت به آن POCO Controller نیز می‌گویند (plain old C#/CLR object).
در ASP.NET Core، همینقدر که یک کلاس public غیر abstract را که نامش به Controller ختم شود، داشته باشید و این کلاس در اسمبلی باشد که ارجاعی را به وابستگی‌های ASP.NET MVC داشته باشد، به عنوان یک کنترلر معتبر شناخته شده و مورد استفاده قرار خواهد گرفت. در نگارش‌های قبلی، شرط ارث بری از کلاس پایه Controller نیز الزامی بود؛ اما در اینجا خیر. هدف از آن نیز کاهش سربارهای وهله سازی یک کنترلر است. اگر صرفا می‌خواهید یک شیء را به صورت JSON بازگشت دهید، شاید وهله سازی یک کلاس ساده، بسیار بسیار سریعتر از نمونه سازی یک کلاس مشتق شده‌ی از Controller، به همراه تمام وابستگی‌های آن باشد.

 البته هنوز هم مانند قبل، کنترلرهای مشتق شده‌ی از کلاس پایه‌ی Controller قابل تعریف هستند:
using Microsoft.AspNetCore.Mvc;
 
namespace Core1RtmEmptyTest.Controllers
{
    public class AboutController : Controller
    {
        public IActionResult Index()
        {
            return Content("Hello from DNT!");
        }
    }
}
با این خروجی:


تفاوت دیگری را که ملاحظه می‌کنید، خروجی IActionResult بجای ActionResult نگارش‌های قبلی است. در اینجا هنوز هم ActionResult را می‌توان بکار برد و اینبار ActionResult، پیاده سازی پیش فرض اینترفیس IActionResult است.
و اگر بخواهیم در POCO Controllers شبیه به return Content فوق را پیاده سازی کنیم، نیاز است تا تمام جزئیات را از ابتدا پیاده سازی کنیم (چون کلاس پایه و ساده ساز Controller در اختیار ما نیست):
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
 
namespace Core1RtmEmptyTest.Controllers
{
    public class HomeController
    {
        [ActionContext]
        public ActionContext ActionContext { get; set; }
 
        public HttpContext HttpContext => ActionContext.HttpContext;
 
        public string Index()
        {
            return "Running a POCO controller!";
        }
 
        public IActionResult About()
        {
            return new ContentResult
            {
                Content = "Hello from DNT!",
                ContentType = "text/plain; charset=utf-8"
            };
        }
    }
}
همانطور که ملاحظه می‌کنید اینبار بجای return Content ساده، باید وهله سازی شیء ContentResult از ابتدا صورت گیرد؛ به همراه تمام جزئیات آن.
به علاوه در اینجا نحوه‌ی دسترسی به HttpContext را هم مشاهده می‌کنید. ویژگی ActionContext سبب تزریق اطلاعات آن به کنترلر جاری شده و سپس از طریق آن می‌توان به HttpContext و تمام قابلیت‌های آن دسترسی یافت.
اینجا است که می‌توان میزان سبکی و سریعتر بودن POCO Controllers را احساس کرد. شاید در کنترلری نیاز به این وابستگی‌ها نداشته باشید. اما زمانیکه کنترلری از کلاس پایه‌ی Controller مشتق می‌شود، تمام این وابستگی‌ها را به صورت پیش فرض و حتی در صورت عدم استفاده، در اختیار خواهد داشت و این در اختیار داشتن یعنی وهله سازی شدن تمام وابستگی‌های مرتبط با شیء پایه‌ی Controller. به همین جهت است که POCO Controllers بسیار سبک‌تر و سریع‌تر از کنترلرهای متداول مشتق شده‌ی از کلاس پایه‌ی Controller عمل می‌کنند.
مطالب
مدیریت پیشرفته‌ی حالت در React با Redux و Mobx - قسمت پنجم - Redux Hooks
تا اینجا الگوی Redux را در برنامه‌های React بررسی کردیم که شامل این موارد است:
- با استفاده از Redux، یک شیء سراسری state، کار مدیریت state تمام برنامه را به عهده می‌گیرد که به آن «single source of truth» نیز گفته می‌شود. البته هرچند می‌توان کامپوننت‌هایی را هم در این بین داشت که state خاص خودشان را داشته باشند و آن‌را در این شیء سراسری ذخیره نکنند.
- در حین کار با Redux، تنها راه تغییر شیء سراسری state آن، صدور رخ‌دادهایی هستند که در اینجا اکشن نامیده می‌شوند. یک اکشن شیءای است که بیان می‌کند چه چیزی قرار است تغییر کند.
- برای ساده سازی ساخت این اشیاء می‌توان متدهایی را به نام action creators ایجاد کرد.
- اگر این متدهای action creator را توسط متد store.dispatch فراخوانی کنیم، سبب dispatch شیء اکشن، به یک تابع Reducer متناظری خواهند شد. این تابع Reducer است که قسمتی از state را که متناظر با نوع اکشن رسیده‌است، تغییر می‌دهد. در این حالت اگر اکشن رسیده، نوع مدنظری را نداشته باشد، خروجی تابع Reducer، همان state اصلی و بدون تغییر خواهد بود.
- Reducerها توابعی خالص هستند و نباید به همراه اثرات جانبی باشند (هر نوع تعاملی با دنیای خارج از تابع جاری) و همچنین نباید شیء state را نیز مستقیما تغییر دهند. این توابع باید یک کپی تغییر یافته‌ی از state را در صورت نیاز بازگشت دهند.
- برای مدیریت بهتر برنامه می‌توان چندین تابع Reducer را بر اساس نوع‌های اکشن‌های ویژه‌ای، پیاده سازی کرد. سپس با ترکیب آن‌ها، یک شیء rootReducer ایجاد می‌شود.
- در نهایت در الگوی Redux، یک مخزن یا store تعریف خواهد شد که تمام این اجزاء را مانند rootReducer و میان‌افزارهای تعریف شده مانند Thunk، در کنار هم قرار می‌دهد و امکان dispatch اکشن‌ها را میسر می‌کند.
- اکنون برای استفاده‌ی از Redux در یک برنامه‌ی React، نیاز است کامپوننت ریشه‌ی برنامه را توسط کامپوننت Provider آن محصور کرد تا قسمت‌های مختلف برنامه بتوانند با امکانات مخزن Redux، کار کرده و با آن ارتباط برقرار کنند.
- قسمت آخر این اتصال جائی است که کامپوننت‌های اصلی برنامه، توسط یک کامپوننت دربرگیرنده که Container نامیده می‌شود، توسط متد connect کتابخانه‌ی react-redux محصور می‌شوند. به این ترتیب این کامپوننت‌ها می‌توانند state و خواص مورد نیاز خود را از طریق props دریافت کرده (mapStateToProps) و یا رویدادها را به سمت store، ارسال کنند (mapDispatchToProps).

از زمان React 16.8، مفهوم جدیدی به نام React Hooks معرفی شد که تعدادی از مهم‌ترین‌های آن‌ها را در سری «React 16x» بررسی کردیم. توسط Hooks، کامپوننت‌های تابعی React اکنون می‌توانند به local state خود دسترسی پیدا کنند و یا با دنیای خارج ارتباط برقرار کنند. پس از آن سایر کتابخانه‌های نوشته شده‌ی برای React نیز شروع به انطباق خود با این الگوی جدید کرده‌اند؛ برای مثال کتابخانه‌ی react-redux v1.7 نیز به همراه تعدادی Hook، جهت ساده سازی آخرین قسمتی است که در اینجا بیان شد، تا بتوانند راه حل دومی برای اتصال کامپوننت‌ها و دربرگیری آن‌ها باشند که در ادامه جزئیات آن‌ها را بررسی خواهیم کرد.


بررسی useSelector Hook

useSelector Hook که توسط کتابخانه‌ی react-redux ارائه می‌شود، معادل بسیار نزدیک تابع mapStateToProps مورد استفاده‌ی در متد connect است. برای مثال در قسمت قبل، دربرگیرنده‌ی کامپوننت Posts در فایل src\containers\Posts.js، یک چنین محتوایی را دارد:
import { connect } from "react-redux";

import Posts from "../components/Posts";

const mapStateToProps = state => {
  console.log("PostsContainer->mapStateToProps", state);
  return {
    ...state.postsReducer
  };
};

export default connect(mapStateToProps)(Posts);
اینبار اگر بخواهیم کل این container را حذف کرده و از useSelector Hook استفاده کنیم، به این ترتیب عمل خواهیم کرد:
پیشتر امضای کامپوننت تابعی Posts واقع در فایل src\components\Posts.jsx، به صورت زیر تعریف شده بود که سه خاصیت را از طریق props دریافت می‌کرد:
const Posts = ({ posts, loading, error }) => {
  return (
  // ...
و این سه خاصیت دقیقا از متد mapStateToProps فوق که ملاحظه می‌کنید، تامین می‌شود. این متد خواص شیء state.postsReducer را به صورت props به کامپوننت Posts از طریق متد connect، ارسال می‌کند. کار postsReducer، فراهم آوردن و مدیریت سه خاصیت { loading: false, posts: [], error: null } است.

اکنون فایل جدید src\components\HooksPosts.jsx را ایجاد کرده و ابتدا و امضای کامپوننت تابعی Posts را به صورت زیر تغییر می‌دهیم:
import { useSelector } from "react-redux";

// ...

const HooksPosts = () => {
  const { posts, loading, error } = useSelector(state => state.postsReducer);
  return (
  // ...
متد useSelector، امکان دسترسی به state ذخیره شده‌ی در مخزن redux را میسر می‌کند. سپس باید همانند متد mapStateToProps، خواصی را که از آن نیاز داریم، دریافت کنیم که در اینجا کل خواص postsReducer دریافت شده (کل state دریافت شده و سپس خاصیت state.postsReducer آن بازگشت داده شده‌است) و در ادامه توسط Object Destructuring، به سه متغیری که پیشتر از طریق props تامین می‌شدند، انتساب داده می‌شود.

یک نکته: خروجی تابع mapStateToProps همواره باید یک شیء باشد، اما چنین محدودیتی در مورد تابع useSelector وجود ندارد و در صورت نیاز می‌توان تنها مقدار یک خاصیت از یک شیء را نیز بازگشت داد.

این کامپوننت، هیچ تغییر دیگری را نیاز ندارد و اگر اکنون به فایل src\App.js مراجعه کنیم، می‌توان دربرگیرنده‌ی کامپوننت Posts را:
import PostsContainer from "./containers/Posts";

function App() {
  return (
    <main className="container">
      <PostsContainer />
    </main>
  );
}
با کامپوننت جدید HooksPosts جایگزین کرد و دیگر نیازی به نوشتن متد connect و ساخت یک container مخصوص آن، نیست:
import HooksPosts from "./components/HooksPosts";

function App() {
  return (
    <main className="container">
      <HooksPosts />
    </main>
  );
}


بررسی useDispatch Hook

تا اینجا موفق شدیم متد mapStateToProps را با useSelector Hook جایگزین کنیم. مرحله‌ی بعد، جایگزین کردن mapDispatchToProps با هوک دیگری به نام useDispatch است. برای مثال در قسمت قبل، دربرگیرنده‌ی کامپوننت FetchPosts در فایل src\containers\FetchPosts.js، چنین تعریفی را دارد:
import { connect } from "react-redux";

import { fetchPostsAsync } from "../actions";
import FetchPosts from "../components/FetchPosts";

const mapDispatchToProps = {
  fetchPostsAsync
};

export default connect(null, mapDispatchToProps)(FetchPosts);
کار این تامین کننده، اتصال action creator ای به نام fetchPostsAsync به props کامپوننت FetchPosts است که در فایل src\components\FetchPosts.jsx به این صورت تعریف شده‌است:
const FetchPosts = ({ fetchPostsAsync }) => {
اکنون برای جایگزین کردن mapDispatchToProps با useDispatch Hook، نگارش دیگری از این کامپوننت تابعی را به نام HooksFetchPosts در فایل src\components\HooksFetchPosts.jsx ایجاد می‌کنیم:
import React from "react";
import { useDispatch } from "react-redux";

import { fetchPostsAsync } from "../actions";

const HooksFetchPosts = () => {
  const dispatch = useDispatch();
  return (
    <section className="card mt-5">
      <div className="card-header text-center">
        <button
          className="btn btn-primary"
          onClick={() => dispatch(fetchPostsAsync())}
        >
          Fetch Posts
        </button>
      </div>
    </section>
  );
};

export default HooksFetchPosts;
عملکر آن نیز بسیار ساده‌است. متد useDispatch، به ما امکان دسترسی به متد store.dispatch را می‌دهد (ارجاعی به آن‌را در اختیار ما قرار می‌دهد). اکنون اگر مانند رخ‌داد onClick تعریف شده، سبب dispatch یک action creator به نام fetchPostsAsync شویم (که اینبار باید به صورت صریح از ماژول مربوطه import شود؛ چون دیگر از طریق props تامین نمی‌شود)، سبب ارسال نتیجه‌ی آن به reducer متناظری می‌شود.

با این تغییر نیز می‌توان به فایل src\App.js مراجعه کرد و المان قبلی FetchPostsContainer را که از ماژول containers/FetchPosts تامین می‌شد، به نحو متداولی با همان کامپوننت جدید HooksFetchPosts، تعویض کرد:
import HooksFetchPosts from "./components/HooksFetchPosts";
import HooksPosts from "./components/HooksPosts";

// ...

function App() {
  return (
    <main className="container">
      <HooksFetchPosts />
      <HooksPosts />
    </main>
  );
}


یک مثال تکمیلی: بازنویسی src\components\counter.jsx با redux hooks

کامپوننت شمارشگر را در قسمت سوم این سری بررسی و تکمیل کردیم. اکنون قصد داریم فایل تامین کننده‌ی آن‌را که به صورت زیر در فایل src\containers\Counter.js تعریف شده:
import { connect } from "react-redux";

import { decrementValue, incrementValue } from "../actions";
import Counter from "../components/counter";

const mapStateToProps = (state, ownProps) => {
  console.log("CounterContainer->mapStateToProps", { state, ownProps });
  return {
    count: state.counterReducer.count
  };
};

const mapDispatchToProps = {
  incrementValue,
  decrementValue
};

export default connect(mapStateToProps, mapDispatchToProps)(Counter);
حذف کرده و با redux hooks جایگزین کنیم. برای این منظور فایل جدید src\components\HooksCounter.jsx را ایجاد می‌کنیم و سپس در ابتدا برای جایگزین کردن قسمت دریافت اطلاعات از this.pros آن:
class Counter extends Component {
  render() {
    console.log("Counter->props", this.props);
    const {
      //counterReducer: { count },
      count,
      incrementValue,
      decrementValue
    } = this.props;
به صورت زیر عمل می‌کنیم:
import React from "react";
import { useDispatch, useSelector } from "react-redux";

import { decrementValue, incrementValue } from "../actions";

const HooksCounter = ({ prop1 }) => {
  const { count } = useSelector(state => {
    console.log("HooksCounter->useSelector", { state, prop1 });
    return {
      count: state.counterReducer.count
    };
  });
  const dispatch = useDispatch();
  return (
  // ...
- متغیر count را با استفاده از useSelector، از شیء state استخراج کرده و با نام خاصیت count بازگشت می‌دهیم.
- اینبار دو action creator مورد استفاده‌ی در متدهای + و - را از ماژول action دریافت کرده‌ایم تا توسط useDispatch مورد استفاده قرار گیرند.
- همچنین دیگر نیازی به ذکر (state, ownProps) نیست. مقدار ownProps، همان props معمولی است که به کامپوننت ارسال می‌شود که برای مثال اینبار نام prop1 را دارد؛ چون هنگامیکه المان کامپوننت HooksCounter را درج و معرفی می‌کنیم، توسط کامپوننت دیگری محصور نشده‌است. تامین آن نیز در فایل src\App.js با درج متداول نام المان کامپوننت HooksCounter و ذکر ویژگی سفارشی prop1 صورت می‌گیرد:
import HooksCounter from "./components/HooksCounter";

//...

function App() {
  const prop1 = 123;
  return (
    <main className="container">
     <HooksCounter prop1={prop1} />
    </main>
  );
}
با این تغییرات، کدهای کامل src\components\HooksCounter.jsx به صورت زیر تکمیل می‌شود که قسمت‌های استفاده از متغیر count و همچنین dispatch دو action creator دریافت شده، در آن مشخص هستند:
import React from "react";
import { useDispatch, useSelector } from "react-redux";

import { decrementValue, incrementValue } from "../actions";

const HooksCounter = ({ prop1 }) => {
  const { count } = useSelector(state => {
    console.log("HooksCounter->useSelector", { state, prop1 });
    return {
      count: state.counterReducer.count
    };
  });
  const dispatch = useDispatch();
  return (
    <section className="card mt-5">
      <div className="card-body text-center">
        <span className="badge m-2 badge-primary">{count}</span>
      </div>
      <div className="card-footer">
        <div className="d-flex justify-content-center align-items-center">
          <button
            className="btn btn-secondary btn-sm"
            onClick={() => dispatch(incrementValue())}
          >
            +
          </button>
          <button
            className="btn btn-secondary btn-sm m-2"
            onClick={() => dispatch(decrementValue())}
          >
            -
          </button>
          <button className="btn btn-danger btn-sm">Reset</button>
        </div>
      </div>
    </section>
  );
};

export default HooksCounter;


مشکل! با استفاده از useSelector، تعداد رندرهای مجدد کامپوننت‌های برنامه افزایش یافته‌است!

برنامه‌ی جاری را پس از این تغییرات  اجرا کنید. با هر بار کلیک بر روی دکمه‌ی fetch posts، حتی کامپوننت شمارشگر درج شده‌ی در صفحه که ربطی به آن ندارد نیز رندر مجدد می‌شود! چرا؟ (این مورد را با مشاهده‌ی کنسول توسعه دهندگان مرورگر می‌توانید مشاهده کنید. در ابتدای متد رندر هر کدام از کامپوننت‌ها، یک console.log قرار داده شده‌است)
زمانیکه اکشنی dispatch می‌شود، useSelector hook با استفاده از مقایسه‌ی ارجاعات اشیاء (strict === reference check)، کار مقایسه‌ی مقدار قبلی و مقدار جدید را انجام می‌دهد. اگر این‌ها متفاوت باشند، کامپوننت را مجبور به رندر مجدد می‌کند. این مورد مهم‌ترین تفاوت بین useSelector hook و متد connect است. متد connect از روش shallow equality checks برای مقایسه‌ی نتایج حاصل از mapStateToProps و تصمیم در مورد رندر مجدد استفاده می‌کند. اما این مقایسه‌ها چه تفاوتی با هم دارند؟
در حالت mapStateToProps، مهم نیست که شیء بازگشت داده شده، دارای یک ارجاع جدید است یا خیر؟ shallow equality checks فقط به معنای مقایسه‌ی خاصیت به خاصیت شیء بازگشت داده شده‌، با نمونه‌ی قبلی است. اما زمانیکه از useSelector hook استفاده می‌کنیم، با بازگشت یک شیء جدید، یعنی یک ارجاع جدید را خواهیم داشت و ... این یعنی اجبار به رندر مجدد کامپوننت‌ها. به همین جهت در این حالت تعداد بار رندر کامپوننت‌ها افزایش یافته‌است، چون خروجی reducerهای تعریف شده‌ی در برنامه، همیشه یک شیء جدید را بازگشت می‌دهند.
برای رفع این مشکل می‌توان از پارامتر دوم متد useSelector که روش مقایسه‌ی اشیاء را مشخص می‌کند، استفاده کرد:
import React from "react";
import { shallowEqual, useSelector } from "react-redux";

import Post from "./Post";

const HooksPosts = () => {
  const { posts, loading, error } = useSelector(
    state => state.postsReducer,
    shallowEqual
  );
  console.log("render HooksPosts");
  return (
  // ...
استفاده از shallowEqual در اینجا سبب خواهد شد تا بجای مقایسه‌ی ارجاعات اشیاء (که همیشه متفاوت خواهند بود؛ چون هربار شیء جدیدی را بازگشت می‌دهیم)، مقادیر تک تک خواص آن‌ها با هم مقایسه شوند.
با اضافه کردن پارامتر shallowEqual به کامپوننت‌های HooksPosts و HooksCounter، دیگر با کلیک بر روی دکمه‌ی fetch posts، کار رندر مجدد کامپوننت شمارشگر، رخ نمی‌دهد.

یک نکته: روش دیگر مشاهده‌ی تعداد بار رندر شدن کامپوننت‌ها، استفاده از افزونه‌ی react dev tools و مراجعه به برگه‌ی profiler آن است. روی دکمه‌ی record آن کلیک کرده و سپس اندکی با برنامه کار کنید. اکنون کار ضبط را متوقف نمائید، تا نتیجه‌ی نهایی نمایش داده شود.

کدهای کامل این قسمت را می‌توانید از اینجا دریافت کنید: state-management-redux-mobx-part05.zip
مطالب
بررسی تغییرات Blazor 8x - قسمت یازدهم - قالب جدید پیاده سازی اعتبارسنجی و احراز هویت - بخش اول
قالب‌های پیش‌فرض Blazor 8x، به همراه قسمت بازنویسی شده‌ی ASP.NET Core Identity برای Blazor هم هستند که در این قسمت به بررسی نحوه‌ی عملکرد آن‌ها می‌پردازیم.


معرفی قالب‌های جدید شروع پروژه‌های Blazor در دات نت 8 به همراه قسمت Identity

در قسمت دوم این سری، با قالب‌های جدید شروع پروژه‌های Blazor 8x آشنا شدیم و هدف ما در آنجا بیشتر بررسی حالت‌های مختلف رندر Blazor در دات نت 8 بود. تمام این قالب‌ها به همراه یک سوئیچ دیگر هم به نام auth-- هستند که توسط آن و با مقدار دهی Individual که به معنای Individual accounts است، می‌توان کدهای پیش‌فرض و ابتدایی Identity UI جدید را نیز به قالب در حال ایجاد، به صورت خودکار اضافه کرد؛ یعنی به صورت زیر:

اجرای قسمت‌های تعاملی برنامه بر روی سرور؛ به همراه کدهای Identity:
dotnet new blazor --interactivity Server --auth Individual

اجرای قسمت‌های تعاملی برنامه در مرورگر، توسط فناوری وب‌اسمبلی؛ به همراه کدهای Identity:
dotnet new blazor --interactivity WebAssembly --auth Individual

برای اجرای قسمت‌های تعاملی برنامه، ابتدا حالت Server فعالسازی می‌شود تا فایل‌های WebAssembly دریافت شوند، سپس فقط از WebAssembly استفاده می‌کند؛ به همراه کدهای Identity:
dotnet new blazor --interactivity Auto --auth Individual

فقط از حالت SSR یا همان static server rendering استفاده می‌شود (این نوع برنامه‌ها تعاملی نیستند)؛ به همراه کدهای Identity:
dotnet new blazor --interactivity None --auth Individual

 و یا توسط پرچم all-interactive--، که interactive render mode را در ریشه‌ی برنامه قرار می‌دهد؛ به همراه کدهای Identity:
 dotnet new blazor --all-interactive --auth Individual

یک نکته: بانک اطلاعاتی پیش‌فرض مورد استفاده‌ی در این نوع پروژه‌ها، SQLite است. برای تغییر آن می‌توانید از پرچم use-local-db-- هم استفاده کنید تا از LocalDB بجای SQLite استفاده کند.


Identity UI جدید Blazor در دات نت 8، یک بازنویسی کامل است


در نگارش‌های قبلی Blazor هم امکان افزودن Identity UI، به پروژه‌های Blazor وجود داشت (اطلاعات بیشتر)، اما ... آن پیاده سازی، کاملا مبتنی بر Razor pages بود. یعنی کاربر، برای کار با آن مجبور بود از فضای برای مثال Blazor Server خارج شده و وارد فضای جدید ASP.NET Core شود تا بتواند، Identity UI نوشته شده‌ی با صفحات cshtml. را اجرا کند و به اجزای آن دسترسی پیدا کند (یعنی عملا آن قسمت UI اعتبارسنجی، Blazor ای نبود) و پس از اینکار، مجددا به سمت برنامه‌ی Blazor هدایت شود؛ اما ... این مشکل در دات نت 8 با ارائه‌ی صفحات SSR برطرف شده‌است.
همانطور که در قسمت قبل نیز بررسی کردیم، صفحات SSR، طول عمر کوتاهی دارند و هدف آن‌ها تنها ارسال یک HTML استاتیک به مرورگر کاربر است؛ اما ... دسترسی کاملی را به HttpContext برنامه‌ی سمت سرور دارند و این دقیقا چیزی است که زیر ساخت Identity، برای کار و تامین کوکی‌های مورد نیاز خودش، احتیاج دارد. صفحات Identity UI از یک طرف از HttpContext برای نوشتن کوکی لاگین موفقیت آمیز در مرورگر کاربر استفاده می‌کنند (در این سیستم، هرجائی متدهای XyzSignInAsync وجود دارد، در پشت صحنه و در پایان کار، سبب درج یک کوکی اعتبارسنجی و احراز هویت در مرورگر کاربر نیز خواهد شد) و از طرف دیگر با استفاده از میان‌افزارهای اعتبارسنجی و احراز هویت ASP.NET Core، این کوکی‌ها را به صورت خودکار پردازش کرده و از آن‌ها جهت ساخت خودکار شیء User قابل دسترسی در این صفحات (شیء context.User.Identity@)، استفاده می‌کنند.
به همین جهت تمام صفحات Identity UI ارائه شده در Blazor 8x، از نوع SSR هستند و اگر در آن‌ها از فرمی استفاده شده، دقیقا همان فرم‌های تعاملی است که در قسمت چهارم این سری بررسی کردیم. یعنی صرفا بخاطر داشتن یک فرم، ضرورتی به استفاده‌ی از جزایر تعاملی Blazor Server و یا Blazor WASM را ندیده‌اند و اینگونه فرم‌ها را بر اساس مدل جدید فرم‌های تعاملی Blazor 8x پیاده سازی کرده‌اند. بنابراین این صفحات کاملا یکدست هستند و از ابتدا تا انتها، به صورت یکپارچه بر اساس مدل SSR کار می‌کنند (که در اصل خیلی شبیه به Razor pages یا Viewهای MVC هستند) و جزیره، جزیره‌ای، طراحی نشده‌اند.

 
روش دسترسی به HttpContext در صفحات SSR

اگر به کدهای Identity UI قالب آغازین یک پروژه که در ابتدای بحث روش تولید آن‌ها بیان شد، مراجعه کنید، استفاده از یک چنین «پارامترهای آبشاری» را می‌توان مشاهده کرد:

@code {

    [CascadingParameter]
    public HttpContext HttpContext { get; set; } = default!;
متد ()AddRazorComponents ای که در فایل Program.cs اضافه می‌شود، کار ثبت CascadingParameter ویژه‌ی فوق را به صورت زیر انجام می‌دهد که در Blazor 8x به آن یک Root-level cascading value می‌گویند:
services.TryAddCascadingValue(sp => sp.GetRequiredService<EndpointHtmlRenderer>().HttpContext);
مقادیر آبشاری برای ارسال اطلاعاتی بین درختی از کامپوننت‌ها، از یک والد به چندین فرزند که چندین لایه ذیل آن واقع شده‌‌اند، مفید است. برای مثال فرض کنید می‌خواهید اطلاعات عمومی تنظیمات یک کاربر را به چندین زیر کامپوننت، ارسال کنید. یک روش آن، ارسال شیء آن به صورت پارامتر، به تک تک آن‌ها است و راه دیگر، تعریف یک CascadingParameter است؛ شبیه به کاری که در اینجا انجام شده‌است تا دیگر نیازی نباشد تا تک تک زیر کامپوننت‌ها این شیء را به یک لایه زیریرین خود، یکی یکی منتقل کنند.

در کدهای Identity UI ارائه شده، از این CascadingParameter برای مثال جهت خروج از برنامه (HttpContext.SignOutAsync) و یا دسترسی به اطلاعات هدرهای رسید (if (HttpMethods.IsGet(HttpContext.Request.Method)))، دسترسی به سرویس‌ها (()<HttpContext.Features.Get<ITrackingConsentFeature)، تامین مقدار Cancellation Token به کمک HttpContext.RequestAborted و یا حتی در صفحه‌ی جدید Error.razor که آن نیز یک صفحه‌ی SSR است، برای دریافت HttpContext?.TraceIdentifier استفاده‌ی مستقیم شده‌است.

نکته‌ی مهم: باید به‌خاطر داشت که فقط و فقط در صفحات SSR است که می‌توان به HttpContext به نحو آبشاری فوق دسترسی یافت و همانطور که در قسمت قبل نیز بررسی شد، سایر حالات رندر Blazor از دسترسی به آن، پشتیبانی نمی‌کنند و اگر چنین پارامتری را تنظیم کردید، نال دریافت می‌کنید.


بررسی تفاوت‌های تنظیمات ابتدایی قالب جدید Identity UI در Blazor 8x با نگارش‌های قبلی آن

مطالب و نکات مرتبط با قالب قبلی را در مطلب «Blazor 5x - قسمت 22 - احراز هویت و اعتبارسنجی کاربران Blazor Server - بخش 2 - ورود به سیستم و خروج از آن» می‌توانید مشاهده کنید.

در قسمت سوم این سری، روش ارتقاء یک برنامه‌ی قدیمی Blazor Server را به نگارش 8x آن بررسی کردیم و یکی از تغییرات مهم آن، حذف فایل‌های cshtml. ای آغاز برنامه و انتقال وظایف آن، به فایل جدید App.razor و انتقال مسیریاب Blazor به فایل جدید Routes.razor است که پیشتر در فایل App.razor نگارش‌های قبلی Blazor وجود داشت.
در این نگارش جدید، محتوای فایل Routes.razor به همراه قالب Identity UI به صورت زیر است:
<Router AppAssembly="@typeof(Program).Assembly">
    <Found Context="routeData">
        <AuthorizeRouteView RouteData="@routeData" DefaultLayout="@typeof(Layout.MainLayout)" />
        <FocusOnNavigate RouteData="@routeData" Selector="h1" />
    </Found>
</Router>
در نگارش‌های قبلی، این مسیریاب داخل کامپوننت CascadingAuthenticationState محصور می‌شد تا توسط آن بتوان AuthenticationState را به تمام کامپوننت‌های برنامه ارسال کرد. به این ترتیب برای مثال کامپوننت AuthorizeView، شروع به کار می‌کند و یا توسط شیء context.User می‌توان به User claims دسترسی یافت و یا به کمک ویژگی [Authorize]، دسترسی به صفحه‌ای را محدود به کاربران اعتبارسنجی شده کرد.
اما ... در اینجا با یک نگارش ساده شده سروکار داریم؛ از این جهت که برنامه‌های جدید، به همراه جزایر تعاملی هم می‌توانند باشند و باید بتوان این AuthenticationState را به آن‌ها هم ارسال کرد. به همین جهت مفهوم جدید مقادیر آبشاری سطح ریشه (Root-level cascading values) که پیشتر در این بحث معرفی شد، در اینجا برای معرفی AuthenticationState استفاده شده‌است.

در اینجا کامپوننت جدید FocusOnNavigate را هم مشاهده می‌کنید. با استفاده از این کامپوننت و براساس CSS Selector معرفی شده، پس از هدایت به یک صفحه‌ی جدید، این المان مشخص شده دارای focus خواهد شد. هدف از آن، اطلاع رسانی به screen readerها در مورد هدایت به صفحه‌ای دیگر است (برای مثال، کمک به نابیناها برای درک بهتر وضعیت جاری).

همچنین در اینجا المان NotFound را هم مشاهده نمی‌کنید. این المان فقط در برنامه‌های WASM جهت سازگاری با نگارش‌های قبلی، هنوز هم قابل استفاده‌است. جایگزین آن‌را در قسمت سوم این سری، برای برنامه‌های Blazor server در بخش «ایجاد فایل جدید Routes.razor و مدیریت سراسری خطاها و صفحات یافت نشده» آن بررسی کردیم.


روش انتقال اطلاعات سراسری اعتبارسنجی یک کاربر به کامپوننت‌ها و جزایر تعاملی واقع در صفحات SSR

مشکل! زمانیکه از ترکیب صفحات SSR و جزایر تعاملی واقع در آن استفاده می‌کنیم ... جزایر واقع در آن‌ها دیگر این مقادیر آبشاری را دریافت نمی‌کنند و این مقادیر در آن‌ها نال است. برای حل این مشکل در Blazor 8x، باید مقادیر آبشاری سطح ریشه را (Root-level cascading values) به صورت زیر در فایل Program.cs برنامه ثبت کرد:
builder.Services.AddCascadingValue(sp =>new Preferences { Theme ="Dark" });
پس از این تغییر، اکنون نه فقط صفحات SSR، بلکه جزایر واقع در آن‌ها نیز توسط ویژگی [CascadingParameter] می‌توانند به این مقدار آبشاری، دسترسی داشته باشند. به این ترتیب است که در برنامه‌های Blazor، کامپوننت‌های تعاملی هم دسترسی به اطلاعات شخص لاگین شده‌ی توسط صفحات SSR را دارند. برای مثال اگر به فایل Program.cs قالب جدید Identity UI عنوان شده مراجعه کنید، چنین سطوری در آن قابل مشاهده هستند
builder.Services.AddCascadingAuthenticationState();
builder.Services.AddScoped<AuthenticationStateProvider, IdentityRevalidatingAuthenticationStateProvider>();
متد جدید AddCascadingAuthenticationState، فقط کار ثبت AuthenticationStateProvider برنامه را به صورت آبشاری انجام می‌دهد.
این AuthenticationStateProvider سفارشی جدید هم در فایل اختصاصی IdentityRevalidatingAuthenticationStateProvider.cs پوشه‌ی Identity قالب پروژه‌های جدید Blazor 8x که به همراه اعتبارسنجی هستند، قابل مشاهده‌است.

یا اگر به قالب‌های پروژه‌های WASM دار مراجعه کنید، تعریف به این صورت تغییر کرده‌است؛ اما مفهوم آن یکی است:
builder.Services.AddCascadingAuthenticationState();
builder.Services.AddScoped<AuthenticationStateProvider, PersistingServerAuthenticationStateProvider>();
در این پروژه‌ها، یک AuthenticationStateProvider سفارشی دیگری در فایل PersistingServerAuthenticationStateProvider.cs ارائه شده‌است (این فایل‌ها، جزو استاندارد قالب‌های جدید Identity UI پروژه‌های Blazor 8x هستند؛ فقط کافی است، یک نمونه پروژه‌ی آزمایشی را با سوئیچ جدید auth Individual-- ایجاد کنید، تا بتوانید این فایل‌های یاد شده را مشاهده نمائید).

AuthenticationStateProviderهای سفارشی مانند کلاس‌های IdentityRevalidatingAuthenticationStateProvider و PersistingServerAuthenticationStateProvider که در این قالب‌ها موجود هستند، چون به صورت آبشاری کار تامین AuthenticationState را انجام می‌دهند، به این ترتیب می‌توان به شیء context.User.Identity@ در جزایر تعاملی نیز به سادگی دسترسی داشت.

یعنی به صورت خلاصه، یکبار قرارداد AuthenticationStateProvider عمومی (بدون هیچ نوع پیاده سازی) به صورت یک Root-level cascading value ثبت می‌شود تا در کل برنامه قابل دسترسی شود. سپس یک پیاده سازی اختصاصی از آن توسط ()<builder.Services.AddScoped<AuthenticationStateProvider, XyzProvider به برنامه معرفی می‌شود تا آن‌را تامین کند. به این ترتیب زیر ساخت امن سازی صفحات با استفاده از ویژگی attribute [Authorize]@ و یا دسترسی به اطلاعات کاربر جاری با استفاده از شیء context.User@ و یا امکان استفاده از کامپوننت AuthorizeView برای نمایش اطلاعاتی ویژه به کاربران اعتبارسنجی شده مانند صفحه‌ی Auth.razor زیر، مهیا می‌شود:
@page "/auth"

@using Microsoft.AspNetCore.Authorization

@attribute [Authorize]

<PageTitle>Auth</PageTitle>

<h1>You are authenticated</h1>

<AuthorizeView>
    Hello @context.User.Identity?.Name!
</AuthorizeView>

سؤال: چگونه یک پروژه‌ی سمت سرور، اطلاعات اعتبارسنجی خودش را با یک پروژه‌ی WASM سمت کلاینت به اشتراک می‌گذارد (برای مثال در حالت رندر Auto)؟ این انتقال اطلاعات باتوجه به یکسان نبودن محل اجرای این دو پروژه (یکی بر روی کامپیوتر سرور و دیگری بر روی مرورگر کلاینت، در کامپیوتری دیگر) چگونه انجام می‌شود؟ این مورد را در قسمت بعد، با معرفی PersistentComponentState و «فیلدهای مخفی» مرتبط با آن، بررسی می‌کنیم.
مطالب
ویژگی Static Using Statements در سی شارپ 6
مروری بر کاربردهای مختلف دستور Using تا پیش از ارائه‌ی سی شارپ 6
1- اضافه کردن فضاهای نام مختلف، برای سهولت دسترسی به اعضای آن:
using System.Collections.Generic;
2- تعریف نام مستعار (alias name) برای نوع داده‌ها و فضای نام‌ها
using BLL = DotNetTipsBLLLayer;//نام مستعار برای فضای نام
using EmployeeDomain = DotNetTipsBLLLayer.Employee;//نام مستعار برای یک نوع داده
3- تعریف یک بازه و مشخص کردن زمان تخریب یک شیء و آزاد سازی حافظه‌ی تخصیص داده شده:
using (var sqlConnection = new SqlConnection())
            {
                //کد 
            }
در سی شارپ 6 ، Static Using Statements برای بهبود کدنویسی و تمیز‌تر نوشتن کد‌ها ارائه شده‌است.
در ابتدا نحوه‌ی عملکرد اعضای static را مرور می‌کنیم. متغیر‌ها و متدهایی که با کلمه‌ی کلیدی static معرفی می‌شوند، اعلام می‌کنند که برای استفاده‌ی از آنها به نمونه سازی کلاس آن‌ها احتیاجی نیست و برای استفاده‌ی از آنها کافی است نام کلاس را تایپ کرده (بدون نوشتن new) و متد و یا خصوصیت مورد نظر را فراخوانی کنیم.
با معرفی ویژگی جدید Static Using Statement نوشتن نام کلاس برای فراخوانی اعضای استاتیک نیز حذف می‌شود.
اتفاق خوبی است اگر بتوان  اعضای استاتیک را همچون  Data Typeهای موجود در سی شارپ استفاده کرد. مثلا بتوان به جای ()Console.WrriteLine  نوشت ()WriteLine  
نحوه استفاده از این ویژگی: در ابتدای فایل و بخش معرفی کتابخانه‌ها بدین شکل عمل می‌کنیم using static namespace.className .
در بخش className،  نام کلاس استاتیک مورد نظر خود را می‌نویسیم .
مثال : 
 using static System.Console;
using static System.Math;

namespace dotnettipsUsingStatic
{
    class Program
    {
        static void Main(string[] args)
        {

            Write(" *** Cal Area *** ");
            int r = int.Parse(ReadLine());
            var result = Pow(r, 2) * PI;
            Write($"Area  is : {result}");
            ReadKey();
       }
    }
}

همان طور که در کدهای فوق می‌بینید، کلاس‌های Console و Math، در ابتدای فایل با استفاده از ویژگی جدید سی شارپ 6 معرفی شده‌اند و در بدنه برنامه تنها با فراخوانی نام متد‌ها و خصوصیت‌ها از آنها استفاده کرده ایم.
 
استفاده از ویژگی using static و Enum:
فرض کنید می‌خواهیم یک نوع داده‌ی شمارشی را برای نمایش جنسیت تعریف کنیم:
enum Gender
    {
        Male,
        Female
    }

تا قبل از سی شارپ 6 برای استفاده‌ی از نوع داده شمارشی بدین شکل عمل می‌کردیم: 

var gender = Gender.Male;

و اکنون بازنویسی استفاده‌ی ازEnum  به کمک ویژگی جدید static using statement :

در قسمت معرفی فضاهای نام بدین شکل عمل می‌کنیم: 

using static dotnettipsUsingStatic.Gender;

و در برنامه کافیست مستقیما نام اعضای Enum  را ذکر کنیم  .

var gender = Male;//تخصیص نوع داده شمارشی
WriteLine($"Employee Gender is : {Male}");//استفاده مستقیم از نوع داده شمارشی


استفاده از ویژگی using static و متد‌های الحاقی :

تا قبل از ارائه سی شارپ 6 اگر نیاز به استفاده‌ی از یک متد الحاقی خاص همچون where در فضای نام System.Linq.Enumeable داشتیم می‌بایستی فضای نام System.Linq را به طور کامل اضافه می‌کردیم و راهی برای اضافه کردن یک فضای نام خاص درون فضای نام بزرگتر وجود نداشت. 

اما با قابلیت جدید اضافه شده می‌توانیم بخشی از یک فضای نام  را اضافه کنیم:

  using static System.Linq.Enumerable;


متد‌های استاتیک و متد‌های الحاقی در زمان استفاده از ویژگی using static:

فرض کنید کلاس  static ای بنام MyStaticClass داشته باشیم که متد Print1  و  Print2 در آن تعریف شده باشند:

public static class MyStaticClass
    {
        public static void Print1(string parameter)
        {
            WriteLine(parameter);
        }
        public static void  Print2(this string parameter)
        {
            WriteLine(parameter);
        }

    }

برای استفاده از متد‌های تعریف شده به شکل زیر عمل می‌کنیم : 

//فراخوانی تابع استاتیک
Print1("Print 1");//روش اول
MyStaticClass.Print1("Prtint 1");//روش دوم
//فراخوانی متد الحاقی استاتیک
MyStaticClass.Print2("Print 2");
"print 2".Print2();


ویژگی‌های جدید ارائه شده در سی شارپ 6 برای افزایش خوانایی برنامه‌ها و تمیز‌تر شدن کد‌ها اضافه شده‌اند. در مورد ویژگی‌های ارائه شده در مقاله‌ی جاری این نکته مهم است که گاهی قید کردن نام کلاس‌ها خود سبب افزایش خوانایی کد‌ها می‌شود .

مطالب
ASP.NET MVC #21

آشنایی با تکنیک‌های Ajax در ASP.NET MVC

اهمیت آشنایی با Ajax، ارائه تجربه‌ کاربری بهتری از برنامه‌های وب، به مصرف کنندگان نهایی آن می‌باشد. به این ترتیب می‌توان درخواست‌های غیرهمزمانی (asynchronous) را با فرمت XML یا Json به سرور ارسال کرد و سپس نتیجه نهایی را که حجم آن نسبت به یک صفحه کامل بسیار کمتر است، به کاربر ارائه داد. غیرهمزمان بودن درخواست‌ها سبب می‌شود تا ترد اصلی رابط کاربری برنامه قفل نشده و کاربر در این بین می‌تواند به سایر امور خود بپردازد. به این ترتیب می‌توان برنامه‌های وبی را که شبیه به برنامه‌های دسکتاپ هستند تولید نمود؛ کل صفحه مرتبا به سرور ارسال نمی‌شود، flickering و چشمک زدن صفحه کاهش خواهد یافت (چون نیازی به ترسیم مجدد کل صفحه نخواهد بود و عموما قسمتی جزئی از یک صفحه به روز می‌شود) یا بدون نیاز به ارسال کل صفحه به سرور، به کاربری خواهیم گفت که آیا اطلاعاتی که وارد کرده است معتبر می‌باشد یا نه (نمونه‌ای از آن‌ را در قسمت Remote validation اعتبار سنجی اطلاعات ملاحظه نمودید).


مروری بر محتویات پوشه Scripts یک پروژه جدید ASP.NET MVC در ویژوال استودیو

با ایجاد هر پروژه ASP.NET MVC‌ جدیدی در ویژوال استودیو، یک سری اسکریپت‌ هم به صورت خودکار در پوشه Scripts آن اضافه می‌شوند. تعدادی از این فایل‌ها توسط مایکروسافت پیاده سازی شده‌اند. برای مثال:
MicrosoftAjax.debug.js
MicrosoftAjax.js
MicrosoftMvcAjax.debug.js
MicrosoftMvcAjax.js
MicrosoftMvcValidation.debug.js
MicrosoftMvcValidation.js

این فایل‌ها از ASP.NET MVC 3 به بعد، صرفا جهت سازگاری با نگارش‌های قبلی قرار دارند و استفاده از آن‌ها اختیاری است. بنابراین با خیال راحت آن‌ها را delete کنید! روش توصیه شده جهت پیاده سازی ویژگی‌های Ajax ایی، استفاده از کتابخانه‌های مرتبط با jQuery می‌باشد؛ از این جهت که 100ها افزونه برای کار با آن توسط گروه وسیعی از برنامه نویس‌ها در سراسر دنیا تاکنون تهیه شده است. به علاوه فریم ورک jQuery تنها منحصر به اعمال Ajax ایی نیست و از آن جهت دستکاری DOM (document object model) و CSS صفحه نیز می‌توان استفاده کرد. همچنین حجم کمی نیز داشته،‌ با انواع و اقسام مرورگرها سازگار است و مرتبا هم به روز می‌شود.

در این پوشه سه فایل دیگر پایه کتابخانه jQuery نیز قرار دارند:
jquery-xyz-vsdoc.js
jquery-xyz.js
jquery-xyz.min.js

فایل vsdoc برای ارائه نهایی برنامه طراحی نشده است. هدف از آن ارائه Intellisense بهتری از jQuery در VS.NET می‌باشد. فایلی که باید به کلاینت ارائه شود، فایل min یا فشرده شده آن است. اگر به آن نگاهی بیندازیم به نظر obfuscated مشاهده می‌شود. علت آن هم حذف فواصل، توضیحات و همچنین کاهش طول متغیرها است تا اندازه فایل نهایی به حداقل خود کاهش پیدا کند. البته این فایل از دیدگاه مفسر جاوا اسکریپت یک مرورگر، فایل بی‌نقصی است!
اگر علاقمند هستید که سورس اصلی jQuery را مطالعه کنید، به فایل jquery-xyz.js مراجعه نمائید.
محل الحاق اسکریپت‌های عمومی مورد نیاز برنامه نیز بهتر است در فایل master page یا layout برنامه باشد که به صورت پیش فرض اینکار انجام شده است.
سایر فایل‌های اسکریپتی که در این پوشه مشاهده می‌شوند، یک سری افزونه عمومی یا نوشته شده توسط تیم ASP.NET MVC برفراز jQuery هستند.

به چهار نکته نیز حین استفاده از اسکریپت‌های موجود باید دقت داشت:
الف) همیشه از متد Url.Content همانند تعاریفی که در فایل Views\Shared\_Layout.cshtml مشاهده می‌کنید،‌ برای مشخص سازی مسیر ریشه سایت، استفاده نمائید. به این ترتیب صرفنظر از آدرس جاری صفحه، همواره آدرس صحیح قرارگیری پوشه اسکریپت‌ها در صفحه ذکر خواهد شد.
ب) ترتیب فایل‌های js مهم هستند. ابتدا باید کتابخانه اصلی jQuery ذکر شود و سپس افزونه‌های آن‌ها.
ج) اگر اسکریپت‌های jQuery در فایل layout سایت تعریف شده‌اند؛ نیازی به تعریف مجدد آن‌ها در View‌های سایت نیست.
د) اگر View ایی به اسکریپت ویژه‌ای جهت اجرا نیاز دارد، بهتر است آن‌را به شکل یک section داخل view تعریف کرد و سپس به کمک متد RenderSection این قسمت را در layout سایت مقدار دهی نمود. مثالی از آن‌را در قسمت 20 این سری مشاهده نمودید (افزودن نمایش جمع هر ستون گزارش).


یک نکته
اگر آخرین به روز رسانی‌های ASP.NET MVC را نیز نصب کرده باشید، فایلی به نام packages.config به صورت پیش فرض به هر پروژه جدید ASP.NET MVC اضافه می‌شود. به این ترتیب VS.NET به کمک NuGet این امکان را خواهد یافت تا شما را از آخرین به روز رسانی‌های این کتابخانه‌ها مطلع کند.


آشنایی با Ajax Helpers توکار ASP.NET MVC

اگر به تعاریف خواص و متدهای کلاس WebViewPage دقت کنیم:

using System;

namespace System.Web.Mvc
{
public abstract class WebViewPage<TModel> : WebViewPage
{
protected WebViewPage();
public AjaxHelper<TModel> Ajax { get; set; }
public HtmlHelper<TModel> Html { get; set; }
public TModel Model { get; }
public ViewDataDictionary<TModel> ViewData { get; set; }
public override void InitHelpers();
protected override void SetViewData(ViewDataDictionary viewData);
}
}

علاوه بر خاصیت Html که وهله‌ای از آن امکان دسترسی به Html helpers توکار ASP.NET MVC را در یک View فراهم می‌کند، خاصیتی به نام Ajax نیز وجود دارد که توسط آن می‌توان به تعدادی متد AjaxHelper توکار دسترسی داشت. برای مثال توسط متد Ajax.ActionLink می‌توان قسمتی از صفحه را به کمک ویژگی‌های Ajax، به روز رسانی کرد.


مثالی در مورد به روز رسانی قسمتی از صفحه به کمک متد Ajax.ActionLink

ابتدا نیاز است فایل Views\Shared\_Layout.cshtml را اندکی ویرایش کرد. برای این منظور سطر الحاق jquery.unobtrusive-ajax.min.js را به فایل layout برنامه اضافه نمائید (اگر این سطر اضافه نشود، متد Ajax.ActionLink همانند یک لینک معمولی رفتار خواهد کرد):

<head>
<title>@ViewBag.Title</title>
<link href="@Url.Content("~/Content/Site.css")" rel="stylesheet" type="text/css" />
<script src="@Url.Content("~/Scripts/jquery-1.5.1.min.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery.unobtrusive-ajax.min.js")" type="text/javascript"></script>
</head>

سپس مدل ساده و منبع داده زیر را نیز به پروژه اضافه کنید:

namespace MvcApplication18.Models
{
public class Employee
{
public int Id { set; get; }
public string Name { set; get; }
}
}

using System.Collections.Generic;

namespace MvcApplication18.Models
{
public static class EmployeeDataSource
{
public static IList<Employee> CreateEmployees()
{
var list = new List<Employee>();
for (int i = 0; i < 1000; i++)
{
list.Add(new Employee { Id = i + 1, Name = "name " + i });
}
return list;
}
}
}

در ادامه کنترلر جدیدی را به برنامه با محتوای زیر اضافه کنید:

using System.Linq;
using System.Web.Mvc;
using MvcApplication18.Models;

namespace MvcApplication18.Controllers
{
public class HomeController : Controller
{
[HttpGet]
public ActionResult Index()
{
return View();
}

[HttpPost] //for IE-8
public ActionResult EmployeeInfo(int? id)
{
if (!Request.IsAjaxRequest())
return View("Error");

if (!id.HasValue)
return View("Error");

var list = EmployeeDataSource.CreateEmployees();
var data = list.Where(x => x.Id == id.Value).FirstOrDefault();
if (data == null)
return View("Error");

return PartialView(viewName: "_EmployeeInfo", model: data);
}
}
}

بر روی متد Index کلیک راست کرده و گزینه Add view را انتخاب کنید. یک View خالی را به آن اضافه نمائید. همچنین بر روی متد EmployeeInfo کلیک راست کرده و با انتخاب گزینه Add view در صفحه ظاهر شده یک partial view را اضافه نمائید. جهت تمایز بین partial view و view هم بهتر است نام partial view با یک underline شروع شود.
کدهای partial view مورد نظر را به نحو زیر تغییر دهید:

@model MvcApplication18.Models.Employee

<strong>Name:</strong> @Model.Name

سپس کدهای View متناظر با متد Index را نیز به صورت زیر اعمال کنید:

@{
ViewBag.Title = "Index";
}
<h2>
Index</h2>

<div id="EmployeeInfo">
@Ajax.ActionLink(
linkText: "Get Employee-1 info",
actionName: "EmployeeInfo",
controllerName: "Home",
routeValues: new { id = 1 },
ajaxOptions: new AjaxOptions
{
HttpMethod = "POST",
InsertionMode = InsertionMode.Replace,
UpdateTargetId = "EmployeeInfo",
LoadingElementId = "Progress"
})
</div>

<div id="Progress" style="display: none">
<img src="@Url.Content("~/Content/images/loading.gif")" alt="loading..." />
</div>

توضیحات جزئیات کدهای فوق

متد Ajax.ActionLink لینکی را تولید می‌کند که با کلیک کاربر بر روی آن، اطلاعات اکشن متد واقع در کنترلری مشخص، به کمک ویژگی‌های jQuery Ajax دریافت شده و سپس در مقصدی که توسط UpdateTargetId مشخص می‌گردد، بر اساس مقدار InsertionMode،‌ درج خواهد شد (می‌تواند قبل از آن درج شود یا پس از آن و یا اینکه کل محتوای مقصد را بازنویسی کند). HttpMethod آن هم به POST تنظیم شده تا با IE‌ مشکلی نباشد. از این جهت که IE پیغام‌های GET را کش می‌کند و مساله ساز خواهد شد. توسط پارامتر routeValues، آرگومان مورد نظر به متد EmployeeInfo ارسال خواهد شد.
به علاوه یکی دیگر از خواص کلاس AjaxOptions، برای معرفی حالت بروز خطایی در سمت سرور به نام OnFailure در نظر گرفته شده است. در اینجا می‌توان نام یک متد JavaScript ایی را مشخص کرده و پیغام خطای عمومی را در صورت فراخوانی آن به کاربر نمایش داد. یا توسط خاصیت Confirm آن می‌توان یک پیغام را پیش از ارسال اطلاعات به سرور به کاربر نمایش داد.
به این ترتیب در مثال فوق، id=1 به متد EmployeeInfo به صورت غیرهمزمان ارسال می‌گردد. سپس کارمندی بر این اساس یافت شده و در ادامه partial view مورد نظر بر اساس اطلاعات کاربر مذکور، رندر خواهد شد. نتیجه کار، در یک div با id مساوی EmployeeInfo درج می‌گردد (InsertionMode.Replace). متد Ajax.ActionLink از این جهت داخل div تعریف شده‌است که پس از کلیک کاربر و جایگزینی محتوا، محو شود. اگر نیازی به محو آن نبود، آن‌را خارج از div تعریف کنید.
عملیات دریافت اطلاعات از سرور ممکن است مدتی طول بکشد (برای مثال دریافت اطلاعات از بانک اطلاعاتی). به همین جهت بهتر است در این بین از تصاویری که نمایش دهنده انجام عملیات است، استفاده شود. برای این منظور یک div با id مساوی Progress تعریف شده و id آن به LoadingElementId انتساب داده شده است. این div با توجه به display: none آن، در ابتدای امر به کاربر نمایش داده نخواهد شد؛ در آغاز کار دریافت اطلاعات از سرور توسط متد Ajax.ActionLink نمایان شده و پس از خاتمه کار مجددا مخفی خواهد شد.
به علاوه اگر به کدهای فوق دقت کرده باشید، از متد Request.IsAjaxRequest نیز استفاده شده است. به این ترتیب می‌توان تشخیص داد که آیا درخواست رسیده از طرف jQuery Ajax صادر شده است یا خیر. البته آنچنان روش قابل ملاحظه‌ای نیست؛ چون امکان دستکاری Http Headers همیشه وجود دارد؛ اما بررسی آن ضرری ندارد. البته این نوع بررسی‌ها را در ASP.NET MVC بهتر است تبدیل به یک فیلتر سفارشی نمود؛ به این ترتیب حجم if و else نویسی در متدهای کنترلرها به حداقل خواهد رسید. برای مثال:

[AttributeUsage(AttributeTargets.Class|AttributeTargets.Method)]
public class AjaxOnlyAttribute : ActionFilterAttribute
{
  public override void OnActionExecuting(ActionExecutingContext filterContext)
  {
    if (filterContext.HttpContext.Request.IsAjaxRequest())
    {
      base.OnActionExecuting(filterContext);
    }
    else
    {
      throw new InvalidOperationException("This operation can only be accessed via Ajax requests");
    }
  }
}

و برای استفاده از آن خواهیم داشت:

[AjaxOnly]
public ActionResult SomeAjaxAction()
{
    return Content("Hello!");
}


در مورد کلمه unobtrusive در قسمت بررسی نحوه اعتبار سنجی اطلاعات، توضیحاتی را ملاحظه نموده‌اید. در اینجا نیز از ویژگی‌های data-* برای معرفی پارامترهای مورد نیاز حین ارسال اطلاعات به سرور، استفاده می‌گردد. برای مثال خروجی متد Ajax.ActionLink به شکل زیر است. به این ترتیب امکان حذف کدهای جاوا اسکریپت از صفحه فراهم می‌شود و توسط یک فایل jquery.unobtrusive-ajax.min.js که توسط تیم ASP.NET MVC تهیه شده، اطلاعات مورد نیاز به سرور ارسال خواهد گردید:
<a data-ajax="true" data-ajax-loading="#Progress" data-ajax-method="POST" 
data-ajax-mode="replace" data-ajax-update="#EmployeeInfo"
href="/Home/EmployeeInfo/1">Get Employee-1 info</a>

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


به روز رسانی اطلاعات قسمتی از صفحه بدون استفاده از متد Ajax.ActionLink

الزامی به استفاده از متد Ajax.ActionLink و فایل jquery.unobtrusive-ajax.min.js وجود ندارد. اینکار را مستقیما به کمک jQuery نیز می‌توان به نحو زیر انجام داد:

<a href="#" onclick="LoadEmployeeInfo()">Get Employee-1 info</a>
@section javascript
{
<script type="text/javascript">
function LoadEmployeeInfo() {
showProgress();
$.ajax({
type: "POST",
url: "/Home/EmployeeInfo",
data: JSON.stringify({ id: 1 }),
contentType: "application/json; charset=utf-8",
dataType: "json",
// controller is returning a simple text, not json
complete: function (xhr, status) {
var data = xhr.responseText;
if (status === 'error' || !data) {
//handleError
}
else {
$('#EmployeeInfo').html(data);
}
hideProgress();
}
});
}
function showProgress() {
$('#Progress').css("display", "block");
}
function hideProgress() {
$('#Progress').css("display", "none");
}
</script>
}

توضیحات:
توسط متد jQuery.Ajax نیز می‌توان درخواست‌های Ajax ایی خود را به سرور ارسال کرد. در اینجا type نوع http verb مورد نظر را مشخص می‌کند که به POST تنظیم شده است. Url آدرس کنترلر را دریافت می‌کند. البته حین استفاده از متد توکار Ajax.ActionLink،‌ این لینک به صورت خودکار بر اساس تعاریف مسیریابی برنامه تنظیم می‌شود. اما در صورت استفاده مستقیم از jQuery.Ajax باید دقت داشت که با تغییر تعاریف مسیریابی برنامه نیاز است تا این Url نیز به روز شود.
سه سطر بعدی نوع اطلاعاتی را که باید به سرور POST شوند مشخص می‌کند. نوع json است و همچنین contentType آن برای ارسال اطلاعات یونیکد ضروری است. از متد JSON.stringify برای تبدیل اشیاء به رشته کمک گرفته‌ایم. این متد در تمام مرورگرهای امروزی به صورت توکار پشتیبانی می‌شود و استفاده از آن سبب خواهد شد تا اطلاعات به نحو صحیحی encode شده و به سرور ارسال شوند. بنابراین این رشته ارسالی اطلاعات را به صورت دستی تهیه نکنید؛ چون کاراکترهای زیادی هستند که ممکن است مشکل ساز شده و باید پیش از ارسال به سرور اصطلاحا escape یا encode شوند.
متداول است از پارامتر success برای دریافت نتیجه عملیات متد jQuery.Ajax استفاده شود. اما در اینجا از پارامتر complete آن استفاده شده است. علت هم اینجا است که return PartialView یک رشته را بر می‌گرداند. پارامتر success انتظار دریافت خروجی از نوع json را دارد. به همین جهت در این مثال خاص باید از پارامتر complete استفاده کرد تا بتوان به رشته بدون فرمت خروجی بدون مشکل دسترسی پیدا کرد.
به علاوه چون از یک section برای تعریف اسکریپت‌های مورد نیاز استفاده کرده‌ایم، برای درج خودکار آن در هدر صفحه باید قسمت هدر فایل layout برنامه را به صورت زیر مقدار دهی کرد:

@RenderSection("javascript", required: false)



دسترسی به اطلاعات یک مدل در View، به کمک jQuery Ajax

اگر جزئی از صفحه که قرار است به روز شود، پیچیده است، روش استفاده از partial viewها توصیه می‌شود؛ برای مثال می‌توان اطلاعات یک مدل را به همراه یک گرید کامل از اطلاعات، رندر کرد و سپس در صفحه درج نمود. اما اگر تنها به اطلاعات چند خاصیت از مدلی نیاز داشتیم، می‌توان از روش‌هایی با سربار کمتر نیز استفاده کرد. برای مثال متد جدید زیر را به کنترلر Home اضافه کنید:

[HttpPost] //for IE-8        
public ActionResult EmployeeInfoData(int? id)
{
if (!Request.IsAjaxRequest())
return Json(false);

if (!id.HasValue)
return Json(false);

var list = EmployeeDataSource.CreateEmployees();
var data = list.Where(x => x.Id == id.Value).FirstOrDefault();
if (data == null)
return Json(false);

return Json(data);
}

سپس View برنامه را نیز به نحو زیر تغییر دهید:

<a href="#" onclick="LoadEmployeeInfoData()">Get Employee-2 info</a>
@section javascript
{
<script type="text/javascript">
function LoadEmployeeInfoData() {
showProgress();
$.ajax({
type: "POST",
url: "/Home/EmployeeInfoData",
data: JSON.stringify({ id: 1 }),
contentType: "application/json; charset=utf-8",
dataType: "json",
// controller is returning the json data
success: function (result) {
if (result) {
alert(result.Id + ' - ' + result.Name);
}
hideProgress();
},
error: function (result) {
alert(result.status + ' ' + result.statusText);
hideProgress();
}
});
}

function showProgress() {
$('#Progress').css("display", "block");
}
function hideProgress() {
$('#Progress').css("display", "none");
}
</script>
}

در این مثال، کنترلر برنامه، اطلاعات مدل را تبدیل به Json کرده و بازگشت خواهد داد. سپس می‌توان به اطلاعات این مدل و خواص آن در View برنامه، در پارامتر success متد jQuery.Ajax، مطابق کدهای فوق دسترسی یافت. اینبار چون خروجی کنترلر تعریف شده از نوع Json است، امکان استفاده از پارامتر success فراهم شده است. همه چیز هم در اینجا خودکار است؛ تبدیل یک شیء به Json و برعکس.
یک نکته: اگر نوع متد کنترلر، HttpGet باشد، نیاز خواهد بود تا پارامتر دوم متد بازگشت Json، مساوی JsonRequestBehavior.AllowGet قرار داده شود.


ارسال اطلاعات فرم‌ها به سرور، به کمک ویژگی‌های Ajax

متد کمکی توکار دیگری به نام Ajax.BeginForm در ASP.NET MVC وجود دارد که کار ارسال غیرهمزمان اطلاعات یک فرم را به سرور انجام داده و سپس اطلاعاتی را از سرور دریافت و قسمتی از صفحه را به روز خواهد کرد. مکانیزم کاری کلی آن بسیار شبیه به متد Ajax.ActionLink می‌باشد. در ادامه با تکمیل مثال قسمت جاری، به بررسی این ویژگی خواهیم پرداخت.
ابتدا متد جستجوی زیر را به کنترلر برنامه اضافه کنید:

[HttpPost] //for IE-8        
public ActionResult SearchEmployeeInfo(string data)
{
if (!Request.IsAjaxRequest())
return Content(string.Empty);

if (string.IsNullOrWhiteSpace(data))
return Content(string.Empty);

var employeesList = EmployeeDataSource.CreateEmployees();
var list = employeesList.Where(x => x.Name.Contains(data)).ToList();
if (list == null || !list.Any())
return Content(string.Empty);

return PartialView(viewName: "_SearchEmployeeInfo", model: list);
}

سپس بر روی نام متد کلیک راست کرده و گزینه add view را انتخاب کنید. در صفحه باز شده، گزینه create a stronlgly typed view را انتخاب کرده و قالب scaffolding را هم بر روی list قرار دهید. سپس گزینه ایجاد partial view را نیز انتخاب کنید. نام آن‌را هم _SearchEmployeeInfo وارد نمائید. برای نمونه خروجی حاصل به نحو زیر خواهد بود:

@model IEnumerable<MvcApplication18.Models.Employee>

<table>
<tr>
<th>
Name
</th>
</tr>

@foreach (var item in Model) {
<tr>
<td>
@Html.DisplayFor(modelItem => item.Name)
</td>
</tr>
}

</table>

تا اینجا یک متد جستجو را ایجاد کرده‌ایم که می‌تواند لیستی از رکوردهای کارمندان را بر اساس قسمتی از نام آن‌ها که توسط کاربری جستجو شده است، بازگشت دهد. سپس این اطلاعات را به partial view مورد نظر ارسال کرده و یک جدول را بر اساس آن تولید خواهیم نمود.
اکنون به فایل Index.cshtml مراجعه کرده و فرم Ajax ایی زیر را اضافه نمائید:

@using (Ajax.BeginForm(actionName: "SearchEmployeeInfo",
controllerName: "Home",
ajaxOptions: new AjaxOptions
{
HttpMethod = "POST",
InsertionMode = InsertionMode.Replace,
UpdateTargetId = "EmployeeInfo",
LoadingElementId = "Progress"
}))
{
@Html.TextBox("data")
<input type="submit" value="Search" />
}

اینبار بجای استفاده از متد Html.BeginForm از متد Ajax.BeginForm استفاده شده است. به کمک آن اطلاعات Html.TextBox تعریف شده، به کنترلر Home و متد SearchEmployeeInfo آن، بر اساس HttpMethod تعریف شده، ارسال گردیده و نتیجه آن در یک div با id مساوی EmployeeInfo درج می‌گردد. همچنین اگر اطلاعاتی یافت نشد، به کمک متد return Content یک رشته خالی بازگشت داده می‌شود.
متد Ajax.BeginForm نیز از ویژگی‌های data-* برای تعریف اطلاعات مورد نیاز ارسالی به سرور استفاده می‌کند. بنابراین نیاز به سطر الحاق jquery.unobtrusive-ajax.min.js در فایل layout برنامه جهت وفق دادن این اطلاعات unobtrusive به اطلاعات مورد نیاز متد jQuery.Ajax وجود دارد.

<form action="/Home/SearchEmployeeInfo" data-ajax="true" 
data-ajax-loading="#Progress" data-ajax-method="POST"
data-ajax-mode="replace" data-ajax-update="#EmployeeInfo"
id="form0" method="post">
<input id="data" name="data" type="text" value="" />
<input type="submit" value="Search" />
</form>


کتابخانه کمکی «ASP.net MVC Awesome - jQuery Ajax Helpers»
علاوه بر متدهای توکار Ajax همراه با ASP.NET MVC، سایر علاقمندان نیز یک سری Ajax helper را بر اساس افزونه‌های jQuery تدارک دیده‌اند که از آدرس زیر قابل دریافت هستند:
http://awesome.codeplex.com/


افزودن فرم‌ها به کمک jQuery.Ajax و فعال سازی اعتبار سنجی سمت کلاینت

در ASP.NET MVC چون ViewState حذف شده است، امکان تزریق فرم‌های جدید به صفحه یا به روز رسانی قسمتی از صفحه توسط jQuery Ajax به سهولت و بدون دریافت پیغام «viewstate is corrupted» در حین ارسال اطلاعات به سرور، میسر است.
در این حالت باید به یک نکته مهم نیز دقت داشت: «اعتبار سنجی سمت کلاینت دیگر کار نمی‌کند». علت اینجا است که در حین بارگذاری متداول یک صفحه، متد زیر به صورت خودکار فراخوانی می‌گردد:
$.validator.unobtrusive.parse("#{form-id}");

اما با به روز رسانی قسمتی از صفحه، دیگر اینچنین نخواهد بود و نیاز است این فراخوانی را دستی انجام دهیم. برای مثال:

$.ajax
({
url: "/{controller}/{action}/{id}",
type: "get",
success: function(data)
{
$.validator.unobtrusive.parse("#{form-id}");
}
});

//or
$.get('/{controller}/{action}/{id}', function (data) { $.validator.unobtrusive.parse("#{form-id}"); });

شبیه به همین مساله را حین استفاده از Ajax.BeginForm نیز باید مد نظر داشت:

@using (Ajax.BeginForm(
    "Action1",
    "Controller",
    null,
    new AjaxOptions {
        OnSuccess = "onSuccess",
        UpdateTargetId = "result"
    },
    null)
)
{
    <input type="submit" value="Save" />
}

var onSuccess = function(result) {
    // enable unobtrusive validation for the contents
    // that was injected into the <div id="result"></div> node
    $.validator.unobtrusive.parse("#result");
};

در این مثال در پارامتر UpdateTargetId، مجددا یک فرم رندر می‌شود. بنابراین اعتبار سنجی سمت کلاینت آن دیگر کار نخواهد کرد مگر اینکه با مقدار دهی خاصیت OnSuccess، مجددا متد unobtrusive.parse را فراخوانی کنیم.


مطالب
Asp.Net Identity #1
API ، Identity جدید مایکروسافت جهت مدیریت کاربران در برنامه‌های ASP.NET می‌باشد. نقطه‌ی اتکای مایکروسافت در سالهای اخیر برای مدیریت کاربران سیستم ASP.NET Membership بود که از ضعف‌های طراحی رنج می‌برد. مهمترین محدودیت این سیستم این بود که داده‌های ذخیره شده توسط Schema، فقط قابلیت کار با SQL Server را دارا بود که توسعه‌ی آن بدون پیاده سازی دوباره‌ی کلاسهای تامین کننده ( Provider Classes ) بسیار مشکل بود. بعد از آن مایکروسافت جهت کاهش پیچیدگی سیستم ASEP.NET Membership، دو سیستم دیگر به نامهای Simple Membership و ASP.NET Universal Providers را عرضه نمود. این سیستم‌ها تا حدودی پیچیدگی را کم کردند، اما همچنان برمبنای SQL Server بودند. تا اینکه مایکروسافت سیستم ASP.NET Membership را با سیستم Identity جهت حل دو مشکل مطرح شده تعویض نمود. سیستم Identity هم پایدار است و هم قابل توسعه. به علاوه با استفاده از قابلیت Open Source بودن، قابل وفق دادن می‌باشد (یعنی برحسب نیاز پروژه می‌توان کل یا قسمتهایی را پیاده سازی نمود). برای بسیاری از توسعه دهندگان برنامه‌های ASP.NET، سیستم Identity اولین پرده برداری از  OWIN خواهد بود. در حقیقت OWIN یک لایه‌ی Abstract می‌باشد که برنامه‌ی وب را از محیطی که آن‌را میزبانی کرده، ایزوله می‌کند. هدف از ایزوله کردن، ایجاد انعطاف پذیری بیشتر در محیط هایی که برنامه‌های ASP.Net را میزبانی می‌کنند و همچنین ایجاد یک زیرساخت سبک وزن جهت سرویس دهی می‌باشد. همان طور که عنوان شد OWIN یک استاندارد باز است و مایکروسافت پروژه KATANA را به منظور پیاده سازی استانداردی از OWIN طراحی کرد. این طراحی همراه با یک سری کامپوننت به منظور تامین ویژگی‌هایی (Functionalities) که یک برنامه وب به آنها نیاز دارد همراه بود. زیبایی کار مایکروسافت در این بود که OWIN/KATANA پشته‌ی تکنولوژی ASP.NET را از بقیه‌ی قسمتهای .NET Framework ایزوله کرده که اجازه نرخ بالاتری از تغییرات را می‌دهد.
توسعه دهندگان OWIN به جای استفاده از کل پلتفرم (بر عکس چیزی که در حال حاضر در سرویسهای ASP.NET رخ می‌دهد) فقط سرویس‌هایی را که برنامه آنها نیاز دارد، انتخاب می‌کنند. این سرویسهای منفرد که در واژه نامه‌ی OWIN با نام Middleware شناخته می‌شوند، می‌توانند در نرخ‌های مختلفی توسعه داده شوند و توسعه دهندگان را قادر می‌سازند که بین تأمین کنندگان سرویسهای مختلف یکی را انتخاب کنند و فقط وابسته به سرویس‌های پیاده سازی شده توسط مایکروسافت نباشند.
و در پایان باید گفت که پلتفرم ASP.NET و IIS کنار گذاشته نخواهند شد. مایکروسافت شفاف سازی کرده‌است که یکی از ابعاد زیبای OWIN این است که به توسعه دهندگان اجازه انعطاف پذیری بیشتر را می‌دهد که در آن کامپوننت‌های Middleware بوسیله IIS میزبانی می‌شوند و هم اکنون پروژه‌ی KATANA توسط فضاهای نام System.Web پشتیبانی می‌شود. 
خوب دوستان این مقدمه ای بود بر سیستم Identity . انشالله در نوشته‌های بعدی بیشتر سیستم Identity را تحلیل و بررسی خواهیم کرد.