نظرات مطالب
EF Code First #12
شرمنده
کتابخانه ی structuremap را هم از NuGet اضافه کردم و هم از Github.
ولی هر دور در زمان اجرا اخطار زیر را میدهند :
"The type or namespace name 'StructureMap' could not be found (are you missing a using directive or an assembly reference?)"
مطالب
آموزش Linq - بخش ششم : عملگرهای پرس و جو قسمت سوم
عملگر‌های تبدیل Conversion Operator

عملگر‌های پرس و جوی تبدیل، توالی‌هایی را که از جنس <IEnumerable<T هستند، به انواع دیگر مجموعه تبدیل می‌کنند.
از عملگر‌های پرس و جوی زیر می‌توان برای تبدیل توالی‌ها استفاده کرد :
  • OfType
  • Cast
  • ToArray
  • ToList
  • ToDictionary
  • ToLookup

عملگر OfType


این عملگر عناصری از توالی را که نوع آنها را مشخص می‌کنیم باز می‌گرداند.
امضاء عملگر پرس و جوی OfType  به صورت زیر است :
 public static IEnumerable<TResult> OfType<TResult>(this IEnumerable source)
همانطور که مشاهده می‌کنید توالی ورودی از یک نوع IEnumerable غیر جنریک می‌باشد. بدین معنی که عناصر توالی ورودی می‌توانند از نوع داده‌های مختلف باشند (توالی از اشیاء، از جنس Object).
در مثال زیر یک توالی IEnumerable (آرایه‌ای از اشیاء)، از عناصر با نوع داده‌های مختلفی را ایجاد کرده‌ایم. عملگر OfType در اینجا کلیه عناصر از جنس (string) را باز می‌گرداند. توالی خروجی یک نوع IEnumerable جنریک است(در این مثال <IEnumerable<List).
مثال :
IEnumerable input = new object[] { "Apple", 33, "Sugar", 44, 'a', new DateTime()};
IEnumerable<string> query = input.OfType<string>();
foreach (var item in query)
{
   Console.WriteLine(item);
}
خروجی مثال بالا :
Apple
Sugar
عملگر OfType را می‌توان به‌همراه  Strongly Type‌‌ها نیز استفاده کرد.
مثال :کد زیر یک ساختار سلسله مراتبی شیء گرا را نمایش می‌دهد:
 class Ingredient
  {
     public string Name { get; set; }
  }
  class DryIngredient : Ingredient
  {
     public int Grams { get; set; }
  }

  class WetIngredient : Ingredient
  {
     public int Millilitres { get; set; }
  }
کد زیر چگونگی استفاده از OfType را برای بدست آوردن یک زیر نوع (Subtype) مشخص، نشان می‌دهد (در این مثال، نوع WetIngredient):
IEnumerable<Ingredient> input = new Ingredient[]
{
   new DryIngredient { Name = "Flour" },
   new WetIngredient { Name = "Milk" },
   new WetIngredient { Name = "Water" }
};

IEnumerable<WetIngredient> query = input.OfType<WetIngredient>();
foreach (WetIngredient item in query)
{
   Console.WriteLine(item.Name);
}
خروجی مثال بالا :
Milk
Water

پیاده سازی توسط عبارت‌های جستجو


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


عملگر Cast


عملگر Cast همانند عملگر OfType رفتار می‌کند. این عملگر یک توالی ورودی را دریافت و بر اساس نوع مشخص شده، توالی خروجی را تولید می‌کند. همه‌ی عناصر توالی ورودی به نوع مشخص شده Cast می‌شوند. اما بر عکس عملگر OfType که عناصری را که با نوع داده‌ی ما سازگاری نداشت، نادیده می‌گرفت، این عملگر در صورت عدم موفقیت در عملیات تغییر نقش (Cast)، یک استثناء را پرتاب می‌کند.
مثال : 
IEnumerable input = new object[]
{
   "Apple", 33, "Sugar", 44, 'a', new DateTime()
};

IEnumerable<string> query = input.Cast<string>();
foreach (string item in query)
{
   Console.WriteLine(item);
}
با اجرای برنامه‌ی فوق، خطای زیر را مشاهده خواهید کرد:
 Unhandled Exception: System.InvalidCastException: Unable to cast object of type 'System.Int32' to type 'System.String'.

پیاده سازی توسط عبارت‌های جستجو


کلمه‌ی کلیدی جایگزینی برای عملگر Cast، در عبارت‌های جستجو وجود ندارد.این عملگر با استفاده از متغیر Range که در مطالب قبلی این سری معرفی شد، قابل پیاده سازی می‌باشد.
IEnumerable input = new object[]{ "Apple", "Sugar", "Flour" };
IEnumerable<string> query =
from string i in input
select i;

foreach (var item in query)
{
   Console.WriteLine(item);
}
نکته:  در مثال فوق تعریف صریح (Explicit) نوع داده، قبل از متغیر Range انجام شده است (معادل همان نوع داده در عملیات Cast).


عملگر ToArray


عملگر ToArray یک توالی ورودی را دریافت و یک توالی خروجی را به صورت آرایه تولید می‌کند. این عملگر باعث اجرای سریع پرس و جو می‌شود و رفتار پیش فرض LINQ را که اجرای با تاخیر می‌باشد، تحریف/بازنویسی (Override) می‌کند.
مثال: در این مثال یک توالی از نوع <IEnumerable<string به یک آرایه رشته‌ای تبدیل شده است (تبدیل لیست به آرایه).
 IEnumerable<string> input = new List<string> { "Apple", "Sugar", "Flour" };
string[] array = input.ToArray();

پیاده سازی توسط عبارت‌های جستجو


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


عملگر ToList

عملگر ToList همچون ToArray، اجرای با تاخیر را نادیده می‌گیرد. عملگر ToList همانطور که از نامش پیداست، توالی خروجی را به‌صورت لیست مهیا می‌کند.
مثال:
 IEnumerable<string> input = new[] { "Apple", "Sugar", "Flour" };
List<string> list = input.ToList();

پیاده سازی توسط عبارت‌های جستجو


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


عملگر ToDictionary

این عملگر توالی ورودی را به یک  دیکشنری جنریک تبدیل می‌کند (<Dictinary<TKey,TValue) .
ساده‌ترین امضاء عملگر ToDictionary، یک عبارت Lambda می‌باشد. این عبارت Lambda  نشان دهنده‌ی یک تابع است که عنصر کلید(Key) را در دیکشنری، مشخص می‌کند.
مثال:
class Recipe
{
   public int Id { get; set; }
   public string Name { get; set; }
   public int Rating { get; set; }
}

IEnumerable<Recipe> recipes = new[]
{
   new Recipe { Id = 1, Name = "Apple Pie", Rating = 5 },
   new Recipe { Id = 2, Name = "Cherry Pie", Rating = 2 },
   new Recipe { Id = 3, Name = "Beef Pie", Rating = 3 }
};

Dictionary<int, Recipe> dict = recipes.ToDictionary(x => x.Id);
foreach (KeyValuePair<int, Recipe> item in dict)
{
   Console.WriteLine($"Key={item.Key}, Recipe={item.Value}");
}
در کد بالا ، کلید دیکشنری نهایی، از نوع  int می‌باشد که بر اساس Id کلاس Recipe تنظیم شده است. مقادیر (value) دیکشنری هم همان اشیاء از جنس کلاس Recipe می‌باشند.
خروجی مثال بالا:
Key=1, Recipe=Apple Pie
Key=2, Recipe=Cherry Pie
Key=3, Recipe=Beef Pie

پیاده سازی توسط عبارت‌های جستجو


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


عملگر ToLookup


این عملگر رفتاری شبیه به عملگر ToDictionary را دارد، اما به جای تولید خروجی از نوع دیکشنری، نمونه‌ای از جنس ILookUp را ایجاد می‌کند.
در کد زیر خروجی ایجاد شده توسط lookup دستورالعمل‌ها (Recipes) را بر حسب  امتیاز آنها گروه بندی کرده است. در این مثال کلید، بر حسب Byte می‌باشد.
مثال :
class Recipe
{
   public int Id { get; set; }
   public string Name { get; set; }
   public byte Rating { get; set; }
}

IEnumerable<Recipe> recipes = new[]
{
   new Recipe { Id = 1, Name = "Apple Pie", Rating = 5 },
   new Recipe { Id = 1, Name = "Banana Pie", Rating = 5 },
   new Recipe { Id = 2, Name = "Cherry Pie", Rating = 2 },
   new Recipe { Id = 3, Name = "Beef Pie", Rating = 3 }
};

ILookup<byte, Recipe> look = recipes.ToLookup(x => x.Rating);
foreach (IGrouping<byte, Recipe> ratingGroup in look)
{
   byte rating = ratingGroup.Key;
   Console.WriteLine($"Rating {rating}");
   foreach (var recipe in ratingGroup)
   {
      Console.WriteLine($" - {recipe.Name}");
   }
}
خروجی مثال بالا:
 Rating 5
 - Apple Pie
 - Banana Pie
Rating 2
 - Cherry Pie
Rating 3
 - Beef Pie

پیاده سازی توسط عبارت‌های جستجو


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


عملگر‌های عناصر  Element Operators

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


عملگر First

این عملگر اولین عنصر توالی را باز می‌گرداند.
مثال :
Ingredient[] ingredients =
{
   new Ingredient {Name = "Sugar", Calories = 500},
   new Ingredient {Name = "Egg", Calories = 100},
   new Ingredient {Name = "Milk", Calories = 150},
   new Ingredient {Name = "Flour", Calories = 50},
   new Ingredient {Name = "Butter", Calories = 500}
};

Ingredient element = ingredients.First();
Console.WriteLine(element.Name);
خروجی مثال بالا :
 Sugar
امضای دیگر این متد، امکان تعریف یک شرط را مهیا می‌کند. خروجی این حالت اولین عنصری است که شرط را تامین می‌کند. در کد زیر اولین عنصری که کالری آن برابر 150 باشد به خروجی ارسال می‌شود.
Ingredient[] ingredients =
{
   new Ingredient {Name = "Sugar", Calories = 500},
   new Ingredient {Name = "Egg", Calories = 100},
   new Ingredient {Name = "Milk", Calories = 150},
   new Ingredient {Name = "Flour", Calories = 50},
   new Ingredient {Name = "Butter", Calories = 500}
};

Ingredient element = ingredients.First(x=>x.Calories==150);
Console.WriteLine(element.Name);
خروجی مثال بالا:
 Milk
در زمان استفاده از عملگر First، اگر توالی ورودی هیچ عنصری نداشته باشد، یک استثناء رخ خواهد داد:
 Unhandled Exception: System.InvalidOperationException: Sequence contains no elements
