مطالب
استفاده از Dialect سفارشی در NHibernate

Dialects در NHibernate کلاس‌هایی هستند جهت معرفی تعاریف ویژگی‌های خاص بانک‌های اطلاعاتی مختلف؛ مثلا SQL Server 2008 چه ویژگی‌های جدیدی دارد یا SQL Server CE 4.0 که جدیدا ارائه شده، امکان تعریف offset را در کوئری‌های خود میسر کرده (چیزی که قرار است در نگارش بعدی SQL Server اصلی(!) در دسترس باشد) ، اکنون چگونه می‌توان این ویژگی را فعال کرد (باید Dialect آن به روز شود و ... همین). یک سری Dialect از پیش تعریف شده هم برای اکثر بانک‌های اطلاعاتی در NHibernate وجود دارد. ممکن است این Dialects پیش فرض الزاما خواسته شما را برآورده نکنند یا مو به مو مستندات بانک‌ اطلاعاتی مرتبط را پیاده سازی نکرده باشند و سؤال این است که اکنون چه باید کرد؟ آیا باید حتما سورس‌ها را دستکاری و بعد کامپایل کرد؟ به این صورت هر بار با ارائه یک نگارش جدید NHibernate به مشکل برخواهیم خورد چون باید کل عملیات تکرار شود.
خبر خوب اینکه می‌توان از همین Dialects موجود ارث بری کرد، سپس مواردی را که نیاز داریم override کرده یا به کلاس مشتق شده افزود. اکنون می‌توان از این Dialect سفارشی به جای Dialect اصلی استفاده کرد. در ادامه با یک نمونه آشنا خواهیم شد.
فرض کنید Dialect انتخابی مرتبط است با SQL Server CE استاندارد. کوئری ساده زیر را می‌نویسیم، به ظاهر باید کار کند:
var list = session.Query<SomeClass>().Where(x=>x.Date.Year==2011).ToList();
اما کار نمی‌کند! علت این است که تمام Dialects در NHibernate از یک Dialect پایه مشتق شده‌اند. در این Dialect پایه، تعریف تابع استخراج year از یک تاریخ به نحو زیر است:
extract(year, ?1)
اما در SQL CE این تابع باید به صورت زیر تغییر کند تا کار کند:
datepart(year, ?1)
و ... این Override انجام نشده (تا نگارش فعلی آن). مهم نیست! خودمان انجام خواهیم داد! به صورت زیر:
using NHibernate;
using NHibernate.Dialect;
using NHibernate.Dialect.Function;

namespace Test1
{
public class CustomSqlCeDialect : MsSqlCeDialect
{
public CustomSqlCeDialect()
{
RegisterFunction("year", new SQLFunctionTemplate(NHibernateUtil.Int32, "datepart(year, ?1)"));
}
}
}
خوب تا اینجا ما یک Dialect جدید را با ارث بری از MsSqlCeDialect اصلی تولید کرده‌ایم. مرحله بعد نوبت به معرفی آن به NHibernate است. اینکار توسط Fluent NHibernate به سادگی زیر است:
var dbType = MsSqlCeConfiguration.Standard
...
.Dialect<CustomSqlCeDialect>();

پس از آن کوئری LINQ ابتدای بحث بدون مشکل اجرا خواهد شد چون اکنون می‌داند که بجای extract year ، باید از تابع datepart‌ استفاده کند.
مرحله بعد هم می‌تواند تهیه یک patch و ارسال به گروه اصلی برای به روز رسانی پروژه NH باشد.

نظرات مطالب
نحوه استفاده از ViewModel در ASP.NET MVC
سلاام ...
من تاجاییکه میتونستم چیزهایی که درباره viemodel  و automapper توی سایت بود رو خوندم ولی چیزی که میخواستم را پیدا نکردم شاید هم درک درستی ازش نداشتم در کل سوال سوال من از شما دوستان اینه : 
یک viewmodel دارم که از چندین جدول توی db ایجاد شده که در زیر نوشتمش : 
public class ResumeViewModel
    {
        public ResumeViewModel()
        {
            
        }
        public ResumeViewModel(IEnumerable<Resume> resum, IEnumerable<Work_Experience_Job_Seeker> workExperienceJobSeekerOfViewModel, IEnumerable<Job_Expertises> jobExpertisesOfViewModel, IEnumerable<Degrees_Work_Experience_Required> degreesWorkExperienceRequiredOfViewModel, IEnumerable<Specialized_Course> specializedCourseOfViewModel, IEnumerable<Book_Published> bookPublishedOfViewModel, IEnumerable<Basic_Table> basicTable)
        {
            ResumeOfViewModel = resum;
            WorkExperienceJobSeekerOfViewModel = workExperienceJobSeekerOfViewModel;
            JobExpertisesOfViewModel = jobExpertisesOfViewModel;
            DegreesWorkExperienceRequiredOfViewModel = degreesWorkExperienceRequiredOfViewModel;
            SpecializedCourseOfViewModel = specializedCourseOfViewModel;
            BookPublishedOfViewModel = bookPublishedOfViewModel;
            BasicTable = basicTable;
        }
        public IEnumerable<Resume> ResumeOfViewModel { get; set; }
        public IEnumerable<Work_Experience_Job_Seeker> WorkExperienceJobSeekerOfViewModel { get; set; }
        public IEnumerable<Job_Expertises> JobExpertisesOfViewModel { get; set; }
        public IEnumerable<Degrees_Work_Experience_Required> DegreesWorkExperienceRequiredOfViewModel { get; set; }
        public IEnumerable<Specialized_Course> SpecializedCourseOfViewModel { get; set; }
        public IEnumerable<Book_Published> BookPublishedOfViewModel { get; set; }
        public IEnumerable<Basic_Table> BasicTable { get; set; }

        public int NumberForm { get; set; } // EditResumes.chtml & ShowResumes.chtml  ===> baraye select kardan formha :D 

        }
و این viewmodel رو توی متد Edit Resume  استفاده کردم که متد get  بصورت زیر ::
        [HttpGet]
        public ActionResult EditResumes(int id)
        {
            var contex = new Final_My_ProjectEntities2();
            var res1 = contex.Resumes.Where(rec => rec.Resume_ID == id);
            var res2 = contex.Work_Experience_Job_Seeker.Where(rec => rec.Resume_ID == id).ToList();
            var res3 = contex.Job_Expertises.Where(rec => rec.Resume_ID == id).ToList();
            var res4 = contex.Degrees_Work_Experience_Required.Where(rec => rec.Resume_ID == id).ToList();
            var res5 = contex.Specialized_Course.Where(rec => rec.Resume_ID == id).ToList();
            var res6 = contex.Book_Published.Where(rec => rec.Resume_ID == id).ToList();
            var res12 = contex.Basic_Table.ToList();
            var viewModel = new ResumeViewModel(res1, res2, res3, res4, res5, res6,res12);
            var items = new SelectList(
                 new[]
                    {
                        new {Value = "1", Text = "فرم مهارت ها"},
                        new {Value = "2", Text = "فرم کتاب/مقالات منتشر شده"},
                        new {Value = "3", Text = "فرم سابقه کاری"},
                        new {Value = "4", Text = "فرم دوره‌های تخصصی گذرانده"},
                        new {Value = "5", Text = "فرم تخصص‌های شغلی"},
                        new {Value = "6", Text = "فرم مدارک تحصیلی"}
                    },
             "Value", "Text");

            ViewBag.Form = new SelectList(items, "Value", "Text");

            var res7 = contex.Basic_Table.Where(rec => rec.Domain == "MilitaryStatus").ToList();
            ViewBag.MilitaryStatus = new SelectList(res7, "Value", "Meaning",res1);

            var res8 = contex.Basic_Table.Where(rec => rec.Domain == "Sex");
            ViewBag.Sex = new SelectList(res8, "Value", "Meaning", res1);

            var res9 = contex.Basic_Table.Where(rec => rec.Domain == "MartialStatus").ToList();
            ViewBag.MartialStatus = new SelectList(res9, "Value", "Meaning", res1);

            var res10 = contex.Basic_Table.Where(rec => rec.Domain == "Degree").ToList();
            ViewBag.Degree = new SelectList(res10, "Value", "Meaning");

            var res11 = contex.Basic_Table.Where(rec => rec.Domain == "Ability").ToList();
            ViewBag.Ability = new SelectList(res11, "Value", "Meaning");


            return View(viewModel);
        }
و view  این متد بصورت زیر هست البته قسمتی از آن :: 
@model Final_My_Project.ViewModels.ResumeViewModel

@{
    ViewBag.Title = "ویرایش رزومه";
    ViewBag.PartOne = "فرم مهارت ها";
    ViewBag.PartTwo = "فرم کتاب/مقالات منتشر شده";
    ViewBag.Part3 = "فرم سابقه کاری";
    ViewBag.Part4 = "فرم دوره‌های تخصصی گذرانده";
    ViewBag.Part5 = "فرم تخصص‌های شغلی";
    ViewBag.Part6 = "فرم مدارک تحصیلی";

}
<h2 style="font-family:  Arial;">@ViewBag.Title</h2><br/>
   <script type="text/javascript">
       $(function () {
           $('#Gender').change(function () {
               var selectKind = $(this).find('option:selected').text();
               var divMilitary;
               if (selectKind == "زن") {
                   divMilitary = $('#Military');
                   divMilitary.hide();
                   divMilitary.css('display', 'none');
               }
               else if (selectKind == "مرد") {
                   divMilitary = $('#Military');
                   divMilitary.show();
                   divMilitary.css('display', 'block');

               }
           });
       });
</script>

