مطالب
مروری بر کدهای کلاس SqlHelper

قسمتی از یک پروژه به همراه کلاس SqlHelper آن در کامنت‌های مطلب «اهمیت Code review» توسط یکی از خوانندگان بلاگ جهت Code review مطرح شده که بهتر است در یک مطلب جدید و مجزا به آن پرداخته شود. قسمت مهم آن کلاس SqlHelper است و مابقی در اینجا ندید گرفته می‌شوند:

//It's only for code review purpose!  
using System.Data;
using System.Data.SqlClient;
using System.Web.Configuration;


public sealed class SqlHelper
{
private SqlHelper() { }


// Send Connection String
//---------------------------------------------------------------------------------------
public static string GetCntString()
{
return WebConfigurationManager.ConnectionStrings["db_ConnectionString"].ConnectionString;
}


// Connect to Data Base SqlServer
//---------------------------------------------------------------------------------------
public static SqlConnection Connect2Db(ref SqlConnection sqlCnt, string cntString)
{
try
{
if (sqlCnt == null) sqlCnt = new SqlConnection();
sqlCnt.ConnectionString = cntString;
if (sqlCnt.State != ConnectionState.Open) sqlCnt.Open();
return sqlCnt;
}
catch (SqlException)
{
return null;
}
}


// Run ExecuteScalar Command
//---------------------------------------------------------------------------------------
public static string RunExecuteScalarCmd(ref SqlConnection sqlCnt, string strCmd, bool blnClose)
{
Connect2Db(ref sqlCnt, GetCntString());
using (sqlCnt)
{
using(SqlCommand sqlCmd = sqlCnt.CreateCommand())
{
sqlCmd.CommandText = strCmd;
object objResult = sqlCmd.ExecuteScalar();
if (blnClose) CloseCnt(ref sqlCnt, true);
return (objResult == null) ? string.Empty : objResult.ToString();
}
}
}

// Close SqlServer Connection
//---------------------------------------------------------------------------------------
public static bool CloseCnt(ref SqlConnection sqlCnt, bool nullSqlCnt)
{
try
{
if (sqlCnt == null) return true;
if (sqlCnt.State == ConnectionState.Open)
{
sqlCnt.Close();
sqlCnt.Dispose();
}
if (nullSqlCnt) sqlCnt = null;
return true;
}
catch (SqlException)
{
return false;
}
}
}


مثالی از نحوه استفاده ارائه شده:

protected void BtnTest_Click(object sender, EventArgs e)
{
SqlConnection sqlCnt = new SqlConnection();
string strQuery = "SELECT COUNT(UnitPrice) AS PriceCount FROM [Order Details]";


// در این مرحله پارامتر سوم یعنی کانکشن باز نگه داشته شود
string strResult = SqlHelper.RunExecuteScalarCmd(ref sqlCnt, strQuery, false);



strQuery = "SELECT LastName + N'-' + FirstName AS FullName FROM Employees WHERE (EmployeeID = 9)";
// در این مرحله پارامتر سوم یعنی کانکشن بسته شود
strResult = SqlHelper.RunExecuteScalarCmd(ref sqlCnt, strQuery, true);
}


مروری بر این کد:

1) نحوه کامنت نوشتن
بین سی شارپ و زبان سی++ تفاوت وجود دارد. این نحوه کامنت نویسی بیشتر در سی++ متداول است. اگر از ویژوال استودیو استفاده می‌کنید، مکان نما را به سطر قبل از یک متد منتقل کرده و سه بار پشت سر هم forward slash را تایپ کنید. به صورت خودکار ساختار خالی زیر تشکیل خواهد شد:
/// <summary>
///
/// </summary>
/// <param name="sqlCnt"></param>
/// <param name="cntString"></param>
/// <returns></returns>
public static SqlConnection Connect2Db(ref SqlConnection sqlCnt, string cntString)

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

2) وجود سازنده private
احتمالا هدف این بوده که نه شخصی و نه حتی کامپایلر، وهله‌ای از این کلاس را ایجاد نکند. بنابراین بهتر است کلاسی را که تمام متدهای آن static است (که به این هم خواهیم رسید!) ، راسا static معرفی کنید. به این ترتیب نیازی به سازنده private نخواهد بود.

3) وجود try/catch
یک اصل کلی وجود دارد: اگر در حال طراحی یک کتابخانه پایه‌ای هستید، try/catch را در هیچ متدی از آن لحاظ نکنید. بله؛ درست خوندید! لطفا try/catch ننویسید! کرش کردن برنامه خوب است! لا‌یه‌های بالاتر برنامه که در حال استفاده از کدهای شما هستند متوجه خواهند شد که مشکلی رخ داده و این مشکل توسط کتابخانه مورد استفاده «خفه» نشده. برای مثال اگر هم اکنون SQL Server در دسترس نیست، لایه‌های بالاتر برنامه باید این مشکل را متوجه شوند. Exception اصلا چیز بدی نیست! کرش برنامه اصلا بد نیست!
فرض کنید که دچار بیماری شده‌اید. اگر مثلا تبی رخ ندهد، از کجا باید متوجه شد که نیاز به مراقبت پزشکی وجود دارد؟ اگر هیچ علامتی بروز داده نشود که تا الان نسل بشر منقرض شده بود!

4) وجود ref و out
دوستان گرامی! این ref و out فقط جهت سازگاری با زبان C در سی شارپ وجود دارد. لطفا تا حد ممکن از آن استفاده نکنید! مثلا استفاده از توابع API‌ ویندوز که با C نوشته شده‌اند.
یکی از مهم‌ترین کاربردهای pointers در زبان سی، دریافت بیش از یک خروجی از یک تابع است. برای مثال یک متد API ویندوز را فراخوانی می‌کنید؛ خروجی آن یک ساختار است که به کمک pointers به عنوان یکی از پارامترهای همان متد معرفی شده. این روش به وفور در طراحی ویندوز بکار رفته. ولی خوب در سی شارپ که از این نوع مشکلات وجود ندارد. یک کلاس ساده را طراحی کنید که چندین خاصیت دارد. هر کدام از این خاصیت‌ها می‌توانند نمایانگر یک خروجی باشند. خروجی متد را از نوع این کلاس تعریف کنید. یا برای مثال در دات نت 4، امکان دیگری به نام Tuples معرفی شده برای کسانی که سریع می‌خواهند چند خروجی از یک تابع دریافت کنند و نمی‌خواهند برای اینکار یک کلاس بنویسند.
ضمن اینکه برای مثال در متد Connect2Db، هم کانکشن یکبار به صورت ref معرفی شده و یکبار به صورت خروجی متد. اصلا نیازی به استفاده از ref در اینجا نبوده. حتی نیازی به خروجی کانکشن هم در این متد وجود نداشته. کلیه تغییرات شما در شیء کانکشنی که به عنوان پارامتر ارسال شده، در خارج از آن متد هم منعکس می‌شود (شبیه به همان بحث pointers در زبان سی). بنابراین وجود ref غیرضروری است؛ وجود خروجی متد هم به همین صورت.

5) استفاده از using در متد RunExecuteScalarCmd
استفاده از using خیلی خوب است؛ همیشه اینکار را انجام دهید!
اما اگر اینکار را انجام دادید، بدانید که شیء sqlCnt در پایان بدنه using ، توسط GC نابوده شده است. بنابراین اینجا bool blnClose دیگر چه کاربردی دارد؟! تصمیم شما دیگر اهمیتی نخواهد داشت؛ چون کار تخریبی پیشتر انجام شده.

6) متد CloseCnt
این متد زاید است؛ به دلیلی که در قسمت (5) عنوان شد. using های استفاده شده، کار را تمام کرده‌اند. بنابراین بستن اشیاء dispose شده معنا نخواهد داشت.

