مطالب
پیدا کردن وابستگی‌های اشیاء در SQL Server

با بالا رفتن تعداد اشیاء تعریف شده در SQL server ، نگهداری آنها نیز مشکل‌تر می‌شود. در این حالت تغییر کوچکی در یکی از اشیاء ممکن است باعث از کار افتادن قسمتی از سیستم شود. بنابراین قبل از هر گونه تغییری در یک شیء، ابتدا باید سایر اشیاء وابسته به آن‌ را یافت و در نظر داشت ( dependencies ). برای این منظور ( impact analysis ) راه‌کارهای مختلفی در SQL server وجود دارد که در ادامه به آن‌ها خواهیم پرداخت:

الف) استفاده از امکانات management studio (اس کیوال سرور 2005 به بعد)

ساده‌ترین راه ممکن که گزارش مفصلی را نیز ارائه می‌دهد، کلیک بر روی یک شیء در management studio و انتخاب گزینه view dependencies است (شکل زیر).


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

ب) کوئری گرفتن از جداول سیستمی

امکانات قسمت قبل را با استفاده از اطلاعات جدول syscomments نیز می‌توان شبیه سازی کرد. در این جدول اطلاعات تعاریف کلیه view ، trigger ، رویه‌های ذخیره شده و غیره نگهداری می‌شود. برای مثال فرض کنید قصد داریم در جدول Orders دیتابیس Northwind ، نام فیلد OrderDate را تغییر دهیم. قبل از این‌کار بهتر است کوئری زیر را اجرا کنیم تا نام اشیاء وابسته را بدست آوریم:
SELECT NAME
FROM syscomments c
JOIN sysobjects o
ON c.id = o.id
WHERE TEXT LIKE '%OrderDate%'
AND TEXT LIKE '%Orders%'


این روش انعطاف پذیری بیشتری را نسبت به امکانات قسمت الف ، ارائه می‌دهد. برای نمونه فرض کنید می‌خواهید در یک دیتابیس کلیه اشیایی که عملیات delete را انجام می‌دهند پیدا کنید (جستجوی اشیاء حاوی یک عبارت خاص). در این مورد خواهیم داشت:

SELECT NAME
FROM syscomments c
JOIN Northwind.dbo.sysobjects o
ON c.id = o.id
WHERE TEXT LIKE '%delete%'

ج) استفاده از رویه ذخیره شده سیستمی sp_depends

جدول سیستمی دیگری در اس کیوال سرور به نام sysdepends وجود دارد که اطلاعات وابستگی‌های اشیاء در آن‌ها نگهداری می‌شود. برای دسترسی به اطلاعات این جدول ، اس کیوال سرور رویه ذخیره شده سیستمی sp_depends را ارائه داده است. برای مثال فرض کنید می‌خواهیم لیست اشیایی را که به جدول Oredres دیتابیس Northwind وابسته هستند، پیدا کنیم. در این حالت داریم:
USE Northwind
EXEC sp_depends 'Orders'


د) استفاده از schema view

با استفاده از view سیستمی INFORMATION_SCHEMA.ROUTINES ، که از ترکیب جداول syscolumns و sysobjects ایجاد شده است نیز می‌توان عملکرد sp_depends را شبیه سازی کرد اما جداول و view ها از گزارش آن حذف شده‌اند.
SELECT routine_name,
routine_type
FROM INFORMATION_SCHEMA.ROUTINES
WHERE routine_definition LIKE '%Orders%'

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



ه) استفاده از برنامه SQL Dependency Tracker

نسخه آزمایشی برنامه ذکر شده را از این آدرس می‌توان دریافت کرد.


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


مطالب
توسعه برنامه های Cross Platform با Xamarin Forms & Bit Framework - قسمت پانزدهم
در این قسمت قصد داریم تا با زدن کدهای Platform Specific در Xamarin آشنا شویم. صد البته که در Xamarin Forms به کتابخانه‌های NET. ای دسترسی داریم و مواردی چون Entity Framework Core، Auto Mapper، Autofac و ... را می‌توانیم استفاده کنیم و در کنار اینها، مواردی چون Linq, Parallel Linq, Socket و ... نیز در دسترس ما هستند. در رابطه با مواردی چون کار با Clipboard, Geocoding, Gyroscope, Secure Store و ... نیز می‌توان از کتابخانه فوق العاده کاربردی Xamarin Essentials استفاده کرد که با یک کد CSharp می‌توانید روی Android/iOS/Windows جواب بگیرید.
اما فرض کنید که جستجو کرده اید و کد Cross Platform آماده‌ای برای استفاده نیافته‌اید؛ یا پیدا کرده‌اید، ولی صد در صد منطبق با نیازهای شما نیست. حال باید چه کنید؟ ابتدا باید کد مربوطه را بدانید که در Android/iOS/Windows (بسته به نیازتان) چگونه باید نوشت. در مورد Windows، خب تمامی امکانات سیستم عامل ویندوز را در زبان CSharp هم دارید. خبر خوب این است که این مهم نه تنها برای ویندوز که در مورد Android و iOS نیز برقرار است. به علاوه مستندات استفاده از آنها به زبان CSharp نیز موجود است. برای مثال نگاهی بیاندازید به روش Platform Specific استفاده از Bluetooth در Windows و AR Kit 2 در iOS و Job Scheduler در Android
صد البته که کتابخانه فوق العاده BluetoothLE وجود دارد و یک بار نوشتن کد، نه تنها روی Windows/Android/iOS که بر روی macOS و tvOS هم کار می‌کند!

با مثال گرفتن "ورژن برنامه" شروع می‌کنیم. هر چند با استفاده از Xamarin Essentials می شود با یک خط کد، ورژن برنامه را در هر پلتفرمی که باشیم گرفت؛ ولی فرض کنید که نمی‌شود. برای پیاده سازی این قابلیت ابتدا یک Interface را تعریف می‌کنیم و آن را در فولدر Contracts در پروژه XamApp قرار می‌دهیم:
public interface IAppVersionService
{
    string GetAppVersion();
}
سپس در پروژه XamApp.Android، در فولدر Implementations، کلاس زیر را می‌سازیم: (چون این کلاس در پروژه Android است، به 100% امکانات Android دسترسی داریم)
public class AndroidAppVersionService : IAppVersionService
{
    public Android.Content.Context Context { get; set; }

    public string GetAppVersion()
    {
        return Context.PackageManager.GetPackageInfo(Context.PackageName, 0).VersionName;
    }
}
این کد را از روی این جواب در StackOverFlow نوشته‌ام. همانطور که می‌بینید، دو کد، ساختاری شبیه به یکدیگر دارند. فقط تفاوت این است که Context.GetPackageManager در Java، در CSharp به Context.PackageManager تبدیل می‌شود؛ زیرا در Java چیزی به صورت Property و Get,Set وجود ندارد و Context.PackageManager در Java معادل می‌شود با دو متد Context.GetPackageManager و Context.SetPackageManager
تقریبا برای هر کاری در Android نیاز به Context دارید که می‌توانید آن را با Property Injection دریافت کنید.
سپس در فایل MainActivity.cs در کلاس XamAppPlatformInitializer، در متد RegisterTypes داریم:
containerBuilder.RegisterType<AndroidAppVersionService>()
    .As<IAppVersionService>()
    .PropertiesAutowired(PropertyWiringOptions.PreserveSetValues);
