مطالب
C# 7 - Tuple return types and deconstruction
روش‌های زیادی برای بازگشت چندین مقدار از یک متد وجود دارند؛ مانند استفاده‌ی از آرایه‌ها برای بازگشت اشیایی از یک جنس، ایجاد یک کلاس سفارشی با خواص متفاوت و استفاده از پارامترهای out و ref همانند روش‌های متداول در C و ++C. در این بین روش دیگری نیز به نام Tuples از زمان NET 4.0. برای بازگشت چندین شیء با نوع‌های مختلف، ارائه شده‌است که در C# 7 نحوه‌ی تعریف و استفاده‌ی از آن‌ها بهبود قابل ملاحظه‌ای یافته‌است.


Tuple چیست؟

هدف از کار با Tupleها، عدم تعریف یک کلاس جدید به همراه خواص آن، جهت بازگشت بیش از یک مقدار از یک متد، توسط وهله‌ای از این کلاس جدید می‌باشد. برای مثال اگر بخواهیم از متدی، دو مقدار شهر و ناحیه را بازگشت دهیم، یک روش آن، ایجاد کلاس مکان زیر است:
public class Location   
{ 
     public string City { get; set; } 
     public string State { get; set; } 
 
     public Location(string city, string state) 
     { 
           City = city; 
           State = state; 
     } 
}
و سپس، وهله سازی و بازگشت آن:
 var location = new Location("Lake Charles","LA");
اما توسط Tuples، بدون نیاز به تعریف یک کلاس جدید، باز هم می‌توان به همین دو خروجی، دسترسی یافت:
 var location = new Tuple<string,string>("Lake Charles","LA");   
// Print out the address
var address = $"{location.Item1}, {location.Item2}";


مشکلات نوع Tuple در نگارش‌های قبلی دات نت

هرچند Tuples از زمان دات نت 4 در دسترس هستند، اما دارای این کمبودها و مشکلات می‌باشند:
static Tuple<int, string, string> GetHumanData()
{
   return Tuple.Create(10, "Marcus", "Miller");
}
الف) پارامترهای خروجی آن‌ها ثابت و با نام‌هایی مانند Item1، Item2 و امثال آن هستند که در حین استفاده، به علت ضعف نامگذاری، کاربرد آن‌ها دقیقا مشخص نیست و کاملا بی‌معنا هستند:
 var data = GetHumanData();
Console.WriteLine("What is this value {0} or this {1}",  data.Item1, data.Item3);
ب) Reference Type هستند (کلاس هستند) و در زمان وهله سازی، میزان مصرف حافظه‌ی بیشتری را نسبت به Value Types (معادل Tuples در C# 7) دارند.
ج) Tuples در دات نت 4، صرفا یک کتابخانه‌ی اضافه شده‌ی به فریم ورک بوده و زبان‌های دات نتی، پشتیبانی توکاری را از آن‌ها جهت بهبود و یا ساده سازی تعریف آن‌ها، ارائه نمی‌دهند.


ایجاد Tuples در C# 7

برای ایجاد Tuples در سی شارپ 7، از پرانتزها به همراه ذکر نام و نوع پارامترها استفاده می‌شود.
(int x1, string s1) = (3, "one");
Console.WriteLine($"{x1} {s1}");
در مثال فوق، یک Tuple ایجاد شده‌است و در آن مقدار 3 به x1 و مقدار "one" به s1 انتساب داده شده‌اند. به این عملیات deconstruction هم می‌گویند.
دسترسی به این مقادیر نیز همانند متغیرهای معمولی است.

اگر سعی کنیم این قطعه کد را کامپایل نمائیم، با خطای ذیل متوقف خواهیم شد:
 error CS8179: Predefined type 'System.ValueTuple`2' is not defined or imported
برای رفع این مشکل نیاز است بسته‌ی نیوگت ذیل را نیز نصب کرد:
 PM> install-package System.ValueTuple

تعاریف متغیرهای بازگشتی، خارج از پرانتزها هم می‌توانند صورت گیرند:
int x2;
string s2;
(x2, s2) = (42, "two");
Console.WriteLine($"{x2} {s2}");


بازگشت Tuples از متدها

متد ذیل، دو خروجی نتیجه و باقیمانده‌ی تقسیم دو عدد صحیح را باز می‌گرداند:
static (int, int) Divide(int x, int y)
{
   int result = x / y;
   int reminder = x % y;
 
   return (result, reminder);
}
برای این منظور، نوع خروجی متد به صورت (int, int) و همچنین مقدار بازگشتی نیز به صورت یک Tuple از نتیجه و باقیمانده‌ی تقسیم، تعریف شده‌است.
در ادامه نحوه‌ی استفاده‌ی از این متد را مشاهده می‌کنید:
 (int result, int reminder) = Divide(11, 3);
Console.WriteLine($"{result} {reminder}");

در اینجا امکان استفاده‌ی از var نیز برای تعریف نوع متغیرهای دریافتی از یک Tuple نیز وجود دارد و کامپایلر به صورت خودکار نوع آن‌ها را بر اساس نوع خروجی tuple مشخص می‌کند:
 (var result1, var reminder1) = Divide(11, 3);
