مطالب
یکپارچه سازی TortoiseSVN و YouTrack
پیش نیاز
اگر در مورد TortoiseSVN و سورس کنترل اطلاعات پایه ندارید، کتاب  مدیریت فایلهای یک پروژه نرم افزاری با استفاده از Subversion  آقای نصیری را مطالعه کنید و همچنین سیستم پیگیری خطای  YouTrack را نگاهی بیاندازید (البته اگر اطلاعی ندارید).

مقدمه
هنگام کار روی یک پروژه، باگ ها، وظیفه‌ها و موضوعاتی به شما واگذار می‌شود که باید انها را انجام دهید. هنگام commit کردن تغییرات، برای مشخص شدن اینکه تغییرات مربوط به کدام Bug-Id بوده است بود است باید سیستم Bug/Issue Tracker رو با سورس کنترل یکپارچه کنیم. 

یکپارچه سازی TortoiseSVN و YouTrack
1- روی یک نسخه کاری پروژه راست کلیک، از منوی TortoiseSVN گزینه Properties را انتخاب کنید.

گزینه Properties در TortoiseSVN

2- از پنجر باز شده دکمه New، گزینه Other را انتخاب کنید. در پنجره باز شده از منوی کشویی مربوط به Property Name، مقادیر خصلت‌های زیر را تنظیم کنید:
bugtraq:url : آدرس YouTrack Sever که به این صورت وارد می‌شود: %http://localhost:8080/issue/%BUGID
bugtraq:message : درو اقع الگویی پیامی هست که برای نگهداری Bug-Id استفاده می‌شود و باید شامل کلمه %BUGID% باشد. مثلا: %Issue: %BUGID
bugtraq:number : مقدار این خصلت را false وارد کنید؛ چون Bug-Idهای‌های YouTrack می‌توانند شامل عدد و حروف باشند.

دیالوگ Propeties


بعد از اینکه این سه خصلت را مقداردهی کرید، تغییرات را Commit کنید. همانطور که می‌بینید یک Textbox (بالا، سمت راست) اضافه شده که محل وارد کردن Bug-Id مربوط به تغییرات است. از این پس، می‌توانید Bug-Id یا Issue-Id‌های مربوط به هر تغییرات را در آن Textbox وارد کنید.


همچنین تغییرات در پلاگین AnkhSVN در ویژال استودیو نیز اعمال می‌شود:


اکنون، در متن commitها شماره Bud-Id نیز ذکر شده است.


