مطالب
چگونه یک ایمیل مفید خودکار را طراحی کنیم؟

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

1) حتما در انتهای ایمیل خودکار ارسالی، ساعت و تاریخ شمسی ارسال پیام را نیز ذکر کنید.
عموما از آنجائیکه سیستم استاندارد ارسال ایمیل بر اساس تاریخ میلادی است و تقریبا تمام کلاینت‌های دریافت ایمیل موجود نیز توانایی شمسی سازی تاریخ دریافت و ارسال ایمیل را ندارند (مگر با یک سری افزونه و یا دستکاری در سیستم عامل که آنچنان خوشایند و مرسوم نیست)، ذکر تاریخ شمسی در انتهای پیام بسیار مفید خواهد بود و در اکثر اوقات استناد به ایمیل‌های دریافت شده بر اساس تاریخ دریافت آن‌ها است.

2) سعی کنید از بکارگیری عناوین (subject) ثابت جهت ارسال ایمیل‌های خودکار پرهیز کنید.
دقیقا یادم میاد زمانیکه برای مدیر عامل شرکتی سه بار پشت سرهم ایمیلی با یک عنوان ارسال شده بود بنده را بازخواست کردند که چرا برنامه‌ی شما ایمیل تکراری ارسال می‌کند!
بله، سعی می‌کنند محتوا را از روی عنوان ایمیل حدس بزنند و زمانیکه یک عنوان ثابت را برای ایمیل‌های خودکار خود انتخاب کردید، تکراری به نظر خواهند رسید یا حتی ممکن است به اشتباه پیش از خوانده شدن حذف شوند.
برای مثال فرض کنید ایمیل ارجاع کاری را قرار است به صورت خودکار ارسال کنید. انتخاب عنوان ثابت برای مثال "ارجاع کار جدید" اشتباه است! این عنوان باید بر اساس نوع کار هر بار به صورت پویا متغیر باشد؛ مثلا: "ارجاع کار جدید: از طرف : ... ، موضوع: ... ، درجه اهمیت: ..." که این سه نقطه‌ها باید توسط برنامه هر بار پر شوند.

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

4) ایمیل شما باید حاوی لینکی جهت باز کردن برنامه‌ی تحت وب مرتبط نیز باشد.
کاری ارجاع شده است؟ بهتر است لینک پویایی را جهت هدایت کاربر به صفحه‌ی مرتبط رسیدگی به همان کار ارجاعی ارسال کنید. به این صورت زحمت او را کمتر کرده و یک مرحله گزارش گیری را حذف خواهید کرد. یا حداقل یک محل مراجعه‌ی کلی بعدی را به این صورت می‌توان ارائه داد.

5) از بکارگیری قسمت from ایی مانند DoNotReply@Site.Com خودداری کنید.
کاربر دریافت کننده‌ی ایمیل باید بداند که در صورت وجود مشکل باید به کجا مراجعه کند؟ چه کسی این ایمیل را ارسال کرده؟
هرچند برنامه به صورت خودکار تمام قسمت‌های این ایمیل ارسالی را تهیه می‌کند اما اگر خبرنامه‌ی تنظیم شده‌ای نیست، حتما شخص ارسال کننده‌ای دارد. یا حداقل یک ایمیل عمومی را برای این مورد تنظیم کنید (ایمیلی که وجود خارجی داشته و هر از چندگاهی بررسی می‌شود).

6) رنگ زمینه و اندازه‌ی قلم مناسبی را انتخاب کنید.
دقیقا برای هر کدام از موارد ذکر شده چندین بار مشکل داشته‌ام! عموما کسانی که ایمیل‌ها را دریافت می‌کنند سن و سال دار هستند. بنابراین انتخاب فونت tahoma با اندازه‌ی 8 یا pt 7 سبب توبیخ زود هنگام شما خواهد شد!
همچنین هر چه ساده‌تر بهتر. دقیقا مشکلات از زمانی آغاز می‌شوند که طرحی را انتخاب کنید یا رنگی را برای زمینه بکار ببرید. اینجا است که هر روز یک سلیقه‌ی تحمیلی را باید پذیرا باشید.

7) دقیقا مشخص کنید که ایمیل دریافتی آیا رونوشت‌ است یا خیر!
همان مبحث ارجاع کار را در نظر بگیرید. پس از اینکه سیستم راه اندازی شد، مدیر یکی از قسمت‌ها چند روز بعد این درخواست را "حتما" ارسال خواهد کرد: رونوشت تمام کارهای ارجاعی به کلیه پرسنل بخش و همچنین ریز اقدامات آن‌ها باید برای بنده نیز ارسال شود.
در اینجا تنها افزودن قسمت CC به ایمیل‌های خودکار کفایت نمی‌کند. حتما به صورت درشت در بالای ایمیل، قبل از شروع بدنه ذکر کنید که ایمیل دریافتی یک رونوشت است. در غیر اینصورت باید پاسخگوی علت دریافت ایمیل‌هایی باشید که به درخواست خودشان CC شده است!

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

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


خوب! حالا به نظر شما این ایمیل خودکار ارسالی سایت IDevCenter که اخیرا اضافه شده است چه نمره‌ای را کسب می‌کند؟



- تاریخ شمسی در انتهای ایمیل ندارد.
- عنوان‌ها ثابت هستند.
- هیچ جزئیاتی ارائه نشده است.
- لینک مرتبط دارد.
- قسمت from مناسبی دارد.
- ساده است؛ خوب است! فقط اندازه قلم آن بهتر است یک شماره بزرگتر شود.
- بحث رونوشت اینجا مورد ندارد.
- بحث لاگ ... شخصی است.
- امکان تنظیم دریافت ایمیل پیش بینی شده است.
نمره از 7 : 3.5

پاسخ به بازخورد‌های پروژه‌ها
توضیح گام های اجرا شده در پروژه
سلام؛
هدف از انجام این پروژه برای من چسباندن قطعات مختلف یک پازل به هم بودند تا بتوان به یک تصویر خوب رسید.منظور من این است که entity framework و ASP.NET MVC و bootstrap و best practice‌های آن‌ها به تنهایی و جدا از هم به نظر ساده و راحت و خوب بیایند، اما درگیر شدن همه‌ی آن‌ها در یک پروژه‌ی واقعی، واقعا چالش بر انگیز است.
من دانشجو هستم و تقریبا استارت این پروژه را از آبان ماه زدم، اما به دلیل یک سری مشکلات از جمله همین دانشجو بودن، کار به کندی پیش رفت و حتی وقفه‌های چند ماهه در آن پیش اومد. هدف من این بود که اساسا یک سیستم با کیفیت بنویسم و در ابتدای کار هم، کار به خوبی پیش می‌رفت، اما با توجه به مشکلات ذکر شده، عمده کار کدنویسی در تعطیلات عید نوروز صورت گرفت، و کاملا از کدنویسی انجام شده مشهود است ک ههمان قسمت هایی که در عید نوروز کدنویسی شده اند، اصطلاحا سرهم بندی شده اند( به خصوص در کدهای سمت کلاینت)
در مورد گام‌های انجام شده؛ پروژه به این منوال انجام شد:
- تحلیل ساختار بانک اطلاعاتی مورد نیاز
- شروع به تحقیق در مورد امکانات مورد نیاز 
- دعوت همکای برای کار گروهی توسط دوستان ( کسی قبول نکرد البته دی:)
- با توجه به محدودیت‌های یافت شده در تحقیقات، ساختار بانک اطلاعاتی نهایی می‌شود.
- انتخاب فریم ورک‌های مناسب( که در اینجا Entity Framework برای orm و ASP.NET MVC برای کدنویسی سمت سرور و bootstrap برای css و jquery هم برای جاوا اسکریپت)
- تحقیق در مورد best practice‌های موجود در مورد هر یک از فریم ورک‌های فوق
-شروع کدنویسی
در مورد قسمت مدیریت کاربران، هدف طراحی یک سیستم خیلی منعطف بود که قطعا با memebrship خود دات نت امکان پذیر نبود. متاسفانه به دلیل مشکلات پیش اومده این قسمت از پروژه هم سرهم بندی کردم و به یک سیستم ساده اکتفا کردم.
برای پیاده سازی آن هم شما کافیست در گوگل عبارت implement custom membership in asp.net mvc را سرچ کنید. مطمئن باشید کلی مطلب پیدا خوهید کرد که با جمع بندی آن یک سیستم خوب می‌توانید پیاده سازی کنید.
الان همین سیستم پیاده سازی شده در سایت یک باگ دارد که بعد از مدتی remember me آ از کار می‌افتد.کوکی کاربر اعتبار دارد، اما رویداد متناظر آن برای اعتبار سنجی اتفاق نمی‌افتد!
الان هم در حال تحقیق برای پیاده سازی یک سیستم اعتبارسنجی  کامل‌تر و اصولی‌تر و یک پارچه‌تر با ASP.NET MVC  هستم که مقاله‌ی زیر خیلی به من کمک کرد.(امیدوارم برای شما هم مفید باشد)
الان هم برنامه ای برای ارتقا این سیستم دارم و مهمترین تغییر آن را می‌توان به استفاده از angularjs برای نوشتن بخش مدیریتی و پیاده سازی آن به صورت single page دانست.( البته اگر این کمردرد بزاره دی:)
امیدوارم دوستان با بازخوردهای خوب خودشون، در ارتقای سطح کیفی کار کمک کنند.
مطالب
بررسی ابزار SQL Server Profiler