7) در مورد نحوه استفاده
اگر SqlHelper را در اینجا مثلا یک DAL ساده فرض کنیم (data access layer)، جای قسمت BLL (business logic layer) در اینجا خالی است. عموما هم چون توضیحات این موارد را خیلی بد ارائه داده‌اند، افراد از شنیدن اسم آن‌ها هم وحشت می‌کنند. BLL یعنی کمی دست به Refactoring بزنید و این پیاده سازی منطق تجاری ارائه شده در متد BtnTest_Click را به یک کلاس مجزا خارج از code behind پروژه منتقل کنید. Code behind فقط محل استفاده نهایی از آن باشد. همین! فعلا با همین مختصر شروع کنید.
مورد دیگری که در اینجا باز هم مشهود است، عدم استفاده از پارامتر در کوئری‌ها است. چون از پارامتر استفاده نکرده‌اید، SQL Server مجبور است برای حالت EmployeeID = 9 یکبار execution plan را محاسبه کند، برای کوئری بعدی مثلا EmployeeID = 19، اینکار را تکرار کند و الی آخر. این یعنی مصرف حافظه بالا و همچنین سرعت پایین انجام کوئری‌ها. بنابراین اینقدر در قید و بند باز نگه داشتن یک کانکشن نباشید؛ مشکل اصلی جای دیگری است!

8) برنامه وب و اطلاعات استاتیک!
این پروژه، یک پروژه ASP.NET است. دیدن تعاریف استاتیک در این نوع پروژه‌ها یک علامت خطر است! در این مورد قبلا مطلب نوشتم:
متغیرهای استاتیک و برنامه‌های ASP.NET


یک درخواست عمومی!
لطف کنید در پروژ‌های «جدید» خودتون این نوع کلاس‌های SqlHelper رو «دور بریزید». یاد گرفتن کار با یک ORM جدید اصلا سخت نیست. مثلا طراحی Entity framework مایکروسافت به حدی ساده است که هر شخصی با داشتن بهره هوشی در حد یک عنکبوت آبی یا حتی جلبک دریایی هم می‌تونه با اون کار کنه! فقط NHibernate هست که کمی مرد افکن است و گرنه مابقی به عمد ساده طراحی شده‌اند.
مزایای کار کردن با ORM ها این است:
- کوئری‌های حاصل از آن‌ها «پارامتری» است؛ که این دو مزیت عمده را به همراه دارد:
امنیت: مقاومت در برابر SQL Injection
سرعت و همچنین مصرف حافظه کمتر: با کوئری‌های پارامتری در SQL Server همانند رویه‌های ذخیره شده رفتار می‌شود.
- عدم نیاز به نوشتن DAL شخصی پر از باگ. چون ORM یعنی همان DAL که توسط یک سری حرفه‌ای طراحی شده.
- یک دست شدن کدها در یک تیم. چون همه بر اساس یک اینترفیس مشخص کار خواهند کرد.
- امکان استفاده از امکانات جدید زبان‌های دات نتی مانند LINQ و نوشتن کوئری‌های strongly typed تحت کنترل کامپایلر.
- پایین آوردن هزینه‌های آموزشی افراد در یک تیم. مثلا EF را می‌شود به عنوان یک پیشنیاز در نظر گرفت؛ عمومی است و همه گیر. کسی هم از شنیدن نام آن تعجب نخواهد کرد. کتاب(های) آموزشی هم در مورد آن زیاد هست.
و ...


نظرات مطالب
طراحی یک گرید با Angular و ASP.NET Core - قسمت دوم - پیاده سازی سمت کلاینت
فیلتر نوشته شده در متد ApplyFiltering  فقط داده هایی که دقیقا برابر با رشته ارسال شده باشند را باز می‌گرداند ، برای اعمال فیلترینگ بصورت شامل (Like) این قطعه کد را جایگزین متد  ApplyFiltering   کنید :
public static IQueryable<T> ApplyFiltering<T>(
          this IQueryable<T> query,
          IPagedQueryModel model,
          IDictionary<string, Expression<Func<T, object>>> columnsMap)
        {
            if (string.IsNullOrWhiteSpace(model.FilterByValue) || !columnsMap.ContainsKey(model.FilterByColumn))
            {
                return query;
            }

            var func = columnsMap[model.FilterByColumn].Compile();
            return query.Where(x => func(x).ToString().Contains( model.FilterByValue.Trim()));
        }


مطالب
آزمون واحد در MVVM به کمک تزریق وابستگی
یکی از خوبی‌های استفاده از Presentation Pattern‌ها بالا بردن تست پذیری برنامه و در نتیجه نگهداری کد می‌باشد.
MVVM الگوی محبوب برنامه نویسان WPF و Silverlight می‌باشد.  به صرف استفاده از الگوی MVVM نمی‌توان اطمینان داشت که ViewModel کاملا تست پذیری داریم. به عنوان مثلا اگر در ViewModel خود مستقیما DialogBox کنیم یا ارجاعی از View دیگری داشته باشیم نوشتن آزمون‌های واحد تقریبا غیر ممکن می‌شود. قبلا درباره‌ی این مشکلات و راه حل آن مطلب در سایت منتشر شده است : 
در این مطلب قصد داریم سناریویی را بررسی کنیم که ViewModel از Background Worker جهت انجام عملیات مانند دریافت داده‌ها استفاده می‌کند.
Background Worker کمک می‌کند تا اعمال طولانی در یک Thread دیگر اجرا شود در نتیجه رابط کاربری Freeze نمی‌شود.
به این مثال ساده توجه کنید : 
    public class BackgroundWorkerViewModel : BaseViewModel
    {
        private List<string> _myData;

        public BackgroundWorkerViewModel()
        {
            LoadDataCommand = new RelayCommand(OnLoadData);
        }

        public RelayCommand LoadDataCommand { get; set; }

        public List<string> MyData
        {
            get { return _myData; }
            set
            {
                _myData = value;
                RaisePropertyChanged(() => MyData);
            }
        }

        public bool IsBusy { get; set; }

        private void OnLoadData()
        {
            var backgroundWorker = new BackgroundWorker();
            backgroundWorker.DoWork += (sender, e) =>
                             {
                                 MyData = new List<string> {"Test"};
                                 Thread.Sleep(1000);
                             };
            backgroundWorker.RunWorkerCompleted += (sender, e) => { IsBusy = false; };
            backgroundWorker.RunWorkerAsync();
        }
    }

در این ViewModel با اجرای دستور LoadDataCommand داده‌ها از یک منبع داده دریافت می‌شود. این عمل می‌تواند چند ثانیه طول بکشد ، در نتیجه برای قفل نشدن رابط کاربر این عمل را به کمک Background Worker به صورت Async در پشت صحنه انجام شده است.
آزمون واحد این ViewModel اینگونه خواهد بود : 
    [TestFixture]
    public class BackgroundWorkerViewModelTest
    {
        #region Setup/Teardown

        [SetUp]
        public void SetUp()
        {
            _backgroundWorkerViewModel = new BackgroundWorkerViewModel();
        }

        #endregion

        private BackgroundWorkerViewModel _backgroundWorkerViewModel;

        [Test]
        public void TestGetData()
        {
              
            _backgroundWorkerViewModel.LoadDataCommand.Execute(_backgroundWorkerViewModel);

            Assert.NotNull(_backgroundWorkerViewModel.MyData);
            Assert.IsNotEmpty(_backgroundWorkerViewModel.MyData);
        }
    }

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

آزمون واحد شکست خورده

یک راه حل تزریق BackgroundWorker به صورت وابستگی به ViewModel می‌باشد. همانطور که قبلا اشاره شده یکی از مزایای استفاده از تکنیک‌های تزریق وابستگی  سهولت Unit testing می‌باشد.
در نتیجه یک Interface عمومی و 2  پیاده سازی همزمان و غیر همزمان جهت استفاده در برنامه‌ی واقعی و آزمون واحد تهیه می‌کنیم : 
   public interface IWorker
    {
        void Run(DoWorkEventHandler doWork);
        void Run(DoWorkEventHandler doWork, RunWorkerCompletedEventHandler onComplete);
    }