نکته 1: اگر YouTrack روی یک سرور نصب هست، بجای localhost نام کامپیوتر سرور یا آی پی آن را وارد کنید. پورت 8080 نیز بصورت پیش فرض است و اگر هنگام نصب آن را تغییر داده اید، اینجا نیز آنرا تغییر دهید.
نکته 2: خصلت bugtraq:message یک الگوی پیام از شما می‌گیرد؛ یعنی الگو را تحت هر شکلی می‌توان وارد کرد. بعنوان مثال الگو را به این شکل وارد کنید: "برای مشاهده جزئیات بیشتر به Bug-Id شماره %BUGID% مراجعه کنید."
نکته 3: اگر خصلت bugtraq:number مقدارش true باشد، برای وارد کردن Bug-Id فقط از عدد می‌توانید استفاده کنید. بصورت پیش فرض مقدار این خصلت true است.
نکته 4: می‌توانید این تنظیمات را در یک فایل Export کنید و در بقیه پروژه ها، با یک مرحله و بسادگی آنرا Import کنید.
خصلت‌های دیگری نیز می‌توان برروی مخزن کد اعمال کرد که از حوزه این مقاله خارج است. همچنین تنظمیات اختیاری جانبی دیگری نیز برای یکپارچه سازی وجود دارند. برای دیدن این تنظمیات روی نسخه کاری راست کلیک، از منوی TortoiseSVN گزینه Properties را انتخاب کنید و از پنجره باز شده روی دکمه New و گزینه ( Bugtraq (Issue tracker integration  را انتخاب کنید.

برای اطلاعات بیشتر در مورد این تنظیمات، داکیومنت یکپارچه سازی با سیستم‌های Bug tracking / Issue Tracking  را مطالعه کنید. 
مطالب
نحوه انتقال اطلاعات استخراج شده از وب سرویس به SQL Server به کمک SSIS
ممکن است در مواقعی نیاز به اطلاعات استخراج شده از وب سرویسی داشته باشید که در همان مقطع زمانی به آن دسترسی ندارید . مسلما برای این منظور باید آن اطلاعات را ذخیره کرده تا در صورت نیاز بتوان به آنها رجوع کرد . یکی از راه حل‌ها ذخیره آن در پایگاه داده (در اینجا Sql Server) است که در این پست به کمک امکانات BIDS در پکیج‌های SSIS و کوئری‌های SQL این مشکل را برطرف میکنیم. برای مشاهده نحوه استخراج اطلاعات از وب سرویس به اینجا مراجعه کنید .
تنها تفاوتی که در این پست در کار با سرویس با پست اشاره شده در بالا وحود دارد ذخیره اطلاعات استخراج شده است که در آن پست در یک فایل xml ذخیره شدند ولی در اینجا ما نیاز داریم تا اطلاعات را در یک متغیر با حوزه کاری Package ذخیره کنیم (به این معنی که مختص به همان flow نباشد و در تمام پکیج دیده شود) 

به دلیل اینکه هدر xml خروجی از سرویس دارای چندین namespace هست هنگام کار با آنها به مشکل خواهیم خورد. (هم هنگام کار با xml task‌ها و هم هنگام کار با xml در sql) 

به همین دلیل باید این قسمت از محتوا را حذف کرد . برای همین پس از گرفتن اطلاعات از سرویس آن را به کمک یک Script task حذف می‌کنیم 

در این مرحله اطلاعات استخراج شده را باید در SQL درج کنیم . برای همین ساختاری که باید اطلاعات را در SQL نگه دارد را در دیتابیس ایجاد می‌کنیم : 

جدول person برای نگهداری اطلاعات سرویس و XmlContainer برای نگهداری xml‌های سرویس .(برای داشتن History) 

برای درج هم از SP استفاده می‌کنیم :

 

و پیکربندی SQL Task :
 

نکته اول ایجاد کانکشن به پایگاه داده است که در اینجا از نمایش جزییات آن صرف نظر شده است . نکته دیگری پارامتر SP است که چون یک پارامتر دارد یک علامت سوال قرار میگیرد . اگر چند پارامتر بود به صورت علامت‌های سوال با ویرگول از هم جدا می‌شوند . ?,?,?,?
سپس در بخش parameter mapping به ترتیب مقدار دهی می‌شوند : 

و مقدار خروجی SP که شناسه آیتم درج شده است را در یک متغیر نگهداری می‌کنیم :
 

برای قدم بعد می‌خواهیم اطلاعات موجود در XML استخراج شده در پایگاه داده را در جدول مربوطه ذخیره کنیم . برای این کار این SP را می‌نویسیم : 
 

نکات مهم موارد زیر هستند : 
1 - استفاده از OpenXml برای parse کردن xml 
2 - استفاده از sp سیستمی sp_xml_preparedocument و sp_xml_removedocument (بیشتر ) 
3- اطلاعات شناسه و نام و نام خانوادگی inner text‌های نود‌های xml هستند . اگر این موارد به صورت attribute باشند باید قبل از نام آنها علامت @ قرار بگیرند. 
پس از ایجاد این Sp باید آن را در package فراخوانی کنیم : 

و پیکربندی این SQL Task :
 

و پارامتر‌های این SP :

و ذخیره نتیجه تراکنش در متغیری در پکیج :
 

و اکنون پکیج ما ظاهری شبیه به این مورد خواهد داشت :
 

 

در نهایت به عنوان یک facility می‌توانیم وضعیت تراکنش را به کاربر نمایش دهیم ( به کمک Script Task ) : 

و تمام ...

 

موفق باشید 

مطالب
لینک‌های هفته دوم آذر

وبلاگ‌ها و سایت‌های ایرانی


Visual Studio


امنیت

ASP. Net


طراحی وب


اس‌کیوال سرور


سی‌شارپ


عمومی دات نت


متفرقه


مطالب
ایجاد اشیاء دفاعی با ES 6 Proxy
ممکن است برای شما نیز پیش آمده باشد که به یک خصوصیت از یک شیء که وجود ندارد، ارجاع داده باشید و متوجه علت خطای رخ داده نشده و مدتی را به دنبال علت خطا صرف کرده باشید. بعضی از افراد به همین علت از جاوااسکریپت متنفر هستند و می‌گویند اگر از یک زبان type-safe استفاده می‌کردیم آنگاه در صورتیکه به خصوصیتی ارجاع می‌دادیم که وجود ندارد، نبودن خصوصیت ارجاع داده شده را اعلام می‌کرد. این مشکل وجود داشت تا وقتی که ECMAScript 6 ارائه شد.

ECMAScript 5

در حالیکه ECMAScript 5 قابلیت‌های فوق العاده‌ای را برای کنترل کردن خصوصیات موجود در اشیاء، در اختیار شما قرار می‌دهد، اما هیچ راه کاری را برای خصوصیاتی که موجود نیستند، ندارد. شما می‌توانید برای خواص موجود، از رونویسی (تنظیم writable برابر false) و یا حذف شدن (تنظیم configurable برابر false) جلوگیری کنید. شما می‌توانید از اختصاص خصوصیات جدید به اشیاء با استفاده از ()Object.preventExtensions و یا تنظیم تمام خصوصیات به صورت فقط خواندنی و یا غیرقابل حذف ()Object.freeze جلوگیری کنید.

اگر شما نمی‌خواهید تمام خصوصیات را فقط خواندنی کنید می‌توانید از ()Object.seal استفاده کنید. این‌ها مانع از اضافه کردن خصوصیات و یا حذف کردن خصوصیات موجود می‌شوند. اگر به یک شیء مهر و موم شده (sealed)، زمانی که از strict mode استفاده می‌کنید، یک خصوصیت جدید اضافه کنید باعث ایجاد خطا می‌شود:

"use strict";

var person = {
    name: "Vahid Mohammad Taheri"
};

Object.seal(person);
person.age = 27;    // Error!
این کار باعث اطلاع شما می‌شود که در حال تلاش برای تغییر اینترفیس یک شیء، با استفاده از اضافه کردن یک ویژگی به آن هستید. هنگامیکه سعی در خواندن ویژگی از یک شیء که جزئی از اینترفیس آن نیست، دارید نیز با خطا مواجه می‌شوید.

نجات با Proxyها

پروکسی‌ها، دارای سابقه طولانی و پیچیده ای در ECMAScript 6 است. طرح اولیه آن توسط Firefox و Chrome قبل از تصمیم TC-39 به تغییر پروکسی‌ها، اجرا شده است. این تغییرات، برای بهتر و روان‌تر شدن پروکسی‌ها از طرح اولیه پروکسی‌ها انجام گرفت.

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

این کار از طریق یک سری از روش‌هایی که به مخفی کردن عملیات در ECMAScript مطابقت دارند، انجام می‌شود. به عنوان مثال زمانیکه بر روی یک ویژگی از یک شیء، عمل خواندن انجام می‌شود، عملیات  [[Get]] در موتور جاوااسکریپت انجام می‌گیرد. نحوه‌ی رفتار [[Get]] را نمی‌توان تغییر داد؛ با این حال، با استفاده از پروکسی‌ها می‌توان دامی برای زمان فراخوانی [[Get]]  قرار داد و عملیات خاص مورد نظر خود را اعمال کرد. به مثال زیر توجه کنید:

var proxy = new Proxy({ name: "Vahid" }, {
    get: function(target, property) {
        if (property in target) {
            return target[property];
        } else {
            return 13;
        }
    }
});

console.log(proxy.time);        // 13
console.log(proxy.name);        // "Vahid"
console.log(proxy.title);       // 13
این پروکسی از یک شیء ساخته شده به عنوان هدف (آرگومان اول به ()Proxy) استفاده می‌کند. آرگومان دوم دامی را که می‌خواهید برای این شیء بسازید، تعریف می‌کند. با استفاده از متد get عملیات مربوط به [[Get]] به دام افتاده و تابع تعریف شده‌ی ما اجرا می‌شود (باقی عملیات به صورت عادی اجرا می‌شوند). دامی که برای شیء مورد نظر تعریف کرده‌ایم دو پارامتر دریافت می‌کند (اول شی هدف، دوم ویژگی مورد نظر). با استفاده از کد نوشته شده در آن ابتدا بررسی می‌شود که شیء مورد نظر دارای ویژگی ارسال شده است یا خیر؟ در صورتی که وجود داشته باشد، مقدار آن بازگشت داده می‌شود و در غیر اینصورت به صورت ثابت مقدار 13 برگشت داده می‌شود.

برای ایجاد اشیاء دفاعی لازم است چگونگی رهگیری عملیات [[Get]] را درک کنید و هدف از این کار صدور خطا در زمان دستیابی به ویژگی ای از شیءایی که وجود ندارد است.
function createDefensiveObject(target) {

    return new Proxy(target, {
        get: function(target, property) {
            if (property in target) {
                return target[property];
            } else {
                throw new ReferenceError("Property \"" + property + "\" does not exist.");
            }
        }
    });
}

تابع ()createDefensiveObject  یک شیء را به عنوان هدف می‌پذیرد و یک شیء دفاعی برای آن ایجاد می‌کند. پروکسی یک دام به نام get دارد؛ برای زمانی که عمل خواندن انجام می‌شود. اگر ویژگی خوانده شده در شیء وجود داشت، مقدار آن برگشت داده می‌شود و از سوی دیگر، وقتی ویژگی خوانده شده در شیء وجود نداشته باشد، سبب بروز خطا می‌شود. به مثال زیر توجه کنید:
var person = {
    name: "Vahid"
};

var defensivePerson = createDefensiveObject(person);

console.log(defensivePerson.name);        // "Vahid"
console.log(defensivePerson.age);         // Error!
در اینجا ویژگی name به طور معمول کار خواهد کرد؛ ولی ویژگی age باعث صدور خطا می‌شود.
اشیاء دفاعی باعث می‌شوند تا بر روی ویژگی‌هایی که در شیء وجود دارند، بتوان عمل خواندن را انجام داد و در ویژگی‌هایی که موجود نیستند در هنگام خواندن، باعث صدور پیام خطا می‌شوند. با این حال هنوز هم شما می‌توانید ویژگی‌های جدید را بدون خطا اضافه کنید:
var person = {
    name: "Vahid"
};

var defensivePerson = createDefensiveObject(person);

console.log(defensivePerson.name);        // "Vahid"

defensivePerson.age = 13;
console.log(defensivePerson.age);         // 13
بنابراین اشیاء توانایی خود را برای جهش و تغییر حفظ می‌کنند. در صورتی که شما چیزی را برای تغییر آنها انجام دهید، همیشه می‌توانید ویژگی‌هایی را به اشیاء اضافه کنید ولی عمل خواندن بر روی ویژگی‌های غیرموجود همیشه باعث صدور خطا و بازگشت مقدار undefined می‌شود.
روش‌های تشخیص ویژگی‌های استاندارد هنوز هم به طور معمول و بدون خطا کار می‌کنند.
var person = {
    name: "Vahid"
};

var defensivePerson = createDefensiveObject(person);

console.log("name" in defensivePerson);               // true
console.log(defensivePerson.hasOwnProperty("name"));  // true

console.log("age" in defensivePerson);                // false
console.log(defensivePerson.hasOwnProperty("age"));   // false
شما می‌توانید از اینترفیس یک شیء، زمانیکه دسترسی به یک ویژگی آن وجود ندارد، صورت می‌گیرد، با رد کردن اضافات و صدور پیام‌های خطا، دفاع کنید.
var person = {
    name: "Vahid"
};

Object.preventExtensions(person);

var defensivePerson = createDefensiveObject(person);


defensivePerson.age = 27;                 // Error!
console.log(defensivePerson.age);         // Error!
در این مورد، defensivePerson برای هر دو حالت خواندن و نوشتن ویژگی‌هایی که وجود ندارند، خطا صادر می‌کند.
شاید مفیدترین زمان برای استفاده از اشیاء دفاعی، در هنگام تعریف یک سازنده باشد و شما می‌توانید این کار را به عنوان یک قرارداد در نوشتن اشیاء حفظ کنید.
برای مثال:
function Person(name) {
    this.name = name;

    return createDefensiveObject(this);
}

var person = new Person("Vahid");
console.log(person.age);         // Error!
به وسیله فراخوانی تابع ()createDefensiveObject درون سازنده، می‌توانید اطمینان کامل داشته باشید که همه‌ی نمونه‌های ساخته شده‌ی از شیء Person، دارای حالت دفاعی می‌باشند.

پاسخ به بازخورد‌های پروژه‌ها
ارسال مستقیم به پرینتر
سلام.. این کد برای من انجام نمیشه و تنظیمات چاپگر رو نشون نمیده.
باید حتما آکروبات ریدر نصب باشه تا نشون بده؟ یا کدهام مشکل داره؟
PdfReport pdfrpt = new PdfReport();
                pdfrpt.DocumentPreferences(doc =>
                {
                    doc.RunDirection(PdfRunDirection.RightToLeft);
                    doc.Orientation(PageOrientation.Landscape);
                    doc.PageSize(PdfPageSize.A4);
                    doc.DocumentMetadata(new DocumentMetadata { Author = "Hovze", Application = "PdfRpt", Keywords = "Report", Subject = "Test Rpt", Title = "Report" });

                    doc.PrintingPreferences(new PrintingPreferences
                    {
                        ShowPrintDialogAutomatically = true
                    });
                })
                .DefaultFonts(fonts =>
                {
                    fonts.Path(fontPath + "\\BNAZANIN.ttf", fontPath + "\\BNAZNNBD.ttf");
                    fonts.Size(13);
                })
                .PagesFooter(footer =>
                {
                    footer.DefaultFooter(PersianDate.ToPersianDateTime(DateTime.Now, "/", false, false));
                })
                .PagesHeader(header =>
                {
              
                     header.CustomHeader(new CustomHeader { Name=Studentdt.Rows[0][1].ToString(),Family= Studentdt.Rows[0][2].ToString(),parvande= Studentdt.Rows[0][3].ToString(),tavalod= Studentdt.Rows[0][5].ToString(),shsh= Studentdt.Rows[0][6].ToString(),sodor= Studentdt.Rows[0][7].ToString(),codeMelli= Studentdt.Rows[0][8].ToString(),codeTahsili= Studentdt.Rows[0][0].ToString(),taahol=Studentdt.Rows[0][22].ToString(), PdfRptFont = header.PdfFont ,_imagePath=imgPath,_imageStud=stdImage});
                    
                        
                     })
                .MainTableTemplate(template =>
                {
                    template.BasicTemplate(BasicTemplate.SilverTemplate);
                })
                .MainTablePreferences(table =>
                {
                    table.ColumnsWidthsType(TableColumnWidthType.Relative);
                    table.NumberOfDataRowsPerPage(0);
                    table.GroupsPreferences(new GroupsPreferences
                    {
                        GroupType = GroupType.HideGroupingColumns,
                        RepeatHeaderRowPerGroup = true,
                        ShowOneGroupPerPage = true,
                        SpacingBeforeAllGroupsSummary = 5f
                    });
                })
                .MainTableDataSource(dataSource =>
                {
                    dataSource.DataTable(dt);
                })
                .MainTableSummarySettings(summarySettings =>
                {
                    summarySettings.PreviousPageSummarySettings("نقل از صفحه قبل");
                })
                .MainTableColumns(columns =>
                {
                 
                    columns.AddColumn(column =>
                    {
                        column.PropertyName("سال");
                        column.CellsHorizontalAlignment(PdfRpt.Core.Contracts.HorizontalAlignment.Center);
                        column.IsVisible(true);
                        column.Order(0);
                        column.Width(2);
                        column.HeaderCell("سال تحصیلی");
                       
                    });
                    columns.AddColumn(column =>
                    {
                        column.PropertyName("نیم سال");
                        column.CellsHorizontalAlignment(PdfRpt.Core.Contracts.HorizontalAlignment.Left);
                        column.IsVisible(true);
                        column.Order(1);
                        column.Width(1);
                        column.HeaderCell("نیم سال");
                       
                    });
                    columns.AddColumn(column =>
                    {
                        column.PropertyName("پایه");
                        column.CellsHorizontalAlignment(PdfRpt.Core.Contracts.HorizontalAlignment.Left);
                        column.IsVisible(true);
                        column.Order(2);
                        column.Width(1);
                        column.HeaderCell("پایه");
                    });
                    columns.AddColumn(column =>
                    {
                        column.PropertyName("درس");
                        column.CellsHorizontalAlignment(PdfRpt.Core.Contracts.HorizontalAlignment.Left);
                        column.IsVisible(true);
                        column.Order(3);
                        column.Width(1);
                        column.HeaderCell("درس");
                    });
                    for (int i = 6; i < countScore + 6; i++)
                    {
                            columns.AddColumn(column =>
                        {
                            column.PropertyName(dt.Columns[i].ToString());
                            column.CellsHorizontalAlignment(PdfRpt.Core.Contracts.HorizontalAlignment.Left);
                            column.IsVisible(true);
                            column.Order(i - 2);
                            column.Width(1);
                            column.HeaderCell(dtTitle.Rows[0][i - 5].ToString());
                            column.CalculatedField(
                          list =>
                          {
                              nimsal = list.GetValueOf("نیم سال").ToString();
                              if (nimsal == "دوم")
                              {
                                  if (k == 13 + countScore)
                                      k = 13;
                                  //return list[i+8].
                                  k++;
                                  

                                  return list[k].PropertyValue.ToString();
                              }
                              else
                              {
                                  if (m == 5 + countScore)
                                      m = 5;
                                  m++;
                                  return list[m].PropertyValue.ToString(); ;
                              }
                          });

                        });
                      
                    }
                   
                    columns.AddColumn(column =>
                    {
                        column.PropertyName("FinalScore1");
                        column.CellsHorizontalAlignment(PdfRpt.Core.Contracts.HorizontalAlignment.Left);
                        column.IsVisible(true);
                        column.Order(countScore + 4);
                        column.Width(1);
                        column.HeaderCell("نمره نهایی نیم سال 1");
                    });
                    columns.AddColumn(column =>
                    {
                        column.PropertyName("t_term1");
                        column.CellsHorizontalAlignment(PdfRpt.Core.Contracts.HorizontalAlignment.Left);
                        column.IsVisible(true);
                        column.Order(countScore+5);
                        column.Width(1);
                        column.HeaderCell("نمره تجدیدی نیم سال 1");
                    });
                    columns.AddColumn(column =>
                    {
                        column.PropertyName("t_term11");
                        column.CellsHorizontalAlignment(PdfRpt.Core.Contracts.HorizontalAlignment.Left);
                        column.IsVisible(true);
                        column.Order(countScore + 6);
                        column.Width(1);
                        column.HeaderCell("نمره استادیاری ترم 1");
                    });
                    columns.AddColumn(column =>
                    {
                        column.PropertyName("FinalScore2");
                        column.CellsHorizontalAlignment(PdfRpt.Core.Contracts.HorizontalAlignment.Left);
                        column.IsVisible(true);
                        column.Order(countScore + 7);
                        column.Width(1);
                        column.HeaderCell("نمره نهایی نیم سال 2");
                    });

                    columns.AddColumn(column =>
                    {
                        column.PropertyName("t_term2");
                        column.CellsHorizontalAlignment(PdfRpt.Core.Contracts.HorizontalAlignment.Left);
                        column.IsVisible(true);
                        column.Order(countScore + 8);
                        column.Width(1);
                        column.HeaderCell("نمره تجدیدی نیم سال 2");
                    });
                    
                    columns.AddColumn(column =>
                    {
                        column.PropertyName("t_term22");
                        column.CellsHorizontalAlignment(PdfRpt.Core.Contracts.HorizontalAlignment.Left);
                        column.IsVisible(true);
                        column.Order(countScore + 9);
                        column.Width(1);
                        column.HeaderCell("نمره استادیاری نیم سال 2");
                    });
                   
                    columns.AddColumn(column =>
                    {
                        column.PropertyName("tabestan");
                        column.CellsHorizontalAlignment(PdfRpt.Core.Contracts.HorizontalAlignment.Left);
                        column.IsVisible(true);
                        column.Order(countScore + 10);
                        column.Width(1);
                        column.HeaderCell("نمره تابستان");
                    });
                   
                    columns.AddColumn(column =>
                    {
                        column.PropertyName("Ghabol");
                        column.CellsHorizontalAlignment(PdfRpt.Core.Contracts.HorizontalAlignment.Left);
                        column.IsVisible(true);
                        column.Order(countScore + 11);
                        column.Width(1);
                        column.HeaderCell("قبول");
                    });
                });
               

                return  pdfrpt.MainTableEvents(events =>
                   {
                       events.DataSourceIsEmpty(message: "داده ای برای نمایش وجو د ندارد");
                       events.MainTableAdded(args =>
                       {
                           var taxTable = new PdfPTable(1);  // Create a clone of the MainTable's structure
                           taxTable.RunDirection = 3;
                           //taxTable.SetWidths(new float[] { 3, 3, 3 });
                           taxTable.WidthPercentage = 100f;
                           taxTable.SpacingBefore = 10f;

                           taxTable.AddSimpleRow(
                               (data, cellProperties) =>
                               {
                                   data.Value = "مهر و امضای مدیر";
                                   cellProperties.ShowBorder = false;
                                   cellProperties.HorizontalAlignment = HorizontalAlignment.Left;
                                   cellProperties.PdfFont = args.PdfFont;
                               });
                           args.PdfDoc.Add(taxTable);
                       });
                   })
                .Export(export =>
                {
                })
                .Generate(data => data.AsPdfFile(fo.Name/*string.Format("{0}\\RptCalculatedFieldsSample-{1}.pdf", Application.StartupPath, Guid.NewGuid().ToString("N")))*/));
               
            }

مطالب
شمسی سازی Date-Picker توکار Angular Material 6x
Angular Material به همراه یک کامپوننت Date-Picker بسیار شکیل و حرفه‌ای است اما ... از تقویم شمسی پشتیبانی نمی‌کند. در این مطلب می‌خواهیم با تدارک یک DateAdapter سفارشی، این مشکل را برطرف کنیم تا در نهایت به یک چنین Date-Picker شمسی برسیم:



تاریخچه‌ی تغییرات کامپوننت Date-Picker

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

DateAdapter شمسی تهیه شده از کتابخانه‌ی jalali-moment برای تبدیل تاریخ‌ها استفاده می‌کند. بنابراین ابتدا نیاز است این وابستگی را نصب کرد:
 npm install jalali-moment --save


افزودن DateAdapter شمسی به پروژه

برای افزودن DateAdapter شمسی تهیه شده، فایل جدید app\shared\material.persian-date.adapter.ts را به برنامه اضافه کرده و به صورت زیر تکمیل کنید:
import { DateAdapter } from "@angular/material";
import * as jalaliMoment from "jalali-moment";

export const PERSIAN_DATE_FORMATS = {
  parse: {
    dateInput: "jYYYY/jMM/jDD"
  },
  display: {
    dateInput: "jYYYY/jMM/jDD",
    monthYearLabel: "jYYYY jMMMM",
    dateA11yLabel: "jYYYY/jMM/jDD",
    monthYearA11yLabel: "jYYYY jMMMM"
  }
};

export class MaterialPersianDateAdapter extends DateAdapter<jalaliMoment.Moment> {

  constructor() {
    super();
    super.setLocale("fa");
  }

  getYear(date: jalaliMoment.Moment): number {
    return this.clone(date).jYear();
  }

  getMonth(date: jalaliMoment.Moment): number {
    return this.clone(date).jMonth();
  }

  getDate(date: jalaliMoment.Moment): number {
    return this.clone(date).jDate();
  }

  getDayOfWeek(date: jalaliMoment.Moment): number {
    return this.clone(date).day();
  }

  getMonthNames(style: "long" | "short" | "narrow"): string[] {
    switch (style) {
      case "long":
      case "short":
        return jalaliMoment.localeData("fa").jMonths().slice(0);
      case "narrow":
        return jalaliMoment.localeData("fa").jMonthsShort().slice(0);
    }
  }

  getDateNames(): string[] {
    const valuesArray = Array(31);
    for (let i = 0; i < 31; i++) {
      valuesArray[i] = String(i + 1);
    }
    return valuesArray;
  }

  getDayOfWeekNames(style: "long" | "short" | "narrow"): string[] {
    switch (style) {
      case "long":
        return jalaliMoment.localeData("fa").weekdays().slice(0);
      case "short":
        return jalaliMoment.localeData("fa").weekdaysShort().slice(0);
      case "narrow":
        return ["ی", "د", "س", "چ", "پ", "ج", "ش"];
    }
  }

  getYearName(date: jalaliMoment.Moment): string {
    return this.clone(date).jYear().toString();
  }

  getFirstDayOfWeek(): number {
    return jalaliMoment.localeData("fa").firstDayOfWeek();
  }

  getNumDaysInMonth(date: jalaliMoment.Moment): number {
    return this.clone(date).jDaysInMonth();
  }

  clone(date: jalaliMoment.Moment): jalaliMoment.Moment {
    return date.clone().locale("fa");
  }

  createDate(year: number, month: number, date: number): jalaliMoment.Moment {
    if (month < 0 || month > 11) {
      throw Error(
        `Invalid month index "${month}". Month index has to be between 0 and 11.`
      );
    }
    if (date < 1) {
      throw Error(`Invalid date "${date}". Date has to be greater than 0.`);
    }
    const result = jalaliMoment()
      .jYear(year).jMonth(month).jDate(date)
      .hours(0).minutes(0).seconds(0).milliseconds(0)
      .locale("fa");

    if (this.getMonth(result) !== month) {
      throw Error(`Invalid date ${date} for month with index ${month}.`);
    }
    if (!result.isValid()) {
      throw Error(`Invalid date "${date}" for month with index "${month}".`);
    }
    return result;
  }

  today(): jalaliMoment.Moment {
    return jalaliMoment().locale("fa");
  }

  parse(value: any, parseFormat: string | string[]): jalaliMoment.Moment | null {
    if (value && typeof value === "string") {
      return jalaliMoment(value, parseFormat, "fa");
    }
    return value ? jalaliMoment(value).locale("fa") : null;
  }

  format(date: jalaliMoment.Moment, displayFormat: string): string {
    date = this.clone(date);
    if (!this.isValid(date)) {
      throw Error("JalaliMomentDateAdapter: Cannot format invalid date.");
    }
    return date.format(displayFormat);
  }

  addCalendarYears(date: jalaliMoment.Moment, years: number): jalaliMoment.Moment {
    return this.clone(date).add(years, "jYear");
  }

  addCalendarMonths(date: jalaliMoment.Moment, months: number): jalaliMoment.Moment {
    return this.clone(date).add(months, "jmonth");
  }

  addCalendarDays(date: jalaliMoment.Moment, days: number): jalaliMoment.Moment {
    return this.clone(date).add(days, "jDay");
  }

  toIso8601(date: jalaliMoment.Moment): string {
    return this.clone(date).format();
  }

  isDateInstance(obj: any): boolean {
    return jalaliMoment.isMoment(obj);
  }

  isValid(date: jalaliMoment.Moment): boolean {
    return this.clone(date).isValid();
  }

  invalid(): jalaliMoment.Moment {
    return jalaliMoment.invalid();
  }

  deserialize(value: any): jalaliMoment.Moment | null {
    let date;
    if (value instanceof Date) {
      date = jalaliMoment(value);
    }
    if (typeof value === "string") {
      if (!value) {
        return null;
      }
      date = jalaliMoment(value).locale("fa");
    }
    if (date && this.isValid(date)) {
      return date;
    }
    return super.deserialize(value);
  }
}
کار این Adapter و یا «وفق دهنده» این است که مشخص می‌کند، هفته‌ی ایرانی از چه روزی شروع می‌شود. نام روزهای هفته‌ی ایرانی چیست؟ برچسب‌های نام ماه‌های ایرانی چگونه باید تامین شوند و در کل جهت وفق دادن تقویم میلادی اصلی با تقویم شمسی، چه اجزایی باید به سیستم معرفی شوند تا این تقویم توکار بدون مشکل مانند قبل کار کند.
 
معرفی وفق دهنده‌ی شمسی به پروژه

پس از تعریف MaterialPersianDateAdapter و همچنین PERSIAN_DATE_FORMATS، برای معرفی آن‌ها به برنامه، فایل app\shared\material.module.ts را گشوده و به صورت زیر تغییر دهید:
import { NgModule } from "@angular/core";
import {  DateAdapter,  MAT_DATE_FORMATS,  MAT_DATE_LOCALE } from "@angular/material";

import { MaterialPersianDateAdapter, PERSIAN_DATE_FORMATS } from "./material.persian-date.adapter";

@NgModule({
  providers: [
    { provide: DateAdapter, useClass: MaterialPersianDateAdapter, deps: [MAT_DATE_LOCALE] },
    { provide: MAT_DATE_FORMATS, useValue: PERSIAN_DATE_FORMATS }
  ]
})
export class MaterialModule {
}
کار این تعاریف، تعویض DateAdapter اصلی میلادی، با نمونه‌ی شمسی است. همچنین فرمت نمایشی برچسب‌ها را نیز جایگزین می‌کند.

پس از آن اگر mat-datepicker را به نحو متداولی به صفحه اضافه کنیم:
<mat-form-field>
    <input matInput [matDatepicker]="picker6" placeholder="json gregorian input" [(ngModel)]="dateControl">
    <mat-datepicker-toggle matSuffix [for]="picker6"></mat-datepicker-toggle>
    <mat-datepicker #picker6></mat-datepicker>
</mat-form-field>
یک چنین خروجی حاصل خواهد شد:




چند مثال تکمیلی از کاربردهای کامپوننت mat-datepicker

1) استفاده از تاریخ میلادی رسیده‌ی از سمت سرور و نمایش آن
<mat-form-field>
    <input matInput [matDatepicker]="picker6" placeholder="json gregorian input" [(ngModel)]="dateControl">
    <mat-datepicker-toggle matSuffix [for]="picker6"></mat-datepicker-toggle>
    <mat-datepicker #picker6></mat-datepicker>