برای پیاده سازی همین امکان در iOS داریم:
public class iOSAppVersionService : IAppVersionService
{
    public string GetAppVersion()
    {
        var infoDictionary = NSBundle.MainBundle.InfoDictionary;
        return infoDictionary?["CFBundleShortVersionString"] as NSString;
    }
}
که از روی این جواب به دست آمده است. البته جواب مربوطه علاوه بر ورژن، نام برنامه را نیز به دست می‌آورد که نیاز ما نیست. اگر سایر جواب‌ها را نگاه کنید، می‌بینید که جواب‌های مربوط به Swift برای برنامه نویسان CSharp خوانایی دارند، ولی این در مورد کدهای Objective-C خیلی صادق نیست(!) برای حل این مشکل، کد Objective-C را در این سایت به Swift تبدیل کرده و سپس معادل CSharp آن را بنویسید.
و در نهایت برای UWP از روی این جواب داریم:
public string GetAppVersion()
{
    return $"{Package.Current.Id.Version.Major}.{Package.Current.Id.Version.Minor}"; 
}
که این دو نیز در AppDelegate.cs برای iOS و MainPage.xaml.cs برای UWP رجیستر می‌شوند.
برای استفاده نیز کافی است در هر View Model ای که قصد استفاده از این سرویس را دارید، یک Property از جنس IAppVersionService را تعریف کنید. در صورت Pull کردن آخرین تغییرات پروژه XamApp، می‌توانید نتیجه را در View و View Model با نام PlatformSpecificSamples ببینید.
خبر خوب این است که تمامی کدها به زبان CSharp نوشته می‌شوند و اگر مثلا وسط یک کد Platform Specific برای Android احتیاج به Auto Mapper پیدا کردید، می‌توانید از آن استفاده کنید. همچنین تمامی این کدها در Visual Studio دیباگ می‌شوند که خود نعمتی است.
حال اگر در ادامه کار، به یک کتابخانه 3rd Party که با Java نوشته شده نیاز پیدا کردیم چه؟ برای مثال این کتابخانه اطلاعاتی را در مورد Ringer گوشی، در اختیار ما قرار می‌دهد!
در Xamarin می‌توانید فایل‌های JAR و AAR و Header‌های Objective-C و Swift را در پروژه اضافه کنید و Wrapper به زبان CSharp تحویل بگیرید! علاوه بر مستندات مفصل خود Xamarin در این مورد که برای Android/iOS می توانید آنها را بخوانید. افراد زیادی بر همین اساس امکان استفاده از کتابخانه‌های 3rd Party زیادی را به Xamarin اضافه کرده‌اند. برخی از ابزارها نیز در این زمینه کاربردی هستند؛ برای مثال، برای ساخت C# Wrapper از روی C++,C از ابزار CppSharp می توانید استفاده کنید.
در نظر داشته باشید، اگر بخواهید کدی بزنید که فقط تفاوت رفتار در Android/iOS/Windows را دارد، یا بسته به گوشی، تبلت یا دسکتاپ بودن قرار است رفتارش تفاوت کند، مثلا یک پیام را فقط به دارندگان گوشی‌های اندرویدی نشان دهید، ولی با IUserDialogs که در هر سه پلتفرم کار می‌کند می‌خواهید این کد را بنویسید، احتیاجی به این کارها نیست و به سادگی تعریف یک Property با نام IDeviceService می‌توانید جواب لازم را بگیرید:
async Task ShowSomeAlertToAndroidPhoneUsersOnly()
{
    if (DeviceService.RuntimePlatform == RuntimePlatform.Android && DeviceService.Idiom == TargetIdiom.Phone)
    {
        await UserDialogs.AlertAsync("Some alert to android phone users only!", "Test");
    }
}

در برخی مواقع ما قصد سفارشی سازی کردن کنترل‌های UI را داریم. برای مثال زمانیکه از Entry در Xamarin Forms استفاده می‌کنیم، این به کنترل معادل Native خودش در هر پلتفرم تبدیل می‌شود، که همین باعث می‌شود بگوییم UI در Xamarin Forms به صورت Native است. حال در iOS که ما UITextField را به عنوان معادل Native کنترل Entry داریم، یک ویژگی داریم به نام ClearButtonMode که وقتی به مقدار WhileEditing تنظیم شود، در موقع تایپ کردن در UITextField، آن X پاک کردن متن باقی می‌ماند. این رفتار پیش فرض نیست و اگر ما قصد تغییر آن را داشته باشیم، یکی از متداول‌ترین راه‌ها، نوشتن Custom Renderer است. برای همین در iOS از EntryRenderer ارث بری می‌کنیم و سفارشی سازی مربوطه را انجام می‌دهیم و در نهایت EntryRenderer خودمان را رجیستر می‌کنیم.
public class XamAppEntryRenderer : EntryRenderer
{
    protected override void OnElementChanged(ElementChangedEventArgs<Entry> e)
    {
        base.OnElementChanged(e);

        if (e.NewElement != null) /* e.NewElement is a Xamarin Forms' Entry */
        {
            Control.ClearButtonMode = UITextFieldViewMode.WhileEditing; // Control is UITextField
        }
    }
}
برای Register کردن نیز داریم:
[assembly: ExportRenderer(typeof(Entry), typeof(XamAppEntryRenderer))]
در واقع این کد می‌گوید که از این به بعد، Entry‌ها در iOS، با کلاس جدید Render شوند. برای درک بهتر این مهم، فایل XamAppEntryRenderer.cs را در فولدر Renderer در پروژه XamApp.iOS مشاهده کنید.
مطالب
Markup Extensions در XAML
Markup Extension‌ها برای مواردی استفاده می‌شوند که قرار است مقداری غیر از یک مقدار ثابت و یک نوع قابل شناسایی در XAML برای یک value تنظیم شود. تمام مواردی در XAML که درون {} قرا می‌گیرند همان Markup Extension‌ها هستند. مانند Binding و یا StaticResoiurces.
علاوه بر Markup Extension‌های از پیش تعریف شده در XAML، می‌توان Markup Extension‌های شخصی را نیز تولید کرد. در واقع به زبان ساده‌تر Markup Extension برای تولید ساده‌ی داده‌های پیچیده در XAML استفاده می‌شوند.

لازم به ذکر است کهMarkup Extension ‌ها می‌توانند به دو صورت Attribute Usage ،درون  {} :
 "{Binding path=something,Mode=TwoWay}”