جهت استفاده در برنامه‌ی واقعی : 
    public class AsyncWorker : IWorker
    {
        public void Run(DoWorkEventHandler doWork)
        {
            Run(doWork, null);
        }

        public void Run(DoWorkEventHandler doWork, RunWorkerCompletedEventHandler onComplete)
        {
            var backgroundWorker = new BackgroundWorker();
            backgroundWorker.DoWork += doWork;
            if (onComplete != null)
                backgroundWorker.RunWorkerCompleted += onComplete;
            backgroundWorker.RunWorkerAsync();
            

        }
    }
جهت اجرا در آزمون واحد : 
    public class SyncWorker : IWorker
    {
        #region IWorker Members

        public void Run(DoWorkEventHandler doWork)
        {
            Run(doWork, null);
        }

        public void Run(DoWorkEventHandler doWork, RunWorkerCompletedEventHandler onComplete)
        {
            Exception error = null;
            var doWorkEventArgs = new DoWorkEventArgs(null);
            try
            {
                doWork(this, doWorkEventArgs);
            }
            catch (Exception ex)
            {
                error = ex;
                throw;
            }
            finally
            {
                onComplete(this, new RunWorkerCompletedEventArgs(doWorkEventArgs.Result, error, doWorkEventArgs.Cancel));
            }
        }

        #endregion
    }
در نتیجه ViewModel اینگونه تغییر خواهد کرد :
    public class BackgroundWorkerViewModel : BaseViewModel
    {
        private readonly IWorker _worker;
        private List<string> _myData;

        public BackgroundWorkerViewModel(IWorker worker)
        {
            _worker = worker;
            LoadDataCommand = new RelayCommand(OnLoadData);
        }

        public RelayCommand LoadDataCommand { get; set; }

        public List<string> MyData
        {
            get { return _myData; }
            set
            {
                _myData = value;
                RaisePropertyChanged(() => MyData);
            }
        }

        public bool IsBusy { get; set; }

        private void OnLoadData()
        {
            IsBusy = true; // view is bound to IsBusy to show 'loading' message.

            _worker.Run(
                (sender, e) =>
                    {
                        MyData = new List<string> {"Test"};
                        Thread.Sleep(1000);
                    },
                (sender, e) => { IsBusy = false; });
        }
    }

کلاس مربوطه به آزمون واحد را مطابق با تغییرات ViewModel :
    [TestFixture]
    public class BackgroundWorkerViewModelTest
    {
        #region Setup/Teardown

        [SetUp]
        public void SetUp()
        {
            _backgroundWorkerViewModel = new BackgroundWorkerViewModel(new SyncWorker());
        }

        #endregion

        private BackgroundWorkerViewModel _backgroundWorkerViewModel;

        [Test]
        public void TestGetData()
        {
              
            _backgroundWorkerViewModel.LoadDataCommand.Execute(_backgroundWorkerViewModel);

            Assert.NotNull(_backgroundWorkerViewModel.MyData);
            Assert.IsNotEmpty(_backgroundWorkerViewModel.MyData);
        }
    }

اکنون اگر Unit Test را اجرا کنیم نتیجه اینگونه خواهد بود :




 
نظرات مطالب
انجام کارهای زمانبندی شده در برنامه‌های ASP.NET توسط DNT Scheduler
چون یک async void را ایجاد کردید و به صورت fire-and-forget کار می‌کند. یعنی IoCWrapper.RunAndDispose بلافاصله خاتمه پیدا خواهد.
امضای مدنظر شما چنین چیزی باید باشد:
public static Task RunAndDispose(Func<Task> action)
تا خود RunAndDispose هم قابلیت await پیدا کند.
مطالب
پیاده‌سازی الگوی Transaction Per Request در EF
قبلاً در سایت جاری در رابطه با پیاده‌سازی الگوی Context Per Request مطالبی منتشر شده است. در ادامه می‌خواهیم تمامی درخواست‌های خود را اتمیک کنیم. همانطور که قبلاً در این مطلب مطالعه کردید یکی از مزایای الگوی Context Per Request، استفاده‌ی صحیح از تراکنش‌ها می‌باشد. به عنوان مثال اگر در حین فراخوانی متد SaveChanges، خطایی رخ دهد، کلیه‌ی عملیات RollBack خواهد شد. اما حالت زیر را در نظر بگیرید:
_categoryService.AddNewCategory(category);
_uow.SaveAllChanges();

throw new InvalidOperationException();

return RedirectToAction("Index");
همانطور که در کدهای فوق مشاهده می‌کنید، قبل از ریدایرکت شدن صفحه، یک استثناء را صادر کرده‌ایم. در این حالت، تغییرات درون دیتابیس ذخیره می‌شوند! یعنی حتی اگر یک استثناء نیز در طول درخواست رخ دهد، قسمتی از درخواست که در اینجا ذخیره‌سازی گروه محصولات است، درون دیتایس ذخیره خواهد شد؛ در نتیجه درخواست ما اتمیک نیست.
برای رفع این مشکل می‌توانیم یکسری وظایف (Tasks) را تعریف کنیم که در نقاط مختلف چرخه‌ی حیات برنامه اجرا شوند. هر کدام از این وظایف تنها کاری که انجام می‌دهند فراخوانی متد Execute خودشان است. در ادامه می‌خواهیم از این وظایف جهت پیاده‌سازی الگوی Transaction Per Request استفاده کنیم. در نتیجه اینترفیس‌های زیر را ایجاد خواهیم کرد:
public interface IRunAtInit
{
       void Execute();
}
public interface IRunAfterEachRequest
{
       void Execute(); 
}
public interface IRunAtStartUp
{
       void Execute(); 
}
public interface IRunOnEachRequest
{
       void Execute(); 
}
public interface IRunOnError
{
       void Execute(); 
}
خوب، این اینترفیس‌ها همانطور که از نامشان پیداست، همان اعمال را پیاده سازی خواهند کرد:
IRunAtInit: اجرای وظایف در زمان بارگذاری اولیه‌ی برنامه.
IRunAfterEachRequest: اجرای وظایف بعد از اینکه درخواستی فراخوانی (ارسال) شد.
IRunAtStartUp: اجرای وظایف در زمان StartUp برنامه.
IRunOnEachRequest: اجرای وظایف در ابتدای هر درخواست.
IRunOnError: اجرای وظایف در زمان بروز خطا یا استثناء‌های مدیریت نشده‌ی برنامه.
خوب، یک کلاس می‌تواند با پیاده‌سازی هر کدام از اینترفیس‌های فوق تبدیل به یک task شود. همچنین از این جهت که اینترفیس‌های ما ساده هستند و هر اینترفیس یک متد Execute دارد، عملکرد آن‌ها تنها اجرای یکسری دستورات در حالات مختلف می‌باشد.
قدم بعدی افزودن قابلیت پشتیبانی از این وظایف در برنامه‌مان است. اینکار را با پیاده‌سازی ریجستری زیر انجام خواهیم داد:
public class TaskRegistry : StructureMap.Configuration.DSL.Registry
{
        public TaskRegistry()
        {
            Scan(scan =>
            {
                scan.Assembliy("yourAssemblyName");
                scan.AddAllTypesOf<IRunAtInit>();
                scan.AddAllTypesOf<IRunAtStartUp>();
                scan.AddAllTypesOf<IRunOnEachRequest>();
                scan.AddAllTypesOf<IRunOnError>();
                scan.AddAllTypesOf<IRunAfterEachRequest>();
            });
        }
}
با این کار استراکچرمپ اسمبلی معرفی شده را بررسی کرده و هر کلاسی که اینترفیس‌های ذکر شده را پیاده‌سازی کرده باشد، رجیستر می‌کند. قدم بعدی افزودن رجیستری فوق و بارگذاری آن درون کانتینرمان است:
ioc.AddRegistry(new TaskRegistry());
اکنون وظایف درون کانتینرمان بارگذاری شده‌اند. سپس نوبت به استفاده‌ی از این وظایف است. 
خوب، باید درون فایل Global.asax کدهای زیر را قرار دهیم. چون همانطور که عنوان شد وظایف ایجاد شده می‌بایستی در نقاط مختلف برنامه اجرا شوند:
protected void Application_Start()
{
   // other code
   foreach (var task in SmObjectFactory.Container.GetAllInstances<IRunAtInit>())
   {
                task.Execute();
    }
}
protected void Application_BeginRequest()
 {
           foreach (var task in SmObjectFactory.Container.GetAllInstances<IRunOnEachRequest>())
           {
                task.Execute();
           }
}
protected void Application_EndRequest(object sender, EventArgs e)
{
            try
            {
                foreach (var task in SmObjectFactory.Container.GetAllInstances<IRunAfterEachRequest>())
                {
                    task.Execute();
                }
            }
            finally
            {
                HttpContextLifecycle.DisposeAndClearAll();
                MiniProfiler.Stop();
            }
}
protected void Application_Error()
{
            foreach (var task in SmObjectFactory.Container.GetAllInstances<IRunOnError>())
            {
                task.Execute();
            }
}
همانطور که مشاهده می‌کنید، هر task در قسمت خاص خود فراخوانی خواهد شد. مثلاً IRunOnError درون رویداد Application_Error و دیگر وظایف نیز به همین ترتیب.
اکنون برنامه به صورت کامل از وظایف پشتیبانی می‌کند. در ادامه، کلاس زیر را ایجاد خواهیم کرد. این کلاس چندین اینترفیس را از اینترفیس‌های ذکر شده، پیاده‌سازی می‌کند:
public class TransactionPerRequest : IRunOnEachRequest, IRunOnError, IRunAfterEachRequest
{
        private readonly IUnitOfWork _uow;
        private readonly HttpContextBase _httpContext;
        public TransactionPerRequest(IUnitOfWork uow, HttpContextBase httpContext)
        {
            _uow = uow;
            _httpContext = httpContext;
        }