مقدمه

Profiler یک ابزار گرافیکی برای ردیابی و نظارت بر کارآئی SQL Server است. امکان ردیابی اطلاعاتی در خصوص رویدادهای مختلف و ثبت این داده‌ها در یک فایل (با پسوند trc) یا جدول برای تحلیل‌های آتی نیز وجود دارد. برای اجرای این ابزار مراحل زیر را انجام دهید:

Start > Programs> Microsoft SQL Server > Performance Tools> SQL Server Profiler
و یا در محیط  Management Studio از منوی Tools گزینه SQL Server Profiler را انتخاب نمائید.


1- اصطلاحات

1-1- رویداد (Event):

یک رویداد، کاری است که توسط موتور بانک اطلاعاتی (Database Engine) انجام می‌شود. برای مثال هر یک از موارد زیر یک رویداد هستند.
-  متصل شدن کاربران (login connections) قطع شدن ارتباط یک login
-  اجرای دستورات T-SQL، شروع و پایان اجرای یک رویه، شروع و پایان یک دستور در طول اجرای یک رویه، اجرای رویه‌های دور Remote Procedure Call
-  باز شدن یک Cursor
-  بررسی و کنترل مجوزهای امنیتی

1-2- کلاس رویداد (Event Class):

برای بکارگیری رویدادها در Profiler، از یک Event Class استفاده می‌کنیم. یک Event Class رویدادی است که قابلیت ردیابی دارد. برای مثال بررسی ورود و اتصال کاربران با استفاده از کلاس Audit Login قابل پیاده سازی است. هر یک از موارد زیر یک Event Class هستند.
-  SQL:BatchCompleted
-  Audit Login
-  Audit Logout
-  Lock: Acquired
-  Lock: Released

1-3- گروه رویداد (Event Category):

یک گروه رویداد شامل رویدادهایی است که به صورت مفهومی دسته بندی شده اند. برای مثال، کلیه رویدادهای مربوط به قفل‌ها از جمله Lock: Acquired (بدست آوردن قفل) و Lock: Released (رها کردن قفل) در گروه رویداد Locks قرار  دارند.

1-4- ستون داده ای (Data Column):

یک ستون داده ای، خصوصیت و جزئیات یک رویداد را شامل می‌شود. برای مثال در یک Trace که رویدادهای Lock: Acquired را نظارت می‌کند، ستون Binary Data شامل شناسه (ID) یک صفحه و یا یک سطر قفل شده است و یا اینکه ستون Duration مدت زمان اجرای یک رویه را نمایش می‌دهد.

1-5- الگو (Template):

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

1-6- ردیاب (Trace):

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

1-7- فیلتر (Filter):

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


2- انتخاب الگو (Profiler Trace Templates)

از آنجائیکه اصولاً انتخاب Eventهای مناسب، کار سخت و تخصصی می‌باشد برای راحتی کار تعدادی Template‌های آماده وجود دارد، برای مثال TSQL_Duration تاکیدش روی مدت انجام کار است و یا SP_Counts در مواردی که بخواهیم رویه‌های ذخیره شده را بهینه کنیم استفاده می‌شود در جدول زیر به شرح هر یک پرداخته شده است:
 الگو  هدف 
 Blank   ایجاد یک Trace کلی 
 SP_Counts   ثبت اجرای هر رویه ذخیره شده برای تشخیص اینکه هر رویه چند بار اجرا شده است 
 Standard   ثبت آمارهای کارائی برای هر رویه ذخیره شده و Query‌های عادی SQL که اجرا می‌شوند و عملیات ورود و خروج هر Login (پیش فرض) 
 TSQL   ثبت یک لیست از همه رویه‌های ذخیره شده و Query‌های عادی SQL که اجرا می‌شوند ولی آمارهای کارائی را شامل نمی‌شود 
 TSQL_Duration   ثبت مدت زمان اجرای هر رویه ذخیره شده و هر Query عادی SQL 
 TSQL_Grouped   ثبت تمام  login‌ها و logout‌ها در طول اجرای رویه‌های ذخیره شده و هر Query عادی SQL، شامل اطلاعاتی برای شناسائی برنامه و کاربری که درخواست را اجرا می‌کند 
 TSQL_Locks   ثبت اطلاعات انسداد (blocking) و بن بست (deadlock) از قبیل blocked processes، deadlock chains، deadlock graphs,... . این الگو همچنین درخواست‌های تمام رویه‌های ذخیره شده و تمامی دستورات هر رویه و  هر Query عادی SQL را دریافت می‌کند 
 TSQL_Replay   ثبت اجرای رویه‌های ذخیره شده و Query‌های SQL در یک SQL Instance و  مهیا کردن امکان اجرای دوباره عملیات در سیستمی دیگر 
 TSQL_SPs   ثبت کارائی برای Query‌های SQL، رویه‌های ذخیره شده و تمامی دستورات درون یک رویه ذخیره شده و نیز عملیات ورود و خروج هر Login 
 Tuning   ثبت اطلاعات کارائی برای Query‌های عادی SQL و رویه‌های ذخیره شده و یا تمامی دستورات درون یک رویه ذخیره شده 

3- انتخاب رویداد (SQL Trace Event Groups)

رویداد‌ها در 21 گروه رویداد دسته بندی می‌شوند که در جدول زیر لیست شده اند:
 گروه رویداد  هدف 
 Broker  13 رویداد برای واسطه سرویس (Service Broker) 
 CLR   1 رویداد برای بارگذاری اسمبلی‌های CLR (Common Language Runtime) 
 Cursors   7 رویداد برای ایجاد، دستیابی و در اختیار گرفتن Cursor 
 Database   6 رویداد برای رشد/کاهش  (grow/shrink) فایل های  Data/Log همچنین تغییرات حالت انعکاس (Mirroring) 
 Deprecation   2 رویداد برای آگاه کردن وضعیت نابسامان درون یک SQL Instance 
 Errors and