Console.WriteLine($"{result1} {reminder1}");
و یا حتی چون نوع var پارامترها در اینجا یکی است و در هر دو حالت به int اشاره می‌کند، می‌توان این var را در خارج از پرانتز هم قرار داد:
 var (result1, reminder1) = Divide(11, 3);

و یا برای نمونه متد GetHumanData دات نت 4 ابتدای بحث را به صورت ذیل می‌توان در C# 7 بازنویسی کرد:
static (int, string, string) GetHumanData()
{
   return (10, "Marcus", "Miller");
}
و سپس به نحو واضح‌تری از آن استفاده نمود؛ بدون استفاده‌ی اجباری از Item1 و غیره (هرچند هنوز هم می‌توان از آن‌ها استفاده کرد):
 (int Age, string FirstName, string LastName) results = GetHumanData();
Console.WriteLine(results.Age);
Console.WriteLine(results.FirstName);
Console.WriteLine(results.LastName);


پشت صحنه‌ی Tuples در C# 7

همانطور که عنوان شد، برای اینکه بتوانید قطعه کدهای فوق را کامپایل کنید، نیاز به بسته‌ی نیوگت System.ValueTuple است. در حقیقت کامپایلر خروجی متد فوق را به نحو ذیل تفسیر می‌کند:
 ValueTuple<int, int> tuple1 = Divide(11, 3);
برای مثال قطعه کد
 (int, int) n = (1,1);
System.Console.WriteLine(n.Item1);
توسط کامپایلر به قطعه کد ذیل ترجمه می‌شود:
 ValueTuple<int, int> n = new ValueTuple<int, int>(1, 1);
System.Console.WriteLine(n.Item1);
- برخلاف نگارش‌های پیشین دات نت که Tuples در آن‌ها reference type بودند، این ValueTuple یک struct است و به همین جهت سربار تخصیص حافظه‌ی کمتری را به همراه داشته و از لحاظ کارآیی و میزان مصرف حافظه بهینه‌تر عمل می‌کند.
- همچنین در اینجا محدودیتی از لحاظ تعداد پارامترهای ذکر شده‌ی در یک Tuple وجود ندارد.
 (int,int,int,int,int,int,int,(int,int))
در اینجا هم مانند قبل (دات نت 4) 8 آیتم را می‌توان تعریف کرد؛ اما چون آخرین آیتم ValueTuple تعریف شده نیز یک Tuple است، در عمل محدودیتی از نظر تعداد پارامتر نخواهیم داشت.


مفهوم Tuple Literals

همانند نگارش‌های پیشین دات نت، خروجی یک Tuple را می‌توان به یک متغیر از نوع var و یا ValueType نیز نسبت داد:
 var tuple2 = ("Stephanie", 7);
Console.WriteLine($"{tuple2.Item1}, {tuple2.Item2}");
در این حالت برای دسترسی به مقادیر Tuple همانند قبل باید از فیلدهای Item1 و Item2 و ... استفاده کرد.
به علاوه در سی شارپ 7  می‌توان برای اعضای یک Tuple نام نیز تعریف کرد که به آن‌ها Tuple literals گویند:
 var tuple3 = (Name: "Matthias", Age: 6);
Console.WriteLine($"{tuple3.Name} {tuple3.Age}");
در این حالت زمانیکه Tuple به یک متغیر از نوع var نسبت داده می‌شود، می‌توان به خروجی آن بر اساس نام‌های اعضای Tuple، بجای ذکر Item1 و ... دسترسی یافت که خوانایی بیشتری دارند.

و یا هنگام تعریف نوع خروجی، می‌توان نام پارامترهای متناظر را نیز ذکر کرد که به آن named elements هم می‌گویند:
static (int radius, double area) CalculateAreaOfCircle(int radius)
{
   return (radius, Math.PI * Math.Pow(radius, 2));
}
و نمونه‌ای از کاربرد آن به صورت ذیل است که در اینجا خروجی Tuple صرفا به یک متغیر از نوع var نسبت داده شده‌است و توسط نام پارامترهای خروجی متد، می‌توان به اعضای Tuple دسترسی یافت.
 var circle = CalculateAreaOfCircle(2);
Console.WriteLine($"A circle of radius, {circle.radius}," +
 $" has an area of {circle.area:N2}.");


مفهوم Deconstructing Tuples

مفهوم deconstruction که در ابتدای بحث عنوان شد صرفا مختص به Tuples نیست. در C# 7 می‌توان مشخص کرد که چگونه یک نوع خاص، به اجزای آن تجزیه شود. برای مثال کلاس شخص ذیل را درنظر بگیرید:
class Person
{
    private readonly string _firstName;
    private readonly string _lastName;
 
    public Person(string firstname, string lastname)
    {
        _firstName = firstname;
        _lastName = lastname;
    }
 
    public override String ToString() => $"{_firstName} {_lastName}";
 