</mat-form-field>
با این کدها:
@Component()
export class PersianDatepickerComponent {

  jsonDate = "2018-01-08T20:21:29.4674496";
  dateControl = this.jsonDate;
}
در اینجا jsonDate همان رشته‌ی تاریخی است که از سمت سرور دریافت شده و میلادی است. با انتساب آن به ngModel، به صورت خودکار شمسی نمایش داده خواهد شد:




2) تعیین تاریخ آغاز تقویم و نمایش آن در حالت انتخاب سال
<mat-form-field>
    <input matInput [matDatepicker]="picker2" placeholder="startAt 2017-01-01 and startView=year">
    <mat-datepicker-toggle matSuffix [for]="picker2"></mat-datepicker-toggle>
    <mat-datepicker #picker2 startView="year" [startAt]="startDate"></mat-datepicker>
</mat-form-field>
با این کدها:
import * as moment from "jalali-moment";

@Component()
export class PersianDatepickerComponent  {

  startDate = moment("2017-01-01", "YYYY-MM-DD"); // = moment.from("2017-01-01", "en");
  
}
در این مثال خاصیت startAt را به یک تاریخ میلادی متصل کرده‌ایم و همچنین خاصیت startView به year تنظیم شده‌است که یک چنین خروجی را در بار اول نمایش تقویم ایجاد می‌کند:




3) تعیین باز‌ه‌ی تاریخی قابل انتخاب توسط کاربر
<mat-form-field>
    <input matInput [matDatepicker]="picker3" [min]="minDate" [max]="maxDate" placeholder="min: 2017-10-02 and max: 1396-07-29">
    <mat-datepicker-toggle matSuffix [for]="picker3"></mat-datepicker-toggle>
    <mat-datepicker #picker3></mat-datepicker>
</mat-form-field>
با این کدها:
import * as moment from "jalali-moment";

@Component()
export class PersianDatepickerComponent  {

  minDate = moment.from("2017-10-02", "en"); // = moment('2017-10-02', 'YYYY-MM-DD');
  maxDate = moment.from("1396-07-29", "fa"); // = moment('1396-07-29', 'jYYYY-jMM-jDD');
}
همانطور که ملاحظه می‌کنید کتابخانه‌ی jalali-moment می‌تواند تاریخ شمسی و یا میلادی را توسط متد from آن دریافت کند و هر دو حالت در اینجا پس از انتساب به خواص min و max تقویم، به خوبی کار کرده و سبب محدود ساختن بازه‌ی قابل انتخاب توسط کاربر می‌شوند.



در این تصویر روزهای خاکستری، قابل انتخاب نیستند و غیرفعال شده‌اند (چون min به 10 مهر و max به 29 مهر تنظیم شده‌است).