Warnings 
 16 رویداد برای خطاها، هشدارها و پیغام‌های اطلاعاتی که ثبت شده است 
 Full Text   3  رویداد برای پیگیری یک شاخص متنی کامل 
 Locks   9 رویداد برای بدست آوردن، رها کردن قفل و بن بست (Deadlock) 
 OLEDB   5 رویداد برای درخواست‌های توزیع شده و RPC (اجرای رویه‌های دور) 
 Objects   3 رویداد برای وقتی که یک شی ایجاد، تغییر یا حذف می‌شود 
 Performance   14 رویداد برای ثبت نقشه درخواست‌ها (Query Plan) برای استفاده نقشه راهنما (Plan Guide) به منظور بهینه سازی کارائی درخواست ها،  همچنین این گروه رویداد در خواست‌های متنی کامل (full text) را ثبت می‌کند 
 Progress Report   10 رویداد برای ایجاد Online Index 
 Query
Notifications 
 4 رویداد برای سرویس اطلاع رسان (Notification Service) 
 Scans   2 رویداد برای وقتی که یک جدول یا شاخص، پویش می‌شود 
 Security Audit   44 رویداد برای وقتی که مجوزی استفاده شود، جابجائی هویتی رخ دهد، تنظیمات امنیتی اشیائی تغییر کند،یک  SQL Instance  شروع و متوقف شود و یک  Database جایگزین شود یا از آن پشتیبان گرفته شود 
 Server  3 رویداد برای Mount Tape، تغییر کردن حافظه سرور و بستن یک فایل Trace 
 Sessions   3 رویداد برای وقتی که Connection‌ها موجود هستند و یک Trace فعال می‌شود، همچنین یک Trigger  و یک تابع دسته بندی(classification functions) مربوط به مدیریت منابع(resource governor) رخ دهد 
 Stored Procedures   12 رویداد برای اجرای یک رویه ذخیره شده و دستورات درون آن ، کامپایل مجدد و استفاده از حافظه نهانی (Cache) 
 Transactions   13 رویداد برای شروع، ذخیره ، تائید و لغو یک تراکنش 
 TSQL   9  رویداد برای اجرای Query‌های SQL و جستجوهای XQUERY (در داده‌های XML)  
 User Configurable   10 رویداد که شما می‌توانید پیکربندی کنید 
به طور معمول بیشتر از گروه رویدادهای Locks، Performance، Security Audit، Stored Procedures و TSQL استفاده می‌شود.


4- انتخاب ستون‌های داده ای ( Data Columns)

اگرچه می‌توان همه‌ی 64 ستون داده ای ممکن را برای ردیابی انتخاب کرد ولیکن داده‌های Trace شما زمانی مفید خواهند بود که اطلاعات ضروری را ثبت کرده باشید. برای مثال شماره ترتیب تراکنش‌ها را،  برای یک رویداد RPC:Completed می‌توانید برگردانید، اما همه رویه‌های ذخیره شده مقادیر را تغییر نمی‌دهند بنابراین شماره ترتیب تراکنش‌ها فضای بیهوده ای را مصرف می‌کند. بعلاوه همه ستون‌های داده ای برای تمامی رویداد‌های Trace معتبر نیستند. برای مثال Read ، Write ،CPU و Duration برای رویداد‌های RPC:Starting و SQL:BatchStarting معتبر نیستند.
ApplicationName، NTUserName، LoginName، ClientProcessID، SPID، HostName، LoginSID، NTDomainName و SessionLoginName ، مشخص می‌کنند چه کسی و از چه منشاء دستور را اجرا کرده است.
ستون SessionLoginName معمولاً نام Login ای که از آن برای متصل شدن به SQL Instance استفاده شده است را نشان می‌دهد. در حالیکه ستون LoginName نام کاربری را که دستور را اجرا می‌کند نشان می‌دهد (EXECUTE AS). ستون ApplicationName خالی است مگر اینکه در ConnectionString برنامه کاربردیمان این خصوصیت (Property) مقداردهی شده باشد. ستون StartTime و EndTime زمان سرحدی برای هر رویداد را ثبت می‌کند این ستون‌ها بویژه در هنگامی که به عملیات Correlate  نیاز دارید مفید هستند.


5- بررسی چند سناریو نمونه

•  یافتن درخواست هائی (Queries) که بدترین کارایی را دارا هستند.

برای ردیابی درخواست‌های ناکارا، از رویداد RPC:Completed از دسته Stored Procedure و رویداد SQL:BatchCompleted از دسته TSQL استفاده می‌شود.

•  نظارت بر کارایی رویه ها

برای ردیابی کارائی رویه ها، از رویدادهای SP:Starting، SP:Completed، SP:StmtCompleted و SP:StmtStaring از کلاس Stored Procedure و رویدادهای SQL:BatchStarting ، SQL:BatchCompleted از کلاس TSQL استفاده می‌شود.

•  نظارت بر اجرای دستورات T-SQL توسط هر کاربر

برای ردیابی دستوراتی که توسط یک کاربر خاص اجرا می‌شود، نیاز به ایجاد یک Trace برای نظارت بر رویدادهای کلاس‌های Sessions، ExistingConnection و TSQL داریم همچنین لازم است نام کاربر در قسمت فیلتر  و با استفاده از DBUserName مشخص شود.

•  اجرا دوباره ردیاب (Trace Replay)

این الگو  معمولاً برای debugging استفاده می‌شود برای این منظور  از الگوی Replay استفاده می‌شود. در ضمن امکان اجرای دوباره عملیات در سیستمی دیگر با استفاده از این الگو مهیا می‌شود.

•  ابزار Tuning Advisor (راهنمای تنظیم کارائی)

این ابزاری برای تحلیل کارائی یک یا چند بانک اطلاعاتی و تاثیر عملکرد آنها بر بار کاری (Workload) سرویس دهنده است. یک بار کاری مجموعه ای از دستورات T-SQL است که روی بانک اطلاعاتی اجرا می‌شود. بعد از تحلیل تاثیر بارکاری بر بانک اطلاعاتی، Tuning Advisor توصیه هائی برای اضافه کردن، حذف و یا تغییر طراحی فیزیکی ساختار بانک اطلاعاتی ارائه می‌دهد این تغییرات ساختاری شامل پیشنهاد برای تغییر ساختاری موارد Clustered Indexes، Nonclustered Indexes، Indexed View و Partitioning است.
برای ایجاد بارکاری می‌توان از یک ردیاب تهیه شده در SQL Profiler استفاده کرد برای این منظور از الگوی Tuning استفاده می‌شود و یا رویدادهای RPC:Completed، SQL:BatchCompleted و SP:StmtCompleted را ردیابی نمائید.

•  ترکیب ابزارهای نظارتی (Correlating Performance and Monitoring Data)

یک Trace برای ثبت اطلاعاتی که در یک SQL Instance رخ می‌دهد، استفاده می‌شود. System Monitor  برای ثبت شمارنده‌های کارائی(performance counters) استفاده می‌شود و همچنین از منابع سخت افزاری و اجزای دیگر که روی سرور اجرا می‌شوند، تصاویری فراهم می‌کند. توجه شود که در مورد  Correlating یک فایل ردیاب (trace file) و یک Counter Log (ابزار Performance )، ستون داده ای StartTime و EndTime باید انتخاب شود، برای این کار از منوی File گزینه Import Performance Data انتخاب می‌شود.

•  جستجوی علت رخ دادن یک بن بست

برای ردیابی علت رخ دادن یک بن بست، از رویدادهای RPC:Starting، SQLBatchStarting از دسته Stored Procedure و رویدادهای Deadlock graph، Lock:Deadlock و Lock:Deadlock Chain از دسته Locks استفاده می‌شود. ( در صورتی که نیاز به یک ارائه گرافیکی دارید از  Deadlock graph استفاده نمائید، خروجی مطابق تصویر زیر می‌شود).


5-1- ایجاد یک Trace

1-  Profiler را اجرا کنید از منوی File گزینه New Trace را انتخاب کنید و به SQL Instance مورد نظرتان متصل شوید.
2-  مطابق تصویر زیر برای Trace یک نام و الگو و تنظیمات ذخیره سازی فایل را مشخص کنید.


