مطالب
کتابخانه GMap.Net
نقشه گوگل در حال حاضر یکی از محبوب‌ترین و کاملترین نقشه‌های جهان است و امکانات خوبی هم دارد. در این راستا بسیاری از مردم سعی در استفاده از این نقشه‌ها و امکانات آن‌ها دارند. به همین دلیل گوگل در بسته‌های api خود نیز این مورد را گنجانده است. ولی استفاده از این api مستلزم نوشتن کدهای جاوا اسکرپیتی و شناخت توابع و ثابت‌های api گوگل است. اما در هر صورت این مستندات مورد مطالعه قرار می‌گیرند.

سال گذشته بود که به بررسی کتابخانه‌های موجود برای دات نت که به ساخت نقشه‌های گوگل (+ ) می‌پردازند پرداختم. ولی مشکلی که وجود داشت، همه آن‌ها در نهایت یک تصویر jpeg تحویل می‌دادند. ولی من می‌خواستم نقشه‌ی من زنده و واکنشگرا باشد تا کاربر بتواند روی آن حرکت کند، زوم کند، مارکر‌ها را جابجا کند و امکانات دیگری که در این نقشه در دسترس است را داشته باشد. برای همین شروع به ساخت یک class library کردم تا کاربر بتواند در محیط سی شارپ، تنظیمات را با اسامی قابل شناخت و یک intellisense قدرتمند بنویسد و در نهایت بر اساس اطلاعات کاربر، این کدها به صورت جاوا اسکریپت تولید شود. می‌توانید سورس نهایی کتابخانه‌ی GMap.Net را در گیت هاب، به همراه یک پروژه‌ی نمونه ببینید.

پروژه‌ی وابسته این کتابخانه،  MS Ajax Minifier جهت کم حجم کردن کدهای جاوا اسکریپت است. در مورد این کتابخانه در سایت جاری بحث شده است.
برای نصب این کتابخانه می‌توانید از طریق دستور زیر در Nuget عمل کنید:
Install-Package GMap.Net

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

برای شروع نیاز است که یک نمونه از کلاس GoogleMapApi را ایجاد کنید. بعد از آن با استفاده از خصوصیت SetLocation، مختصات مرکز نقشه را تنظیم نمایید. سپس با استفاده از خصوصیات دیگر نیز می‌توانید نقشه را تنظیم نمایید. تعدادی از این خصوصیات مثل SetZoomVisibility هستند که با استفاده از آن می‌توانید تنظیمات زوم را روی نقشه پیاده سازی کنید. البته فعال کردن این گزینه به تنهایی کافی نیست و باید از طریق خصوصیت ZoomControlOption پیکربندی کنترل زوم را نیز اینجام دهید که این پیکربندی شامل موقعیت قرارگیری کنترل‌های زوم و اندازه‌ی دکمه‌ها می‌باشد و مابقی تنظیمات هم بدین شکل هستند:

به غیر از تنظیمات نقشه، Overlayهای زیر در این کلاس پشتیبانی می‌شوند:
عنوان
توضیحات
 Marker  یک نشانه گذار که برای مشخص کردن یک محل بر روی نقشه به کار می‌رود. این علامت گذار شامل خصوصیت‌هایی چون نقطه‌ی قرارگیری، آیکن، عنوان و انیمیشنی برای نحوه‌ی نمایش آن می‌باشد. همچنین شامل یک خصوصیت دیگر از نوع InfoWindow است که به شما امکان نمایش یک پنجره‌ی توضیحات را نیز بر روی مارکر می‌دهد. این محتوا می‌تواند به صورت HTML نمایش یابد.
 Circle  در صورتیکه بخواهید ناحیه‌ای دایره‌ای شکل را بر روی نقشه مشخص کنید، کاربرد دارد. با دادن نقطه‌ی مرکزی و شعاع می‌توانید دایره را ترسیم کنید. همچنین شامل خصوصیات ظاهری چون رنگ داخل و حاشیه‌ها و میزان شفافیت نیز می‌باشد.
 Rectangle  به رسم یک مستطیل می‌پردازد و تنها لازم است دو مختصات را به آن بدهید و بر اساس این دو نقطه، ناحیه‌ی مستطیلی شکل ترسیم می‌گردد. در صورتیکه نقاط بیشتری را به آن اضافه کنید، فقط دوتای اولی در نظر گرفته می‌شوند. این گزینه شامل خصوصیات ظاهری نیز می‌گردد.
 Polyline  برای ترسیم مسیرها به صورت چند ضلعی به کار می‌رود و الزامی به بستن مسیرها نیست. دارای خصوصیات ظاهری نیز می‌باشد.
 
polygon
کاملا شبیه Ployline است؛ با این تفاوت که یک چند ضلعی بسته است و می‌تواند داخل آن با رنگ پر باشد. برای بستن این چند ضلعی لازم نیست تا کاری انجام دهید. خود کلاس، نقطه‌ی اول و آخر را به هم وصل می‌کند.

خصوصیات آیتم‌های بالا، شامل موارد زیر می‌شود:

 نام خصوصیت
توضیحات
 Id  در سازنده‌ی هر کدام به طور اجباری قرار گرفته است. این id برای زمانی است که بخواهید با استفاده از جاوااسکرپیت با آن ارتباط برقرار کنید.
 Editable  با فعال کردن این خاصیت، به کاربر این اجازه را می‌دهید که بتواند روی Overlay ویرایش انجام دهد.
 StrokeWeight  ضخامت لبه‌ها را مشخص می‌کند.
 StrokeColor  رنگ لبه را مشخص می‌کند.
 StrokeOpacity  میزان شفافیت لبه را بین 0 تا 1 مشخص می‌کند.
 FillColor  بعضی از المان‌ها مانند چند ضلعی‌های بسته و مستطیل که ناحیه‌ی داخلی دارند، شامل این گزینه هستند و رنگ داخل این ناحیه را مشخص می‌کنند.
 FillOpacity  میزان شفافیت خصوصیت بالا را از 0 تا 1 مشخص می‌کند.
 Points  با استفاده از این خاصیت می‌توانید مختصات را با استفاده از کلاس Location به آن اضافه کنید. برای دایره خصوصیت Point وجود دارد.
 Radius  برای دایره کاربرد دارد. با مقدار نوع Int می‌توانید شعاع آن را مقدار دهی کنید.
کد زیر و تصویر زیر نمونه‌ای از کاربرد این کلاس است:
public class MiladTower
    {
        public GoogleMapApi TestMarker()
        {
            var map=new GoogleMapApi(true);
            var location = new Location(35.7448416, 51.3753212);
            map.SetLocation(location);
            map.SetZoom(17);
            map.SetMapType(MapTypes.ROADMAP);
            map.SetBackgroundColor(Color.Aqua);
            map.ZoomControlVisibilty(true);
            map.ZoomOptions = new zoomControlOptions()
            {
                Position = Position.TOP_LEFT,
                ZoomStyle = ZoomStyle.SMALL
            };
  

            Marker marker=new Marker("mymarker1");
            marker.InfoWindow=new InfoWindow("iw1")
            {
                Content = "<b>Milad Tower</b><i>in Tehran</i><br/>Milad Tower is the highest tower in iran,many people and tourists visit it each year, but it's so expensive that i cant afford it as iranian citizen<br/>please see more info at  <a href=\"https://en.wikipedia.org/wiki/Milad_Tower\"><img width='16px' height='16px' src='https://en.wikipedia.org/favicon.ico'/>wikipedia</a>"
            };
            marker.MarkerPoint = location;
            map.Markers.Add(marker);

            var circle=new CircleMarker("mymarker2");
            circle.FillColor = Color.Green;
            circle.FillOpacity = 0.6f;
            circle.StrokeColor = Color.Red;
            circle.StrokeOpacity = 0.8f;
            circle.Point = location;
            circle.Radius = 30;
            circle.Editable = true;
            circle.StrokeWeight = 3;
            map.Circles.Add(circle);

            Rectangle rect=new Rectangle("rect1");
            rect.FillColor = Color.Black;
            rect.FillOpacity = 0.4f;
            rect.Points.Add(new Location(35.74728723483808, 51.37550354003906));
            rect.Points.Add(new Location(35.74668641224311, 51.376715898513794));
            map.Rectangles.Add(rect);

            Polyline polyline=new Polyline("poly1");
            polyline.Points.Add(new Location(35.74457043569041, 51.373915672302246));
            polyline.Points.Add(new Location(35.74470976097927, 51.37359380722046));
            polyline.Points.Add(new Location(35.744378863020074, 51.37337923049927));
            polyline.StrokeColor = Color.Blue;
            polyline.StrokeWeight = 2;
            map.Polylines.Add(polyline);

            Polygon polygon=new Polygon("poly2");
            polygon.Points.Add(new Location(35.746494844665094, 51.374655961990356));
            polygon.Points.Add(new Location(35.74635552250061, 51.37283205986023));
            polygon.Points.Add(new Location(35.74598109297522, 51.372681856155396));
            polygon.Points.Add(new Location(35.7454934611854, 51.37361526489258));
            polygon.FillColor = Color.Black;
            polygon.FillOpacity = 0.5f;
            polygon.StrokeColor = Color.Gray;
            polygon.StrokeWeight = 1;
            map.Polygons.Add(polygon);

            return map;
        }
    }
بعد از ایجاد چنین کلاسی نیاز است تا آن را در ویوو ترسیم کنیم. ابتدا یک div برای ناحیه‌ی نقشه ایجاد می‌کنیم و سپس خروجی متد ShowMapForMVC را در ناحیه‌ی Head صفحه چاپ می‌کنیم. این خروجی به طور خودکار یک MVCHTMLString را بر می‌گرداند. در صورتیکه از وب فرم استفاده می‌کنید، می‌توانید از گزینه‌ی ShowMap استفاده کنید.
@section javascript
{
    @{
        var map = new MiladTower().TestMarker();
        @map.ShowMapForMvc("mapdiv")
    }
}
<br/><br/>
<div id="mapdiv" style="width:600px;height:600px;"></div>

در نهایت نقشه‌ی زیر نمایش داده می‌شود:


کم حجم کردن کدها
در صورتیکه به سورس صفحه نگاهی بیندازید، می‌بینید که کدهای جاوا اسکریپت، داخل صفحه نوشته شده‌اند. اگر بخواهید برای کم حجم‌تر شدن کد، عملیات minify را انجام دهید، با true شدن خصوصیت minified با استفاده از کتابخانه‌ی وابسته‌اش (MS Ajax Minifier)  اینکار را انجام می‌دهد.

انتقال کدها به یک فایل خارجی
بسیاری از ما برای نوشتن کدهای جاوا اسکریپت، از یک فایل خارجی استفاده می‌کنیم. برای داشتن این قابلیت می‌توانید به جای ShowMapForMVC متد CallJs را صدا بزنید تا کتابخانه api گوگل را صدا بزند و سپس در یک اکشن متد، متد GiveJustJS را صدا بزنید و طبق مقاله‌ی موجود در سایت جاری محتوای آن را برگردانید و به عنوان یک فایل JS به این اکشن متد لینک بدهید. کدهای زیر به شما نحوه‌ی این کار را نشان می‌دهند:
ابتدا در یک اکشن متد، کد زیر را وارد می‌کنیم:
    public ActionResult MiladJs()
        {
            var output = new MiladTower().TestMarker().GiveJustJs("mapdiv");
            Response.ContentType = "text/javascript";
            return Content(output);
        }