کد زیر نمونه‌ای از این حالت است:
Ingredient[] ingredients = { };
Ingredient element = ingredients.First();
در زمان استفاده‌ی از امضاء دیگر عملگر First، اگر هیچ عنصری شرط معرفی شده‌ی در پارامتر را تامین نکند، باز هم یک استثناء رخ خواهد داد:
 Unhandled Exception: System.InvalidOperationException: Sequence contains no matching element
کد زیر حالت فوق را نشان می‌دهد:
 Ingredient[] ingredients =
{
   new Ingredient {Name = "Sugar", Calories = 500},
   new Ingredient {Name = "Egg", Calories = 100},
   new Ingredient {Name = "Milk", Calories = 150},
   new Ingredient {Name = "Flour", Calories = 50},
   new Ingredient {Name = "Butter", Calories = 500}
};
Ingredient element = ingredients.First(x=>x.Calories==1500);

پیاده سازی توسط عبارت‌های جستجو

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


عملگر FirstOrDefault

عملگر FirstOrDefalt همانند عملگر First عمل می‌کند، اما با این تفاوت که به جای پرتاب یک استثناء در شرایط معرفی شده در عملگر First، یک مقدار پیش فرض را بر اساس نوع  عناصر توالی باز می‌گرداند. در صورتیکه توالی از نوع عددی باشد، مقدار 0 و اگر عناصر توالی از انواع ارجاعی باشند، مقدار Null و برای مقادیر منطقی، ارزش False به‌عنوان مقادیر پیش فرض باز گردانده می‌شوند.
مثال :
 Ingredient[] ingredients = { };
Ingredient element = ingredients.FirstOrDefault();
Console.WriteLine(element == null);
خروجی مثال بالا :
 True
پیاده سازی حالتی که هیچ یک از عناصر با شرط عملگر کطالبقت ندارند.
Ingredient[] ingredients =
{
   new Ingredient {Name = "Sugar", Calories = 500},
   new Ingredient {Name = "Egg", Calories = 100},
   new Ingredient {Name = "Milk", Calories = 150},
   new Ingredient {Name = "Flour", Calories = 50},
   new Ingredient {Name = "Butter", Calories = 500}
};

Ingredient element = ingredients.FirstOrDefault(x=>x.Calories==1500);
Console.WriteLine(element==null);
خروجی مثال بالا :
 True

پیاده سازی توسط عبارت‌های جستجو


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


 عملگر Last

این عملگر آخرین عنصر توالی را باز می‌گرداند. همچون عملگر First، این عملگر نیز یک امضاء برای دریافت یک عبارت شرط یا پیش بینی دارد. این پیش بینی، آخرین عنصری را که شرط را تامین کند، باز می‌گرداند. باز هم مثل عملگر First، در صورتی که توالی هیچ عنصری نداشته باشد و یا عدم تامین شرط توسط عناصر توالی، استثنایی رخ خواهد داد.
مثال :
Ingredient[] ingredients =
{
   new Ingredient {Name = "Sugar", Calories = 500},
   new Ingredient {Name = "Egg", Calories = 100},
   new Ingredient {Name = "Milk", Calories = 150},
   new Ingredient {Name = "Flour", Calories = 50},
   new Ingredient {Name = "Butter", Calories = 500}
};
Ingredient element = ingredients.Last(x=>x.Calories==500);
Console.WriteLine(element.Name);
خروجی مثال بالا :
 Flour

پیاده سازی توسط عبارت‌های جستجو


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


عملگر LastOrDefault

این عملگر همچون عملگر FirstOrDefault عمل می‌کند. از بروز استثناء جلوگیری کرده و مقدار پیش فرض را به خروجی ارسال می‌کند.

پیاده سازی توسط عبارت‌های جستجو


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


عملگر Single

عملگر Single ، تنها عنصر توالی ورودی را باز می‌گرداند.در صورتی که توالی ما بیش از یک عنصر داشته باشد و یا توالی هیچ عنصری نداشته باشد، یک استثناء رخ خواهد داد.
Unhandled Exception: System.InvalidOperationException: Sequence contains more than one matching element
Unhandled Exception: System.InvalidOperationException: Sequence contains no matching element
مثال :
Ingredient[] ingredients =
{
   new Ingredient { Name = "Sugar", Calories = 500 }
};

Ingredient element = ingredients.Single();
Console.WriteLine(element.Name);
خروجی مثال بالا :
 Sugar
عملگر Single، یک امضاء دیگر نیز دارد که یک عبارت پیش بینی را می‌پذیرد. در صورتی که بیش از یک عنصر، با پیش بینی مطابقت داشته باشد و یا هیچ عنصری شرط پیش بینی را تامین نکند، استثنائی رخ خواهد داد.
Ingredient[] ingredients =
{
   new Ingredient { Name = "Sugar", Calories = 500 },
   new Ingredient {Name = "Butter", Calories = 150},
   new Ingredient {Name = "Milk", Calories = 500}
};
Ingredient element = ingredients.Single(x => x.Calories == 150);
Console.WriteLine(element.Name);
خروجی مثال بالا :
 Butter

پیاده سازی توسط عبارت‌های جستجو


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


عملگر SingleOrDefault

عملگر SingleOrDefault همچون عملگر Single عمل می‌کند؛ اما با این تفاوت که اگر توالی هیچ عنصری نداشته باشد، مقدار پیش فرض نوع توالی، باز گردانده می‌شود و در صورتیکه هیچ عنصری شرط مشخص شده را تامین نکند، باز هم مقدار پیش فرض توالی، به جای رخ دادن استثناء باز گردانده می‌شود.
مثال : در این مثال هیچ عنصری با پیش بینی مشخص شده مطالبقت ندارد:
 Ingredient[] ingredients =
{
   new Ingredient { Name = "Sugar", Calories = 500 },
   new Ingredient {Name = "Egg", Calories = 100},
   new Ingredient {Name = "Milk", Calories = 50}
};
Ingredient element = ingredients.SingleOrDefault(x => x.Calories == 9999);
Console.WriteLine(element==null);
خروجی مثال بالا :
True
توجه داشته باشید که استثنائی رخ نداده است و مقدار پیش فرض انواع ارجاعی که Null می‌باشد باز گردانده شده است.

پیاده سازی توسط عبارت‌های جستجو

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


عملگر ElementAt


عملگر ElementAt   عنصری را در یک جایگاه مشخص شده‌ی در توالی، باز می‌گرداند.
مثال: در کد زیر سومین عنصر توالی ورودی انتخاب می‌شود:
 Ingredient[] ingredients =
{
   new Ingredient { Name = "Sugar", Calories = 500 },
   new Ingredient {Name = "Egg", Calories = 100},
   new Ingredient {Name = "Milk", Calories = 50}
};

Ingredient element = ingredients.ElementAt(2);
Console.WriteLine(element.Name);
خروجی مثال بالا :
 Milk
باید دقت کرد که مقدار ارسالی به عملگر  ElementAt، اندیسی با نقطه‌ی آغاز صفر می‌باشد. بدین معنی که برای بدست آوردن اولین عنصر باید مقدار 0 را به عملگر ElementAt ارسال کرد. در صورتی که مقدار ارسالی با بازه اندیس‌های عناصر توالی مطابقت نداشته باشد (بزرگتر از شماره اندیس آخرین عنصر توالی باشد) یک استثناء رخ خواهد داد.
 System.ArgumentOutOfRangeException: Index was out of range. Must be non-negative and less than the size of the collection.

پیاده سازی توسط عبارت‌های جستجو


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


عملگر ElementAtOrDefualt

عملگر ElementAtOrDefualt نیز همچون عملگر ElementAt کار می‌کند؛ اما در صورت وارد کردن اندیسی بزرگتر از اندیس مجاز توالی، دیگر یک استثناء رخ نخواهد داد و یک مقدار پیش فرض، بر اساس نوع عناصر توالی باز گردانده می‌شود.
مثال :
Ingredient[] ingredients =
{
   new Ingredient { Name = "Sugar", Calories = 500 },
   new Ingredient {Name = "Egg", Calories = 100},
   new Ingredient {Name = "Milk", Calories = 50}
};
Ingredient element = ingredients.ElementAtOrDefault(5);
Console.WriteLine(element==null);
خروجی مثال بالا:
 True

پیاده سازی توسط عبارت‌های جستجو


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


عملگر DefaultIfEmpty
عملگر DefaultIfEmpty یک توالی را دریافت کرده و به دو شکل عمل می‌کند:
1- اگر توالی شامل حداقل یک عنصر باشد، این توالی بدون هیچ تغییری به خروجی ارسال می‌شود.
2- اگر توالی هیچ عنصری نداشته باشد، توالی خروجی خالی نخواهد بود. در این حالت توالی خروجی تنها یک عضو دارد و آن هم مقدار پیش فرضی بر اساس نوع توالی می‌باشد.
مثال :
Ingredient[] ingredients =
{
   new Ingredient { Name = "Sugar", Calories = 500 },
   new Ingredient {Name = "Egg", Calories = 100},
   new Ingredient {Name = "Milk", Calories = 50}
};

IEnumerable<Ingredient> query = ingredients.DefaultIfEmpty();
foreach (Ingredient item in query)
{
  Console.WriteLine(item.Name);
}
خروجی مثال بالا :
Sugar
Egg
Milk
همانطور که می‌بینید توالی خروجی دقیقا شبیه توالی ورودی می‌باشد.
کد زیر حالت دوم معرفی شده‌ی در تعریف DefaultIfEmpty را نشان می‌دهد.
Ingredient[] ingredients = { };
IEnumerable<Ingredient> query = ingredients.DefaultIfEmpty();
foreach (Ingredient item in query)
{
   Console.WriteLine(item == null);
}
خروجی کد بالا :
 True

پیاده سازی توسط عبارت‌های جستجو


معادل این عملگر، کلمه‌ی کلیدی جدیدی در عبارت‌های جستجو وجود ندارد و ترکیب دو روش می‌تواند خروجی دلخواه را تولید کند.
مطالب
تنظیمات کش توزیع شده‌ی مبتنی بر SQL Server در ASP.NET Core
ASP.NET Core به همراه زیر ساختی‌است جهت خارج کردن امکانات Caching درون حافظه‌ای آن از سرور جاری و انتقال آن به سرورهای دیگر جهت کاهش بار سرور و برنامه. این کش توزیع شده را می‌توان به عنوان زیرساختی برای مدیریت سشن‌ها، مدیریت اطلاعات کش و همچنین مدیریت کوکی‌های حجیم ASP.NET Core Identity نیز بکار گرفت. برای مثال بجای ارسال یک کوکی حجیم بالای 5 کیلوبایت به کلاینت، فقط ID رمزنگاری شده‌ی آن‌را ارسال کرد و اصل کوکی را در داخل دیتابیس ذخیره و بازیابی نمود. این مساله هم مقیاس پذیری برنامه را افزایش خواهد داد و هم امنیت آن‌را با عدم ارسال اصل محتوای کوکی به سمت کلاینت‌ها و یا ذخیره سازی اطلاعات سشن‌ها در بانک اطلاعاتی، مشکلات راه اندازی مجدد برنامه را به طور کامل برطرف می‌کنند و در این حالت بازیابی Application pool و یا کرش برنامه و یا ری استارت شدن کل سرور، سبب از بین رفتن سشن‌های کاربران نخواهند شد. بنابراین آشنایی با نحوه‌ی راه اندازی این امکانات، حداقل از بعد امنیتی بسیار مفید هستند؛ حتی اگر سرور ذخیره کننده‌ی این اطلاعات، همان سرور و بانک اطلاعاتی اصلی برنامه باشند.