3-  بر روی قسمت Events Selection کلیک نمائید.
4-  مطابق تصویر زیر رویداد‌ها و کلاس رویداد‌ها را انتخاب کنید، ستون‌های TextData، NTUserName، LoginName، CPU،Reads،Writes، Duration، SPID، StartTime، EndTime، BinaryData، DataBaseName، ServerName و ObjectName را انتخاب کنید.

5-  روی Column Filters کلیک کنید و مطابق تصویر زیر برای DatabaseName فیلتری تنظیم کنید.


6-  روی Run کلیک کنید. تعدادی Query و رویه ذخیره شده مرتبط با پایگاه داده AdventureWorks اجرا کنید .


5-2- ایجاد یک Counter Log

برای ایجاد یک Counter Log  مراحل زیر  را انجام دهید:
1-  ابزار Performance را اجرا کنید (برای این کار عبارتPerfMon را در قسمت Run بنویسید).
2-  در قسمت Counter Logs یک log ایجاد کنید.
3-  روی Add Counters کلیک کرده و مطابق تصویر موارد زیر را انتخاب کنید.
Select counters from list 
Performance Object 
 Output Queue Length  Network Interface 
 % Processor Time  Processor 
 Processor Queue Length  System 
 Buffer Manager:Page life expectancy  SQLServer 
 
4-  روی Ok کلیک کنید تا Counter Log ذخیره شود سپس روی آن راست کلیک کرده و آنرا Start کنید.


5-3- ترکیب ابزارهای نظارتی (Correlating SQL Trace and System Monitor Data)

1-  Profiler را اجرا کنید از منوی File گزینه Open و سپس Trace File را انتخاب کنید فایل trc را که در گام اول ایجاد کردید، باز نمائید.
2-  از منوی File گزینه Import Performance Data را انتخاب کنید و فایل counter log  را که در مرحله قبل ایجاد کردید  انتخاب کنید.



نکته: اطلاعات فایل trc را می‌توان درون یک جدول وارد کرد، بدین ترتیب می‌توان آنالیز بیشتری داشت به عنوان مثال دستورات زیر این عمل را انجام می‌دهند.


 SELECT * INTO dbo.BaselineTrace
FROM fn_trace_gettable(' c:\performance baseline.trc ', default);
با اجرای دستور زیر جدولی با نام  BaselineTrace ایجاد و محتویات Trace مان (performance baseline.trc) در آن درج می‌گردد.
 
نظرات مطالب
اعتبارسنجی مبتنی بر JWT در ASP.NET Core 2.0 بدون استفاده از سیستم Identity
با تشکر . یعنی موقعی که توکن ایجاد میشود از claimها هم برای ایجاد آن استفاده میشود؟در اینجا
  private async Task<(string AccessToken, IEnumerable<Claim> Claims)> createAccessTokenAsync(User user)
        {
            var claims = new List<Claim>
            {
                // Unique Id for all Jwt tokes
                new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString(), ClaimValueTypes.String, _configuration.Value.Issuer),
                // Issuer
                new Claim(JwtRegisteredClaimNames.Iss, _configuration.Value.Issuer, ClaimValueTypes.String, _configuration.Value.Issuer),
                // Issued at
                new Claim(JwtRegisteredClaimNames.Iat, DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString(), ClaimValueTypes.Integer64, _configuration.Value.Issuer),
                new Claim(ClaimTypes.NameIdentifier, user.Id.ToString(), ClaimValueTypes.String, _configuration.Value.Issuer),
                new Claim(ClaimTypes.Name, user.Username, ClaimValueTypes.String, _configuration.Value.Issuer),
                new Claim("DisplayName", user.DisplayName, ClaimValueTypes.String, _configuration.Value.Issuer),
                // to invalidate the cookie
                new Claim(ClaimTypes.SerialNumber, user.SerialNumber, ClaimValueTypes.String, _configuration.Value.Issuer),
                // custom data
                new Claim(ClaimTypes.UserData, user.Id.ToString(), ClaimValueTypes.String, _configuration.Value.Issuer)
            };

           // add userclaims for permission
            var clmsuser= await _rolesService.FindUserClaimesAsync(user.Id);
            foreach (var cls in clmsuser)
            {
                claims.Add(new Claim(cls.ClaimType,cls.ClaimValue,ClaimValueTypes.String,_configuration.Value.Issuer));
            }
           
            // add roles
            var roles = await _rolesService.FindUserRolesAsync(user.Id);
            foreach (var role in roles)
            {
                claims.Add(new Claim(ClaimTypes.Role, role.Name, ClaimValueTypes.String, _configuration.Value.Issuer));
            }

            var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration.Value.Key));
            var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
            var now = DateTime.UtcNow;
            var token = new JwtSecurityToken(
                issuer: _configuration.Value.Issuer,
                audience: _configuration.Value.Audience,
                claims: claims,
                notBefore: now,
                expires: now.AddMinutes(_configuration.Value.AccessTokenExpirationMinutes),
                signingCredentials: creds);
            return (new JwtSecurityTokenHandler().WriteToken(token), claims);
        }

و هر تغییر در claimها در سمت کاربر برابر میشود با  برگشت توکن دستکاری شده از سمت سرور و مسدود شدن دسترسی؟
مطالب
آشنایی با NHibernate - قسمت دهم

آشنایی با کتابخانه NHibernate Validator

پروژه جدیدی به پروژه NHibernate Contrib در سایت سورس فورج اضافه شده است به نام NHibernate Validator که از آدرس زیر قابل دریافت است:


این پروژه که توسط Dario Quintana توسعه یافته است، امکان اعتبار سنجی اطلاعات را پیش از افزوده شدن آن‌ها به دیتابیس به دو صورت دستی و یا خودکار و یکپارچه با NHibernate فراهم می‌سازد؛ که امروز قصد بررسی آن‌را داریم.

کامپایل پروژه اعتبار سنجی NHibernate

پس از دریافت آخرین نگارش موجود کتابخانه NHibernate Validator از سایت سورس فورج، فایل پروژه آن‌را در VS.Net گشوده و یکبار آن‌را کامپایل نمائید تا فایل اسمبلی NHibernate.Validator.dll حاصل گردد.

بررسی مدل برنامه

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


به این صورت پوشه دومین برنامه از کلاس‌های زیر تشکیل خواهد شد:

namespace NHSample5.Domain
{
public class Patient
{
public virtual int Id { get; set; }
public virtual string FirstName { get; set; }
public virtual string LastName { get; set; }
}
}

using System.Collections.Generic;

namespace NHSample5.Domain
{
public class Doctor
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual IList<Patient> Patients { get; set; }

public Doctor()
{
Patients = new List<Patient>();
}
}
}
برنامه این قسمت از نوع کنسول با ارجاعاتی به اسمبلی‌های FluentNHibernate.dll ،log4net.dll ،NHibernate.dll ، NHibernate.ByteCode.Castle.dll ،NHibernate.Linq.dll ،NHibernate.Validator.dll و System.Data.Services.dll است.

ساختار کلی این پروژه را در شکل زیر مشاهده می‌کنید:


اطلاعات این برنامه بر مبنای NHRepository و NHSessionManager ایی است که در قسمت‌های قبل توسعه دادیم و پیشنیاز ضروری مطالعه آن می‌باشند (سورس پیوست شده شامل نمونه تکمیل شده این موارد نیز هست). همچنین از قسمت ایجاد دیتابیس از روی مدل نیز صرفنظر می‌شود و همانند قسمت‌های قبل است.


تعریف اعتبار سنجی دومین با کمک ویژگی‌ها (attributes)