بعد از آن در ویووی مربوطه کد زیر را داریم:
@section javascript
{
    @{
        var map = new MiladTower().TestMarker();
        @map.CallJs()
        <script type="text/javascript" src="@Url.Action("MiladJs","Home")"></script>
    }   
}
<br/><br/>
<div id="mapdiv" style="width:600px;height:600px;"></div>
بدین ترتیب کدهای شما داخل یک فایل خارجی قرار می‌گیرند.


نحوه‌ی کارکرد این کتابخانه
برای آشنایی با نحوه‌ی کارکرد آن باید بدانید که اساس کار این کتابخانه string interpolation است. این کتابخانه کلاسی را به صورت Partial دارد که بین چندین فایل تقسیم شده است و هر یک از فایل‌ها، با نام محتوای آن نامگذاری شده‌اند. Public methods متدهای عمومی، private methods متدهای خصوصی، Constants یا ثابت‌ها که حاوی تمام دستورات جاوا اسکریپتی هستند و در نهایت خود کلاس اصلی GoogleMapApi که حاوی کدهای اجرایی و تشکیل کد جاوا اسکریپت می‌باشد. در کنار کلاس اصلی، کلاس‌های Overlay هم قرار دارند که شامل اطلاعات اشیاء روی نقشه‌ها هستند؛ مثل مارکرها و چندضلعی‌ها و ... و در نهایت یک سری کلاس و نوع شمارشی Enum شامل خصوصیت‌هایی که برای تنظیمات و پیکربندی نقشه به کار می‌روند.
کلاس GoogleMapApi برای ایجاد کدها، داده‌هایی را که برای هر کلاس در نظر گرفته‌اید، با استفاده از interpolation و ثابت‌های حاوی کد جاوا اسکریپت، در یک رشته‌ی جدید قرار می‌دهند و این رشته‌ها با اتصال درست در موقعیت خود، کد نهایی جاوا اسکریپت را تولید می‌کنند.
مطالب
اثبات قانون مشاهده‌گر در برنامه نویسی
امروز حین کدنویسی به یک مشکل نادر برخورد کردم. کلاسی پایه داشتم (مثلا Person) که یک سری کلاس دیگر از آن ارث بری میکردند (مثلا کلاس‌های Student و Teacher).در اینجا در کلاس پایه بصورت اتوماتیک یک ویژگی(Property) را روی کلاس‌های مشتق شده مقدار دهی میکردم؛ مثلا به این شکل:
 public class Person
    {      
        public Person()
        {
            personId= this.GetType().Name + (new Random()).Next(1, int.MaxValue);          
        }
     }
سپس در یک متد مجموعه‌ای از Studentها و teacher‌ها را ایجاد کرده و به لیستی از Person‌ها اضافه میکنم:
var student1=new Student(){Name="Iraj",Age=21};
var student1=new Student(){Name="Nima",Age=20};
var student1=new Student(){Name="Sara",Age=25};
var student1=new Student(){Name="Mina",Age=22};
var student1=new Student(){Name="Narges",Age=26};
var teacher1=new Student(){Name="Navaei",Age=45};
var teacher2=new Student(){Name="Imani",Age=50};
اما در نهایت اتفاقی که رخ میداد این بود که PersonId همه Student‌ها یکسان می‌شد ولی قضیه به همین جا ختم نشد؛ وقتی خط به خط برنامه را Debug و مقادیر را Watch می‌کردم، مشاهده می‌کردم که PersonId به درستی ایجاد می‌شود.
در فیزیک نوین اصلی هست به نام عدم قطعیت هایزنبرگ که به زبان ساده میتوان گفت نحوه رخداد یک اتفاق، با توجه به وجود یا عدم وجود یک مشاهده‌گر خارجی نتیجه‌ی متفاوتی خواهد داشت.
کم کم داشتم به وجود قانون مشاهده‌گر در برنامه نویسی هم ایمان پیدا میکردم که این کد فقط در صورتیکه آنرا مرحله به مرحله بررسی کنم جواب خواهد داد!
جالب اینکه زمانیکه personId  را نیز ایجاد میکردم، یک دستور برای دیدن خروجی نوشتم مثل این
 public class Person
    {      
        public Person()
        {
            personId= this.GetType().Name + (new Random()).Next(1, int.MaxValue);  
            Debug.Print(personId)       
        }
     }
در این حالت نیز دستورات درست عمل میکردند و personId متفاوتی ایجاد می‌شد!
قبل از خواندن ادامه مطلب شما هم کمی فکر کنید که مشکل کجاست؟
 این مشکل ربطی به قانون مشاهده‌گر و یا دیگر قوانین فیزیکی نداشت. بلکه بدلیل سرعت بالای ایجاد وهله ها(instance) از کلاسی‌های مطروحه (مثلا در زمانی کمتر از یک میلی ثانیه) زمانی در بازه یک کلاک CPU رخ می‌داد.
هر نوع ایجاد کندی (همچون نمایش مقادیر در خروجی) باعث می‌شود کلاک پردازنده نیز تغییر کند و عدد اتفاقی تولید شده فرق کند.
همچنین برای حل این مشکل میتوان از کلاس تولید کننده اعداد اتفاقی، شبیه زیر استفاده کرد:
using System;
using System.Threading;

public static class RandomProvider
{    
    private static int seed = Environment.TickCount;

    private static ThreadLocal<Random> randomWrapper = new ThreadLocal<Random>(() =>
        new Random(Interlocked.Increment(ref seed))
    );

    public static Random GetThreadRandom()
    {
        return randomWrapper.Value;
    }
}

مطالب
الگوهای طراحی API - مکانیزم جلوگیری از پردازش تکراری درخواست ها - Request Deduplication

در فضایی که همواره هیچ تضمینی وجود ندارد که درخواست ارسال شده‌ی به یک API، همواره مسیر خود را همانطور که انتظار می‌رود طی کرده و پاسخ مورد نظر را در اختیار ما قرار می‌دهد، بی‌شک تلاش مجدد برای پردازش درخواست مورد نظر، به دلیل خطاهای گذرا، یکی از راهکارهای مورد استفاده خواهد بود. تصور کنید قصد طراحی یک مجموعه API عمومی را دارید، به‌نحوی که مصرف کنندگان بدون نگرانی از ایجاد خرابی یا تغییرات ناخواسته، امکان تلاش مجدد در سناریوهای مختلف مشکل در ارتباط با سرور را داشته باشند. حتما توجه کنید که برخی از متدهای HTTP مانند GET، به اصطلاح Idempotent هستند و در طراحی آنها همواره باید این موضوع مدنظر قرار بگیرد و خروجی مشابهی برای درخواست‌های تکراری همانند، مهیا کنید.

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

نکته: Idempotence یکی از ویژگی های پایه‌ای عملیاتی در ریاضیات و علوم کامپیوتر است و فارغ از اینکه چندین بار اجرا شوند، نتیجه یکسانی را برای آرگومان‌های همسان، خروجی خواهند داد. این خصوصیت در کانتکست‌های مختلفی از جمله سیستم‌های پایگاه داده و وب سرویس‌ها قابل توجه می‌باشد.

Idempotent and Safe HTTP Methods

طبق HTTP RFC، متدهایی که پاسخ یکسانی را برای درخواست‌های همسان مهیا می‌کنند، به اصطلاح Idempotent هستند. همچنین متدهایی که باعث نشوند تغییری در وضعیت سیستم در سمت سرور ایجاد شود، به اصطلاح Safe در نظر گرفته خواهند شد. برای هر دو خصوصیت عنوان شده، سناریوهای استثناء و قابل بحثی وجود دارند؛ به‌عنوان مثال در مورد خصوصیت Safe بودن، درخواست GET ای را تصور کنید که یکسری لاگ آماری هم ثبت می‌کند یا عملیات بازنشانی کش را نیز انجام می‌دهد که در خیلی از موارد به عنوان یک قابلیت شناسایی خواهد شد. در این سناریوها و طبق RFC، باتوجه به اینکه هدف مصرف کننده، ایجاد Side-effect نبوده‌است، هیچ مسئولیتی در قبال این تغییرات نخواهد داشت. لیست زیر شامل متدهای مختلف HTTP به همراه دو خصوصیت ذکر شده می باشد:

HTTP MethodSafeIdempotent
GETYesYes
HEADYesYes
OPTIONSYesYes
TRACEYesYes
PUTNoYes
DELETENoYes
POSTNoNo
PATCHNoNo

Request Identifier as a Solution

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

در اینجا ابتدا مصرف کننده درخواستی با شناسه «۱» را برای پردازش به سرور ارسال می‌کند. سپس سرور که لیستی از شناسه‌های پردازش شده‌ی قبلی را نگهداری کرده‌است، تشخیص می‌دهد که این درخواست قبلا دریافت شده‌است یا خیر. پس از آن، عملیات درخواستی انجام شده و شناسه‌ی درخواست، به همراه پاسخ ارسالی به کلاینت، در فضایی ذخیره سازی می‌شود. در ادامه اگر همان درخواست مجددا به سمت سرور ارسال شود، بدون پردازش مجدد، پاسخ پردازش شده‌ی قبلی، به کلاینت تحویل داده می شود.

Implementation in .NET

ممکن است پیاده‌سازی‌های مختلفی را از این الگوی طراحی در اینترنت مشاهده کنید که به پیاده سازی یک Middleware بسنده کرده‌اند و صرفا بررسی این مورد که درخواست جاری قبلا دریافت شده‌است یا خیر را جواب می دهند که ناقص است. برای اینکه اطمینان حاصل کنیم درخواست مورد نظر دریافت و پردازش شده‌است، باید در منطق عملیات مورد نظر دست برده و تغییراتی را اعمال کنیم. برای این منظور فرض کنید در بستری هستیم که می توانیم از مزایای خصوصیات ACID دیتابیس رابطه‌ای مانند SQLite استفاده کنیم. ایده به این شکل است که شناسه درخواست دریافتی را در تراکنش مشترک با عملیات اصلی ذخیره کنیم و در صورت بروز هر گونه خطا در اصل عملیات، کل تغییرات برگشت خورده و کلاینت امکان تلاش مجدد با شناسه‌ی مورد نظر را داشته باشد. برای این منظور مدل زیر را در نظر بگیرید:

public class IdempotentId(string id, DateTime time)
{
    public string Id { get; private init; } = id;
    public DateTime Time { get; private init; } = time;
}

هدف از این موجودیت ثبت و نگهداری شناسه‌های درخواست‌های دریافتی می‌باشد. در ادامه واسط IIdempotencyStorage را برای مدیریت نحوه ذخیره سازی و پاکسازی شناسه‌های دریافتی خواهیم داشت:

public interface IIdempotencyStorage
{
    Task<bool> TryPersist(string idempotentId, CancellationToken cancellationToken);
    Task CleanupOutdated(CancellationToken cancellationToken);
    bool IsKnownException(Exception ex);
}

در اینجا متد TryPersist سعی می‌کند با شناسه دریافتی یک رکورد را ثبت کند و اگر تکراری باشد، خروجی false خواهد داشت. متد CleanupOutdated برای پاکسازی شناسه‌هایی که زمان مشخصی (مثلا ۱۲ ساعت) از دریافت آنها گذشته است، استفاده خواهد شد که توسط یک وظیفه‌ی زمان‌بندی شده می تواند اجرا شود؛ به این صورت، امکان استفاده‌ی مجدد از آن شناسه‌ها برای کلاینت‌ها مهیا خواهد شد. پیاده سازی واسط تعریف شده، به شکل زیر خواهد بود:

/// <summary>
/// To prevent from race-condition, this default implementation relies on primary key constraints.
/// </summary>
file sealed class IdempotencyStorage(
    AppDbContext dbContext,
    TimeProvider dateTime,
    ILogger<IdempotencyStorage> logger) : IIdempotencyStorage
{
    private const string ConstraintName = "PK_IdempotentId";

    public Task CleanupOutdated(CancellationToken cancellationToken)
    {
        throw new NotImplementedException(); //TODO: cleanup the outdated ids based on configurable duration
    }

    public bool IsKnownException(Exception ex)
    {
        return ex is UniqueConstraintException e && e.ConstraintName.Contains(ConstraintName);
    }

    // To tackle race-condition issue, the implementation relies on storage capabilities, such as primary constraint for given IdempotentId.
    public async Task<bool> TryPersist(string idempotentId, CancellationToken cancellationToken)
    {
        try
        {
            dbContext.Add(new IdempotentId(idempotentId, dateTime.GetUtcNow().UtcDateTime));
            await dbContext.SaveChangesAsync(cancellationToken);

            return true;
        }
        catch (UniqueConstraintException e) when (e.ConstraintName.Contains(ConstraintName))
        {
            logger.LogInformation(e, "The given idempotentId [{IdempotentId}] already exists in the storage.", idempotentId);
            return false;
        }
    }
}

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

در این پیاده سازی از کتابخانه MediatR استفاده می کنیم؛ در همین راستا برای مدیریت تراکنش ها به صورت زیر می توان TransactionBehavior را پیاده سازی کرد:

internal sealed class TransactionBehavior<TRequest, TResponse>(
    AppDbContext dbContext,
    ILogger<TransactionBehavior<TRequest, TResponse>> logger) :
    IPipelineBehavior<TRequest, TResponse>
    where TRequest : IBaseCommand
    where TResponse : IErrorOr
{
    public async Task<TResponse> Handle(
        TRequest command,
        RequestHandlerDelegate<TResponse> next,
        CancellationToken cancellationToken)
    {
        string commandName = typeof(TRequest).Name;
        await using var transaction = await dbContext.Database.BeginTransactionAsync(IsolationLevel.ReadCommitted, cancellationToken);

        TResponse? result;
        try
        {
            logger.LogInformation("Begin transaction {TransactionId} for handling {CommandName} ({@Command})", transaction.TransactionId, commandName, command);

            result = await next();
            if (result.IsError)
            {
                await transaction.RollbackAsync(cancellationToken);

                logger.LogInformation("Rollback transaction {TransactionId} for handling {CommandName} ({@Command}) due to failure result.", transaction.TransactionId, commandName, command);

                return result;
            }

            await transaction.CommitAsync(cancellationToken);

            logger.LogInformation("Commit transaction {TransactionId} for handling {CommandName} ({@Command})", transaction.TransactionId, commandName, command);
        }
        catch (Exception ex)
        {
            await transaction.RollbackAsync(cancellationToken);

            logger.LogError(ex, "An exception occured within transaction {TransactionId} for handling {CommandName} ({@Command})", transaction.TransactionId, commandName, command);

            throw;
        }

        return result;
    }
}

در اینجا مستقیما AppDbContext تزریق شده و با استفاده از خصوصیت Database آن، کار مدیریت تراکنش انجام شده‌است. همچنین باتوجه به اینکه برای مدیریت خطاها از کتابخانه‌ی ErrorOr استفاده می کنیم و خروجی همه‌ی Command های سیستم، حتما یک وهله از کلاس ErrorOr است که واسط IErrorOr را پیاده سازی کرده‌است، یک محدودیت روی تایپ جنریک اعمال کردیم که این رفتار، فقط برروی IBaseCommand ها اجرا شود. تعریف واسط IBaseCommand به شکل زیر می‌باشد:

 
/// <summary>
/// This is marker interface which is used as a constraint of behaviors.
/// </summary>
public interface IBaseCommand
{
}

public interface ICommand : IBaseCommand, IRequest<ErrorOr<Unit>>
{
}

public interface ICommand<T> : IBaseCommand, IRequest<ErrorOr<T>>
{
}

public interface ICommandHandler<in TCommand> : IRequestHandler<TCommand, ErrorOr<Unit>>
    where TCommand : ICommand
{
    Task<ErrorOr<Unit>> IRequestHandler<TCommand, ErrorOr<Unit>>.Handle(TCommand request, CancellationToken cancellationToken)
    {
        return Handle(request, cancellationToken);
    }

    new Task<ErrorOr<Unit>> Handle(TCommand command, CancellationToken cancellationToken);
}

public interface ICommandHandler<in TCommand, T> : IRequestHandler<TCommand, ErrorOr<T>>
    where TCommand : ICommand<T>
{
    Task<ErrorOr<T>> IRequestHandler<TCommand, ErrorOr<T>>.Handle(TCommand request, CancellationToken cancellationToken)
    {
        return Handle(request, cancellationToken);
    }

    new Task<ErrorOr<T>> Handle(TCommand command, CancellationToken cancellationToken);
}

در ادامه برای پیاده‌سازی IdempotencyBehavior و محدود کردن آن، واسط IIdempotentCommand را به شکل زیر خواهیم داشت:

/// <summary>
/// This is marker interface which is used as a constraint of behaviors.
/// </summary>
public interface IIdempotentCommand
{
    string IdempotentId { get; }
}

public abstract class IdempotentCommand : ICommand, IIdempotentCommand
{
    public string IdempotentId { get; init; } = string.Empty;
}

public abstract class IdempotentCommand<T> : ICommand<T>, IIdempotentCommand
{
    public string IdempotentId { get; init; } = string.Empty;
}

در اینجا یک پراپرتی، برای نگهداری شناسه‌ی درخواست دریافتی با نام IdempotentId در نظر گرفته شده‌است. این پراپرتی باید از طریق مقداری که از هدر درخواست HTTP دریافت می‌کنیم مقداردهی شود. به عنوان مثال برای ثبت کاربر جدید، به شکل زیر باید عمل کرد:

[HttpPost]
public async Task<ActionResult<long>> Register(
     [FromBody] RegisterUserCommand command,
     [FromIdempotencyToken] string idempotentId,
     CancellationToken cancellationToken)
{
     command.IdempotentId = idempotentId;
     var result = await sender.Send(command, cancellationToken);

     return result.ToActionResult();
}

در اینجا از همان Command به عنوان DTO ورودی استفاده شده‌است که وابسته به سطح Backward compatibility مورد نیاز، می توان از DTO مجزایی هم استفاده کرد. سپس از طریق FromIdempotencyToken سفارشی، شناسه‌ی درخواست، دریافت شده و بر روی command مورد نظر، تنظیم شده‌است.

رفتار سفارشی IdempotencyBehavior از ۲ بخش تشکیل شده‌است؛ در قسمت اول سعی می شود، قبل از اجرای هندلر مربوط به command مورد نظر، شناسه‌ی دریافتی را در storage تعبیه شده ثبت کند:

internal sealed class IdempotencyBehavior<TRequest, TResponse>(
    IIdempotencyStorage storage,
    ILogger<IdempotencyBehavior<TRequest, TResponse>> logger) :
    IPipelineBehavior<TRequest, TResponse>
    where TRequest : IIdempotentCommand
    where TResponse : IErrorOr
{
    public async Task<TResponse> Handle(
        TRequest command,
        RequestHandlerDelegate<TResponse> next,
        CancellationToken cancellationToken)
    {
        string commandName = typeof(TRequest).Name;

        if (string.IsNullOrWhiteSpace(command.IdempotentId))
        {
            logger.LogWarning(
                "The given command [{CommandName}] ({@Command}) marked as idempotent but has empty IdempotentId",
                commandName, command);
            return await next();
        }

        if (await storage.TryPersist(command.IdempotentId, cancellationToken) == false)
        {
            return (dynamic)Error.Conflict(
                $"The given command [{commandName}] with idempotent-id [{command.IdempotentId}] has already been received and processed.");
        }

        return await next();
    }
}

در اینجا IIdempotencyStorage تزریق شده و در صورتی که امکان ذخیره سازی وجود نداشته باشد، خطای Confilict که به‌خطای 409 ترجمه خواهد شد، برگشت داده می‌شود. در غیر این صورت ادامه‌ی عملیات اصلی باید اجرا شود. پس از آن اگر به هر دلیلی در زمان پردازش عملیات اصلی،‌ درخواست همزمانی با همان شناسه، توسط سرور دریافت شده و پردازش شود، عملیات جاری با خطای UniqueConstaint برروی PK_IdempotentId در زمان نهایی سازی تراکنش جاری، مواجه خواهد شد. برای این منظور بخش دوم این رفتار به شکل زیر خواهد بود:

internal sealed class IdempotencyExceptionBehavior<TRequest, TResponse>(IIdempotencyStorage storage) :
    IPipelineBehavior<TRequest, TResponse>
    where TRequest : IIdempotentCommand
    where TResponse : IErrorOr
{
    public async Task<TResponse> Handle(
        TRequest command,
        RequestHandlerDelegate<TResponse> next,
        CancellationToken cancellationToken)
    {
        if (string.IsNullOrWhiteSpace(command.IdempotentId)) return await next();

        string commandName = typeof(TRequest).Name;
        try
        {
            return await next();
        }
        catch (Exception ex) when (storage.IsKnownException(ex))
        {
            return (dynamic)Error.Conflict(
                $"The given command [{commandName}] with idempotent-id [{command.IdempotentId}] has already been received and processed.");
        }
    }
}

در اینجا عملیات اصلی در بدنه try اجرا شده و در صورت بروز خطایی مرتبط با Idempotency، خروجی Confilict برگشت داده خواهد شد. باید توجه داشت که نحوه ثبت رفتارهای تعریف شده تا اینجا باید به ترتیب زیر انجام شود:

services.AddMediatR(config =>
{
   config.RegisterServicesFromAssemblyContaining(typeof(DependencyInjection));

   // maintaining the order of below behaviors is crucial.
   config.AddOpenBehavior(typeof(LoggingBehavior<,>));
   config.AddOpenBehavior(typeof(IdempotencyExceptionBehavior<,>));
   config.AddOpenBehavior(typeof(TransactionBehavior<,>));
   config.AddOpenBehavior(typeof(IdempotencyBehavior<,>));
});

به این ترتیب بدنه اصلی هندلرهای موجود در سیستم هیچ تغییری نخواهند داشت و به صورت ضمنی و انتخابی، امکان تعیین command هایی که نیاز است به صورت Idempotent اجرا شوند را خواهیم داشت.

References

https://www.mscharhag.com/p/rest-api-design

https://www.manning.com/books/api-design-patterns

https://codeopinion.com/idempotent-commands/

مطالب
یکی کردن اسمبلی‌های یک پروژه‌ی WPF
فرض کنید پروژه‌ی WPF شما از چندین پروژه‌ی ‍Class library و اسمبلی‌های جانبی دیگر، تشکیل شده‌است. اکنون نیاز است جهت سهولت توزیع آن، تمام این فایل‌ها را با هم یکی کرده و تبدیل به یک فایل EXE نهایی کنیم. مایکروسافت ابزاری را به نام ILMerge، برای یک چنین کارهایی تدارک دیده‌است؛ اما این برنامه با WPF سازگار نیست. در ادامه قصد داریم اسمبلی‌های جانبی را تبدیل به منابع مدفون شده در فایل EXE برنامه کرده و سپس آن‌ها را در اولین بار اجرای برنامه، به صورت خودکار بارگذاری و در برنامه مورد استفاده قرار دهیم.

یک مثال جهت بازتولید کدهای این مطلب
الف) یک پروژه‌ی WPF جدید را به نام MergeAssembliesIntoWPF ایجاد کنید.
ب) یک پروژه‌ی Class library جدید را به نام MergeAssembliesIntoWPF.ViewModels به این Solution اضافه کنید. از آن برای تعریف ViewModelهای برنامه استفاده خواهیم کرد.
برای نمونه کلاس ذیل را به آن اضافه کنید:
namespace MergeAssembliesIntoWPF.ViewModels
{
    public class ViewModel1
    {
        public string Data { set; get; }

        public ViewModel1()
        {
            Data = "Test";
        }
    }
}
ج) یک پروژه‌ی WPF User control library را نیز به نام MergeAssembliesIntoWPF.Shell به این Solution اضافه کنید. از آن برای تعریف Viewهای برنامه کمک خواهیم گرفت.
به این پروژه ارجاعی را به اسمبلی قسمت (ب) اضافه نموده و برای نمونه User control ذیل را به نام View1.xaml به آن اضافه نمائید:
<UserControl x:Class="MergeAssembliesIntoWPF.Shell.View1"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             mc:Ignorable="d" 
             xmlns:VM="clr-namespace:MergeAssembliesIntoWPF.ViewModels;assembly=MergeAssembliesIntoWPF.ViewModels"
             d:DesignHeight="300" d:DesignWidth="300">
    <UserControl.Resources>
        <VM:ViewModel1 x:Key="ViewModel1" />
    </UserControl.Resources>
    <Grid DataContext="{Binding Source={StaticResource ViewModel1}}">
        <TextBlock Text="{Binding Data}" />
    </Grid>
</UserControl>
در پروژه اصلی Solution (قسمت الف)، ارجاعاتی را به دو اسمبلی قسمت‌های ب و ج اضافه کنید. سپس MainWindow.xaml آن‌را به نحو ذیل تغییر داده و برنامه را اجرا کنید:
<Window x:Class="MergeAssembliesIntoWPF.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:V="clr-namespace:MergeAssembliesIntoWPF.Shell;assembly=MergeAssembliesIntoWPF.Shell"
        Title="MainWindow" Height="350" Width="525">
    <Window.Resources>
        <V:View1 x:Key="View1" />
    </Window.Resources>
    <Grid>
        <V:View1 />
    </Grid>
</Window>
تا اینجا باید متن Test در پنجره اصلی برنامه ظاهر شود.


ب) مدفون کردن خودکار اسمبلی‌های جانبی برنامه در فایل EXE آن
فایل csproj پروژه اصلی را خارج از VS.NET باز کنید. در انتهای آن سطر ذیل قابل مشاهده است:
 <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
پس از این سطر، چند سطر ذیل را اضافه کنید:
  <Target Name="AfterResolveReferences">
    <ItemGroup>
      <EmbeddedResource Include="@(ReferenceCopyLocalPaths)" Condition="'%(ReferenceCopyLocalPaths.Extension)' == '.dll'">
        <LogicalName>%(ReferenceCopyLocalPaths.DestinationSubDirectory)%(ReferenceCopyLocalPaths.Filename)%(ReferenceCopyLocalPaths.Extension)</LogicalName>
      </EmbeddedResource>
    </ItemGroup>
  </Target>
این task جدید MSBuild سبب خواهد شد تا با هر بار Build برنامه، اسمبلی‌هایی که در ارجاعات برنامه دارای خاصیت Copy local مساوی true هستند، به صورت خودکار به صورت یک resource جدید در فایل exe برنامه مدفون شوند. عموما ارجاعاتی که دستی اضافه می‌شوند، مانند دو اسمبلی یاد شده در ابتدای بحث، دارای خاصیت Copy local=true نیز هستند.
پس از این تغییر نیاز است یکبار پروژه را بسته و مجددا باز کنید. اکنون پروژه را build کنید و جهت اطمینان بیشتر آن‌را برای مثال توسط ILSpy مورد بررسی قرار دهید:


همانطور که مشاهده می‌کنید، دو اسمبلی مورد استفاده در برنامه به صورت خودکار در قسمت منابع فایل EXE مدفون شده‌اند.
اگر به مسیر LogicalName تنظیمات فوق دقت کنید، DestinationSubDirectory نیز ذکر شده‌است. علت این است که بسیاری از اسمبلی‌های بومی سازی شده WPF با نام‌هایی یکسان اما در پوشه‌هایی مانند fa، fr و امثال آن ذخیره می‌شوند. به همین جهت نیاز است بین این‌ها تمایز قائل شد.


ج) بارگذاری خودکار اسمبلی‌ها در AppDomain برنامه

تا اینجا اسمبلی‌های جانبی را در فایل EXE مدفون کرده‌ایم. اکنون نوبت به بارگذاری آن‌ها در AppDomain برنامه است. برای اینکار نیاز است تا روال رخدادگردان AppDomain.CurrentDomain.AssemblyResolve را تحت نظر قرار داده و اسمبلی‌هایی را که برنامه درخواست می‌کند، در همینجا از منابع خوانده و به AppDomain اضافه کرد.
انجام اینکار در برنامه‌های WinForms ساده‌است. فقط کافی است به متد Program.Main برنامه مراجعه کرده و تعریف یاد شده را به ابتدای متد Main اضافه کرد. اما در WPF هرچند فایل App.xaml.cs به نظر نقطه‌ی آغازین برنامه است، اما در واقع اینطور نیست. برای نمونه، پوشه‌ی obj\Debug برنامه را گشوده و فایل App.g.i.cs آن‌را بررسی کنید. در اینجا می‌توانید همان رویه شبیه به برنامه‌های WinForm را در متد Program.Main آن، مشاهده کنید. بنابراین نیاز است کنترل این مساله را راسا در دست بگیریم:
using System;
using System.Globalization;
using System.Reflection;

namespace MergeAssembliesIntoWPF
{
    public class Program
    {
        [STAThreadAttribute]
        public static void Main()
        {
            AppDomain.CurrentDomain.AssemblyResolve += OnResolveAssembly;
            App.Main();
        }

        private static Assembly OnResolveAssembly(object sender, ResolveEventArgs args)
        {
            var executingAssembly = Assembly.GetExecutingAssembly();
            var assemblyName = new AssemblyName(args.Name);

            var path = assemblyName.Name + ".dll";
            if (assemblyName.CultureInfo.Equals(CultureInfo.InvariantCulture) == false)
            {
                path = String.Format(@"{0}\{1}", assemblyName.CultureInfo, path);
            }

            using (var stream = executingAssembly.GetManifestResourceStream(path))
            {
                if (stream == null)
                    return null;

                var assemblyRawBytes = new byte[stream.Length];
                stream.Read(assemblyRawBytes, 0, assemblyRawBytes.Length);
                return Assembly.Load(assemblyRawBytes);
            }
        }
    }
}
کلاس Program را با تعاریف فوق به پروژه خود اضافه نمائید. در اینجا Program.Main مورد نیاز خود را تدارک دیده‌ایم. کار آن مدیریت روال رخدادگردان AppDomain.CurrentDomain.AssemblyResolve برنامه پیش از شروع به هر کاری است. در روال رخداد گردان OnResolveAssembly، برنامه اعلام می‌کند که به چه اسمبلی خاصی نیاز دارد. ما آن‌را از قسمت منابع خوانده و سپس توسط متد Assembly.Load آن‌را در AppDomain برنامه بارگذاری می‌کنیم.
پس از اینکه کلاس فوق را اضافه کردید، نیاز است کلاس Program اضافه شده را به عنوان Startup object برنامه نیز معرفی کنید:

انجام اینکار ضروری است؛ در غیراینصورت با متد Main موجود در فایل App.g.i.cs تداخل می‌کند.
اکنون برای آزمایش برنامه، یکبار آن‌را Build کرده و بجز فایل Exe، مابقی فایل‌های موجود در پوشه‌ی bin را حذف کنید. سپس برنامه را خارج از VS.NET اجرا کنید. کار می‌کند!
MergeAssembliesIntoWPF.zip
 
پاسخ به بازخورد‌های پروژه‌ها
تراز یک ستون بر اساس مقدار ستون دیگر
باید از نکته مطلب «ایجاد قالب‌های سفارشی ستون‌ها و فرمت شرطی اطلاعات در PdfReport » استفاده کنید. مثلا:
column.ColumnItemsTemplate(template =>
                     {
                         template.TextBlock();
                         template.DisplayFormatFormula(obj => obj == null ? string.Empty : string.Format("{0:n0}", Math.Abs((long)obj)));
                         template.ConditionalFormatFormula(list =>
                         {
                             var currentRowCredit = (long)list.GetValueOf<Payment>(x => x.Credit);
                             if (currentRowCredit > 0)
                             {
                                 return new CellBasicProperties
                                 {
                                     FontColor = new BaseColor(System.Drawing.Color.Brown.ToArgb()),
                                     HorizontalAlignment = HorizontalAlignment.Right
                                 };
                             }
                             return new CellBasicProperties { PdfFontStyle = DocumentFontStyle.Normal };
                         });
                     });