4) غیرفعال کردن روزهای قابل انتخاب بر اساس یک منطق سفارشی
<mat-form-field>
    <input matInput [matDatepicker]="picker4" [matDatepickerFilter]="myFilter" placeholder="Date validation - Datepicker Filter">
    <mat-datepicker-toggle matSuffix [for]="picker4"></mat-datepicker-toggle>
    <mat-datepicker #picker4></mat-datepicker>
</mat-form-field>
با این کدها:
import * as moment from "jalali-moment";

@Component()
export class PersianDatepickerComponent {

  myFilter = (d: moment.Moment): boolean => {
    const day: number = d.day();
    // Prevent Thursday and Friday from being selected.
    return day !== 5 && day !== 4;
  }
}
در اینجا روزهای پنج‌شنبه و جمعه در تقویم نمایش داده شده، بر اساس تعریف matDatepickerFilter سفارشی، دیگر قابل انتخاب نیستند:



5) کار با رخ‌دادهای تقویم
<mat-form-field>
    <input matInput [matDatepicker]="picker5" (dateInput)="onInput($event)" (dateChange)="onChange($event)"
        placeholder="dateInput and dateChange events">
    <mat-datepicker-toggle matSuffix [for]="picker5"></mat-datepicker-toggle>
    <mat-datepicker #picker5></mat-datepicker>