فرض کنید می‌خواهیم بر روی طول نام و نام خانوادگی بیمار محدودیت قرار داده و آن‌ها را با کمک کتابخانه NHibernate Validator ، اعتبار سنجی کنیم. برای این منظور ابتدا فضای نام NHibernate.Validator.Constraints به کلاس بیمار اضافه شده و سپس با کمک ویژگی‌هایی که در این کتابخانه تعریف شده‌اند می‌توان قیود خود را به خواص کلاس تعریف شده اعمال نمود که نمونه‌ای از آن را مشاهده می‌نمائید:

using NHibernate.Validator.Constraints;

namespace NHSample5.Domain
{
public class Patient
{
public virtual int Id { get; set; }

[Length(Min = 3, Max = 20,Message="طول نام باید بین 3 و 20 کاراکتر باشد")]
public virtual string FirstName { get; set; }

[Length(Min = 3, Max = 60, Message = "طول نام خانوادگی باید بین 3 و 60 کاراکتر باشد")]
public virtual string LastName { get; set; }
}
}
اعمال این قیود از این جهت مهم هستند که نباید وقت برنامه و سیستم را با دریافت خطای نهایی از دیتابیس تلف کرد. آیا بهتر نیست قبل از اینکه اطلاعات به دیتابیس وارد شوند و رفت و برگشتی در شبکه صورت گیرد، مشخص گردد که این فیلد حتما نباید خالی باشد یا طول آن باید دارای شرایط خاصی باشد و امثال آن؟

مثالی دیگر:
جهت اجباری کردن و همچنین اعمال Regular expressions برای اعتبار سنجی یک فیلد می‌توان دو ویژگی زیر را به بالای آن فیلد مورد نظر افزود:

[NotNull]
[Pattern(Regex = "[A-Za-z0-9]+")]

تعریف اعتبار سنجی با کمک کلاس ValidationDef

راه دوم تعریف اعتبار سنجی، کمک گرفتن از کلاس ValidationDef این کتابخانه و استفاده از روش fluent configuration است. برای این منظور، پوشه جدیدی را به برنامه به نام Validation اضافه خواهیم کرد و سپس دو کلاس DoctorDef و PatientDef را به آن به صورت زیر خواهیم افزود:

using NHibernate.Validator.Cfg.Loquacious;
using NHSample5.Domain;

namespace NHSample5.Validation
{
public class DoctorDef : ValidationDef<Doctor>
{
public DoctorDef()
{
Define(x => x.Name).LengthBetween(3, 50);
Define(x => x.Patients).NotNullableAndNotEmpty();
}
}
}

using NHSample5.Domain;
using NHibernate.Validator.Cfg.Loquacious;

namespace NHSample5.Validation
{
public class PatientDef : ValidationDef<Patient>
{
public PatientDef()
{
Define(x => x.FirstName)
.LengthBetween(3, 20)
.WithMessage("طول نام باید بین 3 و 20 کاراکتر باشد");

Define(x => x.LastName)
.LengthBetween(3, 60)
.WithMessage("طول نام خانوادگی باید بین 3 و 60 کاراکتر باشد");
}
}
}

استفاده از قیودات تعریف شده به صورت دستی

می‌توان از این کتابخانه اعتبار سنجی به صورت مستقیم نیز اضافه کرد. روش انجام آن‌را در متد زیر مشاهده می‌نمائید.

/// <summary>
/// استفاده از اعتبار سنجی ویژه به صورت مستقیم
/// در صورت استفاده از ویژگی‌ها
/// </summary>
static void WithoutConfiguringTheEngine()
{
//تعریف یک بیمار غیر معتبر
var patient1 = new Patient() { FirstName = "V", LastName = "N" };
var ve = new ValidatorEngine();
var invalidValues = ve.Validate(patient1);
if (invalidValues.Length == 0)
{
Console.WriteLine("patient1 is valid.");
}
else
{
Console.WriteLine("patient1 is NOT valid!");
//نمایش پیغام‌های تعریف شده مربوط به هر فیلد
foreach (var invalidValue in invalidValues)
{
Console.WriteLine(
"{0}: {1}",
invalidValue.PropertyName,
invalidValue.Message);
}
}

//تعریف یک بیمار معتبر بر اساس قیودات اعمالی
var patient2 = new Patient() { FirstName = "وحید", LastName = "نصیری" };
if (ve.IsValid(patient2))
{
Console.WriteLine("patient2 is valid.");
}
else
{
Console.WriteLine("patient2 is NOT valid!");
}
}
ابتدا شیء ValidatorEngine تعریف شده و سپس متد Validate آن بر روی شیء بیماری غیر معتبر فراخوانی می‌گردد. در صورتیکه این عتبار سنجی با موفقیت روبر نشود، خروجی این متد آرایه‌ای خواهد بود از فیلدهای غیرمعتبر به همراه پیغام‌هایی که برای آن‌ها تعریف کرده‌ایم. یا می‌توان به سادگی همانند بیمار شماره دو، تنها از متد IsValid آن نیز استفاده کرد.

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

public static ValidatorEngine GetFluentlyConfiguredEngine()
{
var vtor = new ValidatorEngine();
var configuration = new FluentConfiguration();
configuration
.Register(
Assembly
.GetExecutingAssembly()
.GetTypes()
.Where(t => t.Namespace.Equals("NHSample5.Validation"))
.ValidationDefinitions()
)
.SetDefaultValidatorMode(ValidatorMode.UseExternal);
vtor.Configure(configuration);
return vtor;
}

FluentConfiguration آن مجزا است از نمونه مشابه کتابخانه Fluent NHibernate و نباید با آن اشتباه گرفته شود (در فضای نام NHibernate.Validator.Cfg.Loquacious تعریف شده است).
در این متد کلاس‌های قرار گرفته در پوشه Validation برنامه که دارای فضای نام NHSample5.Validation هستند، به عنوان کلاس‌هایی که باید اطلاعات لازم مربوط به اعتبار سنجی را از آنان دریافت کرد معرفی شده‌اند.
همچنین ValidatorMode نیز به صورت External تعریف شده و منظور از External در اینجا هر چیزی بجز استفاده از روش بکارگیری attributes است (علاوه بر امکان تعریف این قیودات در یک پروژه class library مجزا و مشخص ساختن اسمبلی آن در اینجا).

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

/// <summary>
/// استفاده از اعتبار سنجی ویژه به صورت مستقیم
/// در صورت تعریف آن‌ها با کمک
/// ValidationDef
/// </summary>
static void WithConfiguringTheEngine()
{
var ve2 = VeConfig.GetFluentlyConfiguredEngine();
var doctor1 = new Doctor() { Name = "S" };
if (ve2.IsValid(doctor1))
{
Console.WriteLine("doctor1 is valid.");
}
else
{
Console.WriteLine("doctor1 is NOT valid!");
}

var patient1 = new Patient() { FirstName = "وحید", LastName = "نصیری" };
if (ve2.IsValid(patient1))
{
Console.WriteLine("patient1 is valid.");
}
else
{
Console.WriteLine("patient1 is NOT valid!");
}

var doctor2 = new Doctor() { Name = "شمس", Patients = new List<Patient>() { patient1 } };
if (ve2.IsValid(doctor2))
{
Console.WriteLine("doctor2 is valid.");
}
else
{
Console.WriteLine("doctor2 is NOT valid!");
}
}

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


استفاده از قیودات تعریف شده و سیستم اعتبار سنجی به صورت یکپارچه با NHibernate

کتابخانه NHibernate Validator زمانیکه با NHibernate یکپارچه گردد دو رخداد PreInsert و PreUpdate آن‌را به صورت خودکار تحت نظر قرار داده و پیش از اینکه اطلاعات ثبت و یا به روز شوند، ابتدا کار اعتبار سنجی خود را انجام داده و اگر اعتبار سنجی مورد نظر با شکست مواجه شود، با ایجاد یک exception از ادامه برنامه جلوگیری می‌کند. در این حالت استثنای حاصل شده از نوع InvalidStateException خواهد بود.