پیشنیازهای کار با کش توزیع شده‌ی مبتنی بر SQL Server

برای کار با کش توزیع شده‌ی با قابلیت ذخیره سازی در یک بانک اطلاعاتی SQL Server، نیاز است دو بسته‌ی ذیل را به فایل project.json برنامه اضافه کرد:
{
    "dependencies": {
        "Microsoft.Extensions.Caching.SqlServer": "1.1.0"
    },
    "tools": {
        "Microsoft.Extensions.Caching.SqlConfig.Tools": "1.1.0-preview4-final"
    }
}
وابستگی که در قسمت dependencies ذکر شده‌است، کلاس‌های اصلی کار با کش توزیع شده را به برنامه اضافه می‌کند. ذکر وابستگی قسمت tools، اختیاری است و کار آن، ایجاد جدول مورد نیاز برای ذخیره سازی اطلاعات، در یک بانک اطلاعاتی SQL Server می‌باشد.


ایجاد جدول ذخیره سازی اطلاعات کش توزیع شده به کمک ابزار sql-cache

پس از افزودن و بازیابی ارجاعات فوق، با استفاده از خط فرمان، به پوشه‌ی جاری برنامه وارد شده و دستور ذیل را صادر کنید:
 dotnet sql-cache create "Data Source=(localdb)\MSSQLLocalDB;Initial Catalog=sql_cache;Integrated Security=True;" "dbo" "AppSqlCache"
توضیحات:
- در اینجا می‌توان هر نوع رشته‌ی اتصالی معتبری را به انواع و اقسام بانک‌های SQL Server ذکر کرد. برای نمونه در مثال فوق این رشته‌ی اتصالی به یک بانک اطلاعاتی از پیش ایجاد شده‌ی LocalDB اشاره می‌کند. نام دلخواه این بانک اطلاعاتی در اینجا sql_cache ذکر گردیده و نام دلخواه جدولی که قرار است این اطلاعات را ثبت کند AppSqlCache تنظیم شده‌است و dbo، نام اسکیمای جدول است:


در اینجا تصویر ساختار جدولی را که توسط ابزار dotnet sql-cache ایجاد شده‌است، مشاهده می‌کنید. اگر خواستید این جدول را خودتان دستی ایجاد کنید، یک چنین کوئری را باید بر روی دیتابیس مدنظرتان اجرا نمائید:
CREATE TABLE AppSqlCache (
    Id                         NVARCHAR (449)  COLLATE SQL_Latin1_General_CP1_CS_AS NOT NULL,
    Value                      VARBINARY (MAX) NOT NULL,
    ExpiresAtTime              DATETIMEOFFSET  NOT NULL,
    SlidingExpirationInSeconds BIGINT          NULL,
    AbsoluteExpiration         DATETIMEOFFSET  NULL,
    CONSTRAINT pk_Id PRIMARY KEY (Id)
);

CREATE NONCLUSTERED INDEX Index_ExpiresAtTime
    ON AppSqlCache(ExpiresAtTime);


ایجاد جدول ذخیره سازی اطلاعات کش توزیع شده به کمک ابزار Migrations در EF Core

زیر ساخت کش توزیع شده‌ی مبتنی بر SQL Server هیچگونه وابستگی به EF Core ندارد و تمام اجزای آن توسط Async ADO.NET نوشته شده‌اند. اما اگر خواستید قسمت ایجاد جدول مورد نیاز آن‌را به ابزار Migrations در EF Core واگذار کنید، روش کار به صورت زیر است:
- ابتدا یک کلاس دلخواه جدید را با محتوای ذیل ایجاد کنید:
    public class AppSqlCache
    {
        public string Id { get; set; }
        public byte[] Value { get; set; }
        public DateTimeOffset ExpiresAtTime { get; set; }
        public long? SlidingExpirationInSeconds { get; set; }
        public DateTimeOffset? AbsoluteExpiration { get; set; }
    }
- سپس تنظیمات ایجاد جدول متناظر با آن را به نحو ذیل تنظیم نمائید:
    public class MyDBDataContext : DbContext
    {
        public virtual DbSet<AppSqlCache> AppSqlCache { get; set; }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<AppSqlCache>(entity =>
            {
                entity.ToTable(name: "AppSqlCache", schema: "dbo");
                entity.HasIndex(e => e.ExpiresAtTime).HasName("Index_ExpiresAtTime");
                entity.Property(e => e.Id).HasMaxLength(449);
                entity.Property(e => e.Value).IsRequired();
            });
        }
    }
به این ترتیب این جدول جدید به صورت خودکار در کنار سایر جداول برنامه ایجاد خواهند شد.
البته این مورد به شرطی است که بخواهید از یک دیتابیس، هم برای برنامه و هم برای ذخیره سازی اطلاعات کش استفاده کنید.


معرفی تنظیمات رشته‌ی اتصالی و نام جدول ذخیره سازی اطلاعات کش به برنامه