        void IRunOnEachRequest.Execute()
        {
            _httpContext.Items["_Transaction"] =
                _uow.Database.BeginTransaction(System.Data.IsolationLevel.ReadCommitted);
        }

        void IRunOnError.Execute()
        {
            _httpContext.Items["_Error"] = true;
        }

        void IRunAfterEachRequest.Execute()
        {
            var transaction = (DbContextTransaction) _httpContext.Items["_Transaction"];
            if (_httpContext.Items["_Error"] != null)
            {
                transaction.Rollback();
            }
            else
            {
                transaction.Commit();
            }
        }
}
توضیحات کلاس فوق:
در کلاس TransactionPerRequest به دو وابستگی نیاز خواهیم داشت: IUnitOfWork برای کار با تراکنش‌ها و HttpContextBase برای دریافت درخواست جاری. همانطور که مشاهده می‌کنید در متد IRunOnEachRequest.Execute یک تراکنش را آغاز کرده‌ایم و در IRunAfterEachRequest.Execute یعنی در پایان یک درخواست، تراکنش را commit کرده‌ایم. این مورد را با چک کردن یک فلگ در صورت عدم بروز خطا انجام داده‌ایم. اگر خطایی نیز وجود داشته باشد، کل عملیات roll back خواهد شد. لازم به ذکر است که فلگ خطا نیز درون متد IRunOnError.Execute به true مقداردهی شده است.
خوب، پیاده‌سازی الگوی Transaction Per Request به صورت کامل انجام گرفته است. اکنون اگر برنامه را در حالت زیر اجرا کنید:
_categoryService.AddNewCategory(category);
_uow.SaveAllChanges();

throw new InvalidOperationException();

return RedirectToAction("Index");
خواهید دید که عملیات roll back شده و تغییرات در دیتابیس (در اینجا ذخیره سازی گروه محصولات) اعمال نخواهد شد.
مطالب
ویژگی های کمتر استفاده شده در NET. - بخش سوم

__arglist __reftype __makeref __refvalue کلمات کلیدی

در حالیکه، ویرایشگر Visual Studio این کلمات را به صورت رنگی و جزء کلمات کلیدی نمایش می‌دهد، ولی به دلیل عدم وجود مستندات برای این کلمات کلیدی، برای استفاده از آنها باید مراقب باشید؛ چرا که ممکن است به اندازه کافی تست نشده باشند. 
شما می‌توانید با استفاده از کلمه کلیدی makeref__ یک TypeReference را از یک متغیر، ایجاد کنید. با استفاده از کلمه کلیدی reftype__ می‌توانید نوع اصلی از متغیری را که TypeReference را از آن ایجاد کرده اید، استخراج کنید. در انتها می‌توانید با استفاده از کلمه کلیدی refvalue__ مقدار متغیر را از TypeReference ایجاد شده، بدست آورد. با استفاده از کلمه کلیدی arglist__ همانند کلمه کلیدی params می‌توانید به لیستی از پارامترهای یک تابع دسترسی داشته باشید.
var i = 28;
TypedReference tr = __makeref( i );
Type t = __reftype( tr );
Console.WriteLine( t );
int rv = __refvalue( tr, int );
Console.WriteLine( rv );
ArglistTest.DisplayNumbers( __arglist( 1, 2, 3, 5, 6 ) );
و برای استفاده از arglist__ کلاس ArglistTest را پیاده سازی میکنیم.
public static class ArglistTest
{
    public static void DisplayNumbers( __arglist )
    {
        var ai = new ArgIterator( __arglist );
        while ( ai.GetRemainingCount() > 0 )
        {
            var tr = ai.GetNextArg();
            Console.WriteLine( TypedReference.ToObject( tr ) );
        }
    }
}
شی ArgIterator لیست آرگومان‌ها را از اولین آرگومان اختیاری، شروع به شمارش می‌کند. این سازنده برای استفاده در زبان C++/C ایجاد شده است.

Environment.NewLine

رشته خط جدید (↵  Enter) تعریف شده در محیط در حال استفاده را می‌توان با استفاده از این دستور بدست آورد.
Console.WriteLine( "NewLine: {0}first line{0}second line{0}third line", Environment.NewLine );
این رشته شامل "r\n\" برای پلتفرم‌های غیر یونیکس و رشته "n\" برای پلتفرم‌های یونیکس است.

ExceptionDispatchInfo

ExceptionDispatchInfo بیان کننده یک استثناء در یک نقطه خاص از کد، که وضعیت آن قبلا کپچر شده‌است، می‌باشد. شما می‌توانید با استفاده از متد ExceptionDispatchInfo.Throw  (در فضای نام System.Runtime.ExceptionServices) یک استثناء را (با حفظ Stack Trace اصلی) ایجاد کنید.
ExceptionDispatchInfo possibleException = null;
try
{
    int.Parse( "a" );
}
catch ( FormatException ex )
{
    possibleException = ExceptionDispatchInfo.Capture( ex );
}
possibleException?.Throw();


Debug.Assert & Debug.WriteIf & Debug.Indent 

Debug.Assert  – بررسی صحت شرط تعیین شده و در صورت false بودن شرط، نمایش پیام نوشته شده به همراه call stack مربوطه می‌شود.
Debug.Assert(1 == 0, "عدد 1 برابر با 0 نیست");
و خروجی آن در تصویر زیر قابل مشاهده است:

Debug.WriteIf  – در صورت صحت شرط تعیین شده، پیام مشخص شده‌ای را در پنجره output نشان می‌دهد.
Debug.WriteIf( 1 == 1, "display message in output window :D" );

Debug.Indent/Debug.Unindent - برای افزایش/کاهش یک واحد تورفتگی در خروجی نمایش داده شده در پنجره Output، استفاده می‌شود.
Debug.WriteLine("تست تورفتگی");
Debug.Indent();
Debug.WriteLine("یک واحد افزایش داده شد");
Debug.Unindent();
Debug.WriteLine("یک واحد کاهش داده شد");
Debug.WriteLine("پایان تست");

نظرات مطالب
اعتبارسنجی مبتنی بر JWT در ASP.NET Core 2.0 بدون استفاده از سیستم Identity
یک نکته: روش تعیین اعتبار دستی یک JWT
اگر خواستید رشته‌ی JWT دریافتی را در سمت سرور و بر اساس تنظیمات ابتدایی برنامه، بدون نیاز به تکرار آن‌ها و به صورت دستی اعتبارسنجی کنید، روش انجام کار به صورت زیر است:
public class TokenFactoryService
{
    private readonly JwtBearerOptions _jwtBearerOptions;