    public void Deconstruct(out string firstname, out string lastname)
    {
        firstname = _firstName;
        lastname = _lastName;
    }
}
- در اینجا یک متد جدید را به نام Deconstruct مشاهده می‌کنید. کار این متد جدید که توسط کامپایلر استفاده خواهد شد، ارائه‌ی روشی است برای «تجزیه‌ی» یک نوع، به یک Tuple‌. متد Deconstruct تعریف شده‌ی در اینجا توسط پارامترهایی از نوع out، دو خروجی را مشخص می‌کنند. امکان تعریف این متد ویژه، به صورتیکه یک Tuple را بازگرداند، وجود ندارد.
- علت تعریف این دو خروجی هم به constructor و یا سازنده‌ی کلاس بر می‌گردد که دو ورودی را دریافت می‌کند. اگر یک کلاس چندین سازنده داشته باشد، به همان تعداد می‌توان متد Deconstruct تعریف کرد؛ به همراه خروجی‌هایی متناظر با نوع پارامترهای سازنده‌ها.
- علت استفاده‌ی از نوع خروجی out نیز این است که در #C نمی‌توان چندین overload را صرفا بر اساس نوع خروجی‌های متفاوت متدها تعریف کرد.
- متد Deconstruct به صورت خودکار در زمان تجزیه‌ی یک شیء به یک tuple فراخوانی می‌شود. در مثال زیر، شیء p1 به یک Tuple تجزیه شده‌است و این تجزیه بر اساس متد Deconstruct این کلاس مفهوم پیدا می‌کند:
 var p1 = new Person("Katharina", "Nagel");
(string first, string last) = p1;
Console.WriteLine($"{first} {last}");


امکان تعریف متد Deconstruct‌، به صورت یک متد الحاقی

روش اول تعریف متد ویژه‌ی Deconstruct را در مثال قبل، در داخل کلاس اصلی مشاهده کردید. روش دیگر آن، استفاده‌ی از متدهای الحاقی است که در این مورد خاص نیز مجاز است:
public class Rectangle
{
    public Rectangle(int height, int width)
    {
        Height = height;
        Width = width;
    }
 
    public int Width { get; }
    public int Height { get; }
}
 
public static class RectangleExtensions
{
    public static void Deconstruct(this Rectangle rectangle, out int height, out int width)
    {
        height = rectangle.Height;
        width = rectangle.Width;
    }
}
در اینجا کلاس مستطیل دارای سازنده‌ای با دو پارامتر است؛ اما متد Deconstruct آن به صورت یک متد الحاقی، خارج از کلاس اصلی تعریف شده‌است.
اکنون امکان انتساب وهله‌ای از این کلاس به یک Tuple وجود دارد:
 var r1 = new Rectangle(100, 200);
(int height, int width) = r1;
Console.WriteLine($"height: {height}, width: {width}");


امکان جایگزین کردن Anonymous types با Tuples

قطعه کد ذیل را در نظر بگیرید:
List<Employee> allEmployees = new List<Employee>()
{
  new Employee { ID = 1L, Name = "Fred", Salary = 50000M },
  new Employee { ID = 2L, Name = "Sally", Salary = 60000M },
  new Employee { ID = 3L, Name = "George", Salary = 70000M }
};
var wellPaid =
  from oneEmployee in allEmployees
  where oneEmployee.Salary > 50000M
  select new { EmpName = oneEmployee.Name,
               Income = oneEmployee.Salary };
در اینجا خروجی LINQ تهیه شده یک لیست anonymously typed است؛ با محدودیت‌هایی مانند عدم امکان استفاده‌ی از خروجی آن در سایر اسمبلی‌ها. این نوع‌های ویژه تنها محدود هستند به همان اسمبلی که در آن تعریف می‌شوند. اما در C# 7 می‌توان قطعه کد فوق را با Tuples به صورت ذیل بازنویسی کرد که این محدودیت‌ها را هم ندارد (با هدف به حداقل رساندن تعداد ViewModel‌های تعریفی یک برنامه):
var wellPaid =
  from oneEmployee in allEmployees
  where oneEmployee.Salary > 50000M
  orderby oneEmployee.Salary descending
  select (EmpName: oneEmployee.Name,
          Income: oneEmployee.Salary);
var highestPaid = wellPaid.First().EmpName;


سایر کاربردهای Tuples

از Tuples صرفا برای تعریف چندین خروجی از یک متد استفاده نمی‌شود. در ذیل نحوه‌ی استفاده‌ی از آن‌ها را جهت تعریف کلید ترکیبی یک شیء دیکشنری و یا استفاده‌ی از آن‌ها را در آرگومان جنریک یک متد async هم مشاهده می‌کنید:
public Task<(int index, T item)> FindAsync<T>(IEnumerable<T> input, Predicate<T> match)
{
   var dictionary = new Dictionary<(int, int), string>();
   throw new NotSupportedException();
}
نظرات مطالب
شروع به کار با EF Core 1.0 - قسمت 2 - به روز رسانی ساختار بانک اطلاعاتی
ارتقاء به EF Core 2.1: مقدار دهی اولیه‌ی جداول بانک‌های اطلاعاتی
روشی که در مطلب جاری در مورد متد Seed گفته شده‌است، هنوز هم کار می‌کند. در نگارش 2.1 روش توکاری را برای این منظور در متد OnModelCreating معرفی کرده‌اند:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
      .HasData(new Blog { BlogId = 1, Url = "http://sample.com" });
}
اعمال آن نیز تنها یکبار از طریق اجرای عملیات Migrations صورت می‌گیرد و محاسبات ثبت یا به روز رسانی آن نیز خودکار است.
اگر موجودیت در حال ثبت نیاز به تعریف کلید خارجی داشته باشد، باید از یک anonymous class استفاده کرد:
modelBuilder.Entity<Post>().HasData(
    new {BlogId = 1, PostId = 1, Title = "First post", Content = "Test 1"},
    new {BlogId = 1, PostId = 2, Title = "Second post", Content = "Test 2"});