</mat-form-field>
با این کدها:
import { MatDatepickerInputEvent } from "@angular/material";
import * as moment from "jalali-moment";

@Component()
export class PersianDatepickerComponent {

  onInput(event: MatDatepickerInputEvent<moment.Moment>) {
    console.log("OnInput: ", event.value);
  }

  onChange(event: MatDatepickerInputEvent<moment.Moment>) {
    const x = moment(event.value).format("jYYYY/jMM/jDD");
    console.log("OnChange: ", x);
  }
}
در اینجا نحوه‌ی واکنش نشان دادن به رخ‌دادهای dateInput و dateChange کامپوننت mat-datepicker را ملاحظه می‌کنید:


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


کدهای کامل این مثال را از اینجا می‌توانید دریافت کنید.
مطالب
جلوگیری از ورود همزمان کاربران با نام کاربری و رمز عبور یکسان
در اکثر برنامه‌های وب، کاربر قادر است با یک نام کاربری و رمز عبور در چند Session همزمان لاگین کند. ممکن است سیاست برخی مدیران محصول این باشد که جلوی این مورد را بگیرند تا به عنوان مثال کاربران را به جای استفاده‌ی همزمان از یک نام کاربری و رمز عبور، مجبور به خرید مجوز‌های بیشتری کنند. ASP.NET Identity به صورت پیش فرض این مورد را پشتیبانی نمی‌کند؛ اما به کمک استفاده از امکانات درونی آن می‌توان این پشتیبانی را اضافه کرد.
یکی از فیلد‌های جدول AspNetUsers فیلد SecurityStamp می‌باشد. SecurityStamp یک مقدار تصادفی است:


Security Stamp باید با هربار تغییر اطلاعات احراز هویت (مانند رمز عبور) و اختیارات کاربر(Role) تغییر کند. به عنوان مثال کاربری در چند مرورگر لاگین کرده و گزینه‌ی مرا به خاطر داشته باش را انتخاب کرده است. اگر این کاربر رمز عبورش از هر جایی عوض شود، باید لاگین او در همه‌ی Session‌ها غیر معتبر شود. این مورد با تغییر کردن SecurityStamp بعد از تغییر رمز عبور صورت می‌گیرد. ASP.NET مقدار SecurityStamp را در کوکی کاربر نگه می‌دارد و در بازه‌های زمانی، این مقدار را با مقدار درون دیتابیس مقایسه می‌کند و در صورت عدم برابری، کاربر را احراز هویت نمی‌کند. بازه‌ی زمانی این بررسی در متد ConfigureAuth قابل تنظیم است که در ادامه شرح داده خواهد شد.
صورت مساله یافتن راه حلی جهت جلوگیری از ورود همزمان چند کاربر با یک نام کاربری و رمز عبور به سیستم می‌باشد. یکی از راه‌حل هایی که در ابتدا به ذهن می‌آید استفاده از Session و نگهداری کاربران لاگین کرده در حافظه می‌باشد. پیاده سازی این راه حل می‌تواند به کمک یک کلاس Static صورت پذیرد، اما قسمت چالشی این موضوع این است که چه زمانی باید کاربر از لیست حذف گردد؟ اگر اتصال کاربر قطع شود چه عملی باید صورت گیرد؟
راه حل دیگر استفاده از SecurityStamp هست؛ به این صورت که با هربار لاگین کاربر این مقدار تصادفی به‌روز گردد و ASP.NET Identity به گونه‌ای تنظیم شود که با هر درخواست HTTP، صحت SecurityStamp بررسی گردد. مقدار پیش فرض بازه‌ی زمانی بررسی، هر 30 دقیقه یک بار است.
در مثال‌های رسمی ASP.NET Identity لاگین به صورت ذیل پیاده سازی شده است:
   
        [HttpPost]
        [AllowAnonymous]
        [ValidateAntiForgeryToken]
        public async Task<ActionResult> Login(LoginViewModel model, string returnUrl)
        {
            if (!ModelState.IsValid)
            {
                return View(model);
            }
            // This doesn't count login failures towards account lockout
            // To enable password failures to trigger account lockout, change to shouldLockout: true
            var result = await SignInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, shouldLockout: false);
            switch (result)
            {
                case SignInStatus.Success:
                    return RedirectToLocal(returnUrl);
                case SignInStatus.LockedOut:
                    return View("Lockout");
                case SignInStatus.RequiresVerification:
                    return RedirectToAction("SendCode", new { ReturnUrl = returnUrl, RememberMe = model.RememberMe });
                case SignInStatus.Failure:
                default:
                    ModelState.AddModelError("", "Invalid login attempt.");
                    return View(model);
            }
        }
این کد را باید به گونه‌ای تغییر داد که اگر نام کاربری و رمز عبور معتبر بودند، مقدار SeucrityStamp به‌روز گردد. به همین منظور قبل از فراخوانی PasswordSignInAsync کد ذیل اضافه می‌گردد:
       var loggedinUser = await UserManager.FindAsync(model.Email, model.Password);
            if (loggedinUser != null)
            {
                await UserManager.UpdateSecurityStampAsync(loggedinUser.Id);
            }
همانطور که مشاهده می‌شود، جهت بروز رسانی SecurityStamp از سازوکار درونی ASP.NET Identity، در واقع متد UpdateSecurityStampAsync بهره گرفته شده است.
اکنون باید تنظیمات پیش‌فرض بازه‌ی زمانی بررسی صحت SecurityStamp تغییر داده شود. این تنظیمات در فایل Startup.Auth.cs در پوشه‌ی App_Start قرار دارند:
     app.UseCookieAuthentication(new CookieAuthenticationOptions
            {
                AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
                LoginPath = new PathString("/Account/Login"),
                Provider = new CookieAuthenticationProvider
                {
                    // Enables the application to validate the security stamp when the user logs in.
                    // This is a security feature which is used when you change a password or add an external login to your account.  
                    OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
                        validateInterval: TimeSpan.FromMinutes(30),
                        regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager))
                }
            });
در کد بالا OnValidateIdentity باید مقدار ذیل را بگیرد:
OnValidateIdentity =
                        SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
                            TimeSpan.FromMinutes(0), // <-- Note the timer is set for zero
                            (manager, user) => user.GenerateUserIdentityAsync(manager))
اکنون Framework موظف است در هر درخواست HTTP، صحت SeucirtyStamp را بررسی کند. بنابراین اگر کاربری در سیستم لاگین باشد و کاربر دیگری با همان نام کاربری و رمز عبور لاگین کند، کاربر اول از سیستم لاگ اوت می‌شود؛ چرا که مقدار SecurityStamp او دیگر معتبر نیست. باید در نظر گرفته شود این عمل در سیستم‌های با تعداد کاربر زیاد باعث افزایش درخواست‌های دیتابیس می‌شود. 
اکنون جهت تست، اگر با مرورگر اول در سیستم لاگین صورت گیرد، سپس با همان اطلاعات در مرورگری دیگر، لاگین صورت گیرد، کاربر اول پس از درخواست بعدی، از سیستم لاگ اوت می‌شود. در مثال انتهای مطلب، صفحه‌ی About به صورت غیر عمومی درآمده که می‌توان بررسی راه حل جاری را در آن صفحه صورت داد. 
اگر بخواهیم لاگ اوت شدن کاربر را آنی کنیم، می‌توان در فواصل زمانی مشخصی، یک درخواست Ajax از سمت کلاینت به سرور ارسال کرد و تصدیق هویت کاربر را بررسی کرد:
        window.setInterval(function() {
            $.ajax({
                url:,
                type: "Post",
                dataType: "json",
                success: function(data) {
                    if (!data) {
                        alert("Someone has logged in using your username and password!");
                        location.reload();
                    }
                }
            });
        }, 60000);