و یا Property Element Usage (همانند سایر Element هایWPF) درون <> استفاده شوند:
 <Binding Path="Something" Mode="TwoWay"></Binding>
برای تعریف یک Markup Extension، یک کلاس ایجاد می‌کنیم که از Markup Extensions ارث بری می‌کند. این کلاس یک Abstract Method به نام  ProvideValue دارد که باید پیاده سازی  شود. این متد مقدار خصوصیتی که Markup Extensions را فراخوانی کرده به صورت یک Object بر می‌گرداند که یکبار در زمان Load برای خصوصیت مربوطه‌اش تنظیم می‌شود.
 public abstract Object ProvideValue(IServiceProvider serviceProvider)
همانطورکه ملاحظه می‌کنید ProvideValue یک پارامتر IServiceProvider دارد که ازطریق آن می‌توان به IProvideValueTarget دسترسی داشت. ازاین Interface برای گرفتن اطلاعات کنترل(TargetObject) و خصوصیتی (TargetProperty) که فراخوانی را انجام داده در صورت لزوم استفاده می‌شود.
var target = serviceProviderGetService(typeof(IProvideValueTarget))as IProvideValueTarget;
var host = targetTargetObject as FrameworkElement;
Markup Extension‌ها می‌توانند پارامتر‌های ورودی داشته باشند:
public class ValueExtension : MarkupExtension
{
  public ValueExtension () { }
  public ValueExtension (object value1)
  {
    Value1 = value1;
  }
   public object Value1 { get; set; }
   public override object ProvideValue(IServiceProvider serviceProvider)
   {
     return Value1;
   }
}
و برای استفاده در فایل Xaml:
 <TextBox  Text="{app:ValueExtension test}" ></TextBox>
و یا می‌توان خصوصیت هایی ایجاد کرد و  از آنها برای ارسال مقادیر به آن استفاده کرد:
  <TextBox  Text="{app:ValueExtension Value1=test}" ></TextBox>
تا اینجا موارد کلی برای تعریف و استفاده از Markup Extensions گفته شد. در ادامه یک مثال کاربردی می‌آوریم. برای مثال در نظر بگیرید که نیاز دارید DataType مربوط به یک DataTemplate را برابر یک کلاس Generic قرار بدهید:
public class EntityBase
{
   public int Id{get;set}
}

public class MyGenericClass<TType> where TType : EntityBase
{
   public int Id{get;set}
   public string Test{  get;set; }

In XAML:

<DataTemplate DataType="{app:GenericType ؟}">
برای انجام این کار یک Markup Extensions به صورت زیر ایجاد می‌کنیم که Type را به عنوان ورودی گرفته و یک نمونه از کلاس Generic ایجاد می‌کند:
public class GenericType : MarkupExtension
{
  private readonly Type _of;
  public GenericType(Type of)
  {
     _of = of;
  }
  public override object ProvideValue(IServiceProvider serviceProvider)
  {
      return typeof(MyGenericClass<>)MakeGenericType(_of);
 }
}
و برای استفاده از آن یک نمونه از MarkupExtension ایجاد شده ایجاد کرده و نوع Generic را برای آن ارسال می‌کنیم:
 <DataTemplate DataType="{app:GenericType app:EntityBase}">
این یک مثال ساده از استفاده از Markup Extensions است. هنگام کار با WPF می‌توان استفاده‌های زیادی از این مفهوم داشت، برای مثال زمانی که نیاز است ItemsSource یک  Combobox  از Description‌های یک Enum پر شود می‌توان به راحتی با نوشتن یک Markup Extensions ساده این عمل و کارهای مشابه زیادی را انجام داد.  
مطالب
اشتباهات متداول برنامه‌نویس‌های دات نت

اشتباه 1:
استفاده از

throw ex;
بجای استفاده از
throw;
در حالت اول، تمام stack trace موجود تا نقطه‌ی فراخوانی دستور ذکر شده پاک خواهند شد، اما در حالت دوم stack trace‌ حفظ شده و دیباگ کردن کد را ساده می‌کند.

اشتباه 2:
درک اشتباه عملکرد متد replace :
string s = "Take this out";
s.Replace("this", "that"); //wrong
بجای استفاده از :
s = s.Replace("this", "that");  //correct

اگر از fxCop استفاده کنید، اینگونه خطاها را (عدم استفاده از مقدار بازگشتی) گوشزد می‌کند.

اشتباه 3:
استفاده‌ی بی دقت از متغیرهای استاتیک در یک برنامه وب. دو مثال زیر را در نظر بگیرید:
public static string GetCookieName(Cookie c)
{
return c.Name;
}

static List<string> cookieList = new List<string>();
public static void AddToCookieList(Cookie c)
{
cookieList.Add(c.Name);

}

برنامه‌های وب ذاتا چند ریسمانی هستند و زمانیکه یک متغیر را از نوع استاتیک تعریف می‌کنید، این متغیر، هنگام مراجعه‌ی کاربران بین آن‌ها به اشتراک گذاشته می‌شود و امکان تخریب یا استفاده‌ی ناصحیح از مقادیر آن‌ها وجود خواهد داشت. در حالت اول نیازی به مباحث همزمانی و قفل کردن منابع نیست زیرا متغیری که در متد استفاده می‌شود، thread safe است اما cookieList در مثال دوم خیر و حتما هنگام استفاده از آن باید مباحث قفل کردن منابع را درنظر داشت. (حتی اگر برنامه‌ی شما از نوع وبی هم نیست اما چند ریسمانی است این مطلب باز هم صادق می‌باشد)


اشتباه 4:
مقابله با خطاها به شکلی نادرست: (اصطلاحا خفه کردن خطاها!)
 try
{
//something
File.Delete(blah);
}
catch{}
در این حالت اگر خطایی رخ دهد کاربر متوجه نخواهد شد دقیقا مشکلی وجود داشته یا خیر و علت آن چیست.
هرچند گاهی از اوقات اگر خطای حاصل برای ما اهمیتی نداشت می‌توان از آن استفاده نمود، در غیراینصورت باید حتما از این روش پرهیز کرد.

اشتباه 5:
ارائه‌ی برنامه‌های ASP.Net با گزینه‌ی پیش فرض Debug=true در web.config که پیشتر در مورد آن در این سایت بحث شده است.


اشتباه 6:
عدم استفاده از امکانات ویژه‌ی دات نت فریم ورک هنگام کار با رشته‌ها:
string s = "This ";
s += "is ";
s += "not ";
s += "the ";
s += "best ";
s += "way.";

StringBuilder sb = new StringBuilder();
sb.Append("This ");
sb.Append("is ");
sb.Append("much ");
sb.Append("better. ");

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

اشتباه 7:
عدم استفاده از عبارت using هنگام استفاده از اشیایی از نوع Idisposable . مثالی در این مورد.

using (StreamReader reader=new StreamReader(file))
{
//your code here

}

اشتباه 8:
فراموش کردن بررسی نال بودن یک شیء هنگام استفاده از آن.
string val = Session["xyz"].ToString();

این نوع کد نویسی یکی از اشتباهات متداول تمامی تازه واردان به ASP.Net است. حتما باید پیش از استفاده از متد ToString بررسی شود که آیا این سشن نال است یا نه. در غیراینصورت حاصل کار فقط یک exception خواهد بود. (استفاده از افزونه‌ی ری شارپر در این موارد کمک بزرگی است، زیرا به محض قرار گرفتن مکان نما روی شیءایی که احتمال نال بودن آن میسر است، یک راهنما را به شما ارائه خواهد کرد)

اشتباه 9:
بازگشت دادن یک property عمومی از نوع لیست‌های جنریک.
با توجه به اینکه این نوع لیست‌ها فقط خواندنی نیستند و امکان دستکاری اطلاعات آن توسط فراخوان وجود دارد، توصیه می‌شود از نوع جنریک IEnumerable استفاده شود. همچنین توصیه شده است هنگام انتخاب نوع پارامترهای ورودی یک متد نیز به این مورد دقت شود.

مطالب
استفاده از Fluent Validation در برنامه‌های ASP.NET Core - قسمت پنجم - اعتبارسنجی تنظیمات آغازین برنامه
در برنامه‌های ASP.NET Core، امکان دریافت تنظیمات برنامه از منابع مختلفی مانند فایل‌های JSON وجود دارد که در نگارش‌های اخیر آن، امکان اعتبارسنجی اطلاعات آن‌ها به صورت توکار نیز اضافه شده‌است؛ مانند:
services.AddOptions<BearerTokensOptions>()
           .Bind(configuration.GetSection("BearerTokens"))
           .Validate(bearerTokens =>
          {
                 return bearerTokens.AccessTokenExpirationMinutes < bearerTokens.RefreshTokenExpirationMinutes;
          }, "RefreshTokenExpirationMinutes is less than AccessTokenExpirationMinutes. Obtaining new tokens using the refresh token should happen only if the access token has expired.");
اما این امکان در مقایسه با امکاناتی که FluentValidation در اختیار ما قرار می‌دهد، بسیار ابتدایی به نظر می‌رسد. به همین جهت در این قسمت قصد داریم امکانات اعتبارسنجی کتابخانه‌ی FluentValidation را در حین آغاز برنامه، جهت تعیین اعتبار اطلاعات فایل کانفیگ آن، مورد استفاده قرار دهیم.


معرفی تنظیمات برنامه

فرض کنید فایل appsettings.json برنامه یک چنین محتوایی را دارد:
{
  "ApiSettings": {
    "AllowedEndpoints": [
      {
        "Name": "Service 1",
        "Timeout": 30,
        "Url": "http://service1.site.com"
      },
      {
        "Name": "Service 2",
        "Timeout": 10,
        "Url": "https://service2.site.com"
      }
    ]
  }
}

ایجاد مدل‌های معادل تنظیمات JSON برنامه

بر اساس تعاریف JSON فوق، می‌توان به مدل‌های زیر رسید:
using System;
using System.Collections.Generic;

namespace FluentValidationSample.Models
{
    public class AllowedEndpoint
    {
        public string Name { get; set; }
        public int Timeout { get; set; }
        public Uri Url { get; set; }
    }