اشتراک‌ها
فایل security.txt مخصوص وب سایت‌ها چیست؟

The main purpose of security.txt is to help make things easier for companies and security researchers when trying to secure platforms. Thanks to security.txt, security researchers can easily get in touch with companies about security issues. 

فایل security.txt مخصوص وب سایت‌ها چیست؟
مطالب
تعامل MATLAB (متلب) با دات نت - قسمت اول
متلب (MATLAB) یکی از پرکاربردترین نرم افزارهای محاسباتی در حوزه مهندسی بویژه برق، ریاضیات، مکانیک و ... می‌باشد.
بدون شک تعامل نرم افزارهای مختلف با هم در جهت کاربردی‌تر کردن یک پروژه کمک بسزایی به کاربران نهایی می‌کند. قطعاً استفاده از علوم روز همچون شبکه‌های عصبی، منطق فازی و الگوریتم‌های تکاملی همچون ژنتیک بدون استفاده از متلب بسیار سخت و پیچیده خواهد بود. دستورات و تابع‌های (functions) آماده و ساده در متلب در جهت استفاده از این علوم تقریباً هر پژوهشگر و کاربری را ترغیب به استفاده از متلب می‌کند. طبعاً استفاده از کتابخانه‌های دانت در متلب کمک بسیاری به توسعه دهندگان این حوزه می‌کند.
در این سری از مطالب سعی بر بررسی این تعامل شده است.
بطور کلی دو نوع تعامل در این زمینه وجود دارد :
1- استفاده از اسمبلی‌های دات نت در متلب تحت عنوان MATLAB .NET Interface
2- استفاده از پکیج تابع‌های متلب در پروژه‌های مبتنی بر دات نت تحت عنوان MATLAB Builder NE
در مورد اول از دات نت فقط در پلت فورم ویندوز استفاده می‌شود. کلیه امکانات دات نت 2 را ساپورت میکند و با ورژن‌های 3 و 3.5 سازگار است اما با ورژن 4 تنها بعضی از امکانات در دسترس است و هنوز مورد تست کلی قرار نگرفته است. کلیه امکانات دات نت در #C در متلب بجز یک سری از موارد که در جدول زیر ذکر شده است در دسترس است.

 
به عنوان مثال از کلاس speech synthesizer دات نت 3 در متلب بصورت زیر استفاده می‌کنیم :
function Speak(text)
             NET.addAssembly('System.Speech');
             speak = System.Speech.Synthesis.SpeechSynthesizer;
             speak.Volume = 100;
             Speak(speak,text);
end
سپس برای رندر کردن یک متن به صوت دستور زیر را اجرا می‌کنیم :
 Speak('You can use .NET Libraries in MATLAB');
در ارتباط با استفاده از توابع متلب در یک پروژه مبتنی بر دات نت در قسمت بعد توضیح داده خواهد شد.

منبع : Help متلب
مطالب
React 16x - قسمت 9 - ترکیب کامپوننت‌ها - بخش 3 - Lifecycle Hooks
کامپوننت‌ها در طول چرخه‌ی عمر خود، از چندین مرحله عبور می‌کنند. اولین مرحله، mount نام دارد و زمانی رخ می‌دهد که وهله‌ای از یک کامپوننت، ایجاد و به DOM افزوده شده‌است. در این حالت تعدادی متد خاص را می‌توان به کامپوننت خود اضافه کرد که به صورت خودکار توسط React فراخوانی می‌شوند. به این متدها Lifecycle Hooks می‌گویند. در طی مرحله‌ی mount، سه متد Lifecycle Hooks مخصوص constructor، render و componentDidMount قابل تعریف هستند. React این متدها را به ترتیب فراخوانی می‌کند. دومین مرحله، update نام دارد و زمانی رخ می‌دهد که state و یا props کامپوننت تغییر می‌کنند. در طی مرحله‌ی update، دو متد Lifecycle Hooks مخصوص render و componentDidUpdate قابل تعریف هستند. آخرین فاز یا مرحله، unmount نام دارد و زمانی رخ می‌دهد که کامپوننتی از DOM حذف می‌شود، مانند حذف کامپوننت Counter در قسمت‌های قبل. در طی مرحله‌ی unmount، یک متد Lifecycle Hooks مخصوص componentWillUnmount قابل تعریف است.
البته این Lifecycle Hooks ای که در اینجا نام برده شدند، بیشترین استفاده را دارند. در مستندات React مواردی دیگری نیز ذکر شده‌اند که در عمل آنچنان مورد استفاده قرار نمی‌گیرند.