برای انجام این مرحله یکپارچه سازی ابتدا متد BuildIntegratedFluentlyConfiguredEngine را به شکل زیر باید فراخوانی نمائیم:

/// <summary>
/// از این کانفیگ برای آغاز سشن فکتوری باید کمک گرفته شود
/// </summary>
/// <param name="nhConfiguration"></param>
public static void BuildIntegratedFluentlyConfiguredEngine(ref Configuration nhConfiguration)
{
var vtor = new ValidatorEngine();
var configuration = new FluentConfiguration();
configuration
.Register(
Assembly
.GetExecutingAssembly()
.GetTypes()
.Where(t => t.Namespace.Equals("NHSample5.Validation"))
.ValidationDefinitions()
)
.SetDefaultValidatorMode(ValidatorMode.UseExternal)
.IntegrateWithNHibernate
.ApplyingDDLConstraints()
.And
.RegisteringListeners();
vtor.Configure(configuration);

//Registering of Listeners and DDL-applying here
ValidatorInitializer.Initialize(nhConfiguration, vtor);
}
این متد کار دریافت Configuration مرتبط با NHibernate را جهت اعمال تنظیمات اعتبار سنجی به آن انجام می‌دهد. سپس از nhConfiguration تغییر یافته در این متد جهت ایجاد سشن فکتوری استفاده خواهیم کرد (در غیر اینصورت سشن فکتوری درکی از اعتبار سنجی‌های تعریف شده نخواهد داشت). اگر قسمت‌های قبل را مطالعه کرده باشید، کلاس SingletonCore را جهت مدیریت بهینه‌ی سشن فکتوری به خاطر دارید. این کلاس اکنون باید به شکل زیر وصله شود:

SingletonCore()
{
Configuration cfg = DbConfig.GetConfig().BuildConfiguration();
VeConfig.BuildIntegratedFluentlyConfiguredEngine(ref cfg);
//با همان کانفیگ تنظیم شده برای اعتبار سنجی باید کار شروع شود
_sessionFactory = cfg.BuildSessionFactory();
}

از این لحظه به بعد، نیاز به فراخوانی متدهای Validate و یا IsValid نبوده و کار اعتبار سنجی به صورت خودکار و یکپارچه با NHibernate انجام می‌شود. لطفا به مثال زیر دقت بفرمائید:

/// <summary>
/// استفاده از اعتبار سنجی یکپارچه و خودکار
/// </summary>
static void tryToSaveInvalidPatient()
{
using (Repository<Patient> repo = new Repository<Patient>())
{
try
{
var patient1 = new Patient() { FirstName = "V", LastName = "N" };
repo.Save(patient1);
}
catch (InvalidStateException ex)
{
Console.WriteLine("Validation failed!");
foreach (var invalidValue in ex.GetInvalidValues())
Console.WriteLine(
"{0}: {1}",
invalidValue.PropertyName,
invalidValue.Message);
log4net.LogManager.GetLogger("NHibernate.SQL").Error(ex);
}
}
}

/// <summary>
/// استفاده از اعتبار سنجی یکپارچه و خودکار
/// </summary>
static void tryToSaveValidPatient()
{
using (Repository<Patient> repo = new Repository<Patient>())
{
var patient1 = new Patient() { FirstName = "Vahid", LastName = "Nasiri" };
repo.Save(patient1);
}
}
در اینجا از کلاس Repository که در قسمت‌های قبل توسعه دادیم، استفاده شده است. در متد tryToSaveInvalidPatient ، بدلیل استفاده از تعریف بیماری غیرمعتبر، پیش از انجام عملیات ثبت، استثنایی حاصل شده و پیش از هرگونه رفت و برگشتی به دیتابیس، سیستم از بروز این مشکل مطلع خواهد شد. همچنین پیغام‌هایی را که هنگام تعریف قیودات مشخص کرده بودیم را نیز توسط آرایه ex.GetInvalidValues می‌توان دریافت کرد.

نکته:
اگر کار ساخت database schema را با کمک کانفیگ تنظیم شده توسط کتابخانه اعتبار سنجی آغاز کنیم، طول فیلدها دقیقا مطابق با حداکثر طول مشخص شده در قسمت تعاریف قیود هر یک از فیلدها تشکیل می‌گردد (حاصل از اعمال متد ApplyingDDLConstraints در متد BuildIntegratedFluentlyConfiguredEngine ذکر شده می‌باشد).

public static void CreateValidDb()
{
bool script = false;//آیا خروجی در کنسول هم نمایش داده شود
bool export = true;//آیا بر روی دیتابیس هم اجرا شود
bool dropTables = false;//آیا جداول موجود دراپ شوند

Configuration cfg = DbConfig.GetConfig().BuildConfiguration();
VeConfig.BuildIntegratedFluentlyConfiguredEngine(ref cfg);
//با همان کانفیگ تنظیم شده برای اعتبار سنجی باید کار شروع شود

new SchemaExport(cfg).Execute(script, export, dropTables);
}


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


نظرات مطالب
به دست آوردن اطلاعات کد اجراکننده یک متد
اضافه شدن ویژگی CallerArgumentExpression به C#10.0

ویژگی [CallerArgumentExpression] امکان دریافت آرگومان ارسالی به یک متد را به صورت رشته‌ای میسر می‌کند. برای مثال:
public static void LogExpression<T>(
    T value, 
    [CallerArgumentExpression("value")] string expression = null)
{
    Console.WriteLine($"{expression}: {value}");
}
با ورودی زیر:
var person = new Person("Vahid", "N.");
LogExpression(person.FirstName);
چنین خروجی را تولید می‌کند:
person.FirstName: Vahid

پارامتری که توسط ویژگی [CallerArgumentExpression] معرفی می‌شود، اختیاری بوده و به صورت خودکار توسط کامپایلر مقدار دهی می‌شود. یعنی کامپایلر فراخوانی فوق را به صورت زیر انجام می‌دهد:
LogExpression(person.FirstName, "person.FirstName");
وجود یک چنین قابلیتی، نویسندگان کتابخانه‌ها را از بکارگیری <<Expression<Func<T‌ها یا همان استاتیک ریفلکشن، رهایی می‌بخشد.

یک نمونه کاربرد دیگر آن در بررسی نال بودن مقدار پارامترهای ارسالی است که می‌توان آن‌ها را به صورت زیر خلاصه کرد:
public static void EnsureArgumentIsNotNull<T>(
   T value, 
   [CallerArgumentExpression("value")] string expression = null)
{
    if (value is null)
        throw new ArgumentNullException(expression);
}

public static void Foo(string name)
{
    EnsureArgumentIsNotNull(name); // if name is null, throws ArgumentNullException: "Value cannot be null. (Parameter 'name')"
    ...
}
پیشتر می‌بایستی با استفاده از nameof، نام پارامتر را مشخص کرد. در اینجا کامپایلر قادر است این مقدار را مشخص کند و دیگر نیازی به استفاده از روش زیر نیست:
    if (name is null)
    {
        throw new ArgumentNullException(nameof(name));
    }
البته NET 6.0. به همراه متد جدید زیر که از قابلیت فوق استفاده می‌کند، هست:
ArgumentNullException.ThrowIfNull(name);
و متد ThrowIfNull آن به صورت زیر تعریف شده‌است:
public static void ThrowIfNull(
    [NotNull] object? argument,
    [CallerArgumentExpression("argument")] string? paramName = null)

سؤال: چرا آرگومان اول این متد، هم nullable تعریف شده‌است و هم با ویژگی NotNull مزین گشته‌است؟
nullable بودن آن از این جهت است که ممکن است مقدار ارسالی به آن نال باشد. ویژگی NotNull آن به کامپایلر اعلام می‌کند که اگر این متد با موفقیت به پایان رسید، در سطرهای پس از آن، مقدار این شیء دیگر نال نیست و نیازی نیست تا به استفاده کنند اعلام کند که باید مراقب نال بودن آن باشد.
مطالب
ممنوعیت استفاده از کوکی‌های ثالث توسط مرورگرها و تاثیر آن بر روی اعتبارسنجی برنامه‌ها