    public TokenFactoryService(IOptionsSnapshot<JwtBearerOptions> jwtBearerOptions)
    {
        if (jwtBearerOptions == null)
        {
            throw new ArgumentNullException(nameof(jwtBearerOptions));
        }

        _jwtBearerOptions = jwtBearerOptions.Value ?? throw new ArgumentNullException(nameof(jwtBearerOptions));
    }

    // From: https://github.com/dotnet/aspnetcore/blob/a450cb69b5e4549f5515cdb057a68771f56cefd7/src/Security/Authentication/JwtBearer/src/JwtBearerHandler.cs
    public bool ValidateJwt(string token)
    {
        foreach (var validator in _jwtBearerOptions.SecurityTokenValidators)
        {
            try
            {
                if (validator.CanReadToken(token))
                {
                    validator.ValidateToken(token, _jwtBearerOptions.TokenValidationParameters, out _);
                }
            }
            catch
            {
                return false;
            }
        }

        return true;
    }
}
مطالب
چقدر سی‌شارپ را می‌شناسیم؟!
هر چند که #C به عنوان یک زبان ساده برای درک و یادگیری شناخته میشود، گاهی رفتاری غیرمنتظره را حتی برای توسعه دهنده‌های با تجربه خواهد داشت. در این نوشته مروری بر بعضی از این رفتارها و توضیح دلایل پشت آن خواهیم کرد.

Value 

اگر مقدار null مدیریت نشود، میتواند باعث ایجاد نتایج نامطلوب، یا باعث از کار افتادن برنامه شود. شئ null به خودی خود مخرب نیست؛ اما اگر بخواهیم به یکی از متدها یا خاصیت‌های آن دسترسی داشته باشیم، با استثنای معروف NullReferenceException روبرو می‌شویم. برای در امان ماندن، باید همیشه اطمینان داشته باشیم که پیش از استفاده از امکانات شئ، ارجاع آن null نباشد. در قطعه کد زیر برخی از رفتارهای null value آورده شده:
// Behavior 1 
object obj = null;
bool objValueEqual = obj.Equals(null);

// Behavior 2 
object obj = null;
Type objType = obj.GetType();

// Behavior 3
string str = (string)null;
bool strType = str is string;

// Behavior 4
int num = 5;
Nullable<int> nullableNum = 5;
bool typeEqual = num.GetType() == nullableNum.GetType();