مرحله‌ی Mount

در کامپوننت App، یک constructor را اضافه می‌کنیم تا بتوان مرحله‌ی Mount را بررسی کرد. این سازنده تنها یکبار در زمان وهله سازی این کامپوننت فراخوانی می‌شود. یکی از کاربردهای آن می‌تواند مقدار دهی اولیه‌ی خواص این وهله باشد. برای مثال یکی از کاربردهای آن می‌تواند مقدار دهی اولیه‌ی state بر اساس مقادیر props رسیده باشد.  در اینجا است که می‌توان خاصیت state را مستقیما مقدار دهی کرد (مانند this.state = this.props.something) و در این حالت نیازی به فراخوانی متد this.setState نیست و اگر فراخوانی شود، یک خطا را دریافت می‌کنیم. از این جهت که this.setState را تنها زمانیکه کامپوننتی رندر شده و در DOM قرار گرفته باشد، می‌توان فراخوانی کرد.

یک نکته: فراخوانی this.state = this.props.something در سازنده‌ی کلاس میسر نیست، مگر اینکه props را به صورت پارامتر به سازنده‌ی کلاس و سازنده‌ی base class توسط متد super ارسال کنیم:
  constructor(props) {
    super(props);
    console.log("App - constructor");
    this.state = this.props.something;
  }
در غیراینصورت this.props، مقدار undefined را بازگشت می‌دهد.

دومین متد lifecycle hooks ای که بررسی می‌کنیم، componentDidMount است:
class App extends Component {
  constructor() {
    super();
    console.log("App - constructor");
  }