<script type="text/javascript">
    $(function () {
        $('#SelectForm').change(function () {
            var selectFrom = $(this).find('option:selected').text();

            if (selectFrom == "فرم مهارت ها") {
                $('#PartOne').show();
                $('#PartOne').css('display', 'block');
                $('#PartTwo').hide();
                $('#PartTwo').css('display', 'none');
                $('#Part3').hide();
                $('#Part3').css('display', 'none');
                $('#Part4').hide();
                $('#Part4').css('display', 'none');
                $('#Part5').hide();
                $('#Part5').css('display', 'none');
                $('#Part6').hide();
                $('#Part6').css('display', 'none');


            }
            if (selectFrom == "فرم کتاب/مقالات منتشر شده") {
                $('#PartTwo').show();
                $('#PartTwo').css('display', 'block');
                $('#PartOne').show();
                $('#PartOne').css('display', 'none');
                $('#Part3').hide();
                $('#Part3').css('display', 'none');
                $('#Part4').hide();
                $('#Part4').css('display', 'none');
                $('#Part5').hide();
                $('#Part5').css('display', 'none');
                $('#Part6').hide();
                $('#Part6').css('display', 'none');

            }
            if (selectFrom == "فرم سابقه کاری") {

                $('#Part3').show();
                $('#Part3').css('display', 'block');
                $('#PartTwo').hide();
                $('#PartTwo').css('display', 'none');
                $('#PartOne').show();
                $('#PartOne').css('display', 'none');
                $('#Part4').hide();
                $('#Part4').css('display', 'none');
                $('#Part5').hide();
                $('#Part5').css('display', 'none');
                $('#Part6').hide();
                $('#Part6').css('display', 'none');


            }
            if (selectFrom == "فرم دوره‌های تخصصی گذرانده") {

                $('#Part4').show();
                $('#Part4').css('display', 'block');
                $('#PartTwo').hide();
                $('#PartTwo').css('display', 'none');
                $('#PartOne').show();
                $('#PartOne').css('display', 'none');
                $('#Part3').hide();
                $('#Part3').css('display', 'none');
                $('#Part5').hide();
                $('#Part5').css('display', 'none');
                $('#Part6').hide();
                $('#Part6').css('display', 'none');

            }
            if (selectFrom == "فرم تخصص‌های شغلی") {

                $('#Part5').show();
                $('#Part5').css('display', 'block');
                $('#PartTwo').hide();
                $('#PartTwo').css('display', 'none');
                $('#PartOne').show();
                $('#PartOne').css('display', 'none');
                $('#Part3').hide();
                $('#Part3').css('display', 'none');
                $('#Part4').hide();
                $('#Part4').css('display', 'none');
                $('#Part6').hide();
                $('#Part6').css('display', 'none');

            }

            if (selectFrom == "فرم مدارک تحصیلی") {

                $('#Part6').show();
                $('#Part6').css('display', 'block');
                $('#Part5').show();
                $('#Part5').css('display', 'none');
                $('#PartTwo').hide();
                $('#PartTwo').css('display', 'none');
                $('#PartOne').show();
                $('#PartOne').css('display', 'none');
                $('#Part3').hide();
                $('#Part3').css('display', 'none');
                $('#Part4').hide();
                $('#Part4').css('display', 'none');
            }

        });
    });

</script>

@Html.DropDownListFor(m=>m.NumberForm, (SelectList)ViewBag.Form, new { id = "SelectForm" })


@using (Html.BeginForm())
{
    @Html.ValidationSummary(true)

        <div id="PartOne" >
    <h3 style="font-family: Arial; color: #008080; font-weight: bold; ">@ViewBag.PartOne</h3><br/>
    @foreach (var item in Model.ResumeOfViewModel)
    {
        <table dir="rtl">
            <tr>
                <td>
                    @Html.Label("عنوان رزومه")
                </td>
                <td>
                    @Html.TextBoxFor(model =>item.Title_Of_Resume  , new {@class = "text", style = "width:100 px"})
                    @Html.ValidationMessageFor(model => item.Title_Of_Resume)
                </td>
            </tr>
            <tr>
                <td>
        <div id="Gender" >
        @Html.Label("نوع جنسیت")
         @Html.DropDownList("نوع جنسیت", new SelectList(ViewBag.Sex, 
                       "Value", "Text", item.Sex_ID == 0 ? 0 : item.Sex_ID))
        @Html.ValidationMessageFor(model => item.Sex_ID)
        </div>
    </td>

                <td>
                    <div >
                        @Html.Label("وضعیت تاهل")
         @Html.DropDownList("وضعیت تاهل", new SelectList(ViewBag.MartialStatus, 
                       "Value", "Text", item.Martial_Status_ID == 0 ? 0 : item.Martial_Status_ID))
                        @Html.ValidationMessageFor(model => item.Martial_Status_ID)
                    </div>

                </td>
            </tr>

            <tr id="Military" style="display: none;">
                <td>
                    @Html.Label("وضعیت نظام وظیفه")
                </td>
                <td>
         @Html.DropDownList("وضعیت نظام وظیفه", new SelectList(ViewBag.MilitaryStatus,
                           "Value", "Text", item.Military_Status_ID == 0 ? 0 : item.Military_Status_ID), new { id = "Gender" })
                    @Html.ValidationMessageFor(model => item.Military_Status_ID)
                </td>

            </tr>

            <tr>
                <td>
                    @Html.Label("آشنایی با رایانه")
                </td>
                <td>
                    @Html.DropDownListFor(model => item.Knowledge_Of_Computers_ID, (SelectList)ViewBag.Ability)
                    @Html.ValidationMessageFor(model => item.Knowledge_Of_Computers_ID)
                </td>
            </tr>

            <tr>
                <td>
                    @Html.Label("آشنایی با امور اداری و دفتری")
                </td>
                <td>
                    @Html.DropDownListFor(model => item.Knowledge_Administrative_and_Clerical_ID, (SelectList) ViewBag.Ability)
                    @Html.ValidationMessageFor(model => item.Knowledge_Administrative_and_Clerical_ID)
                </td>
            </tr>

            <tr>
                <td>
                    @Html.Label("آشنایی با زبان انگلیسی")
                </td>
                <td>
                    @Html.DropDownListFor(model => item.Knowledge_Of_English_ID, (SelectList) ViewBag.Ability)
                    @Html.ValidationMessageFor(model => item.Knowledge_Of_English_ID)
                </td>
            </tr>

            <tr>
                <td>
                    @Html.Label("آشنایی با زبان عربی")
                </td>
                <td>
                    @Html.DropDownListFor(model => item.Knowledge_Of_Arabic_ID, (SelectList) ViewBag.Ability)
                    @Html.ValidationMessageFor(model => item.Knowledge_Of_Arabic_ID)
                </td>
            </tr>

            <tr>
                <td>
                    @Html.Label("آشنایی با ماکروسافت آفیس")
                </td>
                <td>
                    @Html.DropDownListFor(model =>item.Knowledge_Of_Office_ID, (SelectList) ViewBag.Ability)
                    @Html.ValidationMessageFor(model => item.Knowledge_Of_Office_ID)
                </td>
            </tr>

            <tr>
                <td>  
                    @Html.Label("آشنایی با امور مالی و حسابداری")
                </td>
                <td>
                    @Html.DropDownListFor(model =>item.Knowledge_Of_Finance_ID, (SelectList) ViewBag.Ability)
                    @Html.ValidationMessageFor(model => item.Knowledge_Of_Finance_ID)
                </td>
            </tr>

            <tr>
                <td>
                    @Html.Label("آشنایی با مدیریت")
                </td>
                <td>
                    @Html.DropDownListFor(model =>item.Knowledge_Of_Manage_ID, (SelectList) ViewBag.Ability)
                    @Html.ValidationMessageFor(model =>item.Knowledge_Of_Manage_ID)
                </td>
            </tr>

            <tr>
                <td>
                    @Html.Label("گواهینامه رانندگی پایه یک")

                </td>
                <td>
                    @Html.DropDownListFor(model =>item.Driving_license_One_ID, (SelectList) ViewBag.Ability)
                    @Html.ValidationMessageFor(model =>item.Driving_license_One_ID)
                </td>
            </tr>

            <tr>
                <td>
                    @Html.Label("گواهینامه رانندگی پایه دو")
                </td>
                <td>
                    @Html.DropDownListFor(model =>item.Driving_license_Two_ID, (SelectList) ViewBag.Ability)
                    @Html.ValidationMessageFor(model =>item.Driving_license_Two_ID)
                </td>
            </tr>

            <tr>
                <td>
                    @Html.Label("گواهینامه رانندگی پایه موتورسیکلت")
                </td>
                <td>
                    @Html.DropDownListFor(model =>item.Certificate_Motor_ID, (SelectList) ViewBag.Ability)
                    @Html.ValidationMessageFor(model =>item.Certificate_Motor_ID)
                </td>
            </tr>

            <tr>
                <td>
                    @Html.Label("ماشین شخصی")
                </td>
                <td>
                    @Html.DropDownListFor(model =>item.Personal_Vehicle_ID, (SelectList) ViewBag.Ability)
                    @Html.ValidationMessageFor(model =>item.Personal_Vehicle_ID)
                </td>
            </tr>

            <tr>
                <td>
                    @Html.Label("روابط عمومی")
                </td>
                <td>
                    @Html.DropDownListFor(model =>item.Public_Relationship_ID, (SelectList) ViewBag.Ability)
                    @Html.ValidationMessageFor(model =>item.Public_Relationship_ID)
                </td>
            </tr>

            <tr>
                <td>
                    @Html.Label("دیگر توانایی ها")

                </td>
                <td>
                    @Html.EditorFor(model =>item.Etc_Ability)
                    @Html.ValidationMessageFor(model =>item.Etc_Ability)
                </td>
            </tr>

        </table>
    }
        </div>

    
        <p>
            <input type="submit" value="Save" onclick="return confirm('از ثبت اطلاعات مطمئن هستید؟')" />
        </p>
}

<div>
    @Html.ActionLink("بازگشت به مدیریت رزومه ها", "ManageOfResumes")
</div>

@section scripts {
    @Scripts.Render("~/bundles/jqueryval")
}
و مشکل اینجاست که بعد از ثبت اطلاعات وقتی به متد post میره مقدارش null  هستش ... درحالیکه فقط در صورت edit  اینجوریه وقتی از همین viewmodel  برای مشاهده رزومه که فقط گزارشگیریه استفاده میکنم نتیجه را میبینم ولی اینجا نه !
متد پست بصورت زیر هستش ... اگر میدونید چطور و چی کار کنم که این درست شه ممنون میشم ... چون دیگه نمیدونم تو متد پست چی بنویسم ... منتظر جوابم ... که چرا null میده و اینکه تو متد پست چطور اینارو ذخیره کنم توی db .../؟
        [HttpPost]
        public ActionResult EditResumes(ResumeViewModel model) // model null mishe ! CHERAA??!
        {
            var contex = new Final_My_ProjectEntities2();
            try
            {
                if (ModelState.IsValid)
                {

                    // Code... che cody?
                    contex.SaveChanges();
                }

            }
            catch (Exception)
            {

                ViewBag.wrong = "لطفا داده‌های ورودی را بررسی نمایید";
            }
            return View(model);

        }

مطالب
استفاده از MVVM زمانیکه امکان Binding وجود ندارد