پس از ایجاد جدول مورد نیاز جهت ذخیره سازی اطلاعات کش، اکنون نیاز است این اطلاعات را به برنامه معرفی کرد. برای این منظور به کلاس آغازین برنامه مراجعه کرده و متد الحاقی AddDistributedSqlServerCache را بر روی مجموعه‌ی سرویس‌های موجود فراخوانی کنید؛ تا سرویس‌های این کش توزیع شده نیز به برنامه معرفی شوند:
public void ConfigureServices(IServiceCollection services)
{
    services.AddDistributedSqlServerCache(options =>
    {
        options.ConnectionString = @"Data Source=(localdb)\MSSQLLocalDB;Initial Catalog=sql_cache;Integrated Security=True;";
        options.SchemaName = "dbo";
        options.TableName = "AppSqlCache";
    });
باتوجه به توزیع شده بودن این کش، هیچ الزامی ندارد که ConnectionString ذکر شده‌ی در اینجا با رشته‌ی اتصالی مورد استفاده‌ی توسط EF Core یکی باشد (هرچند مشکلی هم ندارد).


آزمایش کش توزیع شده‌ی تنظیمی با فعال سازی سشن‌ها

سشن‌ها را همانند نکات ذکر شده‌ی در مطلب «ارتقاء به ASP.NET Core 1.0 - قسمت 16 - کار با Sessions» فعال کنید و سپس مقداری را در آن بنویسید:
public IActionResult Index()
{
   HttpContext.Session.SetString("User", "VahidN");
   return Json(true);
}

public IActionResult About()
{
   var userContent = HttpContext.Session.GetString("User");
   return Json(userContent);
}
اکنون از جدول AppSqlCache کوئری بگیرید:


همانطور که مشاهده می‌کنید، سیستم سشن اینبار بجای حافظه، به صورت خودکار از جدول بانک اطلاعاتی SQL Server تنظیم شده‌، برای ذخیره سازی اطلاعات خود استفاده کرده‌است.


کار با کش توزیع شده از طریق برنامه نویسی

همانطور که در مقدمه‌ی بحث نیز عنوان شد، استفاده‌ی از زیر ساخت کش توزیع شده منحصر به استفاده‌ی از آن جهت ذخیره سازی اطلاعات سشن‌ها نیست و از آن می‌توان جهت انواع و اقسام سناریوهای مختلف مورد نیاز استفاده کرد. در این حالت روش دسترسی به این زیر ساخت، از طریق اینترفیس IDistributedCache است. زمانیکه متد AddDistributedSqlServerCache را فراخوانی می‌کنیم، در حقیقت کار ثبت یک چنین سرویسی به صورت خودکار انجام خواهد شد:
 services.Add(ServiceDescriptor.Singleton<IDistributedCache, SqlServerCache>());
به عبارتی کلاس SqlServerCache به صورت singleton به مجموعه‌ی سرویس‌های برنامه اضافه شده‌است و برای دسترسی به آن تنها کافی است اینترفیس IDistributedCache را به کنترلرها و یا سرویس‌های برنامه تزریق و از امکانات آن استفاده کنیم.

در اینجا یک نمونه از این تزریق وابستگی و سپس استفاده‌ی از متدهای Set و Get اینترفیس IDistributedCache را مشاهده می‌کنید:
using System;
using System.Text;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Caching.Distributed;
 
namespace Core1RtmEmptyTest.Controllers
{
    public class CacheTestController : Controller
    {
        readonly IDistributedCache _cache;
        public CacheTestController(IDistributedCache cache)
        {
            _cache = cache;
        }
 
        public IActionResult SetCacheData()
        {
            var time = DateTime.Now.ToLocalTime().ToString();
            var cacheOptions = new DistributedCacheEntryOptions
            {
                AbsoluteExpiration = DateTime.Now.AddYears(1)
 
            };
            _cache.Set("Time", Encoding.UTF8.GetBytes(time), cacheOptions);
            return View();
        }
 
        public IActionResult GetCacheData()
        {
            var time = Encoding.UTF8.GetString(_cache.Get("Time"));
            ViewBag.data = time;
            return View();
        }
 
        public bool RemoveCacheData()
        {
            _cache.Remove("Time");
            return true;
        }
    }
}
در ابتدای بحث که ساختار جدول ذخیره سازی اطلاعات کش را بررسی کردیم، فیلد value آن یک چنین نوعی را دارد:
  Value  VARBINARY (MAX) NOT NULL,
که در سمت کدهای دات نتی، به شکل آرایه‌ای از بایت‌ها قابل بیان است.
  public byte[] Value { get; set; }
به همین جهت متد Set آن مقدار مدنظر را به صورت آرایه‌ای از بایت‌ها قبول می‌کند.
در این حالت اگر برنامه را اجرا و مسیر http://localhost:7742/CacheTest/SetCacheData را فراخوانی کنیم، اطلاعات ذخیره شده‌ی با کلید Test را می‌توان در بانک اطلاعاتی مشاهده کرد:



Tag helper مخصوص کش توزیع شده

در ASP.NET Core، می‌توان از یک Tag Helper جدید به نام distributed-cache برای کش سمت سرور توزیع شده‌ی محتوای قسمتی از یک View به نحو ذیل استفاده کرد:
<distributed-cache name="MyCacheItem2" expires-sliding="TimeSpan.FromMinutes(30)">
    <p>From distributed-cache</p>
    @DateTime.Now.ToString()
</distributed-cache>
که اطلاعات آن در بانک اطلاعاتی به نحو ذیل ذخیره می‌شود:


در اینجا name به صورت هش شده به صورت کلید کش مورد استفاده قرار می‌گیرد. سپس محتوای تگ distributed-cache رندر شده، تبدیل به آرایه‌ای از بایت‌ها گردیده و در بانک اطلاعاتی ذخیره می‌گردد.
ذکر name در اینجا اجباری است و باید دقت داشت که چون به عنوان کلید بازیابی کش مورد استفاده قرار خواهد گرفت، نباید به اشتباه در قسمت‌های دیگر برنامه با همین نام وارد شود. در غیر اینصورت دو قسمتی که name یکسانی داشته باشند، یک محتوا را نمایش خواهند داد.
مطالب
React 16x - قسمت 34 - توزیع برنامه
در قسمت آخر این سری، نگاهی خواهیم داشت به نحوه‌ی توزیع برنامه‌های React و نکات مرتبط با آن.


افزودن متغیرهای محیطی

در برنامه‌ی نمایش لیست فیلم‌هایی که تا قسمت 29 آن‌را بررسی کردیم، از فایل src\config.json برای ذخیره سازی اطلاعات تنظیمات برنامه استفاده شد. هرچند این روش کار می‌کند اما بر اساس محیط‌های مختلف توسعه، متغیر نیست. اغلب برنامه‌ها باید بتوانند حداقل در سه محیط توسعه، آزمایش و تولید، بر اساس متغیرها و تنظیمات خاص هر کدام، کار کنند. برای مثال بر روی سیستمی که کار توسعه در آن انجام می‌شود، می‌خواهیم apiUrl متفاوتی را نسبت به حالتیکه برنامه توزیع می‌شود، داشته باشیم.
برای رفع این مشکل، برنامه‌هایی که توسط create-react-app تولید می‌شوند، دارای پشتیبانی توکاری از متغیرهای محیطی هستند. برای این منظور نیاز است در ریشه‌ی پروژه (جائیکه فایل package.json قرار دارد) فایل جدید env. را ایجاد کرد. در ویندوز برای ایجاد یک چنین فایل‌هایی که فقط از یک پسوند تشکیل می‌شوند، باید نام فایل را به صورت .env. وارد کرد؛ سپس خود ویندوز نقطه‌ی نهایی را حذف می‌کند. البته اگر از ادیتور VSCode برای ایجاد این فایل استفاده می‌کنید، نیازی به درج نقطه‌ی انتهایی نیست. در این فایل environment ایجاد شده می‌توان تمام متغیرهای محیطی مورد نیاز را با مقادیر پیش‌فرض آن‌ها درج کرد. همچنین می‌توان این مقادیر پیش‌فرض را بر اساس محیط‌های مختلف کاری، بازنویسی کرد. برای مثال می‌توان فایل env.development. را اضافه کرد؛ به همراه فایل‌های env.test. و env.production.


متغیرهای محیطی به صورت key=value درج می‌شوند. این کلیدها نیر باید با REACT_APP_ شروع شوند؛ در غیر اینصورت، کار نخواهند کرد. برای مثال در فایل env.، دو متغیر پیش‌فرض زیر را تعریف می‌کنیم:
REACT_APP_NAME=My App
REACT_APP_VERSION=1
اکنون برای خواندن این متغیرها برای مثال در فایل index.js (و یا هر فایل جاوا اسکریپتی دیگری در برنامه)، سطر زیر را درج می‌کنیم:
console.log(process.env);
process به معنای پروسه‌ی جاری برنامه‌است (و مرتبط است به پروسه‌ی node.js ای که برنامه‌ی React را اجرا می‌کند) و خاصیت env، به همراه تمام متغیرهای محیطی برنامه می‌باشد. در این حالت اگر برنامه را اجرا کنیم، در کنسول توسعه دهندگان مرورگر، به یک چنین خروجی خواهیم رسید:


در این خروجی، متغیر "NODE_ENV: "development به صورت خودکار با تولید بسته‌های مخصوص ارائه‌ی نهایی، به production تنظیم می‌شود. سایر متغیرهای محیطی تعریف شده را نیز در اینجا ملاحظه می‌کنید. با توجه به خواص شیء env، برای مثال جهت دسترسی به نام برنامه می‌توان از مقدار process.env.REACT_APP_NAME استفاده کرد.


یک نکته: با هر تغییری در مقادیر متغیرهای محیطی، نیاز است یکبار دیگر برنامه را از ابتدا توسط دستور npm start، راه اندازی مجدد کرد؛ چون این فایل‌ها به صورت خودکار ردیابی نمی‌شوند.


نحوه‌ی پردازش متغیرهای محیطی درج شده‌ی در برنامه

اگر همان سطر لاگ کردن خروجی process.env را به صورت زیر تغییر دهیم:
console.log("My App Name", process.env.REACT_APP_NAME);
و برنامه را مجددا اجرا کنیم، با مراجعه‌ی به برگه‌ی Sources و انتخاب مسیر localhost:3000/static/js/main.chunk.js و سپس جستجوی "My App Name" ای که در اینجا اضافه کردیم (با فشردن دکمه‌های Ctrl+F)، به خروجی زیر خواهیم رسید:


همانطور که مشاهده می‌کنید، فراخوانی console.log ما، دیگر به همراه متغیر process.env.REACT_APP_NAME نیست؛ بلکه مقدار اصلی این متغیر در اینجا درج شده‌است. بنابراین اگر در در حین توسعه‌ی برنامه، از متغیرهای محیطی استفاده شود، این متغیرها با مقادیر اصلی آن‌ها در حین پروسه‌ی Build نهایی، جایگزین می‌شوند.


Build برنامه‌های React برای محیط تولید

اجرای دستور npm start، سبب ایجاد یک Build مخصوص محیط توسعه می‌شود که بهینه سازی نشده‌است و به همراه اطلاعات اضافی قابل توجهی جهت دیباگ ساده‌تر برنامه‌است. برای رسیدن به یک خروجی بهینه سازی شده‌ی مخصوص محیط تولید و ارائه‌ی نهایی باید دستور npm run build را در خط فرمان اجرا کرد. خروجی نهایی این دستور، در پوشه‌ی جدید build واقع در ریشه‌ی پروژه، قرار می‌گیرد. اکنون می‌توان کل محتویات این پوشه را جهت ارائه‌ی نهایی در وب سرور خود، مورد استفاده قرار داد.
پس از پایان اجرای دستور npm run build، پیام «امکان ارائه‌ی آن توسط static server زیر نیز وجود دارد» ظاهر می‌شود:
> npm install -g serve
> serve -s build
اگر علاقمند باشید تا خروجی حالت production تولید شده را نیز به صورت محلی آزمایش کنید، ابتدا باید static server یاد شده را توسط دستور npm install فوق نصب کنید. سپس ریشه‌ی پروژه را در خط فرمان باز کرده و دستور serve -s build را صادر کنید (البته اگر با خط فرمان به پوشه‌ی build وارد شدید، دیگر نیازی به ذکر پوشه‌ی build نخواهد بود). اکنون می‌توانید برنامه را در آدرس http://localhost:5000 در مرورگر خود بررسی نمائید.

البته با توجه به اینکه backend سرور برنامه‌های ما نیز در همین آدرس قرار دارد و در صورت ورود این آدرس، به صورت خودکار به https://localhost:5001/index.html هدایت خواهید شد، می‌توان این پورت پیش‌فرض را با اجرای دستور  serve -s build -l 1234 تغییر داد. اکنون می‌توان آدرس جدید http://localhost:1234 را در مرورگر آزمایش کرد که ... با خطای زیر کار نمی‌کند:
Access to XMLHttpRequest at 'https://localhost:5001/api/genres' from origin 'http://localhost:1234' has been blocked by CORS policy:
Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.
روش رفع این مشکل را در قسمت 23 بررسی کردیم و در اینجا جهت بهبود آن می‌توان متد WithOrigins فایل Startup.cs را به صورت زیر تکمیل کرد:
WithOrigins("http://localhost:3000", "http://localhost:1234")

یک نکته: زمانیکه از دستور npm start استفاده می‌شود، متغیرهای محیطی از فایل env.development. خوانده خواهند شد و زمانیکه از دستور npm run build استفاده می‌شود، این متغیرها از فایل env.production. تامین می‌شوند. در این حالت‌ها اگر متغیری در این دو فایل درج نشده بود، از مقدار پیش‌فرض موجود در فایل env. استفاده می‌گردد. از فایل env.test. با اجرای دستور npm test، به صورت خودکار استفاده می‌شود.


آماده سازی برنامه‌ی React، برای توزیع نهایی

تا اینجا برنامه‌ی React تهیه شده، اطلاعات apiUrl خودش را از فایل config.json دریافت می‌کند. اکنون می‌خواهیم بر اساس حالات مختلف توسعه و تولید، از apiUrlهای متفاوتی استفاده شود. به همین جهت به فایل env.production. مراجعه کرده و تنظیمات ذیل را به آن اضافه می‌کنیم:
REACT_APP_API_URL=https://localhost:5001/api
REACT_APP_ADMIN_ROLE_NAME=Admin
البته فعلا همین متغیرها را به فایل env.development. نیز می‌توان اضافه کرد؛ چون backend سرور ما در هر دو حالت، در این مثال، در آدرس فوق قرار دارد.

اکنون به برنامه مراجعه کرده و در هرجائی که ارجاعی به فایل config.json وجود دارد، سطر import آن‌را حذف می‌کنیم. با این تغییر، تمام آدرس‌هایی مانند:
const apiEndpoint = apiUrl + "/users";
را به صورت زیر ویرایش می‌کنیم:
const apiEndpoint =  "/users";
در ادامه برای تامین مقدار apiUrl به صورت خودکار، به فایل src\services\httpService.js مراجعه کرده و در ابتدای آن، یک سطر زیر را اضافه می‌کنیم:
 axios.defaults.baseURL = process.env.REACT_APP_API_URL;
به این ترتیب تمام درخواست‌های ارسالی توسط Axios، دارای baseURL ای خواهند شد که از فایل متغیر محیطی جاری تامین می‌شود. همانطور که پیش‌تر نیز عنوان شد، این مقدار در زمان Build، با مقدار ثابتی که از فایل env جاری خوانده می‌شود، جایگزین خواهد شد.
همچنین adminRoleName مورد نیاز در فایل src\services\authService.js را نیز از همان فایل env جاری تامین می‌کنیم:
const adminRoleName =  process.env.REACT_APP_ADMIN_ROLE_NAME;
پس از این تغییرات، نیاز است برای حالت توسعه، یکبار دیگر دستور npm start و یا برای حالت تولید، دستور npm run build را اجرا کرد تا اطلاعات درج شده‌ی در فایل‌های env.، پردازش و جایگزین شوند.


کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: sample-34-frontend.zip و sample-34-backend.zip
مطالب
توسعه برنامه‌های Cross Platform با Xamarin Forms & Bit Framework - قسمت چهاردهم
در این قسمت قصد داریم به بررسی نحوه‌ی مدیریت خطاها و لاگ کردن آنها بپردازیم. همچنین در ادامه Analytics را در برنامه فعال می‌کنیم تا اطلاعاتی از دستگاه‌های کاربران و ... را به دست بیاوریم (اگر آخرین تغییرات XamApp را Pull/Clone کنید، حاوی تمامی تغییرات زیر است).

در برنامه‌های Native موبایل که شامل Xamarin Forms نیز می‌شود، هر خطایی می‌تواند باعث Crash کردن کل برنامه شود. در Bit Framework، تمامی خطاها مدیریت می‌شوند، تا جلوی بسته شدن برنامه گرفته شود و از این بابت مشکلی متوجه برنامه نمی‌شود و در کدها نیازی به نوشتن Try/Catch هم نیست. خطاها پس از رخ دادن، به کلاس BitExceptionHandler ارسال می‌شوند. شما می‌توانید از این کلاس ارث بری کنید و به شکل زیر حداقل از رخ دادن خطاها در Debug Mode مطلع شوید:
public class XamAppExceptionHandler : BitExceptionHandler
{
     public override void OnExceptionReceived(Exception exp, IDictionary<string, string> properties = null)
      {
#if DEBUG
            System.Diagnostics.Debugger.Break();
#endif
            base.OnExceptionReceived(exp, properties);
      }
}
سپس در ابتدای برنامه‌های Android/iOS/Windows یعنی در فایل‌های MainActivity.cs/AppDelegate.cs/App.xaml.cs کد زیر را برای معرفی کلاس خودتان استفاده کنید:
BitExceptionHandler.Current = new XamAppExceptionHandler();
به صورت پیش فرض، تمامی خطاها را در قسمت System.Diagnostics.Debugger.Break مشاهده خواهید نمود، ولی می‌توانید با باز کردن Exception Settings از منوی Debug > Windows > Exception Settings در ویژوال استودیو و تیک زدن Common Language Runtime Exceptions، تمامی خطاها را در جائیکه رخ می‌دهند، مشاهده کنید که به شما اطلاعات بیشتری نیز می‌دهد.


حال برای لاگ کردن این خطاها، می‌توانید از Microsoft's AppCenter استفاده کنید. استفاده از امکانات App Center رایگان بوده و برای استفاده‌ی در ایران محدودیتی ندارد. ابتدا در سایت مربوطه ثبت نام کنید و سپس سه بار Add New app را بزنید و به نام‌های XamApp_Windows، XamApp_iOS و XamApp_Android سه برنامه را بسازید و برای Android و iOS گزینه‌ی Xamarin را انتخاب و برای ویندوز نیز UWP را انتخاب کنید.

سپس پکیج‌های Microsoft.AppCenter.Crashes و Microsoft.AppCenter.Analytics را بر روی XamApp نصب نموده و کد زیر را در سه فایل AppDelegate.cs/MainActivity.cs/App.xaml.cs برای iOS/Android/Windows کپی کنید:

AppCenter.Start("copy-your-guid-key-for-iOS-Android-Windows-here", typeof(Crashes), typeof(Analytics));

برای هر یک از برنامه‌های Android/iOS/Windows یک Guid متفاوت دارید که در قسمت Getting Started در سایت App Center می‌توانید آنها را مشاهده کنید. هر بار که این کد را کپی می‌کنید، مقدار Guid درست را بگذارید.

برای گزارش کردن خطاهای برنامه، کافی است کد زیر را به XamAppExceptionHandler.cs که در ابتدای این قسمت در موردش صحبت کرده بودیم اضافه کنید:

Crashes.TrackError(exp, properties);

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


حال در یک کد ده خطی، اگر در خط پنجم خطایی رخ دهد، اگر چه باعث بسته شدن برنامه نمی‌شود و لاگ نیز می‌شود، ولی در مواقعی خیلی خاص، شاید بخواهید در صورت رخ دادن خطا، چند خط کد بعدی کماکان اجرا شوند. در این حالت شما Try/Catch می‌نویسید که برای عبور کردن از خطا از آن استفاده کرده‌اید. در این صورت، ترجیحا آن را به شکل زیر بنویسید:

// code 1...
try
{
      // code 2...
}
catch (Exception ex)
{
                BitExceptionHandler.Current.OnExceptionReceived(ex, new Dictionary<string, string>
                {
                    { "SomeData", "2" }
                });
}
// code 3...

در این کد مثال، فرض کنیم که برخی اوقات در code 2 خطایی رخ می‌دهد که برای ما مهم نیست و می‌خواهیم حتی در صورت رخ دادن خطا، code 3 اجرا شود. توصیه می‌کنیم در این موارد که در برنامه خیلی هم نباید متداول باشد، لااقل خطا را با کمک کد BitExceptionHandler.Current.OnExceptionReceived لاگ کنید و همچنین با داشتن یک Dictionary می‌توانید حتی دیتای بیشتری را نیز به AppCenter فرستاده و در پرتال مربوطه مشاهده کنید.

به صورت کلی بهتر است از این نوع Try/Catch‌ها پرهیز کنید و حتی اگر جایی Catch ای نوشتید، در نهایت دوباره خطا را throw کنید.

try
{
     // some codes...
}
catch
{
      // Do something related to exception...
      // for example, show some alerts to the user.
      throw; // You don't need to call BitExceptionHandler.Current.OnExceptionReceived...
}

در مثال فوق، قصد داریم وقتی خطایی رخ داد، پیامی را به کاربر اطلاع دهیم. در این صورت، پس از نمایش پیام مربوطه، مجددا خطا را throw کنید. در این صورت، نیازی به فراخوانی BitExceptionHandler.Current.OnExceptionReceieved نیز نیست.


البته AppCenter در زمینه پابلیش کردن برنامه و همچنین Push Notification و ... نیز دارای امکاناتی هست که به موضوع این قسمت ارتباطی ندارند.

مطالب
آغاز به کار با Twitter Bootstrap در ASP.NET MVC
Twitter Bootstrap یک فریم ورک CSS بسیار محبوب سورس باز تولید برنامه‌های وب به کمک HTML، CSS و جاوا اسکریپت است. این فریم ورک حاوی بسیاری از المان‌های مورد نیاز جهت تولید وب سایت‌هایی زیبا، مانند دکمه‌ها، عناصر فرم‌ها، منوها، ویجت‌ها و غیره است. تمام این‌ها نیز همانطور که عنوان شد برمبنای HTML، CSS و جاوا اسکریپت تهیه شده‌اند؛ بنابراین در هر نوع فناوری سمت سروری مانند ASP.NET، PHP، روبی و امثال آن قابل استفاده است.


دریافت Twitter Bootstrap

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


و یا حتی از منابع سایت http://rbootstrap.ir نیز می‌توان استفاده کرد.
برای نمونه دستور زیر را در کنسول پاورشل ویژوال استودیو وارد نمائید تا اسکریپت‌ها و فایل‌های CSS مورد نیاز به پروژه جاری اضافه شوند:
 PM> Install-Package Twitter.BootstrapRTL
پس از نصب، شاهد افزوده شدن چنین فایل‌هایی به یک پروژه ASP.NET MVC خواهیم بود:


در اینجا فایل‌های min، نگارش‌های فشرده شده فایل‌های js یا css هستند که با توجه به امکانات اضافه شده به ASP.NET MVC4، از آن‌ها استفاده نخواهیم کرد و برای افزودن و تعریف آن‌ها از امکانات Bundling and minification توکار فریم ورک ASP.NET MVC به نحوی که در ادامه توضیح داده خواهد شد، استفاده می‌کنیم.
فایل‌های png اضافه شده، آیکون‌های مخصوص Twitter Bootstrap هستند که اصطلاحا به آن‌ها sprite images نیز گفته می‌شود. در این نوع تصاویر، تعداد زیادی آیکون در کنار هم، برای بهینه سازی تعداد بار رفت و برگشت به سرور جهت دریافت تصاویر، طراحی شده و قرار گرفته‌اند.
فایل‌های js این مجموعه اختیاری بوده و برای استفاده از ویجت‌های Twitter Bootstrap مانند آکاردئون کاربرد دارند. این فایل‌ها برای اجرا، نیاز به jQuery خواهند داشت.


افزودن تعاریف اولیه Twitter Bootstrap به یک پروژه ASP.NET MVC

امکانات Bundling and minification در نوع پروژه‌های نسبتا جامع‌تر ASP.NET MVC به صورت پیش فرض لحاظ شده است. اما اگر یک پروژه خالی را شروع کرده‌اید، نیاز است بسته نیوگت آن‌را نیز نصب کنید:
 PM> Install-Package Microsoft.AspNet.Web.Optimization
پس از آن، کلاس کمکی BundleConfig ذیل را به پروژه جاری اضافه نمائید. به کمک آن قصد داریم امکانات Bundling and minification را فعال کرده و همچنین به آن بگوییم اسکریپت‌ها و فایل‌های CSS تعریف شده را به همان نحوی که معرفی می‌کنیم، پردازش کن (توسط کلاس سفارشی AsIsBundleOrderer).

using System.Collections.Generic;
using System.IO;
using System.Web;
using System.Web.Optimization;

namespace Mvc4TwitterBootStrapTest.Helper
{
    /// <summary>
    /// A custom bundle orderer (IBundleOrderer) that will ensure bundles are 
    /// included in the order you register them.
    /// </summary>
    public class AsIsBundleOrderer : IBundleOrderer
    {
        public IEnumerable<FileInfo> OrderFiles(BundleContext context, IEnumerable<FileInfo> files)
        {
            return files;
        }
    }

    public static class BundleConfig
    {
        private static void addBundle(string virtualPath, bool isCss, params string[] files)
        {
            BundleTable.EnableOptimizations = true;

            var existing = BundleTable.Bundles.GetBundleFor(virtualPath);
            if (existing != null)
                return;

            Bundle newBundle;
            if (HttpContext.Current.IsDebuggingEnabled)
            {
                newBundle = new Bundle(virtualPath);
            }
            else
            {
                newBundle = isCss ? new Bundle(virtualPath, new CssMinify()) : new Bundle(virtualPath, new JsMinify());
            }
            newBundle.Orderer = new AsIsBundleOrderer();

            foreach (var file in files)
                newBundle.Include(file);

            BundleTable.Bundles.Add(newBundle);
        }

        public static IHtmlString AddScripts(string virtualPath, params string[] files)
        {
            addBundle(virtualPath, false, files);
            return Scripts.Render(virtualPath);
        }

        public static IHtmlString AddStyles(string virtualPath, params string[] files)
        {
            addBundle(virtualPath, true, files);
            return Styles.Render(virtualPath);
        }

        public static IHtmlString AddScriptUrl(string virtualPath, params string[] files)
        {
            addBundle(virtualPath, false, files);
            return Scripts.Url(virtualPath);
        }

        public static IHtmlString AddStyleUrl(string virtualPath, params string[] files)
        {
            addBundle(virtualPath, true, files);
            return Styles.Url(virtualPath);
        }
    }
}
نکته دیگری که در این کلاس سفارشی درنظر گرفته شده، عدم فعال سازی مباحث Bundling and minification در حالت Debug است. اگر در وب کانفیگ، تنظیمات پروژه را بر روی Release قرار دهید فعال خواهد شد (مناسب برای توزیع) و در حالت توسعه برنامه (حالت دیباگ)، این اسکریپت‌ها و فایل‌های CSS، قابلیت دیباگ و بررسی را نیز خواهند داشت.

پس از افزودن کلاس‌های کمکی فوق، به فایل layout پروژه مراجعه کرده و تعاریف ذیل را به ابتدای فایل اضافه نمائید:
@using Mvc4TwitterBootStrapTest.Helper
<!DOCTYPE html>
<html>
<head>
    <title>@ViewBag.Title</title>
    @BundleConfig.AddStyles("~/Content/css",
                            "~/Content/bootstrap.css",
                            "~/Content/bootstrap-responsive.css",
                            "~/Content/Site.css"
                            )
    @BundleConfig.AddScripts("~/Scripts/js",
                            "~/Scripts/jquery-1.9.1.min.js",
                            "~/Scripts/jquery.validate.min.js",
                            "~/Scripts/jquery.unobtrusive-ajax.min.js",
                            "~/Scripts/jquery.validate.unobtrusive.min.js",
                            "~/Scripts/bootstrap.min.js"
                            )
    @RenderSection("JavaScript", required: false)
</head>
و اگر برنامه را اجرا کنید، بجای حداقل 8 مدخل اشاره کننده به فایل‌های اسکریپت و CSS مورد نیاز یک برنامه ASP.NET MVC، فقط دو مدخل ذیل را مشاهده خواهید نمود:
<!DOCTYPE html>
<html>
<head>
    <title>Index</title>
    <link href="/Content/css?v=vsUQD0OJg4AJ-RZH8jSRRCu_rjl2U1nZrmSsaUyxoAc1" rel="stylesheet"/>
    <script src="/Scripts/js?v=GezdoTDiWY3acc3mI2Ujm_7nKKzh6Lu1Wr8TGyyLpW41"></script>
</head>
به این ترتیب تعداد رفت و برگشت‌های مرورگر به سرور، برای دریافت فایل‌های مورد نیاز جهت رندر صحیح یک صفحه، به شدت کاهش خواهد یافت. همچنین این فایل‌ها در حالت release فشرده نیز خواهند شد؛ به همراه تنظیم خودکار هدر کش شدن آن‌ها برای مدتی مشخص، جهت کاهش بار سرور و کاهش پهنای باند مصرفی آن.


مفاهیم پایه‌ای Twitter Bootstrap

الف) Semantic class names
به عبارتی کلاس‌های Twitter Bootstrap دارای نام‌هایی معنا دار و مفهومی می‌باشند؛ مانند کلاس‌های CSS‌ایی، به نام‌های Succes، Error، Info و امثال آن. این نام‌ها مفهومی را می‌رسانند؛ اما در مورد نحوه پیاده سازی آن‌ها جزئیاتی را بیان نمی‌کنند.
برای نمونه می‌توان کلاسی را به نام redText ایجاد کرد. هر چند این نام، توضیحاتی را در مورد علت وجودی‌اش بیان می‌کند، اما بسیار ویژه بوده و در مورد جزئیات پیاده سازی آن نیز اطلاعاتی را ارائه می‌دهد. در این حالت redText معنایی ندارد. چرا یک Text باید قرمز باشد؟ برای مثال این متن قرمز است چون مثلا شخصی، به آن رنگ ویژه علاقه دارد، یا اینکه قرمز است بخاطر نمایش خطایی در صفحه؟ به همین جهت در Twitter Bootstrap از نام‌های مفهومی یاده شده، مانند Error استفاده می‌شود. نام‌هایی معنا دار اما بدون دقیق شدن در مورد ریز جزئیات پیاده سازی آن‌ها. در این حالت می‌توان قالب جدیدی را تدارک دید و با ارائه تعاریف جدیدی برای کلاس Error و نحوه نمایش دلخواهی را به آن اعمال نمود.
یا برای نمونه نام rightside را برای نمایش ستونی در صفحه، درنظر بگیرید. این نام بسیار ویژه است؛ اما Sematic name آن می‌تواند sidebar باشد تا بدون دقیق شدن در جزئیات پیاده سازی آن، در چپ یا راست صفحه قابل اعمال باشد.
Semantic class names کلیدهایی هستند جهت استفاده مجدد از قابلیت‌های یک فریم ورک CSS.