    public class ApiSettings
    {
        public IEnumerable<AllowedEndpoint> AllowedEndpoints { get; set; }
    }
}
که نحوه‌ی معرفی آن به سیستم تزریق وابستگی‌های برنامه به صورت زیر است:
namespace FluentValidationSample.Web
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.Configure<ApiSettings>(Configuration.GetSection(nameof(ApiSettings)));
و پس از آن در هر قسمتی از برنامه با تزریق <IOptions<ApiSettings می‌توان به اطلاعات تنظیمات برنامه دسترسی یافت.


تعریف شرط‌های اعتبارسنجی مدل‌های تنظیمات برنامه

پس از مدلسازی تنظیمات برنامه و همچنین اتصال آن به <IOptions<ApiSettings، اکنون می‌خواهیم این مدل‌ها، شرایط زیر را برآورده کنند:
- باید مدخل ApiSettings در فایل تنظیمات برنامه وجود خارجی داشته باشد.
- می‌خواهیم AllowedEndpoint‌ها نامدار بوده و هر نام نیز منحصربفرد باشد.
- مقادیر timeout‌ها باید بین 1 و 90 تعریف شده باشند.
- تمام URLها باید منحصربفرد باشند.
- تمام URLها باید HTTPS باشند.

برای این منظور می‌توان تنظیمات زیر را توسط Fluent Validation تعریف کرد:
using System;
using System.Linq;
using FluentValidation;
using FluentValidationSample.Models;

namespace FluentValidationSample.ModelsValidations
{
    public class ApiSettingsValidator : AbstractValidator<ApiSettings>
    {
        public ApiSettingsValidator()
        {
            RuleFor(apiSetting => apiSetting).NotNull()
            .WithMessage("مدخل ApiSettings تعریف نشده‌است.");

            RuleFor(apiSetting => apiSetting.AllowedEndpoints).NotNull().NotEmpty()
            .WithMessage("مدخل AllowedEndpoints تعریف نشده‌است.");

            When(apiSetting => apiSetting.AllowedEndpoints != null,
            () =>
                {
                    RuleFor(apiSetting => apiSetting.AllowedEndpoints)
                        .Must(endpoints => endpoints.GroupBy(endpoint => endpoint.Name).Count() == endpoints.Count())
                        .WithMessage("نام‌های سرویس‌ها باید منحصربفرد باشند.");

                    RuleFor(apiSetting => apiSetting.AllowedEndpoints)
                        .Must(endpoints => !endpoints.Any(endpoint => endpoint.Timeout > 90 || endpoint.Timeout < 1))
                        .WithMessage("مقدار timeout باید بین 1 و 90 باشد");

                    RuleFor(apiSetting => apiSetting.AllowedEndpoints)
                        .Must(endpoints => endpoints.GroupBy(endpoint => endpoint.Url.ToString().ToLower()).Count() == endpoints.Count())
                        .WithMessage("آدرس‌های سرویس‌ها باید منحصربفرد باشند.");

                    RuleFor(apiSetting => apiSetting.AllowedEndpoints)
                        .Must(endpoints => endpoints.All(endpoint => endpoint.Url.Scheme.Equals("https", StringComparison.CurrentCultureIgnoreCase)))
                        .WithMessage("تمام آدرس‌ها باید HTTPS باشند.");
                });
        }
    }
}
که در اینجا نکات زیر قابل ملاحظه هستند:
- چگونه می‌توان از تعریف و وجود یک مدخل فایل JSON، اطمینان حاصل کرد (اعمال RuleFor به کل مدل).
- چگونه می‌توان اگر مدخلی تعریف شده بود، آنگاه برای آن اعتبارسنجی خاصی را تعریف کرد (متد When).
- چگونه می‌توان شرایط سفارشی خاصی را مانند بررسی منحصربفرد بودن‌ها، بررسی کرد (متد Must).


یکپارچه کردن اعتبارسنجی کتابخانه‌ی FluentValidation با اعتبارسنجی توکار مدل‌های تنظیمات برنامه توسط ASP.NET Core

در ابتدای بحث، امکان تعریف متد Validate را که از نگارش ASP.NET Core 2.2 اضافه شده‌است، مشاهده کردید:
services.AddOptions<BearerTokensOptions>()
           .Bind(configuration.GetSection("BearerTokens"))
           .Validate(bearerTokens =>
          {
                 return bearerTokens.AccessTokenExpirationMinutes < bearerTokens.RefreshTokenExpirationMinutes;
          }, "RefreshTokenExpirationMinutes is less than AccessTokenExpirationMinutes. Obtaining new tokens using the refresh token should happen only if the access token has expired.");
می‌توان این متد را با پیاده سازی اینترفیس توکار IValidateOptions نیز به سیستم ارائه داد:
namespace Microsoft.Extensions.Options
{
    public interface IValidateOptions<TOptions> where TOptions : class
    {
        ValidateOptionsResult Validate(string name, TOptions options);
    }
}
و اگر سرویس پیاده سازی کننده‌ی آن‌را با طول عمر Transient به سیستم اضافه کردیم، به صورت خودکار جهت اعتبارسنجی TOptions، مورد استفاده قرار خواهد گرفت. TOptions در این مثال همان ApiSettings است.
در ادامه یک نمونه پیاده سازی جنریک IValidateOptions استاندارد ASP.NET Core را مشاهده می‌کنید:
using System.Linq;
using FluentValidation;
using Microsoft.Extensions.Options;

namespace FluentValidationSample.ModelsValidations
{
    public class AppConfigValidator<TOptions> : IValidateOptions<TOptions> where TOptions : class
    {
        private readonly IValidator<TOptions> _validator;