کوکی‌ها به اندازه‌ی خود اینترنت قدیمی هستند و تا سال‌ها، تنها گزینه‌ی شخصی سازی تجربه‌ی کاربری در وب و انتقال آن از یک صفحه به صفحه‌ی دیگری به‌شمار می‌آمدند؛ به این نوع کوکی‌ها، First-party cookies هم می‌گویند و توسط خود سایت ارائه دهنده‌ی محتوا و برنامه‌ها، تنظیم می‌شوند. در مقابل آن، third-party cookies یا کوکی‌های ثالث هم وجود دارند که از طریق دومین دیگری بجز دومین اصلی برنامه، ارائه و تنظیم می‌شوند؛ به همین جهت به آن‌ها Cross-site cookies هم می‌گویند. یکی از اهداف کوکی‌های ثالث، ردیابی فعالیت کاربران در بین سایت‌های مختلف است و این روزها به علت سوء استفاده‌های زیادی که از آن‌ها می‌شوند،‌ یکی از وسایل به اشتراک‌گذاری و جمع آوری اطلاعات خصوصی کاربران شده‌اند.

البته کوکی‌های ثالث، کاربردهای مفیدی هم دارند؛ مانند امکان پیاده سازی لاگین و اعتبارسنجی یکپارچه‌ی بین چندین برنامه که به آن SSO یا Single Sign On هم گفته می‌شود؛ نمونه‌ی آن، استفاده از Identity server و یا OpenId dict در دنیای دات‌نت است.

اما ... در کل به علت مشکلات یاد شده، اکثر مرورگرها تصمیم به عدم پذیرش و پردازش آن‌ها گرفته‌اند. برای مثال مرورگر Safari سال‌ها است که اینگونه کوکی‌ها را بلاک می‌کند و یا مرورگر فایرفاکس، کوکی‌های ثالث ردیاب‌ها را بلاک می‌کند و ... مرورگر کروم نیز تصمیم گرفته‌است، تا پایان سال 2024، به این جمع محلق شود. هرچند اخیرا اعلام کرده‌اند، بجای اینکه مرورگر کروم، راسا این کار را انجام دهد، قرار است امکان انتخاب این گزینه به خود کاربر واگذار شود (چون ... خود گوگل از این نوع کوکی‌ها منتفع است!). البته این تصمیم، به‌نظر W3C خوش نیامده و اعلام کرده‌اند که این نوع کوکی‌ها باید بروند!

روش آزمایش برنامه‌ها برای بررسی تاثیر ممنوعیت کوکی‌های ثالث

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

chrome://flags/#test-third-party-cookie-phaseout

و سپس این گزینه را فعال کنید و یا روش دوم فعالسازی آن، اجرای مرورگر کروم با سوئیچ test-third-party-cookie-phaseout-- از طریق خط فرمان است.

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

اگر برنامه‌ی SSO شما، مبتنی بر اعتبارسنجی یکپارچه‌ی از نوع implicit flow است، حتما مشکل پیدا می‌کنید. در این حالت بهتر است به نوع امن‌تر authorization flow + PKCE‌ مهاجرت کنید.

علت مسدود شدن نوع اعتبارسنجی و احراز هویت implicit flow که در برنامه‌های تک صفحه‌ای وب (SPA/single-page apps) مرسوم است، شباهت بسیار زیاد آن به کاری است که ردیاب‌های اینترنتی انجام می‌دهند. در این‌حالت، سایت‌های ثالث، یک iframe مخفی را در پشت صحنه، درون سایت جاری باز کرده و توسط آن شروع به استفاده‌ی از امکانات مرتبط با کوکی‌ها می‌کنند. این الگو دقیقا همان کاری است که توسط implicit flow هم انجام می‌شود. بنابراین مرورگری که کوکی‌های ثالث از این دست را مسدود می‌کند، قابلیت اعتبارسنجی یکپارچه‌ی برنامه‌های SPA را هم غیرفعال خواهد کرد.

مطالب
ارتقاء پروژه‌های MSTest به نگارش 2 آن
زمانیکه در ویژوال استودیو 2015، یک Unit Test Project جدید را ایجاد می‌کنید:


پروژه‌ای را مبتنی بر نگارش قدیمی فریم ورک آزمون‌های واحد مایکروسافت و یا همان MSTest، ایجاد می‌کند. در ادامه روش ارتقاء این نوع پروژه‌ها را به نگارش 2 آن بررسی خواهیم کرد.


پیشنیازهای کار با MSTest 2.x

فریم ورک MSTest برای پشتیبانی از دات نت فریم ورک کامل و همچنین NET Core. ارتقاء یافته‌است و اینبار به صورت بسته‌های نیوگت ارائه می‌شود. بنابراین پس از ایجاد این نوع پروژه‌ها در یک پروژه‌ی از نوع دات نت فریم ورک کامل (NET 4.x.)، ابتدا نیاز است به ارجاعات پروژه مراجعه و سپس ارجاع به اسمبلی Microsoft.VisualStudio.QualityTools.UnitTestFramework را به صورت دستی حذف کرد.
پس از آن باید از طریق نیوگت، دو بسته‌ی جدید ذیل را نصب کرد:
PM> install-package MSTest.TestFramework -Pre
PM> install-package MSTest.TestAdapter -Pre
در اینجا ذکر سوئیچ pre، برای دریافت آخرین نگارش آن، الزامی است.


پشتیبانی از DataRow در MSTest 2.x

سایر فریم ورک‌های آزمون واحد، این امکان را فراهم می‌کنند تا بتوان بجای نوشتن چندین متد آزمون واحد برای بررسی پارامترهای مختلف ارسال شده‌ی به یک متد خاص، از طریق ویژگی‌ها بتوان این پارامترها را اعمال کرد. این قابلیت در MSTest 2.x با پشتیبانی از ویژگی جدید DataRow اضافه شده‌است:
[TestClass]
public class TestFrameworkTest
{
    [TestMethod]
    public void SimpleTest()
    {
        Assert.IsTrue(false);
    }

    [DataTestMethod]
    [DataRow(1, 2, 3)]
    [DataRow(2, 2, 4)]
    [DataRow(3, 2, 6)]
    [DataRow(5, 2, 7)]
    public void RowTest(int a, int b, int result)
    {
        Assert.AreEqual(result, a + b);
    }
}
- حالت پیش فرض آزمون‌های واحد MSTest را در متد SimpleTest آن مشاهده می‌کنید. متدی که دارای پارامتر نیست و با ویژگی TestMethod مزین شده‌است.
- در متد RowTest نوشته شده، نحوه‌ی بکارگیری ویژگی جدید DataRow را ملاحظه می‌کنید. در اینجا عملیات مدنظر، جمع زدن دو مقدار است. بجای اینکه 4 متد مختلف را برای بررسی اینکار تهیه کنیم، در اینجا می‌توان پارامترهای مورد نیاز را از طریق DataRow به متد RowTest ارسال کرد.


نحوه‌ی اجرای آزمون‌های MSTest

هرچند ReSharper قابلیت اجرای آزمون‌های MSTest را دارد، اما تا نگارش 2016.3 آن، از ویژگی جدید DataRow پشتیبانی نکرده و قادر به شناسایی آن‌ها نیست. بنابراین تنها روش اجرای این نوع آزمون‌ها، استفاده از همان روش استاندارد توکار ویژوال استودیو است و بسته‌ی MSTest.TestAdapter اضافه شده، آن‌را به روز رسانی می‌کند:
 Test Menu -> Windows -> Test Explorer