  componentDidMount() {
    // Ajax calls
    console.log("App - mounted");
  }
این متد پس از رندر کامپوننت در DOM فراخوانی می‌شود و بهترین محلی است که از آن می‌توان برای ارسال درخواست‌های Ajaxای به سمت سرور و دریافت اطلاعات از backend، استفاده کرد و سپس setState را با اطلاعات جدید فراخوانی نمود.

سومین lifecycle hooks در مرحله‌ی mounting، متد رندر است که در اینجا به ابتدای آن، یک  console.logرا جهت بررسی بیشتر اضافه می‌کنیم:
  render() {
    console.log("App - rendered");
در این حالت اگر برنامه را اجرا کنیم، چنین خروجی را می‌توان در کنسول توسعه دهندگان مرورگر مشاهده کرد:


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

یک نکته: در مرحله‌ی رندر، تمام فرزندان یک کامپوننت نیز به صورت بازگشتی رندر می‌شوند. برای نمایش این ویژگی، به متد Render کامپوننت‌های NavBar، Counters و Counter، متد  console.log ای را جهت درج این مرحله در کنسول، اضافه می‌کنیم:
class Counter extends Component {
  render() {
    console.log("Counter - rendered");
//...

class Counters extends Component {
  render() {
    console.log("Counters - rendered");
//...

const NavBar = ({ totalCounters }) => {
  console.log("NavBar - rendered");
//...

یک نکته: نمی‌توان از lifecycle hooks در کامپوننت‌های بدون حالت تابعی استفاده کرد.

پس از این تغییرات و ذخیره سازی برنامه، با بارگذاری مجدد آن در مرورگر، چنین خروجی در کنسول توسعه دهندگان مرورگر ظاهر می‌شود:


همانطور که مشاهده می‌کنید، پس از فراخوانی App - rendered، تمام فرزندان کامپوننت App رندر شده‌اند و در آخر به App - mounted رسیده‌ایم.


مرحله‌ی Update

مرحله‌ی Update زمانی رخ می‌دهد که state و یا props یک کامپوننت تغییر می‌کنند. برای مثال با کلیک بر روی دکمه‌ی Increment، وضعیت کامپوننت به روز رسانی می‌شود. پس از آن فراخوانی خودکار متد رندر در صف قرار می‌گیرد. به این معنا که تمام فرزندان آن نیز قرار است مجددا رندر شوند. برای آزمایش آن، یکبار لاگ‌های کنسول توسعه دهندگان مرورگر را پاک کنید. سپس بر روی دکمه‌ی Increment کلیک کنید:


همانطور که ملاحظه می‌کنید با کلیک بر روی دکمه‌ی Increment، کل Component tree برنامه مجددا رندر شده‌است. البته این مورد به معنای به روز رسانی کل DOM اصلی در مرورگر نیست. زمانیکه کامپوننتی رندر می‌شود، فقط یک React element حاصل آن خواهد بود که در نتیجه‌ی آن DOM مجازی React به روز رسانی خواهد شد. سپس React، کپی DOM مجازی قبلی را با نمونه‌ی جدید آن مقایسه می‌کند. در آخر، محاسبه‌ی تغییرات صورت گرفته و تنها بر اساس موارد تغییر یافته‌است که DOM اصلی را به روز رسانی می‌کند. به همین جهت زمانیکه بر روی دکمه‌ی Increment کلیک می‌شود، فقط span کنار آن در DOM اصلی به روز رسانی می‌شود. برای اثبات آن در مرورگر بر روی المان span که شماره‌ها را نمایش می‌دهد، کلیک راست کرده و گزینه‌ی inspect را انتخاب کنید. سپس بر روی دکمه‌ی Increment کلیک نمائید. مرورگر قسمتی را که به روز می‌شود، با رنگی مشخص و متمایز، به صورت لحظه‌ای نمایش می‌دهد.

متد componentDidUpdate، پس از به روز رسانی کامپوننت فراخوانی می‌شود. به این معنا که در این حالت وضعیت و یا props جدیدی را داریم. در این حالت می‌توان این اشیاء به روز شده را با نمونه‌های قبلی آن‌ها مقایسه کرد و در صورت وجود تغییری، برای مثال یک درخواست Ajax ای را به سمت سرور برای دریافت اطلاعات تکمیلی ارسال کرد و در غیراینصورت خیر. بنابراین می‌توان به آن به عنوان یک روش بهینه سازی نگاه کرد. برای نمایش این قابلیت می‌توان متد componentDidUpdate را که مقادیر قبلی props و state را دریافت می‌کند، لاگ کرد:
class Counter extends Component {
  componentDidUpdate(prevProps, prevState) {
    console.log("Counter - updated", { prevProps, prevState });
    if (prevProps.counter.value !== this.props.counter.value) {
      // Ajax call and get new data
    }
  }
برای آزمایش آن، یکبار لاگ‌های کنسول توسعه دهندگان مرورگر را پاک کنید. سپس بر روی دکمه‌ی Increment اولین شمارشگر کلیک کنید:


همانطور که مشاهده می‌کنید، مقدار شیء counter، پیش از کلیک بر روی دکمه‌ی Increment، مساوی 4 بوده‌است. در یک چنین حالتی می‌توان مقدار قبلی prevProps.counter.value را با مقدار جدید this.props.counter.value مقایسه کرد و در صورت نیاز یک درخواست Ajax ای را برای دریافت اطلاعات به روز، صادر کرد.


مرحله‌ی Unmount

در این مرحله تنها یک lifecycle hook به نام componentWillUnmount قابل تعریف است که درست پیش از حذف یک کامپوننت از DOM فراخونی می‌شود.
class Counter extends Component {
  componentWillUnmount(){
    console.log("Counter - Unmount");
  }
پس از افزودن متد فوق و بارگذاری مجدد برنامه در مرورگر، یکبار دیگر لاگ‌های کنسول توسعه دهندگان مرورگر را پاک کنید. سپس اولین Counter رندر شده را حذف کنید.


در اینجا پس از حذف یک کامپوننت، state کامپوننت App تغییر کرده‌است. به همین جهت کل Component tree رندر مجدد شده‌است. اینبار یک DOM مجازی جدید را داریم که تعداد Counterهای آن 3 مورد است. سپس React این DOM مجازی جدید را با نمونه‌ی قبلی خود مقایسه کرده و متوجه می‌شود که یکی از Counterها حذف شده‌است. در ادامه متد componentWillUnmount را پیش از حذف این ‍Counter از DOM، فراخوانی می‌کند. به این ترتیب فرصت خواهیم یافت تا رهاسازی منابع را در صورت نیاز انجام دهیم تا برنامه دچار نشتی حافظه نشود.


یک مثال: افزودن دکمه‌ی Decrement به کامپوننت Counter

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


برای پیاده سازی آن ابتدا متد رندر کامپوننت Counter را به صورت زیر تغییر می‌دهیم:
class Counter extends Component {

  render() {
    console.log("Counter - rendered");
    return (
      <div className="row">
        <div className="col-1">
          <span className={this.getBadgeClasses()}>{this.formatCount()}</span>
        </div>
        <div className="col">
          <button
            onClick={() => this.props.onIncrement(this.props.counter)}
            className="btn btn-secondary btn-sm"
          >
            +
          </button>
          <button
            onClick={() => this.props.onDecrement(this.props.counter)}
            className="btn btn-secondary btn-sm m-2"
            disabled={this.props.counter.value === 0 ? "disabled" : ""}
          >
            -
          </button>
          <button
            onClick={() => this.props.onDelete(this.props.counter.id)}
            className="btn btn-danger btn-sm"
          >
            Delete
          </button>
        </div>
      </div>
    );
  }
در اینجا یک row تعریف شده و سپس دو div، با کلاس‌های تعیین عرض ستون‌ها. در ادامه span نمایش شمارشگر، به div اول و دکمه‌ها به div دوم منتقل شده‌اند. همچنین marginها را هم اصلاح کرده‌ایم تا بین دکمه‌ها فضای مناسبی ایجاد شود.
در این بین، دکمه‌ی جدید کاهش مقدار را که با یک - مشخص شده‌است نیز مشاهده می‌کنید. رویدادگردان onClick آن به this.props.onDecrement اشاره می‌کند. همچنین ویژگی disabled نیز به آن اضافه شده‌است تا بر اساس مقدار value شیء counter، در مورد فعال یا غیرفعالسازی دکمه تصمیم گیری کند.
پس از آن نیاز است این this.props.onDecrement را تعریف کنیم. به همین جهت به والد آن که کامپوننت Counters است مراجعه کرده و آن‌را به صورت زیر تغییر می‌دهیم:
<Counter
  key={counter.id}
  counter={counter}
  onDelete={this.props.onDelete}
  onIncrement={this.props.onIncrement}
  onDecrement={this.props.onDecrement}
/>
در اینجا onDecrement اضافه شده‌است تا شیء this.props ارسالی به کامپوننت Counter را مقدار دهی کند. اکنون باید ارجاع به this.props.onDecrement این کامپوننت را نیز تکمیل کرد. این ارجاع نیز به والد Counters که در اینجا کامپوننت App است اشاره می‌کند:
<Counters
  counters={this.state.counters}
  onReset={this.handleReset}
  onIncrement={this.handleIncrement}
  onDecrement={this.handleDecrement}
  onDelete={this.handleDelete}
/>
در کامپوننت App، ویژگی onDecrement ارسالی به کامپوننت Counters، به صورت props مقدار دهی شده‌است. این ویژگی به متد this.handleDecrement اشاره می‌کند که به صورت زیر در کامپوننت App تعریف می‌شود:
  handleDecrement = counter => {
    console.log("handleDecrement", counter);
    const counters = [...this.state.counters]; // cloning an array
    const index = counters.indexOf(counter);
    counters[index] = { ...counter }; // cloning an object
    counters[index].value--;
    console.log("this.state.counters", this.state.counters[index]);
    this.setState({ counters });
  };
که کدهای آن با کدهای handleIncrement بحث شده‌ی در قسمت قبل یکی است. اکنون اگر برنامه را اجرا کنید، به تصویر ابتدای توضیحات این مثال خواهید رسید.


کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: sample-09.zip
مطالب
سازگار کردن لینک‌های قدیمی یک سایت با ساختار جدید آن در ASP.NET MVC
اگر پیشتر سایتی را در آدرس مشخصی در اینترنت داشته‌اید و اکنون تنها نرم افزار آن تغییر کرده است، اما نحوه ارائه خدمات آن خیر، لازم است بتوانید شرایط ذیل را مدیریت کنید:
- موتورهای جستجو مدام اطلاعات قبلی خود را به روز می‌کنند. اگر آدرس قبلی مقاله‌ای در سایت شما http://site/year/month/day/title بوده، برای نمونه گوگل هر از چندگاهی مجددا به این آدرس مراجعه می‌کند تا حداقل مطمئن شود وجود خارجی دارد یا خیر (این نکته را از لاگ‌های خطای سایت استخراج کردم).
- سایت‌های زیادی هستند که پیشتر به سایت شما و مطالب آن لینک داده‌اند. نمی‌توانید از آن‌ها درخواست کنید لطفا بانک اطلاعاتی خود را به روز کنید.
- اگر فید قبلی سایت شما http://site/feeds/posts بوده و اکنون چیز دیگری است، باز هم نمی‌توانید از همه درخواست کنید اطلاعات خود را به روز کنند. عده‌ای اینکار را خواهند کرد و تعداد زیادی هم خیر.

برای مدیریت یک چنین مواردی می‌توان از امکانات مسیریابی موجود در ASP.NET MVC استفاده کرد؛ که نمونه‌ای عملی از آن‌را جهت سازگاری سایت جاری با هاست قبلی آن (بلاگر) در ادامه مطالعه خواهید نمود:

الف) سازگار سازی لینک‌های قدیمی برچسب‌های سایت با ساختار جدید آن
در بلاگر آدرس‌های برچسب‌ها، به صورت http://site/search/label/name تعریف شده است. در سایت جاری برچسب‌ها توسط کنترلر Tag مدیریت می‌شوند. برای هدایت آدرس‌های قدیمی (موجود در موتورهای جستجو یا ثبت شده در سایت‌هایی که به ما لینک داده‌اند) می‌توان از تعریف مسیریابی ذیل در فایل global.asax استفاده کرد:
routes.MapRoute(
                "old_bloger_tags_list", // Route name
                "search/label/{name}", // URL with parameters
                new { controller = "Tag", action = "Index", name = UrlParameter.Optional, area = "" } // Parameter defaults
            );
به این ترتیب به صورت خودکار تمامی آدرس‌های شروع شده با http://site/search/label پالایش شده و سپس قسمت name آن‌ها جدا سازی می‌شود. این نام به متدی به نام Index در کنترلر Tag که دارای پارامتری به نام name است ارسال خواهد شد.

ب) از دست ندادن خوانندگان قدیمی فیدهای سایت
دو نوع فید کلی در بلاگر وجود دارد: http://site/feeds/posts/default و http://site/feeds/comments/default؛ اما در سایت جاری فیدها توسط کنترلری به نام Feed ارائه می‌شوند. برای سازگار سازی آدرس‌های قدیمی و هدایت آن‌ها به صورت خودکار به کنترلر فید می‌توان از دو تعریف مسیریابی ذیل استفاده کرد:
routes.MapRoute(
                "old_bloger_posts_feeds_list", // Route name
                "feeds/posts/default", // URL with parameters
                new { controller = "Feed", action = "Posts", name = UrlParameter.Optional, area = "" } // Parameter defaults
            );

routes.MapRoute(
                "old_bloger_comments_feeds_list", // Route name
                "feeds/comments/default", // URL with parameters
                new { controller = "Feed", action = "Comments", name = UrlParameter.Optional, area = "" } // Parameter defaults
            );            
در اینجا دو آدرس ذکر شده به کنترلر Feed و متدهای Posts و Comments آن هدایت خواهند شد و به این نحو کاربران قدیمی سایت هیچگونه تغییری را احساس نکرده و باز هم فیدخوان‌های آن‌ها، بدون مشکل کار خواهند کرد.

ج) پردازش لینک‌های قدیمی مطالب سایت و هدایت آن‌ها به آدرس‌های جدید
این مورد اندکی مشکل‌تر از موارد قبلی است:
routes.MapRoute(
                "old_bloger_post_urls",
                "{yyyy}/{mm}/{title}",
                new { controller = "Post", action = "OldBloggerLinks" },
                new { yyyy = @"\d{4}", mm = @"\d{1,2}" }
            );
برای نمونه آدرس مقاله‌ای مانند http://site/2012/05/ef-code-first-15.html را درنظر بگیرید. سه قسمت سال، ماه و عنوان آن، حائز اهمیت هستند. این‌ها را در اینجا به کنترلر Post و متد OldBloggerLinks آن هدایت خواهیم کرد. همچنین برای سال و ماه آن نیز قید تعریف شده است. سال عددی 4 رقمی است و ماه عددی یک تا دو رقمی.
کدهای متد OldBloggerLinks را در اینجا مشاهده می‌کنید:
        public virtual ActionResult OldBloggerLinks(int yyyy, int mm, string title)
        {
            var oldUrl = string.Format(CultureInfo.InvariantCulture, "https://www.dntips.ir/{0}/{1}/{2}", yyyy, mm.ToString("00"), title);
            var blogPost = _blogPostsService.FindBlogPost(oldUrl);
            if (blogPost != null)
                return RedirectToActionPermanent(actionName: ActionNames.Index, controllerName: MVC.Post.Name,
                                                 routeValues: new { id = blogPost.Id, name = blogPost.Title.GetPostSlug() });
            return this.Redirect("/");
        }
در اینجا چون ساختار لینک‌ها کلا تغییر کرده است، ابتدا بر اساس پارامترهای دریافت شده، لینک قدیمی بازسازی می‌شود. سپس به بانک اطلاعاتی مراجعه شده و لینک قدیمی به همراه شماره مطلب مرتبط با آن یافت می‌شود (یک فیلد oldUrl برای مطالب قدیمی در بانک اطلاعاتی وجود دارد). در آخر هم به کمک متد  RedirectToActionPermanent آدرس رسیده به آدرس جدید مطلب در سایت ترجمه و هدایت خواهد شد. Permanent بودن آن برای به روز رسانی خودکار اطلاعات موتورهای جستجو مفید است.


نتیجه گیری
به کمک امکانات مسیریابی توکار ASP.NET MVC می‌توان ساختار قدیمی یک سایت را به ساختار جدید آن ترجمه کرد. به این ترتیب لینک‌های قدیمی ثبت شده در صدها سایت اینترنتی که به سایت ما اشاره می‌کنند، مجددا بدون مشکل قابل استفاده بوده و همچنین موتورهای جستجو نیز امکان به روز رسانی اطلاعات خود را خواهند یافت.

نظرات مطالب
آشنایی با TransactionScope
برای قسمت اول مطالب شما باید عنوان کنم که برای Nested Transaction محدودیت خاصی تعیین نشده است. تا زمانی که Timeout رخ ندهد و همه تراکنش‌ها به درستی Commit شوند مشکلی پیش نخواهد آمد. ولی باید به این نکته دقت داشت که اگر هنگام استفاده از تراکنش‌ها تو درتو یکی از تراکنش‌ها به هر دلیلی موفقیت آمیز نباشد یا زمان آن سپری شود یا زمان انجام کل تراکنش به اتمام برسد Exceptionتولید خواهد شد و تمام عملیات RollBack می‌شوند.البته می‌توان برای مدیریت این تراکنش‌ها از TransactionScopeOption که در مطلب عنوان کردم استفاده کنید. اما در توضیح قسمت دوم مطالب شما باید عنوان کنم که برای اجرای تراکنش به این روش نیاز به مجوز اجرای تراکنش از راه دور است. این کار هم از طریق تنظیمات Remote Access MSDTC میسر می‌شود. برای انجام این کار از قسمت زیر استفاده کنید.
ComponentServieces/Computers/MyComputer/Distributed Transaction Coodrinator/Local DTC
بر روی Local DTC کلیک راست کنید و گزینه‌ی Properties رو بزنید. از برگه Security تیک قسمت‌های زیر رو بزنید.
1-Notwork DTC Access
2- Allow Remote Client
3- Allow Remote Administration
4-Allow Inbound
5-Allow OutBound
6 -No Authentication Required.
خود من به شخصه از این روش برای اجرای سرویس‌های WCF به صورت تراکنش استفاده می‌کنم.
مطالب
خلاصه اشتراک‌های روز پنج شنبه 1390/06/31