        public AppConfigValidator(IValidator<TOptions> validator)
        {
            _validator = validator;
        }

        public ValidateOptionsResult Validate(string name, TOptions options)
        {
            if (options is null)
            {
                return ValidateOptionsResult.Fail("Configuration object is null.");
            }

            var validationResult = _validator.Validate(options);
            return validationResult.IsValid
                ? ValidateOptionsResult.Success
                : ValidateOptionsResult.Fail(validationResult.Errors.Select(error => error.ToString()));
        }
    }
}
همانطور که در قسمت دوم این سری این نیز بررسی کردیم، یکی از روش‌های اجرای اعتبارسنجی‌های FluentValidation، کار با اینترفیس IValidator آن است که در اینجا به سازنده‌ی این کلاس تزریق شده‌است. سپس در متد Validate این سرویس، با فراخوانی آن، کار اعتبارسنجی وهله‌ی دریافتی options صورت گرفته و اگر خطایی وجود داشته باشد، بازگشت داده می‌شود.
در آخر روش معرفی آن به سیستم به صورت زیر است:
namespace FluentValidationSample.Web
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.Configure<ApiSettings>(Configuration.GetSection(nameof(ApiSettings)));
            services.AddTransient<IValidateOptions<ApiSettings>, AppConfigValidator<ApiSettings>>();
به این ترتیب هرگاه در برنامه یک چنین تعریفی را داشته باشیم که از طریق IOptions، تنظیمات برنامه را دریافت می‌کند:
namespace FluentValidationSample.Web.Controllers
{
    public class HomeController : Controller
    {
        private readonly IUsersService _usersService;
        private readonly ApiSettings _apiSettings;