ب) Compositional classes
اکثر کلاس‌های Twitter Bootstrap دارای محدوده کاری کوچکی هستند و به سادگی قابل ترکیب با یکدیگر جهت رسیدن به نمایی خاص می‌باشند. برای مثال به سادگی می‌توان به یک table سه ویژگی color، hover و width برگرفته شده از Twitter Bootstrap را انتساب داد و نهایتا به نتیجه دلخواه رسید؛ بدون اینکه نگران باشیم افزودن کلاس جدیدی در اینجا بر روی سایر کلاس‌های انتساب داده شده، تاثیر منفی دارد.

ج) Conventions
برای استفاده از اکثر قابلیت‌های این فریم ورک CSS یک سری قراردادهای پیش فرضی وجود دارند. برای مثال اگر از کلاس توکار pagination به همراه یک سری ul و li استفاده کنید، به صورت خودکار یک pager شکیل ظاهر خواهد شد. یا برای مثال اگر به یک html table کلاس‌های table table-striped table-hover را انتساب دهیم، به صورت خودکار قراردادهای پیش فرض table مجموعه Twitter Bootstrap به آن اعمال شده، به همراه رنگی ساختن یک درمیان زمینه ردیف‌ها و همچنین فعال سازی تغییر رنگ ردیف‌ها با حرکت ماوس از روی آن‌ها.