همانطور که ملاحظه می‌کنید، به ازای هربار قید ویژگی DataRow، یکبار آزمون واحد را به صورت جداگانه تکرار کرده‌است.
مطالب
اعتبار سنجی سمت کاربر wysiwyg-editor ها در ASP.NET MVC
تفاوتی نمی‌کند که از کدامیک از HTML Editorها یا به عبارتی wysiwyg-editorهای موجود، جهت ورود اطلاعات استفاده می‌کنید. هیچکدام از آن‌ها سبب فراخوانی اعتبارسنجی Required سمت کاربر نمی‌شوند. چرا؟
علت اینجا است که با فعال سازی wysiwyg-editorهای موجود، المان HTML پیش فرض مانند یک TextArea حالت مخفی پیدا می‌کند:


و اگر به سورس کد فایل jquery.validate.js مراجعه کنید، یک چنین تعریف پیش فرضی در آن موجود است:
$.extend( $.validator, {
defaults: {
ignore: ":hidden",
به این معنا که در حین اعتبارسنجی سمت کاربر، از کلیه المان‌های hidden بر روی صفحه صرفنظر خواهد شد.
برای رفع این مشکل کافی است بنویسیم:
 jQuery.validator.setDefaults({ ignore: ":hidden:not(textarea)" });
در اینجا پیش فرض‌های jQuery validator بازنویسی شده و در آن از textareaهای مخفی صرفنظر نمی‌شود.
اگر می‌خواهید کلا از چیزی صرفنظر نشود، مقدار آن‌را به "" تنظیم کنید.

مشکل دوم! متد required پیش فرض، فقط به رشته‌های خالی یا نال واکنش نشان می‌دهد. اما اگر کاربری در اینجا <p><br/></p> را وارد کرد، چطور؟
در این حالت نیاز است متد rqeuired پیش فرض jQuery validator را به نحو ذیل بازنویسی کنیم:
    <script type="text/javascript">
        // حذف تمام تگ‌های یک قطعه متن
        function removeAllTagsAndTrim(html) {
            return !html ? "" : jQuery.trim(html.replace(/(<([^>]+)>)/ig, ""));
        }

        // این تنظیم برای پردازش ادیتور مخفی وب لازم است
        jQuery.validator.setDefaults({ ignore: ":hidden:not(textarea)" });

        // متد اصلی اعتبارسنجی را ابتدا ذخیره می‌کنیم
        jQuery.validator.methods.originalRequired = jQuery.validator.methods.required;
        // نحوه بازنویسی متد توکار اعتبار سنجی جهت استفاده از یک متد سفارشی
        jQuery.validator.addMethod("required", function (value, element, param) {
            value = removeAllTagsAndTrim(value);
            if (!value) {
                return false;
            }
            //  فراخوانی متد اصلی اعتبار سنجی در صورت شکست تابع سفارشی
            return jQuery.validator.methods.originalRequired.call(this, value, element, param);
        }, jQuery.validator.messages.required);
    </script>
متد removeAllTagsAndTrim کلیه تگ‌های یک عبارت HTML ایی را حذف می‌کند. متد trim جی‌کوئری نیز سبب حذف فواصل خالی در ابتدا و انتهای یک رشته می‌شود. اکنون نیاز است این متد سفارشی را جهت تمیزسازی عبارت ورودی به متد توکار required اعمال کنیم.
برای اینکار نیاز است متد اصلی required را در جایی ذخیره کنیم تا در صورت شکست اعتبارسنجی سفارشی، همان متد اصلی فراخوانی گردد. در ادامه با فراخوانی jQuery.validator.addMethod و بکارگیری نام required، دقیقا همان متد اصلی required موجود بازنویسی خواهد شد. در ابتدای کار تگ‌های ورودی پاک شده و سپس اعتبارسنجی می‌شوند.
در ادامه فراخوانی متد اصلی required را ملاحظه می‌کنید. همچنین با استفاده از jQuery.validator.messages.required اصل پیام خطای تنظیم شده به کاربر نمایش داده خواهد شد.
مطالب
حذف نمودن کاراکتر های ناخواسته توسط Recursive CTE قسمت اول
شاید برایتان تا حالا پیش آمده باشد که بخواهید یکسری کاراکترهای ناخواسته و اضافه را از یک رشته حذف کنید. بطور مثال تمام کاراکتر هایی غیر عددی را باید از یک رشته حذف نمود تا آن رشته قابلیت تبدیل به نوع integer را بدست بیاورد.

اگر تعداد کاراکترهای ناخواسته محدود و مشخص هستند می‌توانید با دستور REPLACE آنها را حذف کنید، مثلا میخواهیم هر سه کاراکتر ~!@ از رشته حذف شوند:
DECLARE @s VARCHAR(50) = '~~~~~~!@@@@@@@ salam';
SET @s = REPLACE(REPLACE(REPLACE(@s, '~', ''), '!', ''), '@', '');
SELECT @s AS new_string

ولی هنگامی که کاراکتر‌ها نامحدود بوده امکان نوشتن تابع REPLACE به کرات بی معنا است در این حالت باید دنبال روشی پویا و تعمیم پذیر بود.
با جستجویی که در اینترنت انجام دادم متوجه شدم تکنیک WHILE یا همون loop یکی از روش‌های رایج برای انجام اینکار هست، که احتمالا به دلیل سهولت در بکارگیری و سادگی آن بوده که عمومیت پیدا کرده است.
مستقل از این صحبتها هدف معرفی یک روش مجموعه گرا (set-based) برای این مساله می‌باشد.

حذف کاراکترها ناخواسته با تکنیک Recursive CTE
راه حل بر اساس جدول زیر است:
CREATE TABLE test_string
(id integer not null primary key,
 string_value varchar(500) not null);
 
INSERT INTO test_string
VALUES (1, '@@@@ #### salam 12345'),
       (2, 'good $$$$$ &&&&&& bye 00000');

حالا فرض کنید می‌خواهیم هر کاراکتری غیر از حروف الفبای انگلیسی و فاصله(space) از رشته حذف شود. پس دو داده فوق به صورت salam و good bye در انتها در خواهند آمد.
برای حذف کاراکتر‌های ناخواسته فوق query زیر را اجرا کنید.

WITH CTE (ID, MyString, Ix) AS
(
    SELECT id,
           string_value,
           PATINDEX('%[^a-z ]%', string_value)                   
    FROM   test_string
     
    UNION ALL
     
    SELECT id,
           CAST(REPLACE(MyString, SUBSTRING(MyString,Ix , 1), '') AS VARCHAR(500)),
           PATINDEX('%[^a-z ]%', REPLACE(MyString, SUBSTRING(MyString,Ix , 1), ''))
    FROM   CTE
    WHERE  Ix > 0
)
SELECT *
FROM   cte
--WHERE  Ix = 0;
ORDER BY id, CASE WHEN Ix = 0 THEN 1 ELSE 0 END, Ix;

توضیح query:
در قسمت anchor اندیس اولین کاراکتر ناخواسته (خارج از رنج حروف الفبا و فاصله) بدست می‌آید. سپس در قسمت recursive هر کاراکتری که برابر باشد با کاراکتر ناخواسته ای که در مرحله قبل بدست آمده از رشته حذف می‌شود این عملیات توسط تابع replace صورت می‌گیرد و اندیس کاراکتر ناخواسته بعدی بعد از حذف کاراکتر ناخواسته قبلی بدست می‌آید که به مرحله بعد منتقل می‌شود. این مراحل تا آنجایی پیش می‌رود که دیگر کاراکتر ناخواسته ای در رشته وجود نداشته باشد.

به جدول زیر توجه بفرمایید (خروجی query فوق)

نتیجه مطلوب ما آن دو سطری است که در کادر بنفش هستند. که اگر به ستون Ix اشان توجه کنید مقدارش برابر با 0 است.

لطفا به سطر اول جدول توجه بفرمایید مشاهده می‌شود که هر 4 کاراکتر @ یکباره از رشته حذف شدند که بدلیل استفاده از تابع REPLACE میباشد.