مطالب
کوئری هایی با قابلیت استفاده ی مجدد
با توجه به اصل Dry تا می‌توان باید از نوشتن کدهای تکراری خودداری کرد و کد‌ها را تا جایی که ممکن است به قسمت هایی با قابلیت استفاده‌ی مجدد تبدیل کرد. حین کار کردن با ORM‌های معروف مثل NHibernate و EntityFramework زمان زیادی نوشتن کوئری‌ها جهت واکشی داده‌ها از دیتابیس صرف می‌شود. اگر بتوان کوئری هایی با قابلیت استفاده‌ی مجدد نوشت علاوه بر کاهش زمان توسعه قابلیت هایی قدرتمندی مانند زنجیر کردن کوئری‌ها به دنبال هم به دست می‌آید. 
با یک مثال نحوه‌ی نوشتن و مزایای کوئری با قابلیت استفاده‌ی مجدد را بررسی می‌کنیم : 
برای مثال دو جدول شهر‌ها و دانش آموزان را درنظر بگیرید:
namespace ReUsableQueries.Model
{
    public class Student
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public string LastName { get; set; }
        public int Age { get; set; }
    [ForeignKey("BornInCityId")]
        public virtual City BornInCity { get; set; }
        public int BornInCityId { get; set; }
    }

    public class City
    {
        public int Id { get; set; }
        public string Name { get; set; }

        public virtual ICollection<Student> Students { get; set; }
    }
}
در ادامه این کلاس‌ها را در معرض دید EF Code first قرار داده:
using System.Data.Entity;
using ReUsableQueries.Model;

namespace ReUsableQueries.DAL
{
    public class MyContext : DbContext
    {
        public DbSet<City> Cities { get; set; }
        public DbSet<Student> Students { get; set; }
    }
}

و همچنین تعدادی رکورد آغازین را نیز به جداول مرتبط اضافه می‌کنیم:
    public class Configuration : DbMigrationsConfiguration<MyContext>
    {
        public Configuration()
        {
            AutomaticMigrationsEnabled = true;
            AutomaticMigrationDataLossAllowed = true;
        }
        protected override void Seed(MyContext context)
        {
            var city1 = new City { Name = "city-1" };
            var city2 = new City { Name = "city-2" };
            context.Cities.Add(city1);
            context.Cities.Add(city2);
            var student1 = new Student() {Name = "Shaahin",LastName = "Kiassat",Age=22,BornInCity = city1};
            var student2 = new Student() { Name = "Mehdi", LastName = "Farzad", Age = 31, BornInCity = city1 };
            var student3 = new Student() { Name = "James", LastName = "Hetfield", Age = 49, BornInCity = city2 };
            context.Students.Add(student1);
            context.Students.Add(student2);
            context.Students.Add(student3);
            base.Seed(context);
        }
    }
فرض کنید قرار است یک کوئری نوشته شود که در جدول دانش آموزان بر اساس نام ، نام خانوادگی و سن جستجو کند : 
         var context = new MyContext();
          var query= context.Students.Where(x => x.Name.Contains(name)).Where(x => x.LastName.Contains(lastName)).Where(
              x => x.Age == age);
احتمالا هنوز کسانی هستند که فکر می‌کنند کوئری‌های LINQ همان لحظه که تعریف می‌شوند اجرا می‌شوند اما اینگونه نیست . در واقع این کوئری فقط یک Expression از رکورد‌های جستجو شده است و تا زمانی که متد ToList یا ToArray روی آن اجرا نشود هیچ داده ای برگردانده نمی‌شود.
در یک برنامه‌ی واقعی داده‌های باید به صورت صفحه بندی شده و مرتب شده برگردانده شود پس کوئری به این صورت خواهد بود : 
        var query= context.Students.Where(x => x.Name.Contains(name)).Where(x => x.LastName.Contains(lastName)).Where(
              x => x.Age == age).OrderBy(x=>x.LastName).Skip(skip).Take(take);
ممکن است بخواهیم در متد دیگری در لیست دانش آموزان بر اساس نام ، نام خانوادگی ، سن و شهر جستجو کنیم و سپس خروجی را اینبار بر اساس سن مرتب کرده و صفحه بندی نکنیم:
          var query = context.Students.Where(x => x.Name.Contains(name)).Where(x => x.LastName.Contains(lastName)).Where
              (
                  x => x.Age == age).Where(x => x.BornInCityId == 1).OrderBy(x => x.Age);
همانطور که می‌بینید قسمت هایی از این کوئری با کوئری هایی که قبلا نوشتیم یکی است ، همچنین حتی ممکن است در قسمت دیگری از برنامه نتیجه‌ی همین کوئری را به صورت صفحه بندی شده لازم داشته باشیم.
اکنون نوشتن این کوئری‌ها میان کد های Business Logic باعث شده هیچ استفاده‌ی مجددی نتوانیم از این کوئری‌ها داشته باشیم. حال بررسی می‌کنیم که چگونه می‌توان کوئری هایی با قابلیت استفاده‌ی مجدد نوشت : 
namespace ReUsableQueries.Quries
{
    public static class StudentQueryExtension
    {
        public static IQueryable<Student> FindStudentsByName(this IQueryable<Student> students,string name)
        {
            return students.Where(x => x.Name.Contains(name));
        }
        public static IQueryable<Student> FindStudentsByLastName(this IQueryable<Student> students, string lastName)
        {
            return students.Where(x => x.LastName.Contains(lastName));
        }
        public static IQueryable<Student> SkipAndTake(this IQueryable<Student> students, int skip , int take)
        {
            return students.Skip(skip).Take(take);
        }
        public static IQueryable<Student> OrderByAge(this IQueryable<Student> students)
        {
            return students.OrderBy(x=>x.Age);
        }
    }
}
همان طور که مشاهده می‌کنید به کمک متد‌های الحاقی برای شیء IQueryable<Student> چند کوئری نوشته ایم . اکنون در محل استفاده از کوئری‌ها می‌توان این کوئری‌ها را به راحتی به هم زنجیر کرد. همچنین اگر روزی قرار شد منطق یکی از کوئری‌ها عوض شود با عوض کردن آن در یک قسمت برنامه همه جا اعمال می‌شود.  نحوه‌ی استفاده از این متدهای الحاقی به این صورت خواهد بود : 
     var query = context.Students.FindStudentsByName(name).FindStudentsByLastName(lastName).SkipAndTake(skip,take);          
فرض کنید قرار است یک سیستم جستجوی پیشرفته به برنامه اضافه شود که بر اساس شرط‌های مختلف باید یک شرط در کوئری اعمال شود یا نشود ، به کمک این طراحی جدید به راحتی می‌توان بر اساس شرط‌های مختلف یک کوئری را اعمال کرد یا نکرد : 
        var query = context.Students.AsQueryable();
          if (searchByName)
          {
              query= query.FindStudentsByName(name);
          }
          if (orderByAge)
          {
              query = query.OrderByAge();
          }
          if (paging)
          {
             query =  query.SkipAndTake(skip, take);
          }
          return query.ToList();
همچنین این کوئری‌ها وابسته به ORM خاصی نیستند البته این نکته هم مد نظر است که LINQ Provider بعضی ORM‌ها ممکن است بعضی کوئری‌ها را پشتیبانی نکند.

مطالب
Pipeها در Angular 2 – قسمت سوم – Pipeهای Pure و Impure
 در قسمت قبل بیان شد که Angular برای اعمال Pipe بر روی Template expressions باید تمامی رخدادهای برنامه را تحت نظر قرار داده و با مشاهده‌ی هر تغییری بر روی عبارت ورودی Pipe، فراخوانی Pipe را آغاز کند. از جمله این رخدادها می‌توان به رخدادهای mouse move، timer tick، server response و فشرده شدن کلیدهای ماوس و یا کیبورد اشاره کرد. واضح است که بررسی تغییرات عبارت در این همه رخداد می‌تواند مخرب باشد و بر روی کارآئی (Performance) تاثیر منفی خواهد گذاشت. اما Angular برای حل این مشکل و همچنین هنگام مشاهده سریع تغییرات هنگام استفاده از Pipeها، الگوریتم‌های سریع و ساده‌ای در نظر گرفته است که آن‌ها را در این بخش مورد برسی قرار خواهیم داد.


Pipeهای Pure و Impure

Pipeها کلا در دو دسته‌ی Pure و Impure قرار می‌گیرند. هنگام ساخت Pipe سفارشی در صورتیکه نوع Pipe مشخص نشود، به صورت پیش فرض از نوع Pure خواهد بود. برای تعریف Pipeهایی از نوع Impure کافی است در متادیتای Pipe@، پرچم Pure را به مقدار false تنظیم کنید.
@Pipe({ name: 'impurePipe', pure: false })
تفاوت این Pipeها در زمان فراخوانی دوباره آنها است.


Pure Pipe

این نوع Pipeها تنها زمانی فراخوانی مجدد می‌شوند که یک تغییر محض (Pure Change) بر روی عبارت ورودی آنها رخ دهد. هر نوع تغییری بر روی عبارات ورودی از جنس string ، number ، Boolean ، Symbol و عبارات اولیه، یا هرنوع تغییری در ارجاع یک شیء مانند  Date ، Array ، Function و Object نیز تغییر محض محسوب می‌شود. به عنوان مثال هیچکدام از تغییرات زیر یک تغییر محض محسوب نمی‌شوند:
numbers.push(10);
obj.name = ‘javad’;
زیرا با اضافه شدن عنصری به یک آرایه یا تغییر خصوصیتی از یک شیء، باعث تغییری در ارجاع آنها نمی‌شود و همانطور که اشاره شد، در عبارات از نوع آرایه و Object، فقط تغییر در ارجاع آن‌ها یک تغییر محض محسوب می‌شود.
حالا می‌توان به این نتیجه رسید که اضافه شدن مقداری به آرایه یا به‌روزرسانی یک property از object، باعث فراخوانی مجدد Pure Pipe نخواهد نشد. شاید این نوع از Pipeها محدود کننده باشند، اما بسیار سریع هستند (برسی تغییر در ارجاع یک شیء بسیار سریعتر از بررسی کامل یک شیء، صورت می‌گیرد).


Impure Pipe

این نوع Pipeها در اغلب رخدادهای کامپوننت از جمله فشره شدن کلید یا حرکت ماوس و رخدادهای دیگر فراخوانی مجدد می‌شوند. با در نظر گرفتن این نگرانی، هنگام پیاده سازی این نوع Pipeها باید مراقب بود؛ زیرا این نوع Pipeها با اجرای طولانی خود می‌توانند رابط کاربری شما را نابود کنند. برای درک کامل تفاوت این دو نوع از Pipeها مثالی را دنبال می‌کنیم.

مثال: قصد داریم Pipe سفارشی را پیاده سازی کنیم تا آرایه‌ای از اعداد را دریافت و فقط اعداد زوج را فیلتر کرده و نمایش دهد.
برای این منظور یک فایل جدید را با نام even-numbers.pipe.ts با محتویات زیر ایجاد می‌کنیم: 
import { Pipe, PipeTransform } from '@angular/core';