// Behavior 5
Type inType = typeof(int);
Type nullableIntType = typeof(Nullable<int>);
bool typeEqual = inType == nullableIntType;
  • در رفتار اول هرچند که متد Equals از شی null در دسترس است و با مقدار null مقایسه شده اما در زمان اجرا پیغام خطای NullReferenceException را خواهیم داشت. 
  • در رفتار دوم هم پیغام خطا را خواهیم داشت. شئ با مقدار null، در زمان اجرا هیچ نوعی را برنمیگرداند. 
  • در رفتار سوم هر چند که مقدار null صریحا به رشته تبدیل شده و برای چاپ متغیر str پیام خطایی را نخواهیم داشت، اما متغیر strType در خروجی، false خواهد بود. همانطور که در رفتار دوم گفته شد، شیء با مقدار null هیچ نوعی را برنمیگرداند. 
  • خروجی رفتار چهارم true خواهد بود. به این صورت که هر دو از نوع System.int32 خواهند بود.
  • در رفتار پنجم اگر از نوع‌ها، خروجی جداگانه بگیریم، خواهیم دیدکه نوع int از System.int32 و <Nullable<int از نوع System.Nullable`1[System.Int32] میباشند، در نتیجه خروجی false است. اشیای nullable بعد از اینکه مقداری مشخص را دریافت کردند، به صورت یک شیء غیر nullable رفتار خواهند کرد.

مدیریت مقادیر null در سربارگذاری متدها   

        static void Main(string[] args)
        {
            Console.WriteLine(Method(null));
            Console.ReadLine();
        }
        private static string Method(object obj)
        {
            return "Object parameter";
        }
        private static string Method(string str)
        {
            return "String parameter";
        }
در قطعه کد بالا، فراخوانی متد سربارگذاری شده با مقدار ورودی null، باعث اجرای متدی میشود که پارامتر ورودی آن از نوع رشته است. تا زمانیکه یکی از پارامترها بتواند به دیگری تبدیل شود، برنامه بدون خطا کامپایل خواهد شد. اما اگر هیچ تبدیل نوعی بین پارامترها وجود نداشته باشد، کد کامپایل نخواهد شد. بین متدهای سربارگذاری شده، متدی که نوع پارامتر آن مشخص‌تر است، فراخوانی میشود. برای اینکه متد خاصی را مجبور به اجرا کنیم، باید مقدار null را پیش از ارسال، به نوع پارامتر آن متد تبدیل کنید.(object)null

رفتارهای ()Math.Round

var rounded = Math.Round(1.5); // 2
var rounded = Math.Round(2.5); // 2

var rounded = Math.Round(2.5, MidpointRounding.ToEven); // 2
var rounded = Math.Round(2.5, MidpointRounding.AwayFromZero); // 3

var value = 1.4f;
var rounded = Math.Round(value + 0.1f); // 1
متد Round از کلاس Math، ورودی را که عددی اعشاری است، گرد میکند. اگر مقدار اعشار کمتر از ۰.۵ باشد، به سمت پایین و اگر بیشتر از ۰.۵ باشد، به سمت بالا گرد میشود. اما اگر ورودی دقیقا مقدار اعشاری ۰.۵ را داشته باشد چطور؟ متد Round به صورت پیش‌فرض ورودی  را به نزدیکترین عدد زوج گرد میکند، به این دلیل خط‌های ۱ و ۲ از قطعه کد بالا، خروجی یکسان ۲ را خواهند داشت. این متد آرگومان دومی هم دارد که دو حالت MidpointRounding.ToEven و MidpointRounding.AwayFromZero را می‌توان برای آن مشخص کرد. ToEven همان رفتار پیش‌فرض متد است که ورودی را به نزدیکترین عدد زوج گرد میکند و از حالت AwayFromZero میشود برای گرد کردن ورودی به عدد بزرگتر استفاده کرد (خط ۵). 
در خط ۸ یک حالت خاص دیگر نیز داریم. انتظار میرود که خروجی، به نزدیکترین عدد زوج گرد شود و نتیجه ۲ باشد؛ مثل خط ۱، اما خروجی ۱ خواهد بود. وقتی ورودی‌ها را از نوع float در نظر بگیریم، مقدار 0.1f کمی کمتر از ۰.۱ خواهد بود و نتیجه محاسبه کمی کمتر از ۱.۵. برای پرهیز از این مسئله بهتر است ورودی متد Round را از نوع decimal در نظر بگیریم.
 

مقدار دهی اولیه کلاسها 

پیشنهاد میشود برای جلوگیری از وقوع استثناءها از مقدار دهی اولیه کلاسها در سازنده کلاس، بخصوص اگر سازنده استاتیک داشته باشیم، پرهیز کنیم. ترتیب مقدار دهی اولیه زمانیکه از یک کلاس یه وهله ساخته میشود، به قرار زیر است:
  • فیلدهای استاتیک (زمانیکه کلاس برای اولین بار در دسترس قرار میگیرد)
  • سازنده استاتیک (زمانیکه کلاس برای اولین بار در دسترس قرار میگیرد)
  • فیلدهایی از کلاس که در نمونه ساخته شده در دسترس قرار میگیرند.
  • سازنده کلاس که در زمان ایجاد یک نمونه از کلاس در دسترس قرار میگیرد.
در قطعه کد زیر اگر نمونه‌ای از کلاس FailingClass ساخته شود، انتظار میرود که خطای InvalidOperationException صادر شود؛ اما برنامه با خطای TypeInitializationException متوقف میشود. در واقع در زمان اجرا به صورت خودکار خطای TypeInitializationException، خطای InvalidOperationException را پوشش میدهد. اگر بجای  InvalidOperationException یک دستور ساده WriteLine داشته باشیم، سازنده کلاس FailingClass مجال کامل شدن را خواهد داشت. اما با خطایی که داخل سازنده صادر کرده‌ایم، سازنده کلاس بدون اینکه به طور کامل به پایان برسد، متوقف خواهد شد. 
    public static class Config
    {
        public static bool ThrowException { get; set; } = true;
    }

    public class FailingClass
    {
        static FailingClass()
        {
            if (Config.ThrowException)
            {
                throw new InvalidOperationException();
            }
        }
    }
حال که میدانیم خطای اصلی که در این مواقع صادر میشود چیست، شاید بخواهیم به روش زیر آن را مدیریت کنیم.
try
{
   var failedInstance = new FailingClass();
}
catch (TypeInitializationException) { }

Config.ThrowException = false;
var instance = new FailingClass();
اگر قطعه کد بالا را بدون بخش try  اجرا کنیم، برنامه ابتدا صدور خطا را false میکند و بدون مشکل از کلاس نمونه‌ای ساخته میشود. اما اگر بخش try را داشته باشیم، هر چند که خطا در بخش try گرفته میشود و تنظیم صدور خطا false است، باز هم در خط آخر و در زمان ایجاد یک نمونه از کلاس، پیام خطای TypeInitializationException خواهیم داشت. علت آن است که سازنده استاتیک کلاس فقط یک بار فراخوانی میشود و اگر در این فراخوانی خطایی رخ دهد، این خطا در اثر ایجاد سایر نمونه‌ها و یا استفاده مستقیم از کلاس، مجددا صادر خواهد شد. در نتیجه این کلاس تا زمانیکه پردازش آن در جریان است، غیرقابل استفاده خواهد بود. یک مثال دیگر از ترتیب فراخوانی‌ها را بررسی میکنیم.
public class BaseClass
{
    {
        public BaseClass()
        {
            VirtualMethod(1);
        }
        public virtual int VirtualMethod(int dividend)
        {
            return dividend / 1;
        }
    }

    public class DerivedClass : BaseClass
    {
        int divisor;
        public DerivedClass()
        {
            divisor = 1;
        }
        public override int VirtualMethod(int dividend)
        {
            return base.VirtualMethod(dividend / divisor);
        }
    }
در قطعه کد بالا هر چند که همه چیز درست به نظر میرسد، اما اگر از کلاس DerivedClass نمونه‌ای ساخته شود، با پیام خطای DivideByZeroException مواجه میشویم. علت این مشکل ترتیب مقدار دهی اولیه در کلاسهای فرزند است. ابتدا فیلدهای کلاس فرزند مقدار دهی میشوند و بعد فیلدهای کلاس پایه، بعد سازنده کلاس پایه فراخوانی میشود و پس از آن سازنده کلاس فرزند. ترتیب فراخوانی‌ها به همین جا محدود نمیشود. 
در مثال بالا متد VirtualMethod که در سازنده کلاس پایه فراخوانی شده، پیش از این که کد داخل خود را اجرا کند، متد VirtualMethod را در کلاس فرزند، فراخوانی میکند و کلاس فرزند مجالی را برای مقدار دهی متغیر divisor، در سازنده خود نخواهد داشت. در نتیجه مقدار این متغیر در متد VirtualMethod صفر خواهد ماند و باعث صدور استثناء میشود. برای پرهیز از چنین مشکلاتی بهتر است فیلدهای یک کلاس به صورت مستقیم مقدار دهی اولیه بشوند. مقدار دهی اولیه و یا فراخوانی متدهای virtual در سازنده کلاس‌ها میتواند باعث بروز رفتارهای پیش بینی نشده‌ای شوند.

چند ریختی 

 چند ریختی قابلیتی است برای کلاسهای متفاوت تا بتوانند یک اینترفیس مشابه را به صورت‌های مختلفی پیاده‌سازی کنند. اما قطعه کد زیر قاعده چند ریختی را نقض میکند. 
 class Program
    {
        static void Main(string[] args)
        {
            var instance = new DerivedClass();
            var result = instance.Method();
            result = ((BaseClass)instance).Method();
            Console.WriteLine(instance + " -> " + result); // Derived Class ...  -> Method in BaseClass
            Console.ReadLine();

        }
    }

    public class BaseClass
    {
        public virtual string Method()
        {
            return "Method in BaseClass";
        }
    }

    public class DerivedClass : BaseClass
    {
        public override string ToString()
        {
            return "Derived Class ... ";
        }

        public new string Method()
        {
            return "Method in DerivedClass";
        }
    }
در خروجی کنسول هرچند که Instance همچنان وهله‌ای از DerivedClass است اما به دلیل تبدیل در خط ۷، Method کلاس DerivedClass به وسیله کلاس پایه پنهان شده و Method کلاس پایه فراخوانی میشود. در قطعه کد زیر حالت مشابه‌ای را که در بالا داشتیم، برای interface‌ها دیده میشود.
class Program
    {
        static void Main(string[] args)
        {
            var instance = new DerivedClass();
            var result = instance.Method(); // -> Method in DerivedClass
            result = ((IInterface)instance).Method(); // -> Method belonging to IInterface
            Console.WriteLine(result);
            Console.ReadLine();
        }
    }

    public interface IInterface
    {
        string Method();
    }

    public class DerivedClass : IInterface
    {
        public string Method()
        {
            return "Method in DerivedClass";
        }
        string IInterface.Method()
        {
            return "Method belonging to IInterface";
        }
}
هرچند که به نظر میرسد دلیلی برای استفاده از روشهای گفته شده وجود ندارد، اما اگر بخواهیم بیش از یک پیاده‌سازی را برای یک متد در یک کلاس داشته باشیم، میتواند مورد توجه قرار گیرد. بخصوص اگر نیاز باشد که پیاده‌سازی دوم خودش به طور مستقلی در کلاسی دیگر استفاده شود.

Iterators 

Iterator‌ها (تکرار شونده‌ها) ساختارهایی هستند که برای حرکت در عناصر یک collection استفاده میشوند. عموما از دستور foreach استفاده و نوع جنریک <IEnumerable<T را نمایندگی میکنند. هر چند که استفاده از آنها ساده است، اما اگر کارکرد داخلی iteratorها را درک نکنیم ممکن است به دام استفاده نادرست از آنها گرفتار شویم. در قطعه کد زیر کلاس Test صدا زده میشود و مقادیر یک تا پنج به صورت یک IEnumerable از داخل بلوک using بازگشت داده میشود. 
private IEnumerable<int> GetEnumerable(StringBuilder log)
{
     using (var test = new Test(log))
      {
          return Enumerable.Range(1, 5);
      }
}

فرض کنیم کلاس Test اینترفیس IDisposable را پیاده‌سازی کرده و در سازنده و متد Dispose خود پیامهایی را به log اضافه کند. در مثالهای واقعی، کلاس Testمیتواند اتصالی به پایگاه داده باشد و رکوردهای خوانده شده، بازگشت داده شوند. توسط حلقه زیر مقدار خروجی تابع را چاپ میکنیم.
var log = new StringBuilder();
            
foreach (var number in GetEnumerable(log))
{
     log.AppendLine($"{number}");
}
انتظار میرود که خروجی به این صورت باشد که ابتدا رشته Created (از سازنده کلاس Test) چاپ شود بعد اعداد یک تا پنج و در نهایت رشته Disposed (از متد Dispose کلاس Test). به عبارتی در ابتدای کار، بلوک using، سازنده کلاس را فراخوانی کند و بعد از اینکه بلوک به پایان کارش رسید متد Dispose کلاس فراخوانی شود. اما در واقع خروجی به صورت زیر خواهد بود. 
Created
Disposed
1
2
3
4
5
این تفاوت در دنیای واقعی مهم است؛ به اینصورت که مثلا اتصال به پایگاه داده قبل از اینکه داده‌ها خوانده شوند، بسته میشود و قطعه کد به درستی عمل نخواهد کرد. تنها راه حل، پیمایش در collection داخل using و بازگشت هر مقدار به صورت مجزا است، که در زیر آمده است.
 using (var test = new Test(log))
 {
     foreach (var i in Enumerable.Range(1,5))
     {
         yield return i;
     }
 }
فقط در این صورت است که کلاس Test بعد از اتمام کار حلقه و در زمان درست به پایان میرسد. توسط کلمه کلیدی yield و برای متدی که خروجی قابل پیمایش داشته باشد میتوان چندین مقدار را بازگشت داد. ترتیب اجرای دستورات در قطعه کد بالا به این صورت است که ابتدا نمونه‌ای از کلاس Test ایجاد میشود و سازنده کلاس فراخوانی میشود، سپس حلقه foreach به تعداد مشخص شده در Range مقادیر بازگشتی را در خروجی تابع قرار میدهد. وقتی که کار حلقه تمام شد، بلوک using دستورات را ادامه خواهد داد که برابر با خاتمه دادن به تمام نمونه‌ها و منابع استفاده شده در بلوک است؛ یعنی فراخوانی متد Dispose. با استفاده از این روش خروجی به شکل زیر خواهد بود. 
Created
1
2
3
4
5
Disposed

مطالب
بررسی روش مشاهده خروجی SQL حاصل از کوئری‌های Entity framework Core
هنوز تا Entity framework Core 1.1، مفهوم interceptors موجود در EF 6.x پیاده سازی نشده‌است. اما شبیه به مفاهیم «ارتقاء به ASP.NET Core 1.0 - قسمت 17 - بررسی فریم ورک Logging»، در EF Core نیز زیرساختی جهت مشاهده‌ی SQL نهایی تولیدی وجود دارد.


ایجاد یک ثبت کننده‌ی وقایع EF Core

مرحله‌ی اول مشاهده‌ی خروجی‌های نهایی EF Core، پیاده سازی اینترفیس ILoggerProvider است که در آن قرار است وهله‌ی از نوع ILogger بازگشت داده شود. به همین جهت یک کلاس تو در توی خصوصی را در اینجا مشاهده می‌کنید که اینترفیس ILogger را نیز پیاده سازی کرده‌است:
using System;
using Microsoft.Extensions.Logging;

namespace Tests
{
    public class MyLoggerProvider : ILoggerProvider
    {
        public ILogger CreateLogger(string categoryName)
        {
            return new MyLogger();
        }

        public void Dispose()
        { }

        private class MyLogger : ILogger
        {
            public bool IsEnabled(LogLevel logLevel)
            {
                return true;
            }

            public void Log<TState>(
                        LogLevel logLevel, 
                        EventId eventId, 
                        TState state, 
                        Exception exception, 
                        Func<TState, Exception, string> formatter)
            {
                //File.AppendAllText(@"C:\temp\log.txt", formatter(state, exception));
                Console.WriteLine("");
                Console.WriteLine(formatter(state, exception));
            }

            public IDisposable BeginScope<TState>(TState state)
            {
                return null;
            }
        }
    }
}
در اینجا خروجی‌هایی نهایی توسط Console.WriteLine نمایش داده شده‌اند و مناسب برنامه‌های کنسول هستند و یا می‌توان برای مثال توسط File.AppendAllText، اطلاعات رسیده را در یک فایل نیز ذخیره کرد.
در متد Log:
- پارامتر logLevel، سطح اهمیت اطلاعات رسیده را به همراه دارد. برای مثال اطلاعات است یا خطا؟
برای مثال شاید نیاز به ذخیره سازی اطلاعاتی با سطح‌های بحرانی، خطا و یا اخطار در یک بانک اطلاعاتی وجود داشته باشد:
   if (logLevel == LogLevel.Critical || logLevel == LogLevel.Error || logLevel == LogLevel.Warning)
- eventId: نوع رخداد رسیده را مشخص می‌کند.
- state: می‌تواند هر نوع شیءایی، حاوی اطلاعات وضعیت رخداد رسیده باشد.
- exception: بیانگر استثنای احتمالی رخ داده است.
- formatter: کار آن تولید یک رشته‌ی قابل خواندن، توسط اطلاعات حالت و استثناء است.


معرفی Logger تهیه شده به برنامه

پس از تهیه‌ی Logger فوق، جهت معرفی آن به یک برنامه‌ی کنسول، می‌توان به صورت ذیل عمل کرد:
using (var db = new MyContext())
{
   var loggerFactory = (ILoggerFactory)db.GetInfrastructure().GetService(typeof(ILoggerFactory));
   loggerFactory.AddProvider(new MyLoggerProvider());
 }
این ثبت تنها باید یکبار در آغاز برنامه انجام شود و پس از آن تمام وهله‌ی دیگر Context از آن استفاده خواهند کرد.

در برنامه‌های ASP.NET Core، کار معرفی MyLoggerProvider در متد Configure کلاس آغازین برنامه انجام می‌شود:
public void Configure(ILoggerFactory loggerFactory)
{
   loggerFactory.AddProvider(new MyLoggerProvider());


اختصاصی سازی ثبت وقایع رسیده

کلاس MyLoggerProvider، هر نوع اطلاعات داخلی EF Core را نیز لاگ می‌کند. اگر هدف صرفا بررسی خروجی SQL نهایی تولیدی است، می‌توان در متد ذیل:
public ILogger CreateLogger(string categoryName)
بر اساس categoryName رسیده، یا new MyLogger را بازگشت داده و یا یک NullLogger.Instance را که کاری را انجام نمی‌دهد. به این ترتیب می‌توان کار فیلتر کردن اطلاعات رسیده را انجام داد.
برای این منظور، ابتدای Logger تهیه شده چنین شکلی را پیدا می‌کند:
using Microsoft.EntityFrameworkCore.Storage.Internal;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Logging;
using System.Linq;
using System;
 
namespace Tests
{
    public class MyLoggerProvider : ILoggerProvider
    {
        private static readonly string[] _categories =
            {
                typeof(RelationalCommandBuilderFactory).FullName,
                typeof(SqlServerConnection).FullName
            };
        public ILogger CreateLogger(string categoryName)
        {
            if (_categories.Contains(categoryName))
            {
                return new MyLogger();
            }
 
            return NullLogger.Instance;
        }
مطالب دوره‌ها
آشنایی با AOP Interceptors
در حین استفاده از Interceptors، کار مداخله و تحت نظر قرار دادن قسمت‌های مختلف کدها، توسط کامپوننت‌های خارجی صورت خواهد گرفت. این کامپوننت‌های خارجی، به صورت پویا، تزئین کننده‌هایی را جهت محصور سازی قسمت‌های مختلف کدهای شما تولید می‌کنند. این‌ها، بسته به توانایی‌هایی که دارند، در زمان اجرا و یا حتی در زمان کامپایل نیز قابل تنظیم می‌باشند.


ابزارهایی جهت تولید AOP Interceptors

متداول‌ترین کامپوننت‌های خارجی که جهت تولید AOP Interceptors مورد استفاده قرار می‌گیرند، همان IOC Containers معروف هستند مانند StructureMap، Ninject، MS Unity و غیره.
سایر ابزارهای تولید AOP Interceptors، از روش تولید Dynamic proxies بهره می‌گیرند. به این ترتیب مزین کننده‌هایی پویا، در زمان اجرا، کدهای شما را محصور خواهند کرد. (نمونه‌ای از آن‌را شاید در حین کار با ORMهای مختلف دیده باشید).


نگاهی به فرآیند Interception

زمانیکه از یک IOC Container در کدهای خود استفاده می‌کنید، مراحلی چند رخ خواهند داد:
الف) کد فراخوان، از IOC Container، یک شیء مشخص را درخواست می‌کند. عموما اینکار با درخواست یک اینترفیس صورت می‌گیرد؛ هرچند محدودیتی نیز وجود نداشته و امکان درخواست یک کلاس از نوعی مشخص نیز وجود دارد.
ب) در ادامه IOC Container به لیست اشیاء قابل ارائه توسط خود نگاه کرده و در صورت وجود، وهله سازی شیء درخواست شده را انجام و نهایتا شیء مطلوب را بازگشت خواهد داد.
ج) سپس، کد فراخوان، وهله دریافتی را مورد پردازش قرار داده و شروع به استفاده از متدها و خواص آن خواهد نمود.

اکنون با اضافه کردن Interception به این پروسه، چند مرحله دیگر نیز در این بین به آن اضافه خواهند شد:
الف) در اینجا نیز در ابتدا کد فراخوان، درخواست وهله‌ای را بر اساس اینترفیسی خاص به IOC Container ارائه می‌دهد.
ب) IOC Container نیز سعی در وهله سازی درخواست رسیده بر اساس تنظیمات اولیه خود می‌کند.
ج) اما در این حالت IOC Container تشخیص می‌دهد، نوعی که باید بازگشت دهد، علاوه بر وهله سازی، نیاز به مزین سازی توسط  Aspects و پیاده سازی Interceptors را نیز دارد. بنابراین نوع مورد انتظار را در صورت وجود، به یک Dynamic Proxy، بجای بازگشت مستقیم به فراخوان ارائه می‌دهد.
د) در  ادامه Dynamic Proxy، نوع مورد انتظار را توسط Interceptors محصور کرده و به فراخوان بازگشت می‌دهد.
ه) اکنون فراخوان، در حین استفاده از امکانات شیء وهله سازی شده، به صورت خودکار مراحل مختلف اجرای یک Aspect را که در قسمت قبل بررسی شدند، سبب خواهد شد.


نحوه ایجاد Interceptors

برای ایجاد یک Interceptor دو مرحله باید انجام شود:
الف) پیاده سازی یک اینترفیس
ب) اتصال آن به کدهای اصلی برنامه

در ادامه قصد داریم از یک IOC Container معروف به نام StructureMap در یک برنامه کنسول استفاده کنیم. برای دریافت آن نیاز است دستور پاورشل ذیل را در کنسول نوگت ویژوال استودیو فراخوانی کنید:
 PM> Install-Package structuremap
پس از آن یک برنامه کنسول جدید را ایجاد کنید. (هدف از استفاده از این نوع پروژه خاص، توضیح جزئیات یک فناوری، بدون درگیر شدن با لایه UI است)
البته باید دقت داشت که برای استفاده از StructureMap نیاز است به خواص پروژه مراجعه و سپس حالت Client profile را به Full profile تغییر داد تا برنامه قابل کامپایل باشد.
using System;
using StructureMap;

namespace AOP00
{
    public interface IMyType
    {
        void DoSomething(string data, int i);
    }

    public class MyType : IMyType
    {
        public void DoSomething(string data, int i)
        {
            Console.WriteLine("DoSomething({0}, {1});", data, i);
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            ObjectFactory.Initialize(x =>
            {
                x.For<IMyType>().Use<MyType>();
            });

            var myType = ObjectFactory.GetInstance<IMyType>();
            myType.DoSomething("Test", 1);
        }
    }
}
اکنون کدهای این برنامه را به نحو فوق تغییر دهید.
در اینجا یک اینترفیس نمونه و پیاده سازی آن‌را ملاحظه می‌کنید. همچنین نحوه آغاز تنظیمات StructureMap و نحوه دریافت یک وهله متناظر با IMyType نیز بیان شده‌اند.
نکته‌ی مهمی که در اینجا باید به آن دقت داشت، وضعیت شیء myType حین فراخوانی متد myType.DoSomething است. شیء myType در اینجا، دقیقا یک وهله‌ی متداول از کلاس myType است و هیچگونه دخل و تصرفی در نحوه اجرای آن صورت نگرفته است.
خوب! تا اینجای کار را احتمالا پیشتر نیز دیده بودید. در ادامه قصد داریم یک Interceptor را طراحی و مراحل چهارگانه اجرای یک Aspect را در اینجا بررسی کنیم.

در ادامه نیاز خواهیم داشت تا یک Dynamic proxy را نیز مورد استفاده قرار دهیم؛ از این جهت که StructureMap تنها دارای Interceptorهای وهله سازی اطلاعات است و نه Method Interceptor. برای دسترسی به Method Interceptors نیاز به یک Dynamic proxy نیز می‌باشد. در اینجا از Castle.Core استفاده خواهیم کرد:
 PM> Install-Package Castle.Core
برای دریافت آن تنها کافی است دستور پاور شل فوق را در خط فرمان کنسول پاورشل نوگت در VS.NET اجرا کنید.
سپس کلاس ذیل را به پروژه جاری اضافه کنید:
using System;
using Castle.DynamicProxy;

namespace AOP00
{
    public class LoggingInterceptor : IInterceptor
    {
        public void Intercept(IInvocation invocation)
        {
            try
            {
                Console.WriteLine("Logging On Start.");

                invocation.Proceed(); //فراخوانی متد اصلی در اینجا صورت می‌گیرد

                Console.WriteLine("Logging On Success.");
            }
            catch (Exception ex)
            {
                Console.WriteLine("Logging On Error.");
                throw;
            }
            finally
            {
                Console.WriteLine("Logging On Exit.");
            }
        }
    }
}
در کلاس فوق کار Method Interception توسط امکانات Castle.Core انجام شده است. این کلاس باید اینترفیس IInterceptor را پیاده سازی کند. در این متد سطر invocation.Proceed دقیقا معادل فراخوانی متد مورد نظر است. مراحل چهارگانه شروع، پایان، خطا و موفقیت نیز توسط try/catch/finally پیاده سازی شده‌اند.

اکنون برای معرفی این کلاس به برنامه کافی است سطرهای ذیل را اندکی ویرایش کنیم:
        static void Main(string[] args)
        {
            ObjectFactory.Initialize(x =>
            {
                var dynamicProxy = new ProxyGenerator();
                x.For<IMyType>().Use<MyType>();
                x.For<IMyType>().EnrichAllWith(myTypeInterface => dynamicProxy.CreateInterfaceProxyWithTarget(myTypeInterface, new LoggingInterceptor()));
            });

            var myType = ObjectFactory.GetInstance<IMyType>();
            myType.DoSomething("Test", 1);
        }
در اینجا تنها سطر EnrichAllWith آن جدید است. ابتدا یک پروکسی پویا تولید شده است. سپس این پروکسی پویا کار دخالت و تحت نظر قرار دادن اجرای متدهای اینترفیس IMyType را عهده دار خواهد شد.
برای مثال اکنون با فراخوانی متد myType.DoSomething، ابتدا کنترل برنامه به پروکسی پویای تشکیل شده توسط Castle.Core منتقل می‌شود. در اینجا هنوز هم متد DoSomething فراخوانی نشده است. ابتدا وارد بدنه متد public void Intercept خواهیم شد. سپس سطر invocation.Proceed، فراخوانی واقعی متد DoSomething اصلی را انجام می‌دهد. در ادامه باز هم فرصت داریم تا مراحل موفقیت، خطا یا خروج را لاگ کنیم.
تنها زمانیکه کار متد public void Intercept به پایان می‌رسد، سطر پس از فراخوانی متد  myType.DoSomething اجرا خواهد شد.
در این حالت اگر برنامه را اجرا کنیم، چنین خروجی را نمایش می‌دهد:
 Logging On Start.
DoSomething(Test, 1);
Logging On Success.
Logging On Exit.
بنابراین در اینجا نحوه دخالت و تحت نظر قرار دادن اجرای متدهای یک کلاس عمومی خاص را ملاحظه می‌کنید. برای اینکه کنترل کامل را در دست بگیریم، کلاس پروکسی پویا وارد عمل شده و اینجا است که این کلاس پروکسی تصمیم می‌گیرد چه زمانی باید فراخوانی واقعی متد مورد نظر انجام شود.
برای اینکه فراخوانی قسمت On Error را نیز ملاحظه کنید، یک استثنای عمدی را در متد DoSomething قرار داده و مجددا برنامه را اجرا کنید.