ساده‌ترین تعریف MVVM، نهایت استفاده از امکانات Binding موجود در WPF و Silverlight است. اما خوب، همیشه همه چیز بر وفق مراد نیست. مثلا کنترل WebBrowser را در WPF در نظر بگیرید. فرض کنید که می‌خواهیم خاصیت Source آن‌را در ViewModel مقدار دهی کنیم تا صفحه‌ای را نمایش دهد. بلافاصله با خطای زیر متوقف خواهیم شد:

A 'Binding' cannot be set on the 'Source' property of type 'WebBrowser'.
A 'Binding' can only be set on a DependencyProperty of a DependencyObject.

بله؛ این خاصیت از نوع DependencyProperty نیست و نمی‌توان چیزی را به آن Bind کرد. بنابراین این نکته مهم را توسعه دهنده‌های کنترل‌های WPF و Silverlight همیشه باید بخاطر داشته باشند که اگر قرار است کنترل‌های شما MVVM friendly باشند باید کمی بیشتر زحمت کشیده و بجای تعریف خواص ساده دات نتی، خواص مورد نظر را از نوع DependencyProperty تعریف کنید.
الان که تعریف نشده چه باید کرد؟
پاسخ متداول آن این است: مهم نیست؛ خودمان می‌توانیم این‌کار را انجام دهیم! یک Attached property یا به عبارتی یک Behavior را تعریف و سپس به کمک آن عملیات Binding را میسر خواهیم ساخت. برای مثال:
در این Attached property قصد داریم یک خاصیت جدید به نام BindableSource را جهت کنترل WebBrowser تعریف کنیم:

using System;
using System.Windows;
using System.Windows.Controls;

namespace WebBrowserSample.Behaviors
{
public static class WebBrowserBehaviors
{
public static readonly DependencyProperty BindableSourceProperty =
DependencyProperty.RegisterAttached("BindableSource",
typeof(object),
typeof(WebBrowserBehaviors),
new UIPropertyMetadata(null, BindableSourcePropertyChanged));

public static object GetBindableSource(DependencyObject obj)
{
return (string)obj.GetValue(BindableSourceProperty);
}

public static void SetBindableSource(DependencyObject obj, object value)
{
obj.SetValue(BindableSourceProperty, value);
}

public static void BindableSourcePropertyChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
{
WebBrowser browser = o as WebBrowser;
if (browser == null) return;

Uri uri = null;

if (e.NewValue is string)
{
var uriString = e.NewValue as string;
uri = string.IsNullOrWhiteSpace(uriString) ? null : new Uri(uriString);
}
else if (e.NewValue is Uri)
{
uri = e.NewValue as Uri;
}

if (uri != null) browser.Source = uri;
}
}
}


یک مثال ساده از استفاده‌ی آن هم به صورت زیر می‌تواند باشد:
ابتدا ViewModel مرتبط با فرم برنامه را تهیه خواهیم کرد. اینجا چون یک خاصیت را قرار است Bind کنیم، همینجا داخل ViewModel آن‌را تعریف کرده‌ایم. اگر تعداد آن‌ها بیشتر بود بهتر است به یک کلاس مجزا مثلا GuiModel منتقل شوند.

using System;
using System.ComponentModel;

namespace WebBrowserSample.ViewModels
{
public class MainWindowViewModel : INotifyPropertyChanged
{
Uri _sourceUri;
public Uri SourceUri
{
get { return _sourceUri; }
set
{
_sourceUri = value;
raisePropertyChanged("SourceUri");
}
}

public MainWindowViewModel()
{
SourceUri = new Uri(@"C:\path\arrow.png");
}

#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
void raisePropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler == null) return;
handler(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
}

در ادامه بجای استفاده از خاصیت Source که قابلیت Binding ندارد، از Behavior سفارشی تعریف شده استفاده خواهیم کرد. ابتدا باید فضای نام آن تعریف شود، سپس BindableSource مرتبط آن در دسترس خواهد بود:

<Window x:Class="WebBrowserSample.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:VM="clr-namespace:WebBrowserSample.ViewModels"
xmlns:B="clr-namespace:WebBrowserSample.Behaviors"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<VM:MainWindowViewModel x:Key="vmMainWindowViewModel" />
</Window.Resources>
<Grid DataContext="{Binding Source={StaticResource vmMainWindowViewModel}}">
<WebBrowser B:WebBrowserBehaviors.BindableSource="{Binding SourceUri}" />
</Grid>
</Window>



نمونه مشابه این مورد را در مثال «استفاده از کنترل‌های Active-X در WPF» پیشتر در این سایت دیده‌اید.

مطالب
کار با نوع داده‌ی HierarchyID توسط Entity framework
نوع داده‌ی HierarchyID به همراه SQL Server 2008 برای کار با داده‌هایی با ساختار درختی ارائه شد. در حال حاضر هیچکدام از ORMهای موجود، پشتیبانی رسمی را از این نوع داده به عمل نمی‌آورند؛ اما با توجه به سورس باز بودن Entity framework، یک Fork مستقل از آن تهیه شده‌است و این نوع داده‌ی جدید به همراه متدهای مرتبط با آن، به این Fork اضافه شده‌اند.
- اصل Fork در اینجا
- تاریخچه‌ی این Fork غیر رسمی در اینجا
- بسته‌ی نیوگت آن در اینجا

چون تیم EF در نگارش فعلی این کتابخانه حاضر به افزودن این نوع جدید نشده‌است، بنابراین بجای بسته‌ی اصلی Entity framework نیاز است بسته‌ی EntityFrameworkWithHierarchyId را نصب کنید.
 PM> install-package EntityFrameworkWithHierarchyId

یک تذکر مهم:
چون امضای دیجیتال این بسته، با امضای دیجیتال بسته‌ی اصلی EF یکی نیست، اگر پروژه‌ی شما صرفا از EF استفاده می‌کند، مشکلی نخواهید داشت. اما اگر برای مثال از ASP.NET Identity کامپایل شده‌ی برای کار با EF اصلی استفاده کنید، پیام یافت نشدن DLL مرتبط را دریافت خواهید کرد.


تعریفی مدلی با خاصیتی از نوع جدید HierarchyId

public class Employee
{
    public int Id { get; set; }
 
    [Required, MaxLength(100)]
    public string Name { get; set; }
 
    [Required]
    public HierarchyId Node { get; set; } // نوع داده جدید
}
در اینجا مدلی را ملاحظه می‌کنید که از نوع داده‌ی جدید HierarchyId استفاده می‌کند. همانطور که عنوان شد این نوع در بسته‌ی EntityFrameworkWithHierarchyId موجود است.


تعریف Context و مقدار دهی اولیه‌ی آن

در این حالت Context برنامه به همراه تنظیمات اولیه‌ی Migrations آن یک چنین شکلی را پیدا خواهد کرد:
public class MyContext : DbContext
{
    public DbSet<Employee> Employees { get; set; }
 
    public MyContext()
        : base("Connection1")
    {
        this.Database.Log = log => Console.WriteLine(log);
    }
}
 
public class Configuration : DbMigrationsConfiguration<MyContext>
{
    public Configuration()
    {
        AutomaticMigrationsEnabled = true;
        AutomaticMigrationDataLossAllowed = true;
    }
 