@Pipe({
  name: 'evenNumbers'
})
export class EvenNumbersPipe implements PipeTransform {
  transform(numbers: Array<number>): Array<number> {
    var x=numbers.filter(r => r % 2 == 0);
    return x;
  }
}
همانطور که مشخص است این Pipe در متد transform، آرایه‌ای از اعداد را دریافت کرده و فقط اعداد زوج را بازگشت می‌دهد. حالا باید Pipe تعریف شده خود را در AppModule در قسمت declares تعریف کنیم.
// . . .
import { EvenNumbersPipe } from './pipes/even-numbers.pipe'
@NgModule({
  declarations: [
    . . .
    EvenNumbersPipe
  ],
 . . .
})
export class AppModule { }

سپس در کامپوننت مورد نظر خود متغیری را به نام numbers از نوع آرایه، با مقدار اولیه‌ی اعداد از یک تا ده، تعریف می‌کنیم:
numbers: Array<number> = [1,2,3,4,5,6,7,8,9,10];
برای نمایش این اعداد در رابط کاربری تگ‌های زیر را به قالب کامپوننت خود اضافه می‌کنیم:
<h1>All numbers</h1>
<span *ngFor="let number of numbers">
  {{number}}
</span>
همچنین با استفاده از تگ‌های زیر یک input برای اضافه کردن مقدار جدید به آرایه درنظر می‌گیریم:
<p>
  <input type="text" #number />
  <input type="button" (click)="numbers.push(number.value)" value="Add number"/>
</p>

تگ‌های زیر را نیز برای اعمال Pipe نمایش اعداد زوج، به قالب کامپوننت اضافه می‌کنیم:
<h1>even numbers</h1>
<span *ngFor="let number of numbers | evenNumbers">
  {{number}}
</span>
بعد از اجرای برنامه، یک عدد جدید زوج را به آرایه اضافه کنید. متوجه خواهید شد با اینکه لیست اعداد در قسمت All numbers به‌روز می‌شوند، ولی Pipe، متوجه تغییری بر روی آرایه نشده‌است و همچنان اعداد قبلی را نمایش می‌دهد. دلیل این امر همانطور که قبلا نیز اشاره شد، بخاطر Pure بودن Pipe و عدم فراخوانی مجدد این نوع Pipe‌ها در زمان اضافه شدن مقداری به آرایه یا تغییری در خصوصیت یک شیء است.

برای حل این مشکل، هنگام اضافه شدن عدد به آرایه، اگر ارجاع آرایه را تغییر دهیم، Pure Pipe متوجه تغییرات خواهد شد و لیست اعداد را به‌روز رسانی می‌کند (تغییر در ارجاع یک شیء، از نوع تغییرات محض است):
<p>
  <input type="text" #number />
  <input type="button" (click)="numbers = numbers.concat(number.value)" value="Add number"/>
</p>
با تغییر نحوه اضافه شدن عنصر به آرایه به شکل بالا خواهیم دید که با افزودن اعداد جدید، لیست اعداد زوج نیز در لحظه اعمال خواهند شد. این راه‌حل همیشه کارآمد نخواهد بود. همیشه تشخیص محل اضافه شدن عنصر به آرایه در برنامه کار ساده‌ای نیست تا در آنجا ارجاع آرایه را نیز تغییر دهیم. راه‌حل، استفاده از Impure Pipe است. کافی است متادیتای Pipe@ را هنگام تعریف به شکل زیر تغییر دهید:
@Pipe({
  name: 'evenNumbers',
  pure: false
})
export class EvenNumbersPipe implements PipeTransform {
   //…
}

کسانیکه با Angular 1.x آشنایی دارند، شاید اکنون متوجه این شده‌اند که چرا در Angular به مشابه Angular 1.x دیگر خبری ازfilter و orderBy نیست. با توجه به اینکه این دو فیلتر فقط با عبارات از نوع object سروکار داشتند، پیاده‌سازی آنها فقط با Impure Pipeها امکان پذیر بود و با توجه به اینکه Impure Pipeها در هر بار چرخه تغییرات کامپوننت اجرا خواهند شد، باعث کندی در صفحات خواهند شد. 
مطالب
استفاده‌ی همزمان از آپدیت پنل ASP.Net و پلاگین‌های جی‌کوئری

مشکل: زمانیکه یک AsyncPostback در آپدیت پنلASP.Net Ajax رخ دهد، پس از پایان کار، پلاگین جی‌کوئری که در حال استفاده از آن بودید و در هنگام بارگذاری اولیه صفحه بسیار خوب کار می‌کرد، اکنون از کار افتاده است و دیگر جواب نمی‌دهد.

قبل از شروع، نیاز به یک سری پیش زمینه هست (شاید بر اساس روش استفاده شما از آن پلاگین جی‌کوئری، مشکل را حل کنند):
الف) رفع تداخل جی‌کوئری با سایر کتابخانه‌های مشابه.
ب) آشنایی با jQuery Live جهت بایند رخ‌داد‌ها به عناصری که بعدا به صفحه اضافه خواهند شد.
ج) تزریق اسکریپت به صفحه در حین یک AsyncPostback رخ داده در آپدیت پنل

علت بروز مشکل:
علت رخ‌دادن این مشکل (علاوه بر قسمت الف ذکر شده)، عدم فراخوانی document.ready تعریف شده، جهت بایند مجدد پلاگین jQuery مورد استفاده شما پس از هر AsyncPostback رخ داده در آپدیت پنل ASP.Net Ajax است. راه حل استاندارد جی‌کوئری هم همان مورد (ب) فوق می‌باشد، اما ممکن است جهت استفاده از آن نیاز به بازنویسی یک پلاگین موجود خاص وجود داشته باشد، که آنچنان مقرون به صرفه نیست.

مثالی جهت مشاهده‌ی این مشکل در عمل:
می‌خواهیم افزونه‌ی Colorize - jQuery Table را به یک گرید ویوو ASP.Net قرار گرفته درون یک آپدیت پنل اعمال کنیم.

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="UpdatePanelTest.aspx.cs"
Inherits="TestJQueryAjax.UpdatePanelTest" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title></title>
</head>
<body>
<form id="form1" runat="server">
<asp:ScriptManager ID="sm" runat="server">
<Scripts>
<asp:ScriptReference Path="~/js/jquery.js" />
<asp:ScriptReference Path="~/js/jquery.colorize-1.6.0.js" />
</Scripts>
</asp:ScriptManager>
<asp:UpdatePanel ID="uppnl" runat="server">
<ContentTemplate>
<asp:GridView ID="GridView1" runat="server" AllowPaging="True"
OnPageIndexChanging="GridView1_PageIndexChanging">
</asp:GridView>
</ContentTemplate>
</asp:UpdatePanel>
</form>

<script type="text/javascript">
$(document).ready(function() {
$('#<%=GridView1.ClientID %>').colorize();
});
</script>
</body>
</html>

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

namespace TestJQueryAjax
{
public partial class UpdatePanelTest : System.Web.UI.Page
{
void BindTo()
{
List<string> rows = new List<string>();
for (int i = 0; i < 1000; i++)
rows.Add(string.Format("row{0}", i));

GridView1.DataSource = rows;
GridView1.DataBind();
}

protected void Page_Load(object sender, EventArgs e)
{
if (!Page.IsPostBack)
{
BindTo();
}
}

protected void GridView1_PageIndexChanging(object sender, GridViewPageEventArgs e)
{
GridView1.PageIndex = e.NewPageIndex;
BindTo();
}
}
}
مثال بسیار ساده‌ای است جهت اعمال این افرونه به یک گریدویو و مشاهده کار کردن این افزونه در هنگام بارگذاری اولیه صفحه و سپس از کار افتادن آن پس از مشاهده صفحه دوم گرید. در این مثال از نکته "اسکریپت‌های خود را یکی کنید" استفاده شده است.

راه حل:
از ویژگی‌های ذاتی ASP.Net Ajax باید کمک گرفت برای مثال:

<script type="text/javascript">
$(document).ready(function() {
$('#<%=GridView1.ClientID %>').colorize();
});

function pageLoad(sender, args) {
if (args.get_isPartialLoad()) {
$('#<%=GridView1.ClientID %>').colorize();
}
}
</script>

متد استاندارد pageLoad به صورت خودکار پس از هر AsyncPostback رخ داده در آپدیت پنل ASP.Net Ajax فراخوانی می‌شود (و همچنین پس از پایان پردازش و بارگذاری اولیه DOM صفحه). در این متد بررسی می‌کنیم که آیا یک partial postback رخ داده است؟ اگر بله، مجددا عملیات بایند افزونه به گرید را انجام می‌دهیم و مشکل برطرف خواهد شد.

برای مطالعه بیشتر

مطالب
شروع به کار با EF Core 1.0 - قسمت 1 - برپایی تنظیمات اولیه
در ادامه‌ی سری «ارتقاء به ASP.NET Core 1.0» اگر بخواهیم مباحث اعتبارسنجی کاربران و ASP.NET Identity مخصوص آن‌را بررسی کنیم، نیاز است ابتدا مباحث Entity framework Core 1.0 را بررسی کنیم. به همین جهت در طی چند قسمت مباحث پایه‌ای کار با EF Core 1.0 را در ASP.NET Core 1.0، بررسی خواهیم کرد. بنابراین پیشنیاز ضروری این مباحث، مطالعه‌ی سری «ارتقاء به ASP.NET Core 1.0» است و در آن از مباحثی مانند چگونگی کار با فایل‌های کانفیگ جدید، تزریق وابستگی‌ها و سرویس‌ها، فعال سازی سرویس Logging، فعال سازی صفحات مخصوص توسعه دهنده‌ها و ... در ASP.NET Core 1.0 استفاده خواهد شد.


EF Core چیست؟

EF Core یک ORM یا object-relational mapper چندسکویی است که امکان کار با بانک‌های اطلاعاتی مختلف را از طریق اشیاء دات نتی میسر می‌کند. توسط آن قسمت عمده‌ی کدهای مستقیم کار با بانک‌های اطلاعاتی حذف شده و تبدیل به کدهای دات نتی می‌شوند. مزیت این لایه‌ی Abstraction اضافی (لایه‌ای بر روی کدهای مستقیم لایه ADO.NET زیرین)، امکان تعویض بانک اطلاعاتی مورد استفاده، تنها با تغییر کدهای آغازین برنامه‌است؛ بدون نیاز به تغییری در سایر قسمت‌های برنامه. همچنین کار با اشیاء دات نتی و LINQ، مزایایی مانند تحت نظر قرار گرفتن کدها توسط کامپایلر و برخورداری از ابزارهای Refactoring پیشرفته را میسر می‌کنند. به علاوه SQL خودکار تولیدی توسط آن نیز همواره پارامتری بوده و مشکلات حملات تزریق SQL در این حالت تقریبا به صفر می‌رسند (اگر مستقیما SQL نویسی نکنید و صرفا از LINQ استفاده کنید). مزیت دیگر همواره پارامتری بودن کوئری‌ها، رفتار بسیاری از بانک‌های اطلاعاتی با آن‌ها همانند رویه‌های ذخیره شده است که به عدم تولید Query plan‌های مجزایی به ازای هر کوئری رسیده منجر می‌شود که در نهایت سبب بالا رفتن سرعت اجرای کوئری‌ها و مصرف حافظه‌ی کمتری در سمت سرور بانک اطلاعاتی می‌گردد.