طرحبندی صفحات یک سایت به کمک Twitter Bootstrap

بررسی Grid layouts

Layout به معنای طرحبندی و چیدمان محتوا در یک صفحه است. یکی از متداولترین روش‌های طرحبندی صفحات چه در حالت چاپی و چه در صفحات وب، چیدمان مبتنی بر جداول و گریدها است. از این جهت که نحوه سیلان و نمایش محتوا از چپ به راست و یا راست به چپ را به سادگی میسر می‌سازند؛ به همراه اعمال حاشیه‌های مناسب جهت قسمت‌های متفاوت محتوای ارائه شده. Grid در طرحبندی، نمایش بصری نخواهد داشت اما در ساختار صفحه وجود داشته و مباحثی مانند جهت، موقعیت و یکپارچگی و یکدستی طراحی را سبب می‌شود.
به علاوه مرورگرها و مفهوم Grid نیز به خوبی با یکدیگر سازگار هستند. در دنیای HTML و ،CSS طراحی‌ها بر اساس مفهوم ساختار مستطیلی اشیاء صورت می‌گیرد:


برای نمونه در اینجا تصویر CSS Box Model را مشاهده می‌کنید. به این ترتیب، هر المان دارای محدوده‌ای مستطیلی با طول و عرض مشخص، به همراه ویژگی‌هایی مانند Margin، Border و Padding است.
در سال‌های اولیه طراحی وب، عموما کارهای طراحی صفحات به کمک HTML Tables انجام می‌شد. اما با پخته‌تر شدن CSS، استفاده از Tables برای طراحی صفحات کمتر و کمتر گشت تا اینکه نهایتا فریم ورک‌های CSS ایی پدید آمدند تا طراحی‌های مبتنی بر CSS را با ارائه گرید‌ها، ساده‌تر کنند. مانند Blue print، 960 GS و ... Twitter Bootstrap که طراحی مبتنی بر گرید‌های CSS ایی را به مجموعه قابلیت‌های دیگر خود افزوده است.


بررسی Fixed Grids

در اینجا در صفحه layout برنامه، یک Div دربرگیرنده دو Div دیگر را مشاهده می‌کنید:
<body>
    <div>
        <div>
            <h1>
                Title Title
            </h1>
            Text Text Text Text Text Text Text Text Text Text Text Text Text Text Text Text
        </div>
        <div>
            @RenderBody()
        </div>
    </div>
</body>
اگر در این حالت صفحه را در مرورگر بررسی کنیم، تمام قسمت‌های نمایش داده شده به همین نحو از بالا به پایین صفحه قرار خواهند گرفت. اما قصد داریم این محتوا را، دو ستونی نمایش دهیم. Div به همراه Title در یک طرف صفحه و Div دربرگیرنده محتوای صفحات، در قسمتی دیگر.
برای اینکار در Twitter Bootstrap از کلاسی به نام row استفاده می‌شود که بیانگر یک ردیف است. این کلاس را به خارجی‌ترین Div موجود اعمال خواهیم کرد. در یک صفحه، هر تعداد row ایی را که نیاز باشد، می‌توان تعریف کرد. داخل این ردیف‌ها، امکان تعریف ستون‌های مختلف و حتی تعریف ردیف‌های تو در تو نیز وجود دارد. هر ردیف Twitter Bootstrap از 12 ستون تشکیل می‌شود و برای تعریف آن‌ها از کلاس span استفاده می‌گردد. در این حالت جمع اعداد ذکر شده پس از span باید 12 را تشکیل دهند.
<body>
    <div class="row">
        <div class="span7">
            <h1>
                Title Title
            </h1>
            Text Text Text Text Text Text Text Text Text Text Text Text Text Text Text Text
        </div>
        <div  class="span5">
            @RenderBody()
        </div>
    </div>
</body>
برای نمونه در اینجا کلاس row به خارجی‌ترین Div موجود اعمال شده است. دو Div داخلی هر کدام دارای کلاس‌های span ایی جهت تشکیل ستون‌های داخل این ردیف هستند. جمع اعداد بکارگرفته شده پس از آن‌ها، عدد 12 را تشکیل می‌دهد. به این ترتیب به یک طراحی دو ستونی خواهیم رسید.


در این تصویر، قسمت RenderBody کار رندر اکشن متد Index کنترلر Home برنامه را با Viewایی معادل کدهای ذیل، انجام داده است:
@{
    ViewBag.Title = "Index";
}
<h2>
    Index</h2>
<div class="hero-unit">
    <h2>@ViewBag.Message</h2>
    <p>
        This is a template to demonstrate the way to beautify the default MVC template
        using Twitter Bootstrap website. It is pretty simple and easy.</p>
    <p>
        <a href="http://asp.net/mvc" class="btn btn-primary btn-large" style="color: White;">
            To learn more about ASP.NET MVC visit &raquo;</a></p>
</div>

درک نحوه عملکرد Grid در Twitter Bootstrap