در کد بالا به کمک window.setInterval، هر یک دقیقه یک بار لاگین بودن کاربر بررسی می‌گردد و در صورت لاگین نبودن، پیغام لازم به کاربر نمایش داده می‌شود. در نظر داشته باشید، این کد تنها باید در صفحات غیر عمومی قرار داده شود.
کد اکشن بررسی لاگین بودن به سادگی ذیل است:
        [AllowAnonymous]
        public virtual ActionResult Authenticated()
        {
            return Json(User.Identity.IsAuthenticated);
        }

نکته‌ی مهم این است که خصیصه‌ی AllowAnonymous بالای اکشن قرار گرفته باشد تا در صورتیکه Controller به صورت عمومی در دسترس نیست، اکشن همیشه و حتی وقتی کاربر لاگ اوت شده در دسترس باشد. در مثال انتهای مطلب صفحه‌ی About تنها در اختیار کاربران احراز هویت شده قرار گرفته است، بنابراین اگر دو کاربر با اطلاعات یکسانی به سیستم لاگین کنند، کاربر اول پیغام خطای ذیل را گرفته و به صفحه‌ی لاگین می‌رود. این کد در صفحه‌ی About در مثال انتهای مطلب قرار گرفته است:

مطالب
قراردادهای کوئری نویسی در OData و WCF Data Service - قسمت دوم
در مطلب قبلی قراردادهای کوئری نویسی در OData و WCF Data Service - قسمت اول با قراردادهای کوئری نویسی آشنا شدید در این مطلب به جزئیات بیشتر این قراردادها می‌پردازیم.

عمگرهای منطقی
در OData نه عملگر منطقی داریم که امکان مقایسه منطقی در عبارات‌های شرطی را در اختیارمان قرار می‌دهد.
  1. eq عملگر برابری
  2. ne عملگر مخالف
  3. lt عملگر کوچکتری
  4. le عملگر کوچکتر یا مساوی
  5. gt عملگر بزرگنری
  6. ge عملگر بزرگتر یا مساوی
  7. and
  8. or
  9. not
عملگرهای ریاضی
پنج عمگر ریاضی وجود دارد که امکان انجام عملیات ریاضی در کوئری را میسر می‌سازد.
  1. add جمع دو عملوند
  2. sub تفریق دو عملوند
  3. mul ضرب دو عملوند
  4. div تقیسم دو عملوند
  5. mod باقیمانده تقسیم دو عملوند