تفاوت EF Core با نگارش‌های دیگر Entity framework در چیست؟

سورس باز بودن
EF از نگارش‌های آخر آن بود که سورس باز شد؛ اما EF Core از زمان نگارش‌های پیش نمایش آن به صورت سورس باز در GitHub قابل دسترسی است.

چند سکویی بودن
EF Core برخلاف EF 6.x (آخرین نگارش مبتنی بر Full Framework آن)، نه تنها چندسکویی است و قابلیت اجرای بر روی Mac و لینوکس را نیز دارا است، به علاوه امکان استفاده‌ی از آن در انواع و اقسام برنامه‌های دات نتی مانند UWP یا Universal Windows Platform و Windows phone که پیشتر با EF 6.x میسر نبود، وجود دارد. لیست این نوع سکوها و برنامه‌های مختلف به شرح زیر است:
 • All .NET application (Console, ASP.NET 4, WinForms, WPF)
 • Mac and Linux applications (Mono)
 • UWP (Universal Windows Platform)
 • ASP.NET Core applications
 • Can use EF Core in Windows phone and Windows store app

افزایش تعداد بانک‌های اطلاعاتی پشتیبانی شده
در EF Full یا EF 6.x، هدف اصلی، تنها کار با بانک‌های اطلاعاتی رابطه‌‌ای بود و همچنین مایکروسافت صرفا نگارش‌های مختلف SQL Server را به صورت رسمی پشتیبانی می‌کرد و برای سایر بانک‌های اطلاعاتی دیگر باید از پروایدرهای ثالث استفاده کرد.
در EF Core علاوه بر افزایش تعداد پروایدرهای رسمی بانک‌های اطلاعاتی رابطه‌ای، پشتیبانی از بانک‌های اطلاعاتی NoSQL هم اضافه شده‌است؛ به همراه پروایدر ویژه‌‌ای به نام In Memory جهت انجام ساده‌تر Unit testing. کاری که با نگارش‌های پیشین EF به سادگی و از روز اول پشتیبانی نمی‌شد.

حذف و یا عدم پیاده سازی تعدادی از قابلیت‌های EF 6.x
اگر موارد فوق جزو مهم‌ترین مزایای کار با EF Core باشند، باید درنظر داشت که به علت حذف و یا تقلیل یافتن یک سری از ویژگی‌ها در NET Core.، مانند Reflection (جهت پشتیبانی از دات نت در سکوهای مختلف کاری و خصوصا پشتیبانی از حالتی که کامپایلر مخصوص برنامه‌های UWP نیاز دارد تمام نوع‌ها را همانند زبان‌های C و ++C، در زمان کامپایل بداند)، یک سری از قابلیت‌های EF 6.x مانند Lazy loading هنوز در EF Core پشتیبانی نمی‌شوند. لیست کامل و به روز شده‌ی این موارد را در اینجا می‌توانید مطالعه کنید.
بنابراین امکان انتقال برنامه‌های EF 6.x به EF Core 1.0 عموما وجود نداشته و نیاز به بازنویسی کامل دارند. هرچند بسیاری از مفاهیم آن با EF Code First یکی است.


برپایی تنظیمات اولیه‌ی EF Core 1.0 در یک برنامه‌ی ASP.NET Core 1.0

برای نصب تنظیمات اولیه‌ی EF Core 1.0 در یک برنامه‌ی ASP.NET Core 1.0، جهت کار با مشتقات SQL Server (و SQL LocalDB) نیاز است سه بسته‌ی ذیل را نصب کرد (از طریق منوی Tools -> NuGet Package Manager -> Package Manager Console):
PM> Install-Package Microsoft.EntityFrameworkCore.SqlServer
PM> Install-Package Microsoft.EntityFrameworkCore.Tools -Pre
PM> Install-Package Microsoft.EntityFrameworkCore.SqlServer.Design
البته در این قسمت صرفا از بسته‌ی اول که جهت اتصال به SQL Server است استفاده می‌کنیم. بسته‌های دیگر را در قسمت‌های بعد، برای به روز رسانی اسکیمای بانک اطلاعاتی (مباحث Migrations) و مباحث scaffolding استفاده خواهیم کرد.
پس از اجرای سه دستور فوق، تغییرات مداخل فایل project.json برنامه به صورت ذیل خواهند بود:
{
    "dependencies": {
       // same as before
        "Microsoft.EntityFrameworkCore.SqlServer": "1.0.0",
        "Microsoft.EntityFrameworkCore.Tools": "1.0.0-preview2-final",
        "Microsoft.EntityFrameworkCore.SqlServer.Design": "1.0.0"
    }
}
این مداخلی که توسط نیوگت اضافه شده‌اند، نیاز به اصلاح دارند؛ به این صورت:
{
    "dependencies": {
       // same as before
        "Microsoft.EntityFrameworkCore.SqlServer": "1.0.0",
        "Microsoft.EntityFrameworkCore.Tools": {
            "version": "1.0.0-preview2-final",
            "type": "build"
        },
        "Microsoft.EntityFrameworkCore.SqlServer.Design": {
            "version": "1.0.0",
            "type": "build"
        }
    },

    "tools": {
       // same as before
        "Microsoft.EntityFrameworkCore.Tools": {
            "version": "1.0.0-preview2-final",
            "imports": [
                "portable-net45+win8"
            ]
        }   
   }
}
نیاز است در قسمت dependencies مشخص کنیم که ابزارهای اضافه شده مخصوص build هستند و نه اجرای برنامه. همچنین قسمت tools را باید با Microsoft.EntityFrameworkCore.Tools مقدار دهی کرد تا بتوان از این ابزار در خط فرمان، جهت اجرای فرامین migrations استفاده کرد.
بنابراین از همین ابتدای کار، بدون مراجعه‌ی به Package Manager Console، چهار تغییر فوق را به فایل project.json اعمال کرده و آن‌را ذخیره کنید؛ تا کار به روز رسانی و نصب بسته‌ها، به صورت خودکار و همچنین صحیحی انجام شود.


فعال سازی صفحات مخصوص توسعه دهنده‌های EF Core 1.0

در مطلب «ارتقاء به ASP.NET Core 1.0 - قسمت 5 - فعال سازی صفحات مخصوص توسعه دهنده‌ها» با تعدادی از اینگونه صفحات آشنا شدیم. برای EF Core نیز بسته‌ی مخصوصی به نام Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore وجود دارد که امکان فعال سازی صفحه‌ی نمایش خطاهای بانک اطلاعاتی را میسر می‌کند. بنابراین ابتدا به فایل project.json مراجعه کرده و این بسته را اضافه کنید:
{
    "dependencies": {
       // same as before
        "Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore": "1.0.0"
    }
}
سپس می‌توان متد جدید UseDatabaseErrorPage را در متد Configure کلاس آغازین برنامه، فراخوانی کرد:
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
   if (env.IsDevelopment())
   {
      app.UseDatabaseErrorPage();
   }
با فعال سازی این صفحه، اگر در حین توسعه‌ی برنامه و اتصال به بانک اطلاعاتی، خطایی رخ دهد، بجای مشاهده‌ی یک صفحه‌ی خطای عمومی (اگر UseDeveloperExceptionPage را فعال کرده باشید)، اینبار ریز جزئیات بیشتری را به همراه توصیه‌هایی از طرف تیم EF مشاهده خواهید کرد.


تعریف اولین Context برنامه و مشخص سازی رشته‌ی اتصالی آن


در این تصویر، زیر ساخت نگاشت‌های EF Core را مشاهده می‌کنید. در سمت چپ، ظرفی را داریم به نام DB Context که در برگیرنده‌ی Db Setها است. در سمت راست که بیانگر ساختار کلی یک بانک اطلاعاتی است، معادل این‌ها را مشاهده می‌کنیم. هر Db Set به یک جدول بانک اطلاعاتی نگاشت خواهد شد و متشکل است از کلاسی به همراه یک سری خواص که این‌ها نیز به فیلدها و ستون‌های آن جدول در سمت بانک اطلاعاتی نگاشت می‌شوند.
بنابراین برای شروع کار، پوشه‌ای را به نام Entities به پروژه اضافه کرده و سپس کلاس ذیل را به آن اضافه می‌کنیم:
namespace Core1RtmEmptyTest.Entities
{
    public class Person
    {
        public int PersonId { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
    }
}
کلاس Person بیانگر ساختار جدول اشخاص بانک اطلاعاتی است. برای اینکه این کلاس را تبدیل و نگاشت به یک جدول کنیم، نیاز است آن‌را به صورت یک DbSet در معرض دید EF Core قرار دهیم و اینکار در کلاسی که از DbContex مشتق می‌شود، صورت خواهد گرفت:
using Microsoft.EntityFrameworkCore;

namespace Core1RtmEmptyTest.Entities
{
    public class ApplicationDbContext : DbContext
    {
        public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options)
        {
        }

        public DbSet<Person> Persons { get; set; }
    }
}
بنابراین در ادامه کلاس جدید ApplicationDbContext را که از کلاس پایه DbContext مشتق می‌شود تعریف کرده و سپس کلاس Person را به صورت یک DbSet در معرض دید EF Core قرار می‌دهیم.
سازنده‌ی این کلاس نیز به نحو خاصی تعریف شده‌است. اگر به سورس‌های EF Core مراجعه کنیم، کلاس پایه‌ی DbContext دارای دو سازنده‌ی با و بدون پارامتر است:
protected DbContext()
   : this((DbContextOptions) new DbContextOptions<DbContext>())
{
}

public DbContext([NotNull] DbContextOptions options)
{
  // …
}
اگر از سازنده‌ی بدون پارامتر استفاده کنیم و برای مثال در کلاس ApplicationDbContext فوق، به طور کامل سازنده‌ی تعریف شده را حذف کنیم، باید به نحو ذیل تنظیمات بانک اطلاعاتی را مشخص کنیم:
using Microsoft.EntityFrameworkCore;

namespace Core1RtmEmptyTest.Entities
{
    public class ApplicationDbContext : DbContext
    {
        public DbSet<Person> Persons { get; set; }

        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            optionsBuilder.UseSqlServer(@"... connection string ...");
        }
    }
}
در این حالت باید متد OnConfiguring را override و یا بازنویسی کنیم، تا بتوان از اول مشخص کرد که قرار است از پروایدر SQL Server استفاده کنیم و ثانیا رشته‌ی اتصالی به آن چیست.
اما چون در یک برنامه‌ی ASP.NET Core، کار ثبت سرویس مربوط به EF Core، در کلاس آغازین برنامه انجام می‌شود و در آنجا به سادگی می‌توان به خاصیت Configuration برنامه دسترسی یافت و توسط آن رشته‌ی اتصالی را دریافت کرد، مرسوم است از سازنده‌ی با پارامتر DbContext به نحوی که در ابتدا عنوان شد، استفاده شود.
بنابراین در ادامه، پس از مطالعه‌ی مطلب «ارتقاء به ASP.NET Core 1.0 - قسمت 7 - کار با فایل‌های config» به فایل appsettings.json مراجعه کرده و تنظیمات رشته‌ی اتصالی برنامه را به صورت ذیل در آن مشخص می‌کنیم:
{
    "ConnectionStrings": {
        "ApplicationDbContextConnection": "Data Source=(local);Initial Catalog=TestDbCore2016;Integrated Security = true"
    }
}
باید دقت داشت که نام این مداخل کاملا اختیاری هستند و در نهایت باید در کلاس آغازین برنامه به صورت صریحی مشخص شوند.
در اینجا به وهله‌ی پیش فرض SQL Server اشاره شده‌است؛ از حالت اعتبارسنجی ویندوزی SQL Server استفاده می‌شود و بانک اطلاعاتی جدیدی به نام TestDbCore2016 در آن مشخص گردیده‌است.