    protected override void Seed(MyContext context)
    {
        if (context.Employees.Any())
            return;
 
        context.Database.ExecuteSqlCommand(
            "ALTER TABLE [dbo].[Employees] ADD NodePath as Node.ToString() persisted");
        context.Database.ExecuteSqlCommand(
            "ALTER TABLE [dbo].[Employees] ADD Level AS Node.GetLevel() persisted");
        context.Database.ExecuteSqlCommand(
            "ALTER TABLE [dbo].[Employees] ADD ManagerNode as Node.GetAncestor(1) persisted");
        context.Database.ExecuteSqlCommand(
            "ALTER TABLE [dbo].[Employees] ADD ManagerNodePath as Node.GetAncestor(1).ToString() persisted");
 
        context.Database.ExecuteSqlCommand(
            "ALTER TABLE [dbo].[Employees] ADD CONSTRAINT [UK_EmployeeNode] UNIQUE NONCLUSTERED (Node)");
        context.Database.ExecuteSqlCommand(
            "ALTER TABLE [dbo].[Employees]  WITH CHECK ADD CONSTRAINT [EmployeeManagerNodeNodeFK] " +
            "FOREIGN KEY([ManagerNode]) REFERENCES [dbo].[Employees] ([Node])");
 
        context.Employees.Add(new Employee { Name = "Root", Node = new HierarchyId("/") });
        context.Employees.Add(new Employee { Name = "Emp1", Node = new HierarchyId("/1/") });
        context.Employees.Add(new Employee { Name = "Emp2", Node = new HierarchyId("/2/") });
        context.Employees.Add(new Employee { Name = "Emp3", Node = new HierarchyId("/1/1/") });
        context.Employees.Add(new Employee { Name = "Emp4", Node = new HierarchyId("/1/1/1/") });
        context.Employees.Add(new Employee { Name = "Emp5", Node = new HierarchyId("/2/1/") });
        context.Employees.Add(new Employee { Name = "Emp6", Node = new HierarchyId("/1/2/") });
 
        base.Seed(context);
    }
}
در اینجا نحوه‌ی تعریف رکوردهای جدید مبتنی بر HierarchyId را مشاهده می‌کنید که توسط آن‌ها تعدادی کارمند، در یک سازمان فرضی ثبت شده‌اند.
همچنین چند فیلد محاسباتی نیز بر اساس امکانات توکار SQL Server اضافه شده‌اند. متدهایی مانند ToString، GetLevel، GetAncestor و امثال آن جزئی از پیاده سازی توکار SQL Server هستند. همچنین این متدها توسط کتابخانه‌ی EntityFrameworkWithHierarchyId نیز ارائه شده‌اند.


کوئری نویسی

مرتب سازی رکوردها بر اساس HierarchyId آن‌ها

using (var context = new MyContext())
{
    Console.WriteLine("\ngetItems OrderByDescending(employee => employee.Node)");
 
    var employees = context.Employees.OrderByDescending(employee => employee.Node).ToList();
    foreach (var employee in employees)
    {
        Console.WriteLine("{0} {1}", employee.Id, employee.Node);
    }
 }
با این خروجی
SELECT
    [Extent1].[Id] AS [Id],
    [Extent1].[Name] AS [Name],
    [Extent1].[Node] AS [Node]
    FROM [dbo].[Employees] AS [Extent1]
    ORDER BY [Extent1].[Node] DESC


6 /2/1/
3 /2/
7 /1/2/
5 /1/1/1/
4 /1/1/
2 /1/
1 /


یافتن یک HierarchyId خاص و سپس یافتن کلیه‌ی فرزندان آن در یک سطح پایین‌تر

using (var context = new MyContext())
{
    Console.WriteLine("\nGetAncestor(1) of /1/");
 
    var firstItem = context.Employees.Single(employee => employee.Node == new HierarchyId("/1/"));
    foreach (var item in context.Employees.Where(employee => firstItem.Node == employee.Node.GetAncestor(1)))
    {
        Console.WriteLine("{0} {1}", item.Id, item.Name);
    }
}
این کوئری را به این شکل نیز می‌توان عنوان کرد: یافتن یک HierarchyId و سپس یافتن کلیه نودهایی که والدشان (GetAncestor) این HierarchyId است. عدد یک در اینجا مشخص کننده‌ی Level یا سطح است.
با این خروجی:
SELECT TOP (2)
    [Extent1].[Id] AS [Id],
    [Extent1].[Name] AS [Name],
    [Extent1].[Node] AS [Node]
    FROM [dbo].[Employees] AS [Extent1]
    WHERE cast('/1/' as hierarchyid) = [Extent1].[Node]

SELECT
    [Extent1].[Id] AS [Id],
    [Extent1].[Name] AS [Name],
    [Extent1].[Node] AS [Node]
    FROM [dbo].[Employees] AS [Extent1]
    WHERE (@p__linq__0 = ([Extent1].[Node].GetAncestor(1))) OR ((@p__linq__0 IS
NULL) AND ([Extent1].[Node].GetAncestor(1) IS NULL))
-- p__linq__0: '/1/' (Type = Object)

4 Emp3
7 Emp6

کوئری‌های فوق را می‌توان بجای استفاده از متد GetAncestor، با استفاده از متد IsDescendantOf به شکل زیر نیز نوشت:
var list = context.Employees.Where(
          employee => employee.Node.IsDescendantOf(new HierarchyId("/1/")) &&
                              employee.Node.GetLevel() == 2).ToList();
با این خروجی SQL (یک کوئری بجای دو کوئری):
SELECT
    [Extent1].[Id] AS [Id],
    [Extent1].[Name] AS [Name],
    [Extent1].[Node] AS [Node]
    FROM [dbo].[Employees] AS [Extent1]
    WHERE (([Extent1].[Node].IsDescendantOf(cast('/1/' as hierarchyid))) = 1) 
    AND (2 = ([Extent1].[Node].GetLevel()))


جابجا کردن نودها توسط متد GetReparentedValue

در کوئری ذیل، تمامی فرزندان ریشه‌ی /1/ یافت شده و سپس والد آن‌ها به صورت پویا تغییر داده می‌شود:
var items = context.Employees.Where(employee => employee.Node.IsDescendantOf(new HierarchyId("/1/")))
    .Select(employee => new
    {
        Id = employee.Id,
        OrigPath = employee.Node,
        ReparentedValue = employee.Node.GetReparentedValue(new HierarchyId("/1/"), HierarchyId.GetRoot()),
        Level = employee.Node.GetLevel()
    }).ToList();
 
foreach (var item in items)
{
    Console.WriteLine("Id:{0}; OrigPath:{1}; ReparentedValue:{2}; Level:{3}", item.Id, item.OrigPath, item.ReparentedValue, item.Level);
}
با این خروجی
SELECT
    [Extent1].[Id] AS [Id],
    [Extent1].[Node] AS [Node],
    [Extent1].[Node].GetReparentedValue(cast('/1/' as hierarchyid), hierarchyid::GetRoot()) AS [C1],
    [Extent1].[Node].GetLevel() AS [C2]
    FROM [dbo].[Employees] AS [Extent1]
    WHERE ([Extent1].[Node].IsDescendantOf(cast('/1/' as hierarchyid))) = 1


Id:2; OrigPath:/1/; ReparentedValue:/; Level:1
Id:4; OrigPath:/1/1/; ReparentedValue:/1/; Level:2
Id:5; OrigPath:/1/1/1/; ReparentedValue:/1/1/; Level:3
Id:7; OrigPath:/1/2/; ReparentedValue:/2/; Level:2

کدهای کامل این مثال را از اینجا می‌توانید دریافت کنید
HierarcyIdTests.zip
مطالب
Serialization #2

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

[DataContract]
public class News
{
     [DataMember] public int Id;
     [DataMember] public string Body;
     [DataMember] public DateTime NewsDate;
}

با Override  کردن  [DataContract]به صورت [("DataContract(Name=”MyCustomNews] می‌توان نام ریشه XML فایل را به MyCustomNews  تغییر داد. همچنین با Override  کردن [DataMember]  بصورت [("DataMember(Name=”MyCustomFieldName]  می‌شود به هر فیلدی عنوان دلخواهی داد و همچنین  با تعیین عبارت  NameSpace به صورت [("DataContract(Name = "MyCustomNews", Namespace = "http://www.my.com] می‌شود فضای نام را تغییر داد که با این تغییرات، خروجی زیر حاصل می‌شود: 

<?xml version="1.0" encoding="utf-8"?>
<MyCustomNews xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.my.com">
  <Body>NewsBody</Body>
  <MyCustomFieldName>111</MyCustomFieldName>
  <NewsDate>2012-10-04T00:00:00</NewsDate>
</MyCustomNews>


ویژگی [DataMember] هم ازفیلدها و هم از propertyها، پشتیبانی می‌کند، خواه عمومی باشند یا خصوصی و  نوع فیلد یا Property  می‌تواند به یکی از اشکال زیر باشد:

  1. انواع اولیه .
  2. انواع DateTime ،TimeSpan، Guid ،Uri و انواع Enum
  3. انواع پوچ پذیر هر کدام از موارد بالا
  4. نوع byte[]
  5. انواع تعریف شده توسط کاربر که توسط صفت [DataContract] محصور شده‌اند.
  6. هر نوع IEnumerable
  7. هر نوعی که با صفت [Serializable] محصور شود و یا اینترفیس ISerializable را پیاده سازی کند.
  8. هر نوعی که اینترفیس IXmlSerializble را پیاده سازی نماید.

تعیین فرمت باینری برای سریالی‌کردن:

برای سریالی کننده‌های  DataContractSerializer و NetDataContractSerializer می‌توان به روش زیر  فرمت خروجی را به شکل فرمت باینری درآورد که خروجی آن تاحد زیادی کوچک‌تر و کم حجم‌تر می‌شود:

var s = new MemoryStream();
using (XmlDictionaryWriter w=XmlDictionaryWriter.CreateBinaryWriter(s))
{
         ds.WriteObject(w,news);
}

و برای Deserialize کردن آن به شیوه زیر عمل می‌کنیم:

var s2 = new MemoryStream(s.ToArray());
News deserializednews;
using (XmlDictionaryReader r=XmlDictionaryReader.CreateBinaryReader(s2,XmlDictionaryReaderQuotas.Max))
{
     deserializednews = (News)ds.ReadObject(r);
}

که در آن از ویژگی Max  کلاس XmlDictionaryReaderQuotas  برای به دست آوردن حداکثر سهمیه فضای دیسک مربوط به  XmlDictionaryReaders  استفاده می‌شود. 

مطالب
آشنایی با Refactoring - قسمت 6

در ادامه بحث «حذف کدهای تکراری»، روش Refactoring دیگری به نام "Extract Superclass" وجود دارد که البته در بین برنامه نویس‌های دات نت به نام Base class بیشتر مشهور است تا Superclass. هدف آن هم انتقال کدهای تکراری بین چند کلاس، به یک کلاس پایه و سپس ارث بری از آن می‌باشد.

یک مثال:
در WPF و Silverlight جهت مطلع سازی رابط کاربری از تغییرات حاصل شده در مقادیر داده‌ها، نیاز است کلاس مورد نظر، اینترفیس INotifyPropertyChanged را پیاده سازی کند:

using System.ComponentModel;

namespace Refactoring.Day6.ExtractSuperclass.Before
{
public class User : INotifyPropertyChanged
{
string _name;
public string Name
{
get { return _name; }
set
{
if (_name == value) return;
_name = value;
raisePropertyChanged("Name");
}
}

public event PropertyChangedEventHandler PropertyChanged;
void raisePropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler == null) return;
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}


و نکته‌ی مهم این است که اگر 100 کلاس هم داشته باشید، باید این کدهای تکراری اجباری مرتبط با raisePropertyChanged را در آن‌ها قرار دهید. به همین جهت مرسوم است برای کاهش حجم کدهای تکرای، قسمت‌های تکراری کد فوق را در یک کلاس پایه قرار می‌دهند:

using System.ComponentModel;

namespace Refactoring.Day6.ExtractSuperclass.After
{
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler == null) return;
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}

و سپس از آن ارث بری می‌کنند:

namespace Refactoring.Day6.ExtractSuperclass.After
{
public class User : ViewModelBase
{
string _name;
public string Name
{
get { return _name; }
set
{
if (_name == value) return;
_name = value;
RaisePropertyChanged("Name");
}
}
}
}


به این ترتیب این کلاس پایه در ده‌ها و صدها کلاس قابل استفاده خواهد بود، بدون اینکه مجبور شویم مرتبا یک سری کد تکراری «اجباری» را copy/paste کنیم.

مثالی دیگر:
اگر با ORM های Code first کار کنید، نیاز است تا ابتدا طراحی کار توسط کلاس‌های ساده دات نتی انجام شود؛ که اصطلاحا به آن‌ها POCO یا Plain old CLR objects یا Plain old .NET Classes هم گفته می‌شود. در بین این کلاس‌ها، متداول است که یک سری از خصوصیات، تکراری و مشترک باشد؛ مثلا تمام کلاس‌ها تاریخ ثبت رکورد را هم داشته باشند به همراه نام کاربر و مشخصاتی از این دست. اینجا هم برای حذف کدهای تکراری، یک Base class طراحی می‌شود: (+)

مطالب
Defensive Programming - بازگشت نتایج قابل پیش بینی توسط متدها
در این مطلب یکی از اهداف Defensive Programming تحت عنوان Predictability مرتبط با متدها را بررسی کرده و تمرکز اصلی، بر روی مقدار بازگشتی متدها خواهد بود. 
پیش نیازها
به طور کلی، نتیجه حاصل از اجرای یک متد می‌تواند یکی از حالت‌های زیر باشد:

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


متد ValidateEmail با خروجی Boolean

        public bool ValidateEmail(string email)
        {
            var valid = true;
            if (string.IsNullOrWhiteSpace(email))
            {
                valid = false;
            }

            var isValidFormat = true;//todo: using RegularExpression
            if (!isValidFormat)
            {
                valid = false;
            }

            var isRealDoamin = true;//todo: Code here that confirms whether domain exists.
            if (!isRealDoamin)
            {
                valid = false;
            }

            return valid;
        }

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

var email = "email@example.com";
var isValid = ValidateEmail(email);
if(isValid)
{
    //do something
}


متد ValidateEmail با صدور استثناء

        public void ValidateEmail(string email)
        {
            if (string.IsNullOrWhiteSpace(email)) throw new ArgumentNullException(nameof(email));

            var isValidFormat = true;//todo: using RegularExpression
            if (!isValidFormat) throw new ArgumentException("email is not in a correct format");

            var isRealDoamin = true;//todo: Code here that confirms whether domain exists.
            if (!isRealDoamin) throw new ArgumentException("email does not include a valid domain.")
        }

روش بالا هم جواب می‌دهد ولی بهتر است کلاس Exception سفارشی به عنوان مثال ValidationException برای این قضیه در نظر گرفته شود تا بتوان وهله‌های صادر شده از این نوع را در لایه‌های بالاتر مدیریت کرد.


متد ValidateEmail با چندین خروجی


برای این منظور چندین راه حل پیش رو داریم.


با استفاده از پارامتر out:

        public bool ValidateEmail(string email, out string message)
        {
            var valid = true;
            message = string.Empty;

            if (string.IsNullOrWhiteSpace(email))
            {
                valid = false;
                message = "email is null.";
            }

            if (valid)
            {
                var isValidFormat = true;//todo: using RegularExpression
                if (!isValidFormat)
                {
                    valid = false;
                    message = "email is not in a correct format";
                }
            }

            if (valid)
            {
                var isRealDoamin = true;//todo: Code here that confirms whether domain exists.
                if (!isRealDoamin)
                {
                    valid = false;
                    message = "email does not include a valid domain.";
                }
            }

            return valid;
        }
و نحوه استفاده از آن:
var email = "email@example.com";
var isValid = ValidateEmail(email, out string message);
if (isValid)
{
    //do something
}
خب کمی بهتر شد؛ ولی امکان دریافت لیست خطاهای اعتبارسنجی را به صورت یکجا نداریم و یک تک پیغام را در اختیار ما قرار می‌دهد. برای بهبود آن می‌توان از یک Tuple به شکل زیر برای تولید خروجی متد بالا نیز استفاده کرد.
Tuple<bool, List<string>> result = Tuple.Create<bool, List<string>>(true, new List<string>());
یا بهتر است یک کلاس مشخصی برای این منظور در نظر گرفت؛ به عنوان مثال:
        public class OperationResult
        {
            public bool Success { get; set; }
            public IList<string> Messages { get; } = new List<string>();

            public void AddMessage(string message)
            {
                Messages.Add(message);
            }
        }
در این صورت بدنه متد ValidateEmail به شکل زیر تغییر خواهد کرد:
        public OperationResult ValidateEmail(string email)
        {
            var result = new OperationResult();

            if (string.IsNullOrWhiteSpace(email))
            {
                result.Success = false;
                result.AddMessage("email is null.");
            }

            if (result.Success)
            {
                var isValidFormat = true;//todo: using RegularExpression
                if (!isValidFormat)
                {
                    result.Success = false;
                    result.AddMessage("email is not in a correct format");
                }
            }

            if (result.Success)
            {
                var isRealDoamin = true;//todo: Code here that confirms whether domain exists.
                if (!isRealDoamin)
                {
                    result.Success = false;
                    result.AddMessage("email does not include a valid domain.");
                }
            }

            return result;
        }

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


استفاده از Exception برای نمایش پیغام برای کاربر نهایی

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

به عنوان مثال داشتن یک کلاس Exception سفارشی تحت عنوان UserFriendlyException در این راستا یک الزام می‌باشد.

   [Serializable]
   public class UserFriendlyException : Exception
   {
       public string Details { get; private set; }
       public int Code { get; set; }

       public UserFriendlyException()
       {
       }

       public UserFriendlyException(SerializationInfo serializationInfo, StreamingContext context)
           : base(serializationInfo, context)
       {

       }

       public UserFriendlyException(string message)
           : base(message)
       {
       }

       public UserFriendlyException(int code, string message)
           : this(message)
       {
           Code = code;
       }

       public UserFriendlyException(string message, string details)
           : this(message)
       {
           Details = details;
       }

       public UserFriendlyException(int code, string message, string details)
           : this(message, details)
       {
           Code = code;
       }

       public UserFriendlyException(string message, Exception innerException)
           : base(message, innerException)
       {
       }

       public UserFriendlyException(string message, string details, Exception innerException)
           : this(message, innerException)
       {
           Details = details;
       }
   }

و همچنین لازم است در بالاترین لایه سیستم خود به عنوان مثال برای یک پروژه ASP.NET MVC یا ASP.NET Core MVC می‌توان یک ExceptionFilter سفارشی نیز تهیه کرد که هم به صورت سراسری استثنا‌ءهای سفارشی شما را مدیریت کند و همچنین خروجی مناسب Json برای استفاده در سمت کلاینت را نیز مهیا کند. به عنوان مثال برای درخواست‌های Ajax ای لازم است در سمت کلاینت نیز پاسخ‌های رسیده از سمت سرور به صورت سراسری مدیریت شوند و برای سایر درخواست‌ها همان نمایش صفحات خطای پیغام مرتبط با استثناء رخ داده شده کفایت می‌کند.


یک مدل پیشنهادی برای تهیه خروجی مناسب برای ارسال جزئیات استثنا رخ داده در درخواست‌های Ajax ای

    [Serializable]
    public class MvcAjaxResponse : MvcAjaxResponse<object>
    {
        public MvcAjaxResponse()
        {
        }

        public MvcAjaxResponse(bool success)
            : base(success)
        {
        }

        public MvcAjaxResponse(object result)
            : base(result)
        {
        }

        public MvcAjaxResponse(ErrorInfo error, bool unAuthorizedRequest = false)
            : base(error, unAuthorizedRequest)
        {
        }
    }
   

    [Serializable]
    public class MvcAjaxResponse<TResult> : MvcAjaxResponseBase
    {
        public MvcAjaxResponse(TResult result)
        {
            Result = result;
            Success = true;
        }

        public MvcAjaxResponse()
        {
            Success = true;
        }

        public MvcAjaxResponse(bool success)
        {
            Success = success;
        }

        public MvcAjaxResponse(ErrorInfo error, bool unAuthorizedRequest = false)
        {
            Error = error;
            UnAuthorizedRequest = unAuthorizedRequest;
            Success = false;
        }

        /// <summary>
        ///     The actual result object of AJAX request.
        ///     It is set if <see cref="MvcAjaxResponseBase.Success" /> is true.
        /// </summary>
        public TResult Result { get; set; }
    }

    public class MvcAjaxResponseBase
    {
        public string TargetUrl { get; set; }

        public bool Success { get; set; }

        public ErrorInfo Error { get; set; }

        public bool UnAuthorizedRequest { get; set; }

        public bool __mvc { get; } = true;
    }

و کلاس  ErrorInfo:
    [Serializable]
    public class ErrorInfo
    {
        public int Code { get; set; }
        public string Message { get; set; }
        public string Detail { get; set; }
        public Dictionary<string, string> ValidationErrors { get; set; }

        public ErrorInfo()
        {
        }
        public ErrorInfo(string message)
        {
            Message = message;
        }
        public ErrorInfo(int code)
        {
            Code = code;
        }

        public ErrorInfo(int code, string message)
            : this(message)
        {
            Code = code;
        }

        public ErrorInfo(string message, string details)
            : this(message)
        {
            Detail = details;
        }

        public ErrorInfo(int code, string message, string details)
            : this(message, details)
        {
            Code = code;
        }
    }

یک مثال واقعی
        public async Task CheckIsDeactiveAsync(long id)
        {
            if (await _organizationalUnits.AnyAsync(a => a.Id == id && !a.IsActive).ConfigureAwait(false))
                throw new UserFriendlyException("واحد سازمانی جاری غیرفعال می‌باشد.");
        }

روش نام گذاری متدهایی که امکان بازگشت خروجی Null را دارند

متد زیر را در نظر بگیرید:
public User GetById(long id);
وظیفه این متد یافت و بازگشت یک وهله از کلاس User می‌باشد و نباید خروجی Null تولید کند. در صورتیکه در پیاده سازی آن امکان یافت چنین کاربری نبود، بهتر است یک استثنای سفارشی دیگر شبیه به EntityNotFoundException زیر را صادر کنید:
    [Serializable]
    public class EntityNotFoundException : Exception
    {
        public Type EntityType { get; set; }
        public object Id { get; set; }
        public EntityNotFoundException()
        {
        }

        public EntityNotFoundException(string message)
            : base(message)
        {

        }

        public EntityNotFoundException(string message, Exception innerException)
            : base(message, innerException)
        {
        }

        public EntityNotFoundException(SerializationInfo serializationInfo, StreamingContext context)
            : base(serializationInfo, context)
        {

        }
        public EntityNotFoundException(Type entityType, object id)
            : this(entityType, id, null)
        {

        }
        public EntityNotFoundException(Type entityType, object id, Exception innerException)
            : base($"There is no such an entity. Entity type: {entityType.FullName}, id: {id}", innerException)
        {
            EntityType = entityType;
            Id = id;
        }

    }
یا اگر امکان بازگشت مقدار Null را داشته باشد، بهتر است نام آن به GetByIdOrNull تغییر یابد. در این صورت تکلیف استفاده کننده از این متد مشخص می‌باشد.

یک مثال واقعی 

        public async Task<UserOrganizationalUnitInfo> GetCurrentOrganizationalUnitInfoOrNullAsync(long userId)
        {
            return (await _setting.GetSettingValueForUserAsync(
                    UserSettingNames.CurrentOrganizationalUnitInfo, userId).ConfigureAwait(false))
                .FromJsonString<UserOrganizationalUnitInfo>();
        }

مطالب
نوشتن آزمون‌های واحد به کمک کتابخانه‌ی Moq - قسمت دوم - تنظیم مقادیر بازگشتی متدها
در قسمت قبل با مفاهیمی مانند fakes ،stubs ،dummies و mocks آشنا شدیم و در اولین آزمایشی که نوشتیم، کار تدارک dummies را به عنوان پارامترهای سازنده‌ی سرویس مورد بررسی، توسط کتابخانه‌ی Moq و اشیاء <Mock<T آن انجام دادیم؛ پارامترهایی که ذکر آن‌ها ضروری بودند، اما در آزمایش ما مورد استفاده قرار نمی‌گرفتند. در این قسمت می‌خواهیم کار تدارک stubs را توسط کتابخانه‌ی Moq انجام دهیم؛ به عبارتی می‌خواهیم مقادیر بازگشتی از متدهای اشیاء Mock شده را تنظیم و کنترل کنیم.


تنظیم خروجی متدهای اشیاء Mock شده

در انتهای قسمت قبل، آزمون واحد متد Accept، با شکست مواجه شد؛ چون متد Validate استفاده شده، همواره مقدار false را بر می‌گرداند:
_identityVerifier.Initialize();
var isValidIdentity = _identityVerifier.Validate(
     application.Applicant.Name, application.Applicant.Age, application.Applicant.Address);

در ادامه شیء Mock از نوع IIdentityVerifier را طوری تنظیم خواهیم کرد که بر اساس یک applicant مشخص، خروجی true را بازگشت دهد:
namespace Loans.Tests
{
    [TestClass]
    public class LoanApplicationProcessorShould
    {
        [TestMethod]
        public void Accept()
        {
            var product = new LoanProduct {Id = 99, ProductName = "Loan", InterestRate = 5.25m};
            var amount = new LoanAmount {CurrencyCode = "Rial", Principal = 2_000_000_0};
            var applicant =
                new Applicant {Id = 1, Name = "User 1", Age = 25, Address = "This place", Salary = 1_500_000_0};
            var application = new LoanApplication {Id = 42, Product = product, Amount = amount, Applicant = applicant};

            var mockIdentityVerifier = new Mock<IIdentityVerifier>();
            mockIdentityVerifier.Setup(x => x.Validate(applicant.Name, applicant.Age, applicant.Address))
                .Returns(true);

            var mockCreditScorer = new Mock<ICreditScorer>();

            var processor = new LoanApplicationProcessor(mockIdentityVerifier.Object, mockCreditScorer.Object);
            processor.Process(application);

            Assert.IsTrue(application.IsAccepted);
        }
    }
}
در اینجا ابتدا کار با شیء Mock شده آغاز می‌شود. سپس باز ذکر متد Setup، می‌توان به صورت strongly typed به تمام متدهای اینترفیس IIdentityVerifier دسترسی یافت و آن‌ها را تنظیم کرد. تا اینجا متد مدنظر را از اینترفیس IIdentityVerifier انتخاب کردیم. سپس توسط متد Returns، خروجی دقیقی را برای آن مشخص می‌کنیم.
به این ترتیب زمانیکه در متد Process کلاس LoanApplicationProcessor کار به بررسی هویت کاربر می‌رسد، اگر متد Validate آن با اطلاعات applicant مشخصی که تنظیم کردیم، یکی بود، متغیر isValidIdentity که حاصل بررسی identityVerifier.Validate_ است، به true مقدار دهی خواهد شد. برای بررسی آن یک break-point را در این نقطه قرار داده و آزمون واحد را در حالت دیباگ اجرا کنید.
البته هرچند اگر اکنون نیز این آزمایش واحد را مجددا بررسی کنیم، باز هم با شکست مواجه خواهد شد؛ چون مرحله‌ی بعدی بررسی، کار با سرویس ICreditScorer است که هنوز تنظیم نشده‌است:
_creditScorer.CalculateScore(application.Applicant.Name, application.Applicant.Address);
if (_creditScorer.Score < MinimumCreditScore)
{
    return application.IsAccepted;
}
فعلا این قسمت از code را comment می‌کنیم تا آزمایش واحد ما با موفقیت به پایان برسد. در قسمت بعدی کار تنظیم مقادیر خواص را انجام داده و این قسمت از code را نیز پوشش خواهیم داد.


تطابق با آرگومان‌های متدها در متدهای Mock شده

با تنظیمی که انجام دادیم، اگر متد Validate به مشخصات شیء applicant مشخص ما برسد، خروجی true را بازگشت می‌دهد. برای مثال اگر در این بین تنها نام شخص تغییر کند، خروجی بازگشت داده شده همان false خواهد بود. اما اگر این نام برای ما اهمیتی نداشت و قصد داشتیم با تمام نام‌های متفاوتی که دریافت می‌کند، بازهم خروجی true را بازگشت دهد، می‌توان از قابلیت argument matching کتابخانه‌ی Moq و کلاس It آن استفاده کرد:
var mockIdentityVerifier = new Mock<IIdentityVerifier>();
mockIdentityVerifier.Setup(x => x.Validate(
        //applicant.Name,
        It.IsAny<string>(),
        applicant.Age, 
        applicant.Address))
    .Returns(true);
()<It.IsAny<string در اینجا به این معنا است که هر نوع ورودی رشته‌ای، قابل قبول بوده و دیگر متد Validate بر اساس یک نام مشخص، مورد بررسی قرار نمی‌گیرد. IsAny یک متد جنریک است و بر اساس نوع آرگومان مدنظر که برای مثال در اینجا رشته‌ای است، نوع جنریک آن مشخص می‌شود.
بدیهی است در این حالت باید سایر پارامترها دقیقا با مقادیر مشخص شده تطابق داشته باشند و اگر این موارد نیز اهمیتی نداشتند، می‌توان به صورت زیر عمل کرد:
var mockIdentityVerifier = new Mock<IIdentityVerifier>();
mockIdentityVerifier.Setup(x => x.Validate(
        //applicant.Name,
        It.IsAny<string>(),
        //applicant.Age,
        It.IsAny<int>(),
        //applicant.Address
        It.IsAny<string>()
        ))
    .Returns(true);
در این حالت متد Validate، صرفنظر از ورودهای آن، همواره مقدار true را باز می‌گرداند.
البته این نوع تنظیمات بیشتر برای حالات غیرمشخص مانند استفادهاز Guidها به عنوان پارامترها و مقادیر، می‌تواند مفید باشد.


تقلید متدهایی که پارامترهایی از نوع out دارند

اگر به اینترفیس IIdentityVerifier که در قسمت قبل معرفی شد دقت کنیم، یکی از متدهای آن دارای خروجی از نوع out است:
using Loans.Models;

namespace Loans.Services.Contracts
{
    public interface IIdentityVerifier
    {
        void Validate(string applicantName, int applicantAge, string applicantAddress, out bool isValid);
// ...
    }
}
این متد خروجی ندارد، اما خروجی اصلی آن از طریق پارامتر isValid، دریافت می‌شود. برای استفاده‌ی از آن، متد Process کلاس LoanApplicationProcessor را به صورت زیر تغییر می‌دهیم:
//var isValidIdentity = _identityVerifier.Validate(
//    application.Applicant.Name, application.Applicant.Age, application.Applicant.Address);
_identityVerifier.Validate(
    application.Applicant.Name, application.Applicant.Age, application.Applicant.Address,
    out var isValidIdentity);
در این حالت اگر آزمون واحد متد Accept را بررسی کنیم، با شکست مواجه خواهد شد. به همین جهت تنظیمات Mocking این متد را به صورت زیر تعریف می‌کنیم:
var isValidOutValue = true;
mockIdentityVerifier.Setup(x => x.Validate(applicant.Name,
    applicant.Age,
    applicant.Address,
    out isValidOutValue));
برای تنظیم متدهایی که پارامترهایی از نوع out دارند، باید ابتدا مقدار مورد انتظار را مشخص کرد. بنابراین مقدار آن‌را به true در اینجا تنظیم کرده‌ایم. سپس در متد Setup، متدی تنظیم شده‌است که پارامتری از نوع out دارد. در آخر نیازی به ذکر متد Returns نیست؛ چون خروجی متد از نوع void است.
اکنون اگر مجددا آزمون واحد متد Accept را اجرا کنیم، با موفقیت به پایان می‌رسد.


تقلید متدهایی که پارامترهایی از نوع ref دارند

اگر به اینترفیس IIdentityVerifier که در قسمت قبل معرفی شد دقت کنیم، یکی از متدهای آن دارای خروجی از نوع ref است:
using Loans.Models;

namespace Loans.Services.Contracts
{
    public interface IIdentityVerifier
    {        
          void Validate(string applicantName, int applicantAge, string applicantAddress,
                             ref IdentityVerificationStatus status);
// ...
    }
}
این متد خروجی ندارد، اما خروجی اصلی آن از طریق پارامتر status، دریافت می‌شود و نوع آن به صورت زیر تعریف شده‌است تا وضعیت تعیین هویت شخص را مشخص کند:
namespace Loans.Models
{
    public class IdentityVerificationStatus
    {
        public bool Passed { get; set; }
    }
}
 برای استفاده‌ی از آن، متد Process کلاس LoanApplicationProcessor را به صورت زیر تغییر می‌دهیم تا بتوان به نمونه‌ی وهله سازی شده‌ی status دسترسی یافت:
IdentityVerificationStatus status = null;
  _identityVerifier.Validate(
      application.Applicant.Name, application.Applicant.Age, application.Applicant.Address,
      ref status);

if (!status.Passed)
{
    return application.IsAccepted;
}
در این حالت اگر آزمون واحد متد Accept را بررسی کنیم، با شکست مواجه خواهد شد. به همین جهت تنظیمات Mocking این متد را به صورت زیر تعریف می‌کنیم که با متدهای out دار مقداری متفاوت است:
ابتدا در سطح کلاس آزمایش واحد یک delegate را تعریف می‌کنیم:
delegate void ValidateCallback(string applicantName,
    int applicantAge,
    string applicantAddress,
    ref IdentityVerificationStatus status);
این delegate دقیقا دارای همان پارامترهای متد Validate در حال بررسی است.
اکنون روش استفاده‌ی از آن برای برپایی تنظیمات mocking متد Validate از نوع ref دار به صورت زیر است:
mockIdentityVerifier
    .Setup(x => x.Validate(applicant.Name,
        applicant.Age,
        applicant.Address,
        ref It.Ref<IdentityVerificationStatus>.IsAny))
    .Callback(new ValidateCallback(
        (string applicantName,
         int applicantAge,
         string applicantAddress,
         ref IdentityVerificationStatus status) =>
            status = new IdentityVerificationStatus {Passed = true}));
تنظیمات قسمت Setup آن آشنا است؛ بجز قسمت ref آن که از It.Ref<IdentityVerificationStatus>.IsAny استفاده کرده‌است. چون نوع پارامتر، ref است، باید از It.Ref استفاده کرد که به نوع بازگشت داده شده‌ی IdentityVerificationStatus اشاره می‌کند. IsAny آن هم هر نوع ورودی از این دست را می‌پذیرد.
سپس متد جدید Callback را مشاهده می‌کنید. توسط آن می‌توان یک قطعه کد سفارشی را زمانیکه متد Mock شده‌ی Validate ما اجرا می‌شود، اجرا کرد. در اینجا delegate سفارشی ما اجرا شده و مقدار status را بر می‌گرداند؛ اما در ادامه این مقدار را به یک new IdentityVerificationStatus سفارشی تنظیم می‌کنیم که در آن مقدار خاصیت Passed، مساوی true است.
اکنون اگر مجددا آزمون واحد متد Accept را اجرا کنیم، با موفقیت به پایان می‌رسد.


تنظیم متدهای Mock شده جهت بازگشت null

فرض کنید اینترفیسی به صورت زیر تعریف شده‌است:
namespace Loans.Services.Contracts
{
    public interface INullExample
    {
        string SomeMethod();
    }
}
و اگر بخواهیم برای آن آزمون واحدی را بنویسیم که خروجی این متد به صورت مشخصی نال باشد، می‌توان تنظیمات Moq آن‌را به صورت زیر انجام داد:
namespace Loans.Tests
{
    [TestClass]
    public class LoanApplicationProcessorShould
    {        
        [TestMethod]
        public void NullReturnExample()
        {
            var mock = new Mock<INullExample>();

            mock.Setup(x => x.SomeMethod());
            //.Returns<string>(null);

            string mockReturnValue = mock.Object.SomeMethod();

            Assert.IsNull(mockReturnValue);
        }
    }
}
در اینجا دو روش را برای بازگشت نال ملاحظه می‌کنید:
الف) می‌توان همانند سابق متد Returns را ذکر کرد که نال بر می‌گرداند؛ اما با این تفاوت که حتما باید نوع آرگومان جنریک آن‌را نیز بر اساس خروجی متد، مشخص کرد.
ب) کتابخانه‌ی Moq، مقدار خروجی پیش‌فرض تمام متدهایی را که یک نوع ارجاعی را باز می‌گردانند، نال درنظر می‌گیرد و عملا نیازی به ذکر متد Returns در اینجا نیست.


کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: MoqSeries-02.zip
مطالب
انجام کارهای زمانبندی شده در برنامه‌های ASP.NET توسط DNT Scheduler
اگر به دو مطلب استفاده از Quartz.Net (^ و ^) و خصوصا نظرات آن دقت کرده باشید به این نتیجه خواهید رسید که ... این کتابخانه‌ی در اصل جاوایی گنگ طراحی شده‌است. در سایت جاری برای انجام کارهای زمانبندی شده (مانند ارسال ایمیل‌های روزانه خلاصه مطالب، تهیه خروجی PDF و XML سایت، تبدیل پیش نویس‌ها به مطالب، بازسازی ایندکس‌های جستجو و امثال آن) از یک Thread timer استفاده می‌شود که حجم نهایی کتابخانه‌ی محصور کننده و مدیریت کننده‌ی وظایف آن جمعا 8 کیلوبایت است؛ متشکل از ... سه کلاس. در ادامه کدهای کامل و نحوه‌ی استفاده از آن را بررسی خواهیم کرد.


دریافت کتابخانه DNT Scheduler و مثال آن

DNTScheduler 
در این بسته، کدهای کتابخانه‌ی DNT Scheduler و یک مثال وب فرم را، ملاحظه خواهید کرد. از این جهت که برای ثبت وظایف این کتابخانه، از فایل global.asax.cs استفاده می‌شود، اهمیتی ندارد که پروژه‌ی شما وب فرم است یا MVC. با هر دو حالت کار می‌کند.



نحوه‌ی تعریف یک وظیفه‌ی جدید

کار با تعریف یک کلاس و پیاده سازی ScheduledTaskTemplate شروع می‌شود:
 public class SendEmailsTask : ScheduledTaskTemplate
برای نمونه :
using System;

namespace DNTScheduler.TestWebApplication.WebTasks
{
    public class SendEmailsTask : ScheduledTaskTemplate
    {
        /// <summary>
        /// اگر چند جاب در یک زمان مشخص داشتید، این خاصیت ترتیب اجرای آن‌ها را مشخص خواهد کرد
        /// </summary>
        public override int Order
        {
            get { return 1; }
        }

        public override bool RunAt(DateTime utcNow)
        {
            if (this.IsShuttingDown || this.Pause)
                return false;

            var now = utcNow.AddHours(3.5);
            return now.Minute % 2 == 0 && now.Second == 1;
        }

        public override void Run()
        {
            if (this.IsShuttingDown || this.Pause)
                return;

            System.Diagnostics.Trace.WriteLine("Running Send Emails");
        }

        public override string Name
        {
            get { return "ارسال ایمیل"; }
        }
    }
}
- در اینجا Order، ترتیب اجرای وظیفه‌ی جاری را در مقایسه با سایر وظیفه‌هایی که قرار است در یک زمان مشخص اجرا شوند، مشخص می‌کند.
- متد RunAt ثانیه‌ای یکبار فراخوانی می‌شود (بنابراین بررسی now.Second را فراموش نکنید). زمان ارسالی به آن UTC است و اگر برای نمونه می‌خواهید بر اساس ساعت ایران کار کنید باید 3.5 ساعت به آن اضافه نمائید. این مساله برای سرورهایی که خارج از ایران قرار دارند مهم است. چون زمان محلی آن‌ها برای تصمیم گیری در مورد زمان اجرای کارها مفید نیست.
در متد RunAt فرصت خواهید داشت تا منطق زمان اجرای وظیفه‌ی جاری را مشخص کنید. برای نمونه در مثال فوق، این وظیفه هر دو دقیقه یکبار اجرا می‌شود. یا اگر خواستید اجرای آن فقط در سال 23 و 33 دقیقه هر روز باشد، تعریف آن به نحو ذیل خواهد بود:
        public override bool RunAt(DateTime utcNow)
        {
            if (this.IsShuttingDown || this.Pause)
                return false;

            var now = utcNow.AddHours(3.5);
            return now.Hour == 23 && now.Minute == 33 && now.Second == 1;
        }
- خاصیت IsShuttingDown موجود در کلاس پایه ScheduledTaskTemplate، توسط کتابخانه‌ی DNT Scheduler مقدار دهی می‌شود. این کتابخانه قادر است زمان خاموش شدن پروسه‌ی فعلی IIS را تشخیص داده و خاصیت IsShuttingDown را true کند. بنابراین در حین اجرای وظیفه‌ای مشخص، به مقدار IsShuttingDown دقت داشته باشید. اگر true شد، یعنی فقط 30 ثانیه وقت دارید تا کار را تمام کنید.
خاصیت Pause هر وظیفه را برنامه می‌تواند تغییر دهد. به این ترتیب در مورد توقف یا ادامه‌ی یک وظیفه می‌توان تصمیم گیری کرد. خاصیت ScheduledTasksCoordinator.Current.ScheduledTasks، لیست وظایف تعریف شده را در اختیار شما قرار می‌دهد.
- در متد Run، منطق وظیفه‌ی تعریف شده را باید مشخص کرد. برای مثال ارسال ایمیل یا تهیه‌ی بک آپ.
- Name نیز نام وظیفه‌ی جاری است که می‌تواند در گزارشات مفید باشد.

همین مقدار برای تعریف یک وظیفه کافی است.


نحوه‌ی ثبت و راه اندازی وظایف تعریف شده

پس از اینکه چند وظیفه را تعریف کردیم، برای مدیریت بهتر آن‌ها می‌توان یک کلاس ثبت و معرفی کلی را مثلا به نام ScheduledTasksRegistry ایجاد کرد:
using System;
using System.Net;

namespace DNTScheduler.TestWebApplication.WebTasks
{
    public static class ScheduledTasksRegistry
    {
        public static void Init()
        {
            ScheduledTasksCoordinator.Current.AddScheduledTasks(
                new SendEmailsTask(),
                new DoBackupTask());

            ScheduledTasksCoordinator.Current.OnUnexpectedException = (exception, scheduledTask) =>
            {
                //todo: log the exception.
                System.Diagnostics.Trace.WriteLine(scheduledTask.Name + ":" + exception.Message);
            };

            ScheduledTasksCoordinator.Current.Start();
        }

        public static void End()
        {
            ScheduledTasksCoordinator.Current.Dispose();
        }

        public static void WakeUp(string pageUrl)
        {
            try
            {
                using (var client = new WebClient())
                {
                    client.Credentials = CredentialCache.DefaultNetworkCredentials;
                    client.Headers.Add("User-Agent", "ScheduledTasks 1.0");
                    client.DownloadData(pageUrl);
                }
            }
            catch (Exception ex)
            {
                //todo: log ex
                System.Diagnostics.Trace.WriteLine(ex.Message);
            }
        }
    }
}
- شیء ScheduledTasksCoordinator.Current، نمایانگر تنها وهله‌ی مدیریت وظایف برنامه است.
- توسط متد ScheduledTasksCoordinator.Current.AddScheduledTasks، تنها کافی است کلاس‌های وظایف مشتق شده از ScheduledTaskTemplate، معرفی شوند.
- به کمک متد ScheduledTasksCoordinator.Current.Start، کار Thread timer برنامه شروع می‌شود.
- اگر در حین اجرای متد Run، استثنایی رخ دهد، آن‌را توسط یک Action delegate به نام ScheduledTasksCoordinator.Current.OnUnexpectedException می‌توانید دریافت کنید. کتابخانه‌ی DNT Scheduler برای اجرای وظایف، از یک ترد با سطح تقدم Below normal استفاده می‌کند تا در حین اجرای وظایف، برنامه‌ی جاری با اخلال و کندی مواجه نشده و بتواند به درخواست‌های رسیده پاسخ دهد. در این بین اگر استثنایی رخ دهد، می‌تواند کل پروسه‌ی IIS را خاموش کند. به همین جهت این کتابخانه کار try/catch استثناهای متد Run را نیز انجام می‌دهد تا از این لحاظ مشکلی نباشد.
- متد ScheduledTasksCoordinator.Current.Dispose کار مدیر وظایف برنامه را خاتمه می‌دهد.
- از متد WakeUp تعریف شده می‌توان برای بیدار کردن مجدد برنامه استفاده کرد.


استفاده از کلاس ScheduledTasksRegistry تعریف شده

پس از اینکه کلاس ScheduledTasksRegistry را تعریف کردیم، نیاز است آن‌را به فایل استاندارد global.asax.cs برنامه به نحو ذیل معرفی کنیم:
using System;
using System.Configuration;
using DNTScheduler.TestWebApplication.WebTasks;

namespace DNTScheduler.TestWebApplication
{
    public class Global : System.Web.HttpApplication
    {
        protected void Application_Start(object sender, EventArgs e)
        {
            ScheduledTasksRegistry.Init();
        }

        protected void Application_End()
        {
            ScheduledTasksRegistry.End();
            //نکته مهم این روش نیاز به سرویس پینگ سایت برای زنده نگه داشتن آن است
            ScheduledTasksRegistry.WakeUp(ConfigurationManager.AppSettings["SiteRootUrl"]);
        }
    }
}
- متد ScheduledTasksRegistry.Init در حین آغاز برنامه فراخوانی می‌شود.
- متد ScheduledTasksRegistry.End در پایان کار برنامه جهت پاکسازی منابع باید فراخوانی گردد.
همچنین در اینجا با فراخوانی ScheduledTasksRegistry.WakeUp، می‌توانید برنامه را مجددا زنده کنید! IIS مجاز است یک سایت ASP.NET را پس از مثلا 20 دقیقه عدم فعالیت (فعالیت به معنای درخواست‌های رسیده به سایت است و نه کارهای پس زمینه)، از حافظه خارج کند (این عدد در application pool برنامه قابل تنظیم است). در اینجا در فایل web.config برنامه می‌توانید آدرس یکی از صفحات سایت را برای فراخوانی مجدد تعریف کنید:
 <?xml version="1.0"?>
<configuration>
  <appSettings>
      <add key="SiteRootUrl" value="http://localhost:10189/Default.aspx" />
  </appSettings>
</configuration>
همینکه درخواست مجددی به این صفحه برسد، مجددا برنامه توسط IIS بارگذاری شده و اجرا می‌گردد. به این ترتیب وظایف تعریف شده، در طول یک روز بدون مشکل کار خواهند کرد.


گزارشگیری از وظایف تعریف شده

برای دسترسی به کلیه وظایف تعریف شده، از خاصیت ScheduledTasksCoordinator.Current.ScheduledTasks استفاده نمائید:
var jobsList = ScheduledTasksCoordinator.Current.ScheduledTasks.Select(x => new
{
   TaskName = x.Name,
   LastRunTime = x.LastRun,
   LastRunWasSuccessful = x.IsLastRunSuccessful,
   IsPaused = x.Pause,
}).ToList();
لیست حاصل را به سادگی می‌توان در یک Grid نمایش داد.
مطالب
مروری بر طراحی Schema less بانک اطلاعاتی SisoDb
اس کیوال سرور، از سال 2005 به بعد، به صورت توکار امکان تعریف و ذخیره سازی اطلاعات schema less و یا schema free را به کمک فیلدهایی از نوع XML ارائه داده است؛ به همراه یکپارچگی آن با زبان XQuery برای تهیه کوئری‌های سریع سمت سرور. در فیلدهای XML می‌توان اطلاعات انواع و اقسام اشیاء را بدون اینکه نیازی به تعریف تک تک فیلدهای مورد نیاز، در بانک اطلاعاتی وجود داشته باشد، ذخیره کرد. یک نمونه از کاربرد چنین امکانی، نوشتن برنامه‌های «فرم ساز» است. برنامه‌هایی که کاربران آن می‌توانند فیلد اضافه و کم کرده و نهایتا اطلاعات را ذخیره و از آن‌ها کوئری بگیرند.
خوب، این فیلد کمتر بحث شده XML، فقط در اس کیوال سرور و نگارش‌های اخیر آن وجود دارد. اگر نیاز به کار با بانک‌های اطلاعاتی سبک‌تری وجود داشت چطور؟ یک راه حل عمومی برای این مساله مراجعه به روش‌های NoSQL است. یعنی بطور کلی بانک‌های اطلاعاتی رابطه‌ای کنار گذاشته شده و به یک سکوی کاری دیگر سوئیچ کرد. در این بین، SisoDb راه حل میانه‌ای را ارائه داده است. با کمک SisoDb می‌توان اطلاعات را به صورت schema less و بدون نیاز به تعریف فیلدهای متناظر آن‌ها، در انواع و اقسام بانک‌های اطلاعاتی SQL Server با فرمت JSON ذخیره و بازیابی کرد. این انواع و اقسام، شامل SQL Server CE نیز می‌شود.


دریافت و نصب SisoDb

دریافت و نصب SisoDb بسیار ساده است. به کمک package manager و امکانات NuGet، کلمه Sisodb را جستجو کنید. در بین مداخل ظاهر شده، پروایدر مورد علاقه خود را انتخاب و نصب نمائید. برای مثال اگر قصد دارید با SQL Server CE کار کنید، SisoDb.SqlCe4 را انتخاب و یا اگر SQL Server 2008 مدنظر شما است، SisoDb.Sql2008 را انتخاب و نصب نمائید.


ثبت و بازیابی اطلاعات به کمک SisoDb

کار با SisoDb بسیار روان است. نیازی به تعاریف نگاشت‌ها و ORM خاصی نیست. یک مثال مقدماتی آن‌را در ادامه ملاحظه می‌کنید:
using SisoDb.Sql2008;

namespace SisoDbTests
{
    public class Customer 
    {
        public int Id { get; set; } 
        public int CustomerNo { get; set; }
        public string Name { get; set; } 
    }

    class Program
    {
        static void Main(string[] args)
        {
            /*var cnInfo = new SqlCe4ConnectionInfo(@"Data source=sisodb2013.sdf;");
            var db = new SqlCe4DbFactory().CreateDatabase(cnInfo);
            db.EnsureNewDatabase();*/

            var cnInfo = new Sql2008ConnectionInfo(@"Data Source=(local);Initial Catalog=sisodb2013;Integrated Security = true");            
            var db = new Sql2008DbFactory().CreateDatabase(cnInfo);            
            db.EnsureNewDatabase();

            var customer = new Customer 
            {
                CustomerNo = 20,
                Name = "Vahid" 
            };
            db.UseOnceTo().Insert(customer);

            using (var session = db.BeginSession()) 
            {
                var info = session.Query<Customer>().Where(c => c.CustomerNo == 20).FirstOrDefault();

                var info2 = session.Query<Customer>().Where(c => c.CustomerNo == 20 && c.Name=="Vahid").FirstOrDefault();
            }
        }
    }
}
در این مثال، ابتدا اتصال به بانک اطلاعاتی برقرار شده و سپس بانک اطلاعاتی جدید تهیه می‌شود. سپس یک وهله از شیء مشتری ایجاد و ذخیره می‌گردد. در ادامه دو کوئری بر روی بانک اطلاعاتی انجام شده است.


ساختار داخلی SisoDb

SisoDb به ازای هر کلاس، حداقل 9 جدول را ایجاد می‌کند. در ادامه نحوه ذخیره سازی شیء مشتری ایجاد شده و مقادیر خواص آن‌را نیز مشاهده می‌نمائید:


ذخیره سازی جداگانه خواص عددی

ذخیره سازی جداگانه خواص رشته‌ای

ذخیره سازی کلی شیء مشتری با فرمت JSON به صورت یک رشته


همانطور که ملاحظه می‌کنید، یک جدول کلی SisoDbIdentities ایجاد شده است که اطلاعات نام اشیاء را در خود نگهداری می‌کند. سپس اطلاعات خواص اشیاء یکبار به صورت JSON ذخیره می‌شوند؛ با تمام اطلاعات تو در توی ذخیره شده در آن‌ها و همچنین یکبار هم هر خاصیت را به صورت یک رکورد جداگانه، بر اساس نوع کلی آن‌ها، در جداول رشته‌ای، عددی و امثال آن ذخیره می‌کند.
شاید بپرسید که چرا به همان فیلد رشته‌ای JSON اکتفاء نشده است؟ از این جهت که پردازشگر سمت بانک اطلاعاتی آن همانند فیلدهای XML در SQL Server و نگارش‌های مختلف آن وجود ندارد (برای مثال به کمک زبان T-SQL می‌توان از زبان XQuery در خود بانک اطلاعاتی، بدون نیاز به واکشی کل اطلاعات در سمت کلاینت، به صورت یکپارچه استفاده کرد). به همین جهت برای کوئری گرفتن و یا تهیه ایندکس، نیاز است این موارد جداگانه ذخیره شوند.
به این ترتیب زمانیکه کوئری تهیه می‌شود، برای مثال:
 var info = session.Query<Customer>().Where(c => c.CustomerNo == 20).FirstOrDefault();
به کوئری زیر ترجمه می‌گردد:
SELECT DISTINCT TOP(1) (s.[StructureId]),
                           s.[Json]
                    FROM   [CustomerStructure] s
                           LEFT JOIN [CustomerIntegers] mem0
                                ON  mem0.[StructureId] = s.[StructureId]
                                AND mem0.[MemberPath] = 'CustomerNo'
                    WHERE  (mem0.[Value] = 20);
و یا کوئری ذیل:
var info2 = session.Query<Customer>().Where(c => c.CustomerNo == 20 && c.Name=="Vahid").FirstOrDefault();
معادل زیر را خواهد داشت:
SELECT DISTINCT TOP(1) (s.[StructureId]),
                           s.[Json]
                    FROM   [CustomerStructure] s
                           LEFT JOIN [CustomerIntegers] mem0
                                ON  mem0.[StructureId] = s.[StructureId]
                                AND mem0.[MemberPath] = 'CustomerNo'
                           LEFT JOIN [CustomerStrings] mem1
                                ON  mem1.[StructureId] = s.[StructureId]
                                AND mem1.[MemberPath] = 'Name'
                    WHERE  ((mem0.[Value] = 20) AND (mem1.[Value] = 'Vahid'));
در هر دو حالت از جداول کمکی تعریف شده برای تهیه کوئری استفاده کرده و نهایتا فیلد JSON اصلی را برای نگاشت نهایی به اشیاء تعریف شده در برنامه بازگشت می‌دهد.

در کل این هم یک روش تفکر و طراحی Schema less است که با بسیاری از بانک‌های اطلاعاتی موجود سازگاری دارد.
برای مشاهده اطلاعات بیشتری در مورد جزئیات این روش می‌توان به Wiki آن مراجعه کرد.