در آخر () برای گروه بنده و اولویت دهی عبارات کاربرد دارد.
توابع عبارت‌های کوئری نویسی
در OData چهار دسته توابع داریم
  1. String Functions در جدول زیر این توابع با توضیح آن آورده شده است:
    آیا رشته p0 شامل رشته p1 هست؟
    مثال:http://services.odata.org//Northwind.svc/Customers?$filter=substringof('Alfreds', CompanyName) eq true
    شرح: مشتریانی که نام شرکت آنها شامل رشته Alfreds باشد
    bool substringof(string p0, string p1)
    آیا رشته p0 با رشته p1 پایان می‌یابد؟
    مثال:http://services.odata.org/Northwind/Northwind.svc/Customers?$filter=endswith(CompanyName, 'Futterkiste')
    شرح: مشتریانی که نام شرکت آنها با رشته FutterKiste پایان می‌یابد
    bool endswith(string p0, string p1)
    آیا رشته p0 با رشته p1 آغاز می‌شود؟
    مثال:http://services.odata.org/Northwind.svc/Customers?$filter=startswith(CompanyName, 'Alfr')
    شرح: مشتریانی که نام شرکت آنها با رشته Alfer آغاز می‌یابد
    boolstartswith(string p0, string p1)
    محاسبه طول رشته دریافتی.
    مثال:http://services.odata.org/Northwind/Northwind.svc/Customers?$filter=length(CompanyName) eq 19
    شرح: مشتریانی که طول نام شرکت آنها برابر 19 باشد
    int length(string p0)
    برگشت اندیس رشته وروری
    مثال:http://services.odata.org/Northwind.svc/Customers?$filter=indexof(CompanyName, 'lfreds') eq 1
    شرح: مشتریانی که نام شرکت آنها با رشته lfreds که از کارکتر دوم شروع می‌یابد
    int indexof(string arg)
    جایگزینی یک رشته در رشته دیگر
    مثال:http://services.odata.org/Northwind.svc/Customers?$filter=replace(CompanyName, ' ', '') eq 'AlfredsFutterkiste'
    شرح: مشتریانی که نام شرکت آنها بدون فاصله برابر AlfredsFutterkiste باشد با پرکردن فاصله با جای خالی
    string replace(string p0, string find, string replace)
    برگرداندن رشته ای از رشته دیگر از شماره اندیس ورودی یا از شماره اندیس تا طول رشته ورودی
    مثال:http://services.odata.org/Northwind.svc/Customers?$filter=substring(CompanyName, 1) eq 'lfreds Futterkiste
    شرح: مشتریانی که نام شرکت آنها از کاراکتر دوم برابر lfreds Futterkiste باشد
    'http://services.odata.org/Northwind.svc/Customers?$filter=substring(CompanyName, 1,2) eq 'lf
    string substring(string p0, int pos)
    string substring(string p0, int pos, int length)
    برگرداندن رشته ورودی با کارکتر بزرگ
    برگرداندن رشته ورودی با کاراکتر کوچک
    string tolower(string p0)
    string toupper(string p0)
    حذف کارکتر‌های Whitespace از دو طرف رشته string trim(string p0)
    الحاق رشته به هم string concat(string p0, string p1)

  2. Date Functions
    برگرداندن سال تاریخ ورودی
    مثال:http://services.odata.org/Northwind.svc/Employees?$filter=year(BirthDate) eq 1971 ن
    int year(DateTimep0)
    برگرداندن ماه تاریخ ورودی
    مثال:http://services.odata.org/Northwind/Northwind.svc/Employees?$filter=month(BirthDate) eq 5
    int month(DateTimep0)
    برگرداندن روز تاریخ ورودی
    برگرداندن تعداد روز فاصله زمانی
    مثال: http://services.odata.org/Northwind.svc/Employees?$filter=day(BirthDate) eq 8
    int days(DateTimep0)
    int day(DateTimep0)
    برگرداندن ساعت تاریخ ورودی
    مثال:http://services.odata.org/Northwind.svc/Employees?$filter=hour(BirthDate) eq 4
    int hour(DateTimep0)
    برگرداندن دقیقه تاریخ ورودی
    مثال:http://services.odata.org/Northwind.svc/Employees?$filter=minute(BirthDate) eq 40
    int minute(DateTimep0)
    int minutes(DateTimep0)
    برگرداندن ثانبه تاریخ ورودی
    مثال:http://services.odata.org/Northwind.svc/Employees?$filter=second(BirthDate) eq 40
    int second(DateTimep0)

  3. Math Functions
    برگرداندن سقف عدد ورودی
    مثال:http://services.odata.org/Northwind.svc/Orders?$filter=ceiling(Freight) eq 32
    double ceiling(double p0)
    decimal ceiling(decimal p0)
    برگرداندن کف عدد ورودی
    مثال:http://services.odata.org/Northwind.svc/Orders?$filter=floor(Freight) eq 32
    double floor(double p0)
    decimal floor(decimal p0)

    گردن کردن عدد ورودی
    مثال:ttp://services.odata.org/Northwind.svc/Orders?$filter=round(Freight) eq 32

    double round(double p0)
    decimal round(decimal p0)

  4. Type Functions
    برگرداندن نوع داده وروری
    مثالhttp://services.odata.org/Northwind.svc/Orders?$filter=isof(Customer, NorthwindModel.MVPCustomer)
    شرح: سفارشاتی که نوع مشتری آنها برابر MVPCustomer باشد
    boolisof(type p0)
    boolisof(expression p0, type p1)
    تبدیل نوع داده ورودی
    <p0> cast(type p0)
    < p1> cast(expression p0, type p1
در آخر چند نکته
  1. برای استفاده از رشته‌ها در عبارات از ' تک کوتشن استفاده نمایید 
  2. برای دستیابی به مقادیر پروپرتی‌ها از value$ استفاده نمایید 
  3. برای دستیابی به آدرس روابط‌های یک موجودیت از links$  استفاده نمایید
    مثال:http://services.odata.org/OData/OData.svc/Categories(1)/$links/Products
  4. select$ برای محدود کردن پروپرتی‌های یک موجودیت استفاده می‌شود
    مثال:http://services.odata.org/OData/OData.svc/Products?$select=Price,Name
  5. از ستاره برای دستیابی به همه پروپرتی‌های یک موجودیت می‌توان استفاده نمود
    مثال:http://services.odata.org/OData/OData.svc/Products?$select=*
ادامه دارد...
 
نظرات مطالب
نحوه ایجاد یک نقشه‌ی سایت پویا با استفاده از قابلیت Reflection
ضمن تشکر بابت نشر این مطلب، من با قسمت route به مشکل برخورد کردم. یعنی، اگر از هر نوع پسوندی برای آدرس استفاده کنم، با خطای ۴۰۴ مواجه می‌شوم (البته به طرز عجیبی بجز aspx). برای تنظیمات مسیریابی به شکل زیر عمل می‌کنم:
routes.MapRoute(
    "SiteMap",
    "sitemap.xml",
    new { controller = "SiteMap", action = "Index", name = UrlParameter.Optional }
);
که البته با همان خطای ۴۰۴ روبرو می‌شود. در نوشتاری در خصوص مقدار RouteExistingFiles توضیح داده شده‌است. پس وضعیت تنظیم سیستم مسیریابی را به شکل زیر اصلاح کردم:
  routes.RouteExistingFiles = true; 
routes.MapRoute(
    "SiteMap",
    "sitemap.xml",
    new { controller = "SiteMap", action = "Index", name = UrlParameter.Optional }
);
نکته اینکه این کار، طبق توصیه، پیش از مسیریابی پیش‌فرض انجام شده است. باز هم خطای ۴۰۴! به آموزش دیگری مراجعه می‌کنم. شگفتا که در این حالت هم باز خطای ۴۰۴! بدون انجام هر تغییری، پسوند xml را با aspx جایگزین می‌کنم. نتیجه:

با پسوند html هم آزمایش می‌کنم. متاسفانه خطای ۴۰۴!

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

/*----------------------*/

متدی که برای اسکن کردن تمام controllerها تدارک دیده شده است، در صورت استفاده از T4MVC ، تمام controllerهای تهیه شده توسط T4MVC را هم به عنوان controller منعکس می‌کند. به عنوان نمونه، هر دو کنترلر زیر در خروجی xml وجود دارند:

<url>
<loc>http://localhost:3989/T4MVC_Blog/Index</loc>
<lastmod>2017-04-09T19:07:41.5751733Z</lastmod>
<changefreq>Always</changefreq>
<priority>0.5</priority>
</url>

<url>
<loc>http://localhost:3989/Blog/Index</loc>
<lastmod>2017-04-09T19:07:41.5751733Z</lastmod>
<changefreq>Always</changefreq>
<priority>0.5</priority>
</url>

آیا امکان تغییر رفتار متد ScanAllControllers وجود دارد؟

نظرات مطالب
MVVM و فراخوانی متدهای اشیاء View از طریق ViewModel
درود بر شما جناب نصیری و همینطور شاهین کیاست که لطف کردن و موضوع رو مطرح کردن. 
من هم با جناب نصیری موافقم که ادامه چنین کاری نیاز به فضای متقابل و طبیعتا دلگرمی و انگیزه داره؛ اما صحبت اینجاست که «من اگر بنشینم، تو اگر بنشینی، چه کسی برخیزد؟». فکر میکنم به گفته Hossein Raziee باید یه حرکتی بهرحال انجام داد؛ اینطور که من برداشت کردم، فکر میکنم دو تا موضوع وبلاگ نویسی IT در ایران رو تحت الشعاع قرار داده: یکی گرایش به تالارهاست. و دومی گرایش به بلاگ های خارجی. اکثر برنامه نویسای قوی که دست به قلم هستن، توی تالارها فعالیت میکنن؛ ظاهرا اهمیت وبلاگ ها هنوز برای ما جا نیفتاده. دوستانی هم که یه مقداری از مشق و تمرین بالاتر میرن، معمولا نه تنها خودشون شروع به انتقال نمیکنن، بلکه حتی برای ارتقای خودشون و پیگیری مطالب سراغ بلاگ های غیر فارسی میرن؛ فکر میکنم نیاز به یه حرکت اصولی و درست و حساب شده داریم تا موضوع تولید محتوی IT به زبان فارسی رو تو یه مسیر درست بندازیم. یکی از راههایی که به ذهن من میرسید، ایجاد یه شبکه بین نویسنده های ایرانی بود؛ حدود یه سال پیش این موضوع رو با چند تا از دوستان اینترنتیم مطرح کردم که استقبال نشد. حالا دوباره از دوستان میپرسم که نظرشون چیه؟ 
نمونه بسیار مفید (ولی تنها یه شمّه) خلاصه اشتراک هایی هست که جناب نصیری منتشر میکنن؛ وقتی یه نفر مثل وحید نصیری لینکی رو منتشر میکنه من مطمئنم که مطلب مفیدی هست؛ پس حتما میرم و میخونم؛ و چه بسا جزو خوانندگان دائمی اون سایت هم بشم؛ حالا عرض بنده اینه که این موضوع باید منسجم و شبکه ای باشه؛ 
با جناب نصیری موافقم؛ با شاهین هم موافقم؛ اما موافق این نیستم که آقای نصیری بگن میخوام بکشم کنار و من و شاهین کیاست هم بگیم نه اینکارو نکنید؛ چون وحید نصیری شاید امروز احساساتی بشه و بخاطر من و امثال من ادامه بده، اما فردا دوباره بی انگیزگی یا خستگی میاد سراغش و باز همین داستان؛
من فکر میکنم باید راه چاره رو پیدا کرد؛ راهی که به نظر من میرسید رو عرض کردم؛ از دوستان که تا جاییکه من میشناسم و مطالب رو دنبال میکنم، میدونم همشون از افراد تاثیر گذار در IT ایران و با دانش و تجربه زیاد هستن هم خواهش میکنم که هر راهی به ذهنشون میرسه عنوان کنن که با همفکری هم بتونیم به یه راه حل درست برسیم؛
به سهم خودم هم از جناب نصیری عمیقا تشکر میکنم و از پرحرفی و زیاده گوییم پوزش میخوام. پاینده باشید؛