در مثال ذیل 5 ردیف را مشاهده می‌کنید:
    <div class="row">
        <div class="span1">1</div>
        <div class="span1">1</div>
        <div class="span1">1</div>
        <div class="span1">1</div>
        <div class="span1">1</div>
        <div class="span1">1</div>
        <div class="span1">1</div>
        <div class="span1">1</div>
        <div class="span1">1</div>
        <div class="span1">1</div>
        <div class="span1">1</div>
        <div class="span1">1</div>
    </div>
    <div class="row">
        <div class="span3">3</div>
        <div class="span4">4</div>
        <div class="span5">5</div>
    </div>
    <div class="row">
        <div class="span5">5</div>
        <div class="span7">7</div>
    </div>
    <div class="row">
        <div class="span3">3</div>
        <div class="span7 offset2">7 offset 2</div>
    </div>
    <div class="row">
        <div class="span12">12</div>
    </div>
در هر ردیف، امکان استفاده از حداکثر 12 ستون وجود دارد که یا می‌توان مانند ردیف اول، 12 ستون را ایجاد کرد و یا مانند ردیف دوم، سه ستون با عرض‌های متفاوت (و یا هر ترکیب دیگری که به جمع 12 برسد).
در ستون چهارم، از کلاس offset نیز استفاده شده است. این مورد سبب می‌شود ستون جاری به تعدادی که مشخص شده است به سمت چپ (با توجه به استفاده از حالت RTL در اینجا) رانده شود و سپس ترسیم گردد.
یا اینکه می‌توان مانند ردیف آخر، یک ستون را به عرض 12 که در حقیقت 940 پیکسل است، ترسیم نمود.
برای اینکه بتوانیم این گرید تشکیل شده و همچنین ستون‌ها را بهتر مشاهده کنیم، به فایل style.css سایت، تنظیم زیر را اضافه کنید:
[class*="span"]
{
background-color: lightblue;
text-align: center;
margin-top: 15px;
}


نکته جالب این گرید، Responsive یا واکنشگرا بودن آن است. در این حالت، عرض مرورگر را کم و زیاد کنید. خواهید دید که ستون‌ها در صورتیکه در عرض نمایشی جاری، قابل ارائه نباشند، به ردیف‌های بعدی منتقل خواهند شد.
البته باید دقت داشت که این گرید هیچگاه یک ستون را نخواهد شکست. برای نمونه ردیف آخر، همواره با همان عرض ثابتش نمایش داده می‌شود و با کوچکتر کردن اندازه مرورگر، یک اسکرول افقی برای نمایش محتوای آن ظاهر خواهد شد.


یک نکته
اگر نمی‌خواهید که چنین رفتار واکنش‌گرایی بروز کند، نیاز است کلیه ردیف‌ها را در div ایی با کلاسی به نام container محصور کنید.
به این ترتیب ابتدا گرید نمایش داده شده، در میانه صفحه ظاهر خواهد شد (پیشتر از سمت راست شروع شده بود). همچنین دیگر با کوچک و بزرگ شدن اندازه مرورگر، ستون‌ها به شکل یک پشته بر روی هم قرار نخواهند گرفت. (اگر پس از این تنظیم، چنین قابلیتی را مشاهده نکردید و هنوز هم طراحی، واکنشگرا بود، تعریف bootstrap-responsive.css را نیاز است برای آزمایش، از هدر صفحه حذف کنید)


بررسی Fluid Grids

به گرید قسمت قبل از این جهت Fixed Grid گفته می‌شود که عرض هر span آن با یک عدد مشخص تعیین گشته است. اما در حالت Fluid Grid، عرض هر span برحسب درصد تعیین می‌شود. بکارگیری درصد در اینجا به معنای امکان تغییر عرض یک ستون بر اساس عرض جاری Container آن است. در اینجا span12 دارای عرض 100 درصد خواهد بود.
در مثال قبل، برای استفاده از Fluid grids، تنها کافی است هرجایی کلاسی مساوی row وجود دارد، به row-fluid تغییر کند. همچنین کلاس container را به container-fluid تغییر دهید.
برای آزمایش آن، اندازه و عرض نمایشی مرورگر خود را تغییر دهید. اینبار مشاهده خواهید کرد که برخلاف حالت Fixed Grid، عرض ستون‌ها به صورت خودکار کم و زیاد می‌شوند. این مورد بر روی محتوای قرار گرفته در این ستون‌ها نیز تاثیر گذار است. برای مثال اگر یک تصویر را در حالت Fluid grid در ستونی قرار دهید، با تغییر عرض مرورگر، اندازه این تصویر نیز تغییر خواهند کرد؛ اما در حالت Fixed Grid خیر.
حالت Fluid، شیوه متداول استفاده از bootstrap در اکثر سایت‌های مهمی است که تابحال از این فریم ورک CSS استفاده کرده‌اند.



مروری بر طراحی واکنشگرا یا Responsive