        public HomeController(IUsersService usersService, IOptions<ApiSettings> apiSettings)
        {
            _usersService = usersService;
            _apiSettings = apiSettings.Value;
        }
اگر در سیستم یک <IValidateOptions<ApiSettings متناظر با <IOptions<ApiSettings ثبت شده باشد (مانند تنظیمات متد ConfigureServices فوق)، هرگاه که فراخوانی apiSettings.Value صورت گیرد، قبل از هرکاری متد Validate سرویس پیاده سازی کننده‌ی IValidateOptions متناظر، فراخوانی شده و اگر خطای اعتبارسنجی وجود داشته باشد، به صورت یک استثناء بازگشت داده می‌شود؛ مانند:
An unhandled exception occurred while processing the request.
OptionsValidationException: تمام آدرس‌ها باید HTTPS باشند.


کدهای کامل این سری را تا این قسمت از اینجا می‌توانید دریافت کنید: FluentValidationSample-part05.zip
مطالب
C# 6 - Null-conditional operators
برنامه نویس‌‌های سی‌شارپ پیشتر با null-coalescing operator یا ?? آشنا شده بودند. برای مثال
 string data = null;
var result = data ?? "value";
در این حالت اگر data یا سمت چپ عملگر، نال باشد، مقدار value (سمت راست عملگر) بازگشت داده خواهد شد؛ که در حقیقت خلاصه شده‌ی چند سطر ذیل است:
if (data == null)
{
    data = "value";
}
var result = data;
در سی شارپ 6، جهت تکمیل عملگرهای کار با مقادیر نال و بالا بردن productivity برنامه نویس‌ها، عملگر دیگری به نام Null-conditional operator و یا .? به این مجموعه اضافه شده‌است. در این حالت ابتدا مقدار سمت چپ عملگر بررسی خواهد شد. اگر مقدار آن مساوی نال بود، در همینجا کار خاتمه یافته و نال بازگشت داده می‌شود. در غیر اینصورت کار بررسی زنجیره‌ی جاری ادامه خواهد یافت.
برای مثال بسیاری از نتایج بازگشتی از متدها، چند سطحی هستند:
class Response
{
    public string Result { set; get; }
    public int Code { set; get; }
}

 
class WebRequest
{
    public Response GetDataFromWeb(string url)
    {
        // ...
        return new Response { Result = null };
    }
}
در اینجا روش مرسوم کار با کلاس درخواست اطلاعات از وب به صورت ذیل است:
 var webData = new WebRequest().GetDataFromWeb("https://www.dntips.ir/");
if (webData != null && webData.Result != null)
{
    Console.WriteLine(webData.Result);
}
چون می‌خواهیم به خاصیت Result دسترسی پیدا کنیم، نیاز است دو مرحله وضعیت خروجی متد و همچنین خاصیت Result آن‌را جهت مشخص سازی نال نبودن آن‌ها، بررسی کنیم و اگر برای مثال خاصیت Result نیز خود متشکل از یک کلاس دیگر بود که در آن برای مثال StatusCode نیز ذکر شده بود، این بررسی به سه سطح یا بیشتر نیز ادامه پیدا می‌کرد.
در این حالت اگر اشاره‌گر را به محل && انتقال دهیم، افزونه‌ی ReSharper پیشنهاد یکی کردن این بررسی‌ها را ارائه می‌دهد:


به این ترتیب تمام چند سطح بررسی نال، به یک عبارت بررسی .? دار، خلاصه خواهد شد:
 if (webData?.Result != null)
{
    Console.WriteLine(webData.Result);
}
در اینجا ابتدا بررسی می‌شود که آیا webData نال است یا خیر؟ اگر نال بود همینجا کار خاتمه پیدا می‌کند و به بررسی Result نمی‌رسد. اگر نال نبود، ادامه‌ی زنجیره تا به انتها بررسی می‌شود.
البته باید دقت داشت که برای تمام سطوح باید از .? استفاده کرد (برای مثال response?.Results?.Status)؛ در غیر اینصورت همانند سابق در صورت استفاده‌ی از دات معمولی، به یک null reference exception می‌رسیم.


کار با متدها و Delegates

این عملگر جدید مقایسه‌ی با نال را بر روی متدها (علاوه بر خواص و فیلدها) نیز می‌توان بکار برد. برای مثال خلاصه شده‌ی فراخوانی ذیل:
 if (x != null)
{
   x.Dispose();
}
با استفاده از Null Conditional Operator به این صورت است:
 x?.Dispose();

و یا بکار گیری آن بر روی delegates (روش قدیمی):
 var copy = OnMyEvent;
if (copy != null)
{
   copy(this, new EventArgs());
}
نیز با استفاده از متد Invoke به نحو ذیل قابل انجام است و نکته جالب یک سطر کد ذیل علاوه بر ساده شدن آن:
 OnMyEvent?.Invoke(this, new EventArgs());
Thread-safe بودن آن نیز می‌باشد. زیرا در این حالت کامپایلر delegate را به یک متغیر موقتی کپی کرده و سپس فراخوانی‌ها را انجام می‌دهد. اگر انجام این کپی موقت صورت نمی‌گرفت، در حین فراخوانی آن از طریق چندین ترد مختلف، ممکن بود یکی از مشترکین delegate از آن قطع اشتراک می‌کرد و در این حالت فراخوانی تردی دیگر در همان لحظه، سبب کرش برنامه می‌شد.


استفاده از Null Conditional Operator بر روی Value types

الف) مقایسه با نال
کد ذیل را درنظر بگیرید:
 var code = webData?.Code;
در اینجا Code یک value type از نوع int است. در این حالت با بکارگیری Null Conditional Operator، خروجی این حاصل، از نوع <Nullable<int و یا ?int درنظر گرفته خواهد شد و با توجه به اینکه عبارات null>0 و همچنین null<0 هر دو false هستند، مقایسه‌ی این خروجی با 0 بدون مشکل انجام می‌شود. برای مثال مقایسه‌ی ذیل از نظر کامپایلر یک عبارت معتبر است و بدون مشکل کامپایل می‌شود:
 if (webData?.Code > 0)
{

}

ب) بازگشت مقدار پیش فرض دیگری بجای نال
اگر نیاز بود بجای null مقدار پیش فرض دیگری را بازگشت دهیم، می‌توان از null-coalescing operator سابق استفاده کرد:
 int count = response?.Results?.Count ?? 0;
در این مثال خاصیت CountT در اصل از نوع int تعریف شده‌است؛ اما بکارگیری .? سبب Nullable شدن آن خواهد شد. بنابراین امکان بکارگیری عملگر ?? یا null-coalescing operator نیز بر روی این متغیر وجود دارد.

ج) دسترسی به مقدار Value یک متغیر nullable
نمونه‌ی دیگر آن قطعه کد ذیل است:
 int? x = 10;
//var value = x?.Value; // invalid
Console.WriteLine(x?.ToString());
در اینجا برخلاف متغیر Code که از ابتدا nullable تعریف نشده‌است، متغیر x نال پذیر است. اما باید دقت داشت که با تعریف .? دیگر نیازی به استفاده از خاصیت Value این متغیر nullable نیست؛ زیرا .? سبب محاسبه و بازگشت خروجی آن می‌شود. بنابراین در این حالت، سطر دوم غیرمعتبر است (کامپایل نمی‌شود) و سطر سوم معتبر.


کار با indexer property و بررسی نال

اگر به عنوان بحث دقت کرده باشید، یک s جمع در انتهای Null-conditional operators ذکر شده‌است. به این معنا که این عملگر مقایسه‌ی با نال، صرفا یک شکل و فرم .? را ندارد. مثال ذیل در حین کار با آرایه‌ها و لیست‌ها بسیار مشاهده می‌شود:
 if (response != null && response.Results != null && response.Results.Addresses != null
  && response.Results.Addresses[0] != null && response.Results.Addresses[0].Zip == "63368")
{

}
در اینجا به علت بکارگیری indexer بر روی Addresses، دیگر نمی‌توان از عملگر .? که صرفا برای فیلدها، خواص، متدها و delegates طراحی شده‌است، استفاده کرد. به همین منظور، عملگر بررسی نال دیگری به شکل […]? برای این بررسی طراحی شده‌است:
 if(response?.Results?.Addresses?[0]?.Zip == "63368")
{

}
به این ترتیب 5 سطح بررسی نال فوق، به یک عبارت کوتاه کاهش می‌یابد.

 
موارد استفاده‌ی ناصحیح از عملگرهای مقایسه‌ی با نال

خوب، عملگر .? کار مقایسه‌ی با نال را خصوصا در دسترسی‌های چند سطحی به خواص و متدها بسیار ساده می‌کند. اما آیا باید در همه جا از آن استفاده کرد؟ آیا باید از این پس کلا استفاده از دات را فراموش کرد و بجای آن از .? در همه جا استفاده کرد؟
مثال ذیل را درنظر بگیرید:
 public void DoSomething(Customer customer)
{
    string address = customer?.Employees
                  ?.SingleOrDefault(x => x.IsAdmin)?.Address?.ToString();
    SendPackage(address);
}
در این مثال در تمام سطوح آن از .? بجای دات استفاده شده‌است و بدون مشکل کامپایل می‌شود. اما این نوع فراخوانی سبب خواهد شد تا یک سری از مشکلات موجود کاملا مخفی شوند؛ خصوصا اعتبارسنجی‌ها. برای مثال در این فراخوانی اگر مشتری نال باشد یا اگر کارمندانی را نداشته باشد، آدرسی بازگشت داده نمی‌شود. بنابراین حداقل دو سطح بررسی و اعتبارسنجی عدم وجود مشتری یا عدم وجود کارمندان آن در اینجا مخفی شده‌اند و دیگر مشخص نیست که علت بازگشت نال چه بوده‌است.
روش بهتر انجام اینکار، بررسی وضعیت customer و انتقال مابقی زنجیره‌ی LINQ به یک متد مجزای دیگر است:
 public void DoSomething(Customer customer)
{
   Contract.Requires(customer != null); 
   string address = customer.GetAdminAddress();
   SendPackage(address);
}
مطالب
انجام اعمال ریاضی بر روی Generics
کامپایلر سی‌شارپ اگر نتواند نوع‌های عملوندها را در حین بکارگیری عملگرها تشخیص دهد، اجازه‌ی استفاده از عملگر را نخواهد داد و کار کامپایل، با یک خطا خاتمه می‌یابد. برای نمونه مثال زیر را در نظر بگیرید:
    public interface ICalculator<T>
    {
        T Add(T operand1, T operand2);
    }

    public class Calculator<T> : ICalculator<T>
    {
        public T Add(T operand1, T operand2)
        {
            return operand1 + operand2;
        }
    }