پس از تعریف رشته‌ی اتصالی، متد OnConfiguring را از کلاس ApplicationDbContext حذف کرده و از همان نگارش دارای سازنده‌ی با پارامتر آن استفاده می‌کنیم. برای اینکار به کلاس آغازین برنامه مراجعه کرده و توسط متد AddDbContext این Context را به سرویس‌های ASP.NET Core معرفی می‌کنیم:
    public class Startup
    {
        public IConfigurationRoot Configuration { set; get; }

        public Startup(IHostingEnvironment env)
        {
            var builder = new ConfigurationBuilder()
                                .SetBasePath(env.ContentRootPath)
                                .AddJsonFile("appsettings.json", reloadOnChange: true, optional: false)
                                .AddJsonFile($"appsettings.{env}.json", optional: true);
            Configuration = builder.Build();
        }

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddSingleton<IConfigurationRoot>(provider => { return Configuration; });
            services.AddDbContext<ApplicationDbContext>(options =>
            {
                options.UseSqlServer(Configuration["ConnectionStrings:ApplicationDbContextConnection"]);
            });
در اینجا جهت یادآوری مطلب «ارتقاء به ASP.NET Core 1.0 - قسمت 7 - کار با فایل‌های config» نحوه‌ی وهله سازی خاصیت Configuration که در متد UseSqlServer مورد استفاده قرار گرفته‌است، نیز ذکر شده‌است.
بنابراین قسمت options.UseSqlServer را یا در اینجا مقدار دهی می‌کنید و یا از طریق بازنویسی متد OnConfiguring کلاس Context برنامه.


یک نکته: امکان تزریق IConfigurationRoot به کلاس Context برنامه وجود دارد

با توجه به اینکه Context برنامه را به صورت یک سرویس به ASP.NET Core معرفی کردیم، امکان تزریق وابستگی‌ها نیز در آن وجود دارد. یعنی بجای روش فوق، می‌توان IConfigurationRoot را به سازنده‌ی کلاس Context برنامه نیز تزریق کرد:
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;

namespace Core1RtmEmptyTest.Entities
{
    public class ApplicationDbContext : DbContext
    {
        private readonly IConfigurationRoot _configuration;

        public ApplicationDbContext(IConfigurationRoot configuration)
        {
            _configuration = configuration;
        }

        //public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options)
        //{
        //}

        public DbSet<Person> Persons { get; set; }

        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            optionsBuilder.UseSqlServer(_configuration["ConnectionStrings:ApplicationDbContextConnection"]);
        }
    }
}
با توجه به اینکه IConfigurationRoot در کلاس ConfigureServices به صورت Singleton، به مجموعه‌ی سرویس‌های برنامه معرفی شده‌است، از آن در تمام کلاس‌های برنامه که تحت نظر سیستم تزریق وابستگی‌های توکار ASP.NET Core هستند، می‌توان استفاده کرد.
در این حالت متد ConfigureServices کلاس آغازین برنامه، چنین شکلی را پیدا می‌کند و ساده می‌شود:
public void ConfigureServices(IServiceCollection services)
{
    services.AddDbContext<ApplicationDbContext>();


یک نکته: امکان تزریق ApplicationDbContext به تمام کلاس‌های برنامه وجود دارد

همینقدر که ApplicationDbContext را به عنوان سرویسی در ConfigureServices تعریف کردیم، امکان تزریق آن در اجزای مختلف یک برنامه‌ی ASP.NET Core نیز وجود دارد:
using System.Linq;
using Core1RtmEmptyTest.Entities;
using Microsoft.AspNetCore.Mvc;

namespace Core1RtmEmptyTest.Controllers
{
    public class TestDBController : Controller
    {
        private readonly ApplicationDbContext _ctx;

        public TestDBController(ApplicationDbContext ctx)
        {
            _ctx = ctx;
        }

        public IActionResult Index()
        {
            var name = _ctx.Persons.First().FirstName;
            return Json(new { firstName = name });
        }
    }
}
در اینجا نحوه‌ی تزریق DB Context برنامه را به یک کنترلر مشاهده می‌کنید. البته هرچند تزریق یک کلاس مشخص به این شکل، تزریق وابستگی‌ها نام ندارد و هنوز این کنترلر دقیقا وابسته‌است به پیاده سازی خاص کلاس ApplicationDbContext، اما ... در کل امکان آن هست.

در این حالت پس از اجرای برنامه، خطای ذیل را مشاهده خواهیم کرد:


علت اینجا است که هنوز این بانک اطلاعاتی ایجاد نشده‌است و همچنین ساختار جداول را به آن منتقل نکرده‌ایم که این موارد را در قسمت‌های بعدی مرور خواهیم کرد.
مطالب
آشنایی با آزمایش واحد (unit testing) در دات نت، قسمت 6

ادامه آشنایی با NUnit

فرض کنید یک RSS reader نوشته‌اید که فیدهای فارسی و انگلیسی را دریافت می‌کند. به صورت پیش فرض هم مشخص نیست که کدام فید اطلاعات فارسی را ارائه خواهد داد و کدامیک انگلیسی. تشخیص محتوای فارسی و از راست به چپ نشان دادن خودکار مطالب ‌آن‌ها به عهده‌ی برنامه نویس است. بهترین روش برای تشخیص این نوع الگوها، استفاده از regular expressions است.
برای مثال الگوی تشخیص اینکه آیا متن ما حاوی حروف انگلیسی است یا خیر به صورت زیر است:

[a-zA-Z]

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

[\u0600-\u06FF]
[ا-یءئ]

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

کلاس زیر را در مورد استفاده از این الگوها تهیه کرده‌ایم:

using System.Text.RegularExpressions;

namespace sample
{
public static class CDetectFarsi
{
public static bool ContainsFarsiData(this string txt)
{
return !string.IsNullOrEmpty(txt) &&
Regex.IsMatch(txt, "[ا-یءئ]");
}

public static bool ContainsFarsi(this string txt)
{
return !string.IsNullOrEmpty(txt) &&
Regex.IsMatch(txt, @"[\u0600-\u06FF]");
}
}
}

همانطور که ملاحظه می‌کنید در اینجا از extension methods سی شارپ 3 جهت توسعه کلاس پایه string استفاده شد.
اکنون می‌خواهیم بررسی کنیم آیا این الگوها مقصود ما را برآورده می‌سازند یا خیر.

using NUnit.Framework;
using sample;

namespace TestLibrary
{
[TestFixture]
public class TestFarsiClass
{
/*******************************************************************************/
[Test]
public void TestContainsFarsi1()
{
Assert.IsTrue("وحید".ContainsFarsi());
}

[Test]
public void TestContainsFarsi2()
{
Assert.IsTrue("گردان".ContainsFarsi());
}

[Test]
public void TestContainsFarsi3()
{
Assert.IsTrue("سپیدTest".ContainsFarsi());
}

[Test]
public void TestContainsFarsi4()
{
Assert.IsTrue("123بررسی456".ContainsFarsi());
}

[Test]
public void TestContainsFarsi5()
{
Assert.IsFalse("Book".ContainsFarsi());
}

[Test]
public void TestContainsFarsi6()
{
Assert.IsTrue("۱۳۸۷".ContainsFarsi());
}

[Test]
public void TestContainsFarsi7()
{
Assert.IsFalse("Здравствуйте!".ContainsFarsi()); //Russian hello!
}


/*******************************************************************************/
[Test]
public void TestContainsFarsiData1()
{
Assert.IsTrue("وحید".ContainsFarsiData());
}

[Test]
public void TestContainsFarsiData2()
{
Assert.IsTrue("گردان".ContainsFarsiData());
}

[Test]
public void TestContainsFarsiData3()
{
Assert.IsTrue("سپیدTest".ContainsFarsiData());
}

[Test]
public void TestContainsFarsiData4()
{
Assert.IsTrue("123بررسی456".ContainsFarsiData());
}

[Test]
public void TestContainsFarsiData5()
{
Assert.IsFalse("Book".ContainsFarsiData());
}

[Test]
public void TestContainsFarsiData6()
{
Assert.IsTrue("۱۳۸۷".ContainsFarsiData());
}

[Test]
public void TestContainsFarsiData7()
{
Assert.IsFalse("Здравствуйте!".ContainsFarsiData()); //Russian hello!
}
}
}

در کلاس فوق هر دو متد را با آزمایش‌های واحد مختلفی بررسی کرده‌ایم، انواع و اقسام حروف فارسی، ترکیبی از فارسی و انگلیسی، ترکیبی از فارسی و اعداد انگلیسی، عبارت کاملا انگلیسی، عدد کاملا فارسی و یک عبارت روسی! (در یک کلاس عمومی با متدهای عمومی بدون آرگومان از نوع void)
کلاس CDetectFarsi در برنامه اصلی قرار دارد و کلاس TestFarsiClass در یک پروژه class library دیگر قرار گرفته است (در این مورد و جدا سازی آزمایش‌ها از پروژه اصلی در قسمت‌های قبل بحث شد)
همچنین به ازای هر عبارت Assert یک متد ایجاد گردید تا شکست یکی، سبب اختلال در بررسی سایر موارد نشود.
نتیجه اجرای این آزمایش واحد با استفاده از امکانات مجتمع افزونه ReSharper به صورت زیر است:



منهای یک مورد، سایر آزمایشات ما با موفقیت انجام شده‌اند. موردی که با شکست مواجه شده، بررسی اعداد کاملا فارسی است که البته در الگوی دوم لحاظ نشده است و انتظار هم نمی‌رود که آن‌را به این شکل پشتیبانی کند.
برای اینکه در حین اجرای آزمایشات بعدی این متد در نظر گرفته نشود، می‌توان ویژگی Test آن‌را به صورت زیر تغییر داد:

[Test,Ignore]

نکته: مرسوم شده است که نام متدهای آزمایش واحد به صورت زیر تعریف شوند (با Test شروع شوند، در ادامه نام متدی که بررسی می‌شود ذکر گردد و در آخر ویژگی مورد بررسی عنوان شود):

Test[MethodToBeTested][SomeAttribute]

ادامه دارد...