این روزها تعدادی از کاربران، با استفاده از ابزارهای موبایل و تبلت‌ها از وب سایت‌ها بازدید می‌کنند. هر کدام از این‌ها نیز دارای اندازه نمایشی متفاوتی می‌باشند. بنابراین نیاز خواهد بود تا حالت بهینه‌ای را جهت اینگونه وسایل نیز طراحی نمود. حالت بهینه در اینجا به معنای قابل خواندن بودن متون، امکان استفاده از لینک‌های ورود به صفحات مختلف و همچنین حذف اسکرول و مباحث زوم جهت مشاهده صفحات است.
یکی از ویژگی‌های CSS به نام media چنین قابلیتی را فراهم می‌سازد. برای نمونه قسمتی از فایل bootstrap-responsive.css دارای چنین تعاریفی است:
@media (min-width: 768px) and (max-width: 979px) {
    .hidden-desktop {
        display: inherit !important;
    }
توسط این تنظیمات، شیوه نامه تعیین شده، تنها به صفحاتی با اندازه‌هایی بین 768 تا 979 پیکسل اعمال می‌شود (تعدادی از تبلت‌ها برای نمونه).
Bootstrap برای مدیریت اندازه‌های مختلف وسایل موبایل، شیوه‌نامه‌های خاصی را تدارک دیده است که از اندازه px480 و یا کمتر، تا px1200 و یا بیشتر را پوشش می‌دهد.
به این ترتیب با اندازه px940 که پیشتر در مورد آن بحث شد، اندازه span12 به صورت خودکار به اندازه‌های متناسب با صفحات نمایشی کوچکتر تنظیم می‌گردد. همچنین برای اندازه‌های صفحات کوچکتر از 768px به صورت خودکار از Fluid columns استفاده می‌گردد.
تنها کاری که برای اعمال این قابلیت باید صورت گیرد، افزودن تعاریف فایل bootstrap-responsive.css به هدر صفحه است که در قسمت قبل انجام گردید. این فایل باید پس از فایل اصلی bootstrap.css اضافه شود.


کلاس‌های کمکی طراحی واکنشگرا

Bootstrap شامل تعدادی کلاس کمکی در فایل bootstrap-responsive.css خود می‌باشد شامل visible-phone، visible-tablet و visible-desktop به علاوه hidden-phone، hidden-tablet و hidden-desktop. به این ترتیب می‌توان محتوای خاصی را جهت وسایل ویژه‌ای نمایان یا مخفی ساخت.
برای مثال محتوای مشخص شده با کلاس hidden-desktop، در اندازه وسایل دسکتاپ نمایش داده نخواهد شد.
برای آزمایش آن، شش div را با کلاس‌های یاد شده و محتوایی دلخواه ایجاد کرده و سپس اندازه عرض مرورگر را تغییر دهید تا بهتر بتوان مخفی یا نمایان ساختن محتوا را بر اساس اندازه صفحه جاری درک کرد.
یکی از کاربردهای این قابلیت، قرار دادن تبلیغاتی با اندازه‌های تصاویری مشخص برای وسایل مختلف است؛ بجای اینکه منتظر شویم تا Fluid layout اندازه تصاویر را به صورت خودکار کوچک یا بزرگ کند، که الزاما بهترین کیفیت را حاصل نخواهد ساخت.
    <div class="container-fluid">
        <div class="row-fluid">
            <div class="span4">
                <div class="visible-phone">
                    visible-phone</div>
            </div>
            <div class="span4">
                <div class="visible-tablet">
                    visible-tablet</div>
            </div>
            <div class="span4">
                <div class="visible-desktop">
                    visible-desktop</div>
            </div>
        </div>
    </div>
    <div class="container-fluid">
        <div class="row-fluid">
            <div class="span4">
                <div class="hidden-phone">
                    hidden-phone</div>
            </div>
            <div class="span4">
                <div class="hidden-tablet">
                    hidden-tablet</div>
            </div>
            <div class="span4">
                <div class="hidden-desktop">
                    hidden-desktop</div>
            </div>
        </div>
    </div>

مطالب
React 16x - قسمت 33 - React Hooks - بخش 4 - useContext Hook
در سری بررسی اعتبارسنجی و احراز هویت کاربران در React، برای انتقال داده‌های کاربر وارد شده‌ی به سیستم، از روش انتقال props، از بالاترین کامپوننت موجود در component tree، به پایین‌ترین کامپوننت آن، به این نحو فرضی استفاده کردیم:
ابتدا شیء user، در بالاترین سطح، دریافت شده و به صفحه‌ای خاص از طریق ویژگی‌های props ارسال می‌شود:
<Page user={user}  />
سپس این کامپوننت Page، کامپوننت PageLayout را رندر می‌کند که آن نیز باید به اطلاعات کاربر دسترسی داشته باشد. بنابراین شیء user را مجددا به این کامپوننت از طریق props ارسال می‌کنیم:
<PageLayout user={user} />
بعد همین کامپوننت PageLayout، کامپوننت NavBar را رندر می‌کند که آن نیز باید بداند کاربر وارد شده‌ی به سیستم کیست؟ به همین جهت یکبار دیگر از طریق props، اطلاعات کاربر را به کامپوننت بعدی موجود در درخت کامپوننت‌ها انتقال می‌دهیم:
<NavigationBar user={user}  />
و همینطور الی آخر. به این روش props drilling گفته می‌شود و ... الگوی مذمومی است. در دنیای واقعی، اطلاعات کاربر و یا خصوصا تنظیمات برنامه مانند آدرس REST API endpoints استفاده شده‌ی در آن، باید بین بسیاری از کامپوننت‌ها به اشتراک گذاشته شود و عموما سطوح به اشتراک گذاری آن، بسیار عمیق‌تر است از سطوحی که در این مثال ساده عنوان شدند. از زمان ارائه‌ی React 16.3.0، راه حل بهتری برای مدیریت اینگونه مسایل با ارائه‌ی React Context ارائه شده‌است که آن‌را در ادامه در دو حالت کامپوننت‌های کلاسی و همچنین تابعی، بررسی خواهیم کرد.


ایجاد شیء Context در برنامه‌های React

React Context، راه حلی است جهت به اشتراک گذاری داده‌ها، در بین انواع و اقسام کامپوننت‌های یک برنامه، بدون اینکه نیازی باشد این اطلاعات را توسط props، از یک سطح، به سطحی دیگر، به صورت دستی انتقال داد. برای ایجاد یک نمونه‌ی از آن، ابتدا پوشه‌ی جدید src\contexts را افزوده و سپس فایل src\contexts\userContext.js را درون آن، با محتوای زیر ایجاد می‌کنیم:
import React from "react";

export const UserContext = React.createContext({ user: {} });

export const UserProvider = UserContext.Provider;
export const UserConsumer = UserContext.Consumer;
متد React.createContext، یک شیء Context را بازگشت می‌دهد. این شیء، دو کامپوننت مهم Provider و Consumer را به همراه دارد که امکان اشتراک به داده‌های مرتبط با آن‌را میسر می‌کنند. زمانیکه React کامپوننتی را رندر می‌کند که مشترک یک شیء Context است، این کامپوننت، امکان خواندن اطلاعات شیء Context را از نزدیک‌ترین کامپوننتی در درخت کامپوننت‌ها که یک Provider را برای آن ارائه داده‌است، خواهد داشت.


تامین یک شیء Context در برنامه، در یک کامپوننت کلاسی و یا تابعی

تا اینجا یک شیء Context را به همراه اجزای export شده‌ی Provider و Consumer آن ایجاد کردیم. اکنون نوبت به پیاده سازی قسمت Provider آن است:
import "../../App.css";

import React, { Component } from "react";

import { UserProvider } from "../../contexts/userContext";
import Main from "./Main";

class App extends Component {
  state = {
    user: { name: "User 1" }
  };

  componentDidMount() {
    // get user from the server or local storage and then set the currently logged in user to the this.state
  }

  render() {
    return (
      <>
        <h1>App Class</h1>
        <UserProvider value={this.state.user}>
          <Main />
        </UserProvider>
      </>
    );
  }
}

export default App;
در این کامپوننت کلاسی (و یا تابعی، نحوه‌ی تعریف UserProvider در هر دو یکی است)، خاصیت user، به state کامپوننت اضافه شده‌است. سپس برای مثال می‌توان این خاصیت را در رویداد componentDidMount از سرور و یا محل ذخیره سازی دیگری دریافت و آنگاه state را بر این اساس به روز رسانی کرد.
در ادامه قصد داریم اطلاعات این شیء user موجود در state را با تمام کامپوننت‌هایی که در درخت رندر کامپوننت جاری قرار می‌گیرند و با کامپوننت Main شروع می‌شوند، به اشتراک بگذاریم. این به اشتراک گذاری با import شیء UserProvider از ماژول contexts/userContext به نحوی که مشاهده می‌کنید، انجام می‌شود. شیء UserProvider، کار محصور سازی کامپوننت Main را انجام می‌دهد. سپس این Provider می‌تواند مقداری را توسط ویژگی value خود دریافت کند که برای مثال در اینجا شیء user است. اکنون این value تا n سطح بعدی که از کامپوننت Main مشتق می‌شوند نیز در دسترس خواهد بود.

یک نکته: متد React.createContext به همراه یک آرگومان defaultValue اختیاری است که در اختیار Consumerهای آن قرار داده می‌شود؛ اگر Provider متناظر با آن‌، در درخت کامپوننت‌های برنامه، یافت نشود. یعنی تعریف Provider الزامی نیست. اگر نیاز است مقدار ثابتی را بین چندین کامپوننت به اشتراک بگذارید، فقط کافی است آن‌ها را توسط React.createContext مقدار دهی اولیه کرده و ... استفاده کنید:
export const DefaultRouteContext = React.createContext({ path: '/welcome' });


خواندن شیء Context در کامپوننتی دیگر

اکنون که یک تامین کننده‌ی Context را ایجاد کردیم، برای خواندن اطلاعات آن در درخت کامپوننت‌های محصور شده‌ی توسط UserProvider، می‌توان به صورت زیر عمل کرد:
import React from "react";

import { UserConsumer } from "../../contexts/userContext";

export default function Main(props) {
  return (
    <>
      <UserConsumer>
        {value => <div>User name: {value.name}.</div>}
      </UserConsumer>
    </>
  );
}
ابتدا UserConsumer را از ماژول contexts/userContext دریافت می‌کنیم. سپس برای دسترسی به خاصیت name شیء ارائه شده‌ی توسط UserProvider، باید قسمتی از متد رندر کامپوننت را توسط شیء UserConsumer، محصور کرد و سپس value آن‌را به نحوی که مشاهده می‌کنید، خواند. Consumer، یک تابع را به عنوان فرزند دریافت می‌کند. این تابع مقدار شیء تامین شده‌ی توسط Context را دریافت کرده (همان value={this.state.user} نزدیک‌ترین کامپوننتی که به همراه یک Provider است) و سپس یک المان React را بازگشت می‌دهد که در این محل رندر خواهد شد.

خروجی برنامه پس از این تغییرات به صورت زیر است:



ساده سازی دسترسی به UserConsumer توسط useContext Hook

نحوه‌ی تعریف یک Provider و محصور سازی فرزندانی که باید از آن ارث‌بری کنند، در بین کامپوننت‌های کلاسی و تابعی، یکی است. اما در کامپوننت‌های تابعی حداقل می‌توان نحوه‌ی دسترسی به UserConsumer را به نحو زیر توسط useContext Hook ساده کرد:
import React, { useContext } from "react";

import { UserContext } from "../../contexts/userContext";

export default function Main() {
  const value = useContext(UserContext);
  return (
    <>
      <div>User name: {value.name}.</div>
    </>
  );
}
متد useContext ابتدا شیء UserContext مهیا شده‌ی توسط ماژول contexts/userContext را دریافت می‌کند. سپس خروجی آن، همان value تنظیم شده‌ی توسط نزدیک‌ترین Provider آن در component tree است. این روش، بار ذهنی کمتری را نسبت به حالت قبلی استفاده‌ی از UserConsumer و کار با یک تابع درون آن‌را به همراه دارد؛ ساده‌تر خوانده می‌شود، ساده‌تر استفاده می‌شود. فقط باید دقت داشت که این متد، کل شیء Context را دریافت می‌کند و نه فقط شیء UserConsumer آن‌را.

مزیت دیگر این روش، ساده سازی کار با چندین شیء Context است. برای مثال اگر دو شیء Context را تعریف کرده باشید، خواندن دو مقدار از آن‌ها، پیشتر چنین شکل تو در تویی را توسط دو Consumer پیدا می‌کرد:
function HeaderBar() {
  return (
    <CurrentUser.Consumer>
      {user =>
        <Notifications.Consumer>
          {notifications =>
            <header>
              Welcome back, {user.name}!
              You have {notifications.length} notifications.
            </header>
          }
      }
    </CurrentUser.Consumer>
  );
}
اما اکنون با استفاده از useContext، نوشتن و خواندن آن به سادگی چند سطر زیر است که بسیار منطقی‌تر و عادی‌تر به نظر می‌رسد:
function HeaderBar() {
  const user = useContext(CurrentUser);
  const notifications = useContext(Notifications);
return (
    <header>
      Welcome back, {user.name}!
      You have {notifications.length} notifications.
    </header>
  );
}


ارسال اطلاعات به کامپوننت Context Provider، از طریق کامپوننت‌های فرزند

تا اینجا با استفاده از React Context، اطلاعات یک Provider را با فرزندان آن به اشتراک گذاشتیم؛ عکس این عمل نیز میسر است. برای اینکار، همانند تمام کامپوننت‌های دیگری که برای ارسال اطلاعات به فراخوان خود از طریق رخ‌دادها عمل می‌کنند، می‌توان یک متد رویدادگردان را در کامپوننت والد، به استفاده کنند‌ه‌ی از Context ارسال کرد:
import "../../App.css";

import React, { Component } from "react";

import { UserProvider } from "../../contexts/userContext";
import Main from "./Main2";

class App extends Component {
  state = {
    user: { name: "User 1" }
  };

  componentDidMount() {
    // get user from the server or local storage and then set the currently logged in user to the this.state
  }

  logout = () => {
    console.log("logout");
    this.setState({ user: {} });
  };

  render() {
    const contextValue = {
      user: this.state.user,
      logoutUser: this.logout
    };
    return (
      <>
        <h1>App Class</h1>
        <UserProvider value={contextValue}>
          <Main />
        </UserProvider>
      </>
    );
  }
}

export default App;
در اینجا ابتدا به خاصیت logout، متدی را نسبت داده‌ایم که با فراخوانی آن، اطلاعات شیء user موجود در state کامپوننت جاری را پاک می‌کند. سپس این خاصیت را به صورت یک خاصیت جدید، به شیءای که به ویژگی value شیء UserProvider انتساب داده شده، اضافه می‌کنیم.
اکنون تمام استفاده کننده‌های از این UserProvider می‌توانند با فراخوانی متد منتسب به logout، سبب پاک شدن اطلاعات کاربر موجود در state کامپوننت App، به روز رسانی state و در نتیجه‌ی آن، رندر مجدد کامپوننت و ارائه‌ی یک UserProvider جدید، با اطلاعاتی جدید به فرزندان آن شوند:
import React, { useContext } from "react";

import { UserContext } from "../../contexts/userContext";

export default function Main() {
  const { user, logoutUser } = useContext(UserContext);
  return (
    <>
      <div>User name: {user.name}.</div>
      <button type="button" className="btn btn-primary" onClick={logoutUser}>
        Logout user
      </button>
    </>
  );
}
در این کامپوننت مصرف کننده‌ی Context، اینبار، مقدار دریافتی، یک شیء با چندین خاصیت است. بنابراین می‌توان با استفاده از Object Destructuring، خواص آن‌را استخراج و استفاده کرد. برای مثال با انتساب onClick={logoutUser} به دکمه‌ی خروج، این کامپوننت می‌تواند اطلاعات state و سپس Context ارائه شده‌ی در کامپوننت App را تغییر دهد.

روش انجام اینکار بدون استفاده از useContext را نیز در ادامه مشاهده می‌کنید که در ابتدا نیاز به تعریف تابعی را دارد که همان خواص استخراجی را دریافت می‌کند. سپس باید بر اساس آن‌ها، المان‌های مدنظر نمایش نام کاربر و دکمه‌ی خروج او را بازگشت داد:
import React from "react";

import { UserConsumer } from "../../contexts/userContext";

export default function Main(props) {
  return (
    <>
      <UserConsumer>
        {({ user, logoutUser }) => (
          <>
            <div>User name: {user.name}.</div>
            <button
              type="button"
              className="btn btn-primary"
              onClick={logoutUser}
            >
              Logout user
            </button>
          </>
        )}
      </UserConsumer>
    </>
  );
}


کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: sample-30-part-04.zip