در اینجا چون کامپایلر نمی‌داند که عملگر + بر روی چه نوع‌هایی قرار است اعمال شود (به علت جنریک تعریف شدن این نوع‌ها و مشخص نبودن اینکه آیا این نوع، اصلا عملگر + دارد یا خیر)، با صدور خطای زیر، عملیات کامپایل را متوقف می‌کند:
 Operator '+' cannot be applied to operands of type 'T' and 'T'
برای حل این مساله، چندین روش مطرح شده‌است که در ادامه تعدادی از آن‌ها را مرور خواهیم کرد.


روش اول: واگذار کردن استراتژی عملیات ریاضی به یک کلاس خارجی

این راه حلی است که توسط اعضای تیم سی‌شارپ در روزهای ابتدایی معرفی جنریک‌ها مطرح شده‌است. فرض کنید می‌خواهیم لیستی از جنریک‌ها را با هم جمع بزنیم:
    public class Calculator2<T>
    {
        public T Sum(List<T> list)
        {
            T sum = 0;
            for (int i = 0; i < list.Count; i++)
                sum += list[i];
            return sum;
        }
    }
این کد نیز قابل کامپایل نبوده و امکان اعمال عملگر + بر روی نوع ناشناخته‌ی T میسر نیست.
    public interface ICalculator<T>
    {
        T Add(T operand1, T operand2);
    }

    public class Int32Calculator : ICalculator<int>
    {
        public int Add(int operand1, int operand2)
        {
            return operand1 + operand2;
        }
    }

    public class AlgorithmLibrary<T> where T : new() 
    {
        private readonly ICalculator<T> _calculator;
        public AlgorithmLibrary(ICalculator<T> calculator)
        {
            _calculator = calculator;
        }

        public T Sum(List<T> items)
        {
            var sum = new T();
            for (var i = 0; i < items.Count; i++)
            {
                sum = _calculator.Add(sum, items[i]);
            }
            return sum;
        }
    }
در راه حل ارائه شده، یک اینترفیس عمومی که متد جمع را تعریف کرده‌است، مشاهده می‌کنیم. سپس این اینترفیس در سازنده‌ی کتابخانه‌ی الگوریتم‌‌های برنامه تزریق شده‌است. اکنون کدهای AlgorithmLibrary بدون مشکل کامپایل می‌شوند. هر زمان که نیاز به استفاده از آن بود، بر اساس نوع T، پیاده سازی خاصی را باید ارائه داد. برای مثال در اینجا Int32Calculator پیاده سازی نوع int را انجام داده‌است. برای استفاده از آن نیز خواهیم داشت:
 var result = new AlgorithmLibrary<int>(new Int32Calculator()).Sum(new List<int> { 1, 2, 3 });

البته این نوع پیاده سازی را که کار اصلی آن واگذاری عملیات جمع، به یک کلاس خارجی است، توسط Func نیز می‌توان خلاصه‌تر کرد:
    public class Algorithms<T> where T : new() 
    {
        public T Calculate(Func<T, T, T> add, IEnumerable<T> numbers)
        {
            var sum = new T();
            foreach (var number in numbers)
            {
                sum = add(sum, number);
            }
            return sum;
        }
    }
استفاده از Action و Func نیز یکی دیگر از روش‌های تزریق وابستگی‌ها است که در اینجا بکار گرفته شده‌است. برای استفاده از آن خواهیم داشت:
 var result = new Algorithms<int>().Calculate((a, b) => a + b, new[] { 1, 2, 3 });
آرگومان اول روش جمع زدن را مشخص می‌کند و آرگومان دوم، لیستی است که باید اعضای آن جمع زده شوند.


روش دوم: استفاده از واژه‌ی کلیدی dynamic

با استفاده از واژه‌ی کلیدی dynamic می‌توان بررسی نوع داده‌ها را به زمان اجرا موکول کرد. به این ترتیب دیگر کامپایلر مشکلی با کامپایل قطعه کد ذیل نخواهد داشت:
    public class Calculator<T> : ICalculator<T>
    {
        public T Add(T operand1, T operand2)
        {
            return (dynamic)operand1 + operand2;
        }
    }
و مثال زیر نیز به خوبی کار می‌کند:
 var test = new Calculator<int>().Add(1, 2);
البته بدیهی است که نوع تعریف شده در اینجا باید دارای عملگر + باشد. در غیر اینصورت در زمان اجرا برنامه با یک خطا خاتمه خواهد یافت.
روش فوق نسبت به حالتی که بر اساس نوع T تصمیم‌گیری شود و از عملگر + متناظری استفاده گردد، خوانایی بهتری دارد:
public T Add(T t1, T t2)
{
    if (typeof(T) == typeof(double))
    {
        var d1 = (double)t1;
        var d2 = (double)t2;
        return (T)(d1 + d2);
    }
    else if (typeof(T) == typeof(int)){
        var i1 = (int)t1;
        var i2 = (int)t2;
        return (T)(i1 + i2);
    }
    else ...
}


روش سوم: استفاده از Expression Trees

روش زیر بسیار شبیه است به حالتیکه از Func در روش اول استفاده شد. در اینجا این Func به صورت پویا تولید و سپس صدا زده می‌شود:
using System;
using System.Linq.Expressions;

namespace GenericsArithmetic
{
    public class Solution3
    {
        public T Add<T>(T a, T b)
        {
            var paramA = Expression.Parameter(typeof(T), "a");
            var paramB = Expression.Parameter(typeof(T), "b");

            var body = Expression.Add(paramA, paramB);
            var add = Expression.Lambda<Func<T, T, T>>(body, paramA, paramB).Compile();
            return add(a, b);
        }
    }
}
البته این مثال، یک مثال ابتدایی در این مورد است. بر همین مبنا و ایده، یک کتابخانه‌ی با کارآیی بالا، تحت عنوان Generic Operators که جزو Misc utils می‌باشد، تهیه شده‌است.
به کمک کتابخانه‌ی Generic Operators، کدهای جمع زدن اعضای یک لیست جنریک به صورت ذیل خلاصه می‌شوند:
public static T Sum<T>(this IEnumerable<T> source)
{
    T sum = Operator<T>.Zero;
    foreach (T value in source)
    {
            sum = Operator.Add(sum, value);
    }
    return sum;
}
مطالب
بازنویسی ساده‌تر پیش فرض‌های EF Code first در نگارش 6 آن
فرض کنید مطابق اصول نامگذاری که تعیین کرده‌اید، تمام جداول بانک اطلاعاتی شما باید با پیشوند tbl شروع شوند. برای انجام اینکار در نگارش‌های قبلی EF Code first می‌بایستی از ویژگی Table جهت مزین کردن تمامی کلاس‌ها استفاده می‌شد و یا به ازای تک تک موجودیت‌ها، یک کلاس تنظیمات ویژه را افزود و سپس از متد ToTable برای تعیین نامی جدید، استفاده می‌شد. در EF 6 امکان بازنویسی ساده‌تر پیش فرض‌های تعیین نام جداول، طول فیلدها و غیره، پیش بینی شده‌اند که در ادامه تعدادی از آن‌ها را مرور خواهیم کرد.


تعیین پیشوندی برای نام کلیه‌ی جداول بانک اطلاعاتی

اگر نیاز باشد تا به تمامی جداول تهیه شده، بر اساس نام کلاس‌های مدل‌های برنامه، یک پیشوند tbl اضافه شود، می‌توان با بازنویسی متد OnModelCreating کلاس Context برنامه شروع کرد:
        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            // TableNameConvention
            modelBuilder.Types()
                        .Configure(entity => entity.ToTable("tbl" + entity.ClrType.Name));                        

            base.OnModelCreating(modelBuilder);
        }
سپس متد modelBuilder.Types، کلیه موجودیت‌های برنامه را در اختیار قرار داده و در ادامه می‌توان برای مثال از متد ToTable، برای تعیین نامی جدید به ازای کلیه کلاس‌های مدل‌های برنامه استفاده کرد.


تعیین نام دیگری برای کلید اصلی کلیه‌ی جداول برنامه

فرض کنید نیاز است کلیه PKها، با پیشوند نام جدول جاری در بانک اطلاعاتی تشکیل شوند. یعنی اگر نام PK مساوی Id است و نام جدول Menu، نام کلید اصلی نهایی تشکیل شده در بانک اطلاعاتی باید MenuId باشد و نه Id.
        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            // PrimaryKeyNameConvention
            modelBuilder.Properties()
                        .Where(p => p.Name == "Id")
                        .Configure(p => p.IsKey().HasColumnName(p.ClrPropertyInfo.ReflectedType.Name + "Id"));

            base.OnModelCreating(modelBuilder);
        }
این مورد نیز با بازنویسی متد OnModelCreating کلاس Context و سپس استفاده از متد modelBuilder.Properties برای دسترسی به کلیه خواص در حال نگاشت، قابل انجام است. در اینجا کلیه خواصی که نام Id دارند، توسط متد IsKey تبدیل به PK شده و سپس به کمک متد HasColumnName، نام دلخواه جدیدی را خواهند یافت.


تعیین حداکثر طول کلیه فیلدهای رشته‌ای تمامی جداول بانک اطلاعاتی

اگر نیاز باشد تا پیش فرض MaxLength تمام خواص رشته‌ای را تغییر داد، می‌توان از پیاده سازی اینترفیس جدید IStoreModelConvention کمک گرفت:
    public class StringConventions : IStoreModelConvention<EdmProperty>
    {
        public void Apply(EdmProperty property, DbModel model)
        {
            if (property.PrimitiveType.PrimitiveTypeKind == PrimitiveTypeKind.String)
            {
                property.MaxLength = 450;
            }
        }
    }
در اینجا MaxLength کلیه خواص رشته‌ای در حال نگاشت به بانک اطلاعاتی، به 450 تنظیم می‌شود. سپس برای معرفی آن به برنامه خواهیم داشت:
        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            modelBuilder.Conventions.Add<StringConventions>();
            base.OnModelCreating(modelBuilder);
        }
توسط متد modelBuilder.Conventions.Add، می‌توان قراردادهای جدید سفارشی را به برنامه افزود.


نظم بخشیدن به تعاریف قراردادهای پیش فرض

اگر علاقمند نیستید که کلاس Context برنامه را شلوغ کنید، می‌توان با ارث بری از کلاس پایه Convention، قراردادهای جدید را تعریف و سپس  توسط متد modelBuilder.Conventions.Add، کلاس نهایی تهیه شده را به برنامه معرفی کرد.
    public class MyConventions : Convention
    {
        public MyConventions()
        {
            // PrimaryKeyNameConvention
            this.Properties()
                .Where(p => p.Name == "Id")
                .Configure(p => p.IsKey().HasColumnName(p.ClrPropertyInfo.ReflectedType.Name + "Id"));

            // TableNameConvention
            this.Types()
                .Configure(entity => entity.ToTable("tbl" + entity.ClrType.Name));
        }
    }


مثال‌های بیشتر
اگر به مستندات EF 6 مراجعه کنید، مثال‌های بیشتری را در مورد بکارگیری اینترفیس IStoreModelConvention و یا بازنویسی قراردادهای موجود، خواهید یافت.
مطالب
تنظیمات کش توزیع شده‌ی مبتنی بر 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 یکسانی داشته باشند، یک محتوا را نمایش خواهند داد.
نظرات مطالب
ASP.NET MVC #12
- از Html.RenderAction استفاده کنید.
+ و یا همچنین layout، مدل محتوای خودش را به ارث می‌برد. یعنی مدلی که در View تنظیم می‌شود، همان مدلی است که layout به آن دسترسی خواهد داشت. به همین جهت مثلا می‌تونید توسط ViewBag، عنوان صفحه را که در layout تعریف شده، مقدار دهی کنید.
اگر می‌خواهید Strongly typed کار کنید، روش Html.RenderAction یک راه حل است و روش دوم به صورت زیر است:
یک کلاس پایه abstract تعریف کنید:

public abstract class BaseViewModel
{
    public string Name { get; set; }
}
بعد تمام مدل‌ها یا ViewModelهایی که قرار است در برنامه شما به Viewها ارسال شوند، باید از این کلاس پایه ارث بری کنند. مثلا:
public class HomeViewModel : BaseViewModel
{
   public int Data1 { set; get;}
   // ...
}
در این حالت و با رعایت این شرط، می‌تونید در فایل layout، نوع مدل را بجای حالت dynamic فعلی، تبدیل کنید به نوع کلاس پایه‌ایی که ذکر شد:
@model BaseViewModel
<!DOCTYPE html>
<html>
    <head>
        <meta name="viewport" content="width=device-width" />
        <title>Test</title>
    </head>
    <body>
        <header>
            Hello @Model.Name
        </header>
        <div>
            @this.RenderBody()
        </div>
    </body>
</html>
الان در layout، نوع کلاس پایه، به عنوان نوع مدل اصلی تعریف شده. بنابراین در این فایل layout مشترک بین تمام Viewها، خواص قرار گرفته شده در کلاس پایه‌ای که توسط ViewModelها به Viewها ارسال می‌شوند، به صورت strongly typed قابل دسترسی خواهند بود.