نظرات مطالب
قسمت اول - ساخت گزارش در محیط Telerik Reporting

با سلام

از نظر قدرت مانور در گزارش گزارش ساز devexpress از  سایر ابزارها مثل ssrs , telerik , crystall  فوق العاده قوی تره و امکان نمایش داده‌ها به صورت پدر و فرزندی رو تا n  لایه ارائه میده چیزی ، امکان تغذیه گزارش با داده هایی که خود شما می‌خواید مثل یک  iqueryable یا list  یا datatable و میتونه کانکشن نداشته باشه و در هر سطحی از گزارش شما امکانات کامل کد نویسی رو برای هر رویدادی در گزارش دارید که فوق العاده است و یک گزارش هم در وب و هم در ویندوز قابل استفاده مجدده ولی مشکلی که هست و باعث شد بنده فعلا با ssrs کار کنم اینه که در وب چیدمان گزارش به هم میخوره که در مثال‌های شرکت سازنده چنین چیزی دیده نمیشه - رتبه دوم متعلق به ssrs هست که ابزاری پر قدرت با قابلیت برنامه نویسی توکار vb.net و درج هر گونه کتابخانه خارجی در صورت نیاز است ، تا اونجایی که بنده اطلاع دارم telerik یکی از ضعبف‌ترین نوع گزارشات رو در بین بقیه شرکت‌ها ارائه میده

مطالب
نمایش درصد پیشرفت، Watermark و گزارشات چند ستونی در PdfReport
گزارشی را در نظر بگیرید با این نیازها:
می‌خواهیم
الف) یک Watermark قطری را بر روی تمام صفحات گزارش ظاهر کنیم.
ب) عدد‌های درصد پیشرفت یک ستون را به صورت میله‌ای نمایش دهیم.
ج) در هر صفحه بجای اینکه یک جدول، اطلاعات را نمایش دهد و تمام صفحه را پر کند، دو جدول در دو ستون کنار هم اینکار را انجام دهند تا در حین چاپ گزارش، در میزان تعداد صفحات مصرفی صرفه جویی صورت گیرد.
د) مقادیر true با چک مارک و موارد false با علامت ضربدر نمایش داده شوند.

یک چنین شکلی در نهایت مد نظر است:


روش انجام کار را توسط کتابخانه PdfReport در ادامه بررسی خواهیم کرد.
ابتدا کلاس مدل زیر را در نظر بگیرید:
namespace PdfReportSamples.Models
{
    public class Task
    {
        public int Id { set; get; }
        public string Name { set; get; }
        public int PercentCompleted { set; get; }
        public bool IsActive { set; get; }
    }
}
به این ترتیب یک کلاس فعالیت تعریف شده است که در آن نام فعالیت، درصد پیشرفت و همچنین درجریان بودن آن قابل تنظیم است. از این کلاس جهت تهیه منبع داده گزارش استفاده می‌شود:
using System;
using System.Collections.Generic;
using System.Drawing;
using iTextSharp.text;
using PdfReportSamples.Models;
using PdfRpt;
using PdfRpt.Core.Contracts;
using PdfRpt.Core.Helper;
using PdfRpt.FluentInterface;

namespace PdfReportSamples.ProgressReport
{
    public class ProgressReportPdfReport
    {
        private IPdfFont getWatermarkFont()
        {
            var watermarkFont = new GenericFontProvider(
                                        AppPath.ApplicationPath + "\\fonts\\irsans.ttf",
                                        Environment.GetEnvironmentVariable("SystemRoot") + "\\fonts\\verdana.ttf");
            watermarkFont.Color = BaseColor.LIGHT_GRAY;
            watermarkFont.Size = 50;
            return watermarkFont;
        }

        public IPdfReportData CreatePdfReport()
        {
            return new PdfReport().DocumentPreferences(doc =>
            {
                doc.RunDirection(PdfRunDirection.RightToLeft);
                doc.Orientation(PageOrientation.Portrait);
                doc.PageSize(PdfPageSize.A4);
                doc.DocumentMetadata(new DocumentMetadata { Author = "Vahid", Application = "PdfRpt", Keywords = "Test", Subject = "Test Rpt", Title = "Test" });
                doc.DiagonalWatermark(new DiagonalWatermark
                {
                    Text = "نمایش درصد پیشرفت",
                    RunDirection = PdfRunDirection.RightToLeft,
                    Font = getWatermarkFont()
                });
            })
            .DefaultFonts(fonts =>
            {
                fonts.Path(AppPath.ApplicationPath + "\\fonts\\irsans.ttf",
                                  Environment.GetEnvironmentVariable("SystemRoot") + "\\fonts\\verdana.ttf");
            })
            .PagesFooter(footer =>
            {
                footer.DefaultFooter(PersianDate.ToPersianDateTime(DateTime.Now, "/", true));
            })
            .PagesHeader(header =>
            {
                header.DefaultHeader(defaultHeader =>
                {
                    defaultHeader.Message("گزارش جدید ما");
                    defaultHeader.ImagePath(AppPath.ApplicationPath + "\\Images\\01.png");
                });
            })
            .MainTableTemplate(template =>
            {
                template.BasicTemplate(BasicTemplate.SilverTemplate);
            })
            .MainTablePreferences(table =>
            {
                table.ColumnsWidthsType(TableColumnWidthType.Relative);
                table.MultipleColumnsPerPage(new MultipleColumnsPerPage
                {
                    ColumnsGap = 20,
                    ColumnsPerPage = 2,
                    ColumnsWidth = 250,
                    IsRightToLeft = true,
                    TopMargin = 7
                });
            })
            .MainTableDataSource(dataSource =>
            {
                var listOfRows = new List<Task>();
                var rnd = new Random();
                for (int i = 0; i < 400; i++)
                {
                    listOfRows.Add(new Task
                    {
                        Id = rnd.Next(1000, 10000),
                        Name = "Task" + i,
                        PercentCompleted = rnd.Next(1, 100),
                        IsActive = rnd.Next(0, 2) == 1 ? true : false
                    });
                }
                dataSource.StronglyTypedList<Task>(listOfRows);
            })
            .MainTableColumns(columns =>
            {
                columns.AddColumn(column =>
                {
                    column.PropertyName("rowNo");
                    column.IsRowNumber(true);
                    column.CellsHorizontalAlignment(HorizontalAlignment.Center);
                    column.IsVisible(true);
                    column.Order(0);
                    column.Width(1);
                    column.HeaderCell("ردیف", captionRotation: 90);
                });

                columns.AddColumn(column =>
                {
                    column.PropertyName<Task>(x => x.Id);
                    column.CellsHorizontalAlignment(HorizontalAlignment.Center);
                    column.IsVisible(true);
                    column.Order(1);
                    column.Width(2);
                    column.HeaderCell("شماره فعالیت");
                });

                columns.AddColumn(column =>
                {
                    column.PropertyName<Task>(x => x.Name);
                    column.CellsHorizontalAlignment(HorizontalAlignment.Center);
                    column.IsVisible(true);
                    column.Order(2);
                    column.Width(3);
                    column.HeaderCell("فعالیت");
                });

                columns.AddColumn(column =>
                {
                    column.PropertyName<Task>(x => x.PercentCompleted);
                    column.CellsHorizontalAlignment(HorizontalAlignment.Center);
                    column.IsVisible(true);
                    column.Order(3);
                    column.Width(3);
                    column.HeaderCell("درصد پیشرفت");
                    column.ColumnItemsTemplate(template =>
                    {
                        template.ProgressBar(progressBarColor: Color.SkyBlue, showPercentText: true);
                        template.DisplayFormatFormula(obj =>
                        {
                            if (obj == null) return "% 0";
                            return "% " + obj.ToString();
                        });
                    });
                });

                columns.AddColumn(column =>
                {
                    column.PropertyName<Task>(x => x.IsActive);
                    column.CellsHorizontalAlignment(HorizontalAlignment.Center);
                    column.IsVisible(true);
                    column.Order(4);
                    column.Width(2);
                    column.HeaderCell("در جریان");
                    column.ColumnItemsTemplate(template =>
                    {
                        template.Checkmark(checkmarkFillColor: Color.Green, crossSignFillColor: Color.DarkRed);
                    });
                });
            })
            .MainTableEvents(events =>
            {
                events.DataSourceIsEmpty(message: "There is no data available to display.");
            })
            .Export(export =>
            {
                export.ToExcel();
            })
            .Generate(data => data.AsPdfFile(AppPath.ApplicationPath + "\\Pdf\\ProgressReportSample.pdf"));
        }
    }
}

توضیحات:

- همانطور که در کدهای فوق ملاحظه می‌کنید، برای تعریف یک watermark قطری در سراسر سند تولیدی، نیاز است در متد DocumentPreferences، تنظیمات DiagonalWatermark را مشخص کرد:
doc.DiagonalWatermark(new DiagonalWatermark
                {
                    Text = "نمایش درصد پیشرفت",
                    RunDirection = PdfRunDirection.RightToLeft,
                    Font = getWatermarkFont()
                });
در اینجا Text، متنی است که نمایش داده خواهد شد. تنظیم PdfRunDirection.RightToLeft برای نمایش صحیح متون فارسی الزامی است. همچنین این watermark نیاز به قلم مناسب و متفاوتی نسبت به قلم‌های پیش فرض گزارش نیز دارد:
        private IPdfFont getWatermarkFont()
        {
            var watermarkFont = new GenericFontProvider(
                                        AppPath.ApplicationPath + "\\fonts\\irsans.ttf",
                                        Environment.GetEnvironmentVariable("SystemRoot") + "\\fonts\\verdana.ttf");
            watermarkFont.Color = BaseColor.LIGHT_GRAY;
            watermarkFont.Size = 50;
            return watermarkFont;
        }
قلم‌هایی از جنس IPdfFont را توسط کلاس توکار GenericFontProvider به نحوی که ملاحظه می‌کنید می‌توان ایجاد کرد.

- برای ستون بندی گزارش باید به متد MainTablePreferences رجوع نمود. در اینجا می‌توان تنظیمات دقیق ستون‌های گزارش را مشخص کرد:
table.MultipleColumnsPerPage(new MultipleColumnsPerPage
                {
                    ColumnsGap = 20,
                    ColumnsPerPage = 2,
                    ColumnsWidth = 250,
                    IsRightToLeft = true,
                    TopMargin = 7
                });
برای مثال در اینجا 2 ستون در هر صفحه تعریف شده است (ColumnsPerPage). فاصله بین این ستون‌ها 20 است (ColumnsGap). عرض هر ستون 250 درنظر گرفته شده (ColumnsWidth) و همچنین توسط تنظیم IsRightToLeft، سبب خواهیم شد تا جداول از راست به چپ شروع و در صفحه نمایش داده شوند. (اگر به شماره ردیف‌ها در شکل ابتدای بحث دقت کنید، ردیف 1 در سمت راست صفحه قرار دارد).

- برای نمایش درصد پیشرفت در یک سلول خاص تنها کافی است قالب مخصوص آن‌را انتخاب و مقدار دهی کنیم:
                    column.ColumnItemsTemplate(template =>
                    {
                        template.ProgressBar(progressBarColor: Color.SkyBlue, showPercentText: true);
                        template.DisplayFormatFormula(obj =>
                        {
                            if (obj == null) return "% 0";
                            return "% " + obj.ToString();
                        });
                    });
قالب از پیش تعریف شده ProgressBar، مقدار سلول جاری را دریافت و آن‌را تبدیل به یک میله افقی درصد پیشرفت می‌کند. همچنین در اینجا توسط DisplayFormatFormula، یک علامت درصد هم به متنی که قرار است نمایش داده شود، اضافه کرده‌ایم.

- نمایش چک مارک و علامت ضربدر نیز به همین منوال است. باید قالب مناسبی را برای آن انتخاب و اعمال کرد:
                    column.ColumnItemsTemplate(template =>
                    {
                        template.Checkmark(checkmarkFillColor: Color.Green, crossSignFillColor: Color.DarkRed);
                    });
قالب Checkmark نیز جزو قالب‌های از پیش تعریف شده PdfReport است و بر اساس گرافیک برداری کار می‌کند.
مطالب
مشکلات نصب به روز رسانی‌های اخیر

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


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


مشکل نصب به روز رسانی‌های سیلورلایت
اگر هنگام نصب به روز رسانی جدید سیلورلایت هر بار پیغام silverlight.msi File Missing را دریافت می‌کنید، مسیر زیر را از رجیستری ویندوز حذف کنید. سپس به روز رسانی سیلورلایت بدون مشکل نصب خواهد شد:
HKEY_CLASSES_ROOT / installer / products / D7314F9862C648A4DB8BE2A5B47BE100


پ.ن.
من هر دو مشکل را با ویندوز سرور 2003 داشتم.

مطالب
بررسی Bad code smell ها: فیلدهای موقتی
فیلد موقتی یا Temporary field در دسته بندی الگوهای «بد استفاده کنندگان از شیء گرایی» قرار می‌گیرد. در این الگوی بد، فیلدها یا خصوصیات یک کلاس، در شرایط خاصی مقدار گرفته و مورد استفاده قرار می‌گیرند و در بقیه شرایط خالی هستند. 
زمانیکه در یک کلاس، متدی برای انجام فعالیت خود، تعدادی پارامتر ورودی زیادی نیاز داشته باشد، ممکن است برنامه نویس برای مواجه نشدن با تعداد پارامترهای زیاد ورودی، فیلدها یا خصوصیاتی را در کلاس مربوط به آن متد ایجاد کند. این فیلدها عملا فقط زمان صدا زدن آن متد مقدار گرفته و در بقیه شرایط خالی هستند.
خواندن و استفاده از این نوع کدها معمولا مشکل و چالش برانگیز است. زیرا خواننده شاهد فیلدهایی است که در اکثر مواقع خالی هستند. همچنین زمان استفاده از این کلاس نمی‌توان از وجود مقادیر فیلدها یا خصوصیات آن‌ها اطمینان لازم را داشت.

روش‌های اصلاح این کد بد بو 

برای اصلاح چنین بوی بدی به طور معمول دو راه وجود دارد. 
اول: با در نظر گرفتن اینکه تمامی کد موجود در متد و فیلدهای مرتبط به آن قابلیت انتقال به کلاس خاص خودشان را دارند، می‌تواند کلاس مجزایی را برای آن متد و فیلدهای مربوطه ایجاد کرد. 
دوم: برای فیلدها و خصوصیاتی که در خیلی مواقع خالی هستند، می‌توان با روش Null object برای وضعیت خالی بودن آن، یک شیء خالی بی اثر را ایجاد کرد.  
به مثال زیر توجه کنید: 
فرض کنید در حال تولید سیستمی هستید که در روال خاصی، نیاز به محاسبه پورسانت فروشنده‌ها دارید. برای محاسبه پورسانت به موارد زیر نیاز است:
  • لیست محصولات فروخته شده
  • درصد کمیسیون خام
  • تاریخ فروش
  • شعبه فروش
  • نوع پرداخت 
به طور نمونه اگر فروشنده فروش نقدی ای انجام دهد، کمیسیون بیشتری نسبت به کمیسیون پیشفرض، به او تعلق خواهد گرفت و … 
ممکن است طراحی اولیه برای چنین متدی به صورت زیر باشد: 
public class Salesman 
{ 
    public void Method1() 
    { 
       return; 
    } 
    public void Method2() 
    { 
        return; 
    } 
    public void Method3() 
    { 
       return; 
    }
    public decimal CalculateCommission(dynamic products, dynamic commissionRate, dynamic saleDate, dynamic branch, dynamic paymentType) 
    { 
       return decimal.MaxValue; 
    } 
}
با مشاهده پارامتر‌های زیاد متد، برنامه نویس می‌تواند از روش‌های اصلاح بوی بد «تعداد زیاد پارامترهای ورودی» استفاده کند. یا اینکه برنامه نویس برای خلاصی از این کد بد بو، بجای ارسال پارامتر، فیلدهایی را در کلاس Salesman ایجاد کند؛ مانند کد زیر:  
public class SalesmanV2 
{ 
    public IEnumerable<dynamic> Products { get; set; } 
    public dynamic CommisionRate { get; set; } 
    public dynamic SaleDate { get; set; } 
    public dynamic Branch { get; set; } 
    public dynamic PaymentType { get; set; } 
    public void Method1() 
    { 
        return; 
    } 
    public void Method2() 
    { 
        return; 
    } 
    public void Method3() 
    { 
        return; 
    } 
    public decimal CalculateCommission() 
    { 
       return decimal.MaxValue; 
    } 
}
با این تغییر، پارامترهای متد CalculateCommision به خصوصیاتی در کلاس Salesman تبدیل خواهند شد. دقت کنید این کلاس متدهای دیگری برای فعالیت‌های مختلف دارد.  
در روش‌های اصلاح این کد بد بو، اشاره به انتقال منطق متد مذکور به کلاس مجزا و مخصوص به خود شده بود. در واقع کد بد بوی «فیلد موقتی» ناشی از عدم رعایت اصل single responsibility است و محاسبه پورسانت را از نظر ذاتی می‌توان وظیفه‌ی اضافه‌ای در این کلاس دانست. با توجه به اینکه می‌توان محاسبه پورسانت را به صورت جداگانه پیاده سازی کرد. به چنین پیاده سازی ای خواهیم رسید.  
public class SalesmanV3 
{ 
    public void Method1() 
    { 
        return; 
    } 
    public void Method2() 
    { 
        return; 
    } 
    public void Method3() 
    { 
        return; 
    } 
} 

public class CommissionCalculator 
{ 
    private IEnumerable<dynamic> _products; 
    private dynamic _commisionRate; 
    private dynamic _saleDate; 
    private dynamic _branch; 
    private dynamic _paymentType; 
    public CommissionCalculator(IEnumerable<dynamic> products, dynamic commisionRate, 
            dynamic saleDate, dynamic branch, dynamic paymentType) 
    { 
        _products = products; 
        _commisionRate = commisionRate; 
        _saleDate = saleDate; 
        _branch = branch; 
        _paymentType = paymentType; 
    } 
}
در شرایط نادری، کد بد بوی «فیلد موقتی» ناشی از عدم رعایت اصل single responsibility نیست. در چنین شرایطی می‌توان از null object برای رفع این بوی بد استفاده کرد.

جمع بندی 

همان‌طور که در متن نیز اشاره شد، عدم رعایت اصل single responsibility می‌تواند منجر به چنین کد بد بویی شود. این کد بد بو با روش‌های ساده‌ای قابل اصلاح است. اصلاح چنین بویی خوانایی و قابلیت نگهداری کد را افزایش خواهد داد. 
مطالب
آموزش (jQuery) جی کوئری 4#
در ادامه مطلب قبلی  آموزش (jQuery) جی کوئری 3# به ادامه بحث  می‌پردازیم.

با توجه به حالت‌های مختلف و گزینه‌های گوناگونی که انتخاب کننده‌ها در اختیار ما گذاشته اند، اگر هنوز دنبال قدرت بیشتری از انتخاب کننده‌ها هستید در ادامه به چند مورد از آنها اشاره خواهیم کرد.
3-1- انتخاب عناصر بر اساس موقعیت
گاهی اوقات انتخاب عناصر با توجه به مکان آنها و یا موقعیت مکانی آن‌ها نسبت به سایر اجزا صورت می‌پذیرد؛ برای مثال اولین لینک  صفحه و یا اولین لینک هر پاراگراف و یا گزینه‌ی آخر از لیست، jQuery شیوه ای خاص را برای چنین انتخاب هایی ارایه کرده است. برای مثال دستور زیر اولین لینک موجود در صفحه را انتخاب می‌کند:
a:first

دستور زیر چکاری انجام می‌دهد؟
p:odd

دستور بالا تمامی پاراگراف‌های فرد را انتخاب می‌کند. روش‌های دیگری هم ممکن است بخواهیم استفاده کنیم؛ مثلا دستور زیر تمامی پاراگراف‌های زوج را انتخاب می‌کند:
p:even

یا با استفاده از دستور زیر میتوان آخرین فرزند یک والد را انتخاب کرد؛ در زیر آخرین <lii> فرزند یک <ul> انتخاب می‌شود. علاوه بر انتخاب کننده هایی که ذکر شد؛ تعداد قابل توجه دیگری نیز وجود دارند که در جدول 2-2 ذکر شده اند.

جدول 2-2: انتخاب گر‌های پیشرفته موقعیت عناصر که توسط jQuery پشتیبانی می‌شوند
 توضیح فیلتر
اولین عنصر که با شرط ما مطابقت می‌کند را انتخاب می‌کند، li a:first اولین لینکی را که فرزند لیست به حساب می‌آیند؛ را بر می‌گرداند
:first
آخرین عنصری که با شرط ما مطابقت کند را انتخاب می‌کند. li a:last آخرین لینک از فرزندان لیست را برمی گرداند.
:last
اولین فرزند عنصر که با شرط ما مطابقت می‌کند را انتخاب می‌کند. li a:first-child اولین عنصر لینک از هر لیست را برمی گرداند.
:first-child
آخرین فرزند عنصر که با شرط ما مطابقت می‌کند را انتخاب می‌کند. li a:last-child اولین عنصر لینک از هر لیست را برمی گرداند.
:last-child
تمام عناصری که پدر انها تنها همان فرزند را داد، برمی گرداند.
:only-child
nامین فرزند عنصری که با شرط ما مطابقت داشته باشد را انتخاب می‌کند. li: nth-child:(2) //comment دومین عنصر از هر لیست را برمی گرداند.
:nth-child(n)
فرزندان زوج یا فرد عنصر را انخاب می‌کند. li:nth-child(even) //commentتمام عناصر زوج لیست‌ها را بر می‌گرداند.
:nth-child(even یا odd)
nامین فرزند عنصری که از طریق فرمول ارایه شده به دست می‌آید را انتخاب می‌کند. اگر Y صفر باشد، نیازی به نوشتن آن نیست. li:nth-child(3n)//comment تمام عناصر ضریب 3 لیست‌ها را بر می‌گرداند، در حالی که li:nth-child(5n+1)// comment عناصری از لیست را برمیگرداند که بعد از عنصرهای ضریب 5 لیست‌ها قرار گرفته باشند.
:nth-child(Xn+Y)
تمام عناصر زوج یا فرد که با شرط ما مطابقت کنند را انتخاب می‌کند. li:evenتمامی عناصر زوج لیست‌ها را بر می‌گرداند.
:even 
یا
:odd
n امین عنصر انتخاب شده را برمی گرداند.
:eq(n)
عناصر بعد از n امین عنصر را بر میگرداند. (در واقع عناصری که بزرگتر از عنصر n ام هستند را بر می‌گرداند)
:gt(n)
عناصر قبل از n امین عنصر را بر میگرداند. (در واقع عناصری که کوچکتر از عنصر n ام هستند را بر می‌گرداند) 
:lt(n)
  پ.ن : در جدول بالا در توضیحات بعضی از انتخاب گرها comment// نوشه شده است، اینها جز دستور نبوده و فقط برای نمایش صحیح پردانتز در صفحه اینترنتی نوشته شده است، در عمل نیازی به اینها نیست.

نکته ای که در مورد انتخاب گرهای جدول بالا وجود دارد این است که در فیلتر nth-child: برای سازگاری با CSS، مقدار شمارشگر از 1 آغاز می‌شود، اما در سایر فیلترها از قاعده ای که اکثر زبانهای برنامه نویسی استفاده شده است و شمارشگر آنها از صفر اغاز می‌شود. برای درک این موضوع مثال زیر را در نظر بگیرید:
<table id="languages">
     <thead>
          <tr>
               <th>Language</th>
               <th>Type</th>
               <th>Invented</th>
          </tr>
     </thead>
<tbody>
<tr>
     <td>Java</td>
     <td>Static</td>
     <td>1995</td>
</tr>
<tr>
     <td>Ruby</td>
     <td>Dynamic</td>
     <td>1993</td>
</tr>
<tr>
     <td>Smalltalk</td>
     <td>Dynamic</td>
     <td>1972</td>
</tr>
<tr>
     <td>C++</td>
     <td>Static</td>
     <td>1983</td>
</tr>
</tbody>
</table>

حال می‌خواهیم از این جدول، محتویات تمام خانه هایی که نام یک زبان برنامه نویسی در آنهاست را انتخاب نماییم. از انجا که نام این زبانها در اولین ستون از هر سطر قرار دارد. می‌توانیم دستوری مانند زیر بنویسیم:
table#languages tbody td:first-child

و یا با استفاده از دستور زیر این کار را انجام دهیم:
table#languages tbody td:nth-child(1)

اما دستور اول مختصر‌تر و خواناتر است. پس از آن برای دسترسی به نوع هریک از زبانهای برنامه نویسی، دستور انتخاب کننده دوم را به صورت
:nth-child(2)
تغییر می‌دهیم، و همچنین با تغییر پارامتر 2 به 3 سالی که هر یک از زبانها ابداع شده اند ، انتخاب می‌شوند. بدیهی است در این حالت دو دستور
:nth-child(3)
یا
:last-child
با یکدیگر برابرند. اما هردوی آنها ستون آخر از هر سطر را انخاب می‌کنند، در شرایطی که بخواهیم آخرین خانه جدول انتخاب شود (خانه ای با مقدار 1983)، از td:last استفاده می‌کنیم. توجه کنید در حالی که دستور td:eq(2) //comment خانه ای با مقدار 1995 را انتخاب می‌کند، دستور td:nth-child(2)//comment تمام خانه‌های بیان کننده نوع زبانها را انتخاب می‌کند. بنابراین به خاطر داشته باشید که مقدار ابتدایی شمارشگر فیلتر eq: از صفر است و این مقدار برای فیلتر nth-child: یک تعیین شده است.

در پست بعدی انتخاب گرهای CSS و فیلتر‌های سفارشی jQuery را بررسی خواهیم کرد.
مطالب
مدیریت خطا ها در ASP.NET MVC
احتمال بوجود اومدن خطا در اجرای یک برنامه همیشه هست. خطاهایی که برنامه نویس ممکنه هیچ وقت فکر نکنه چنین خطایی در اجرای برنامه ای که نوشته بوجود بیاد و هیچ وقت هم از اون خطا اطلاع پیدا نکنه. ثبت و بررسی خطاهایی که در برنامه بوجود میاد میتونه مفید باشه اما آیا باید همه‌ی خطا‌ها رو ثبت کرد؟
خیر. ممکنه برخی از این خطا‌ها سلامت سیستم رو به خطر نندازه و اصلا ارتباطی هم به برنامه نداشته باشه . به طور مثال زمانی که کاربر یک صفحه ای و یا فایلی رو درخواست میده که وجود نداره.
توسط رویداد OnException میتونیم به خطاهای بوجود اومده در سطح یک Controller دسترسی داشته باشیم و اون رو مدیریت کنیم.
protected override void OnException(ExceptionContext filterContext)
{
    // Bail if we can't do anything
    if (filterContext == null)
        return;

    // log
    var ex = filterContext.Exception ??
            new Exception("No further information exists.");
    // save log ex.Message
    
    filterContext.ExceptionHandled = true;

    filterContext.Result = View("ViewName");
    base.OnException(filterContext);
}

ما تمام خطا هایی که در سطح یک کنترل اتفاق می‌افته رو با دوباره نویسی(override) متد OnException مدیریت میکنیم. اما اگه بخواهیم محدوده‌ی این مدیریت رو بیشتر کنیم و کاری کنیم که روی تمام نرم افزار اعمال بشه باید چه کار کنیم؟
رویداد Application_Error در Global.asax محل مناسبی برای انجام این کار هست اما باید چند مسئله رو در نظر بگیریم:

  • Server.Transfer : این متد یک فایل (میتونه یک صفحه باشه)رو به خروجی میفرسته بدون اینکه آدرس تغییر کنه(کاربر به صفحه دیگه ای منتقل بشه) نکته ای که وجود داره اینه که برای انتقال نیاز به وجود یک فایل فیزیکی بر روی سیستم داره تا اون فایل رو به خروجی منتقل کنه ، در پروژه‌های MVC فایل فیزیکی (به شکلی که در ASP.NET وجود داره) وجود نداره و بوسیله route‌ها ، controllerها و  view ‌ها یک صفحه(خروجی) تولید میشه بنابراین ما نمیتونیم  در ASP.NET MVC از این متد استفاده کنیم و باید به دنبال راه حلی برای فرستادن پاسخ مناسب باشیم.
  • Response.Redirect : این متد کاربر رو به یک صفحه‌ی دیگه منتقل میکنه و StatusCode رو برابر با 301 مقدار دهی میکنه که معنای انتقال صفحه هست و برای مشخص کردن "خطای داخلی سرور" (Internal Server Error) با کد 500 و پیدا نشدن فایل با کد 404 مناسب نیست .این کد(StatusCode) برای موتور‌های جستجو اهمیت زیادی داره لیست کامل این کدها و توضیحاتشون رو میتونید در این آدرس مطالعه کنید.
  • Response.Clear : این متد باید فراخوانی بشه تا خروجی تولید شده تا این لحظه رو پاک کنیم.
  • Server.ClearError :  این متد باید فراخوانی بشه تا از ایجاد صفحه زرد رنگ خطا جلوگیری کنه.
با توجه به نکات بالا، با طی کردن مراحل زیر میتونیم خطا‌های ایجاد شده رو مدیریت کنیم.
  1. بدست آوردن آخرین خطای ایجاد شده.
  2. بدست آوردن کد خطای ایجاد شده.
  3. ثبت خطا برای بررسی (ممکن هست شما برخی خطاها مثل پیدا نشدن فایل رو ثبت نکنید).
  4. پاک کردن خروجی ایجاد شده تا این لحظه.
  5. پاک کردن خطای ایجاد شده در سرور.
  6. فرستادن یک خروجی مناسب بدون تغییر مسیر.
protected void Application_Error(object sender, EventArgs e)
{
    var error = Server.GetLastError();
    var code = (error is HttpException) ? (error as HttpException).GetHttpCode() : 500;

    if (code != 404)
    {
        // save log error.Message
    }

    Response.Clear();
    Server.ClearError();

    string path = Request.Path;
    Context.RewritePath(string.Format("~/Errors/Http{0}", code), false);
    IHttpHandler httpHandler = new MvcHttpHandler();
    httpHandler.ProcessRequest(Context);
    Context.RewritePath(path, false);
}
  خروجی مناسب به کاربر از طریق از یکی از Action هایی انجام میشه که در ErrorsController هست. باید توجه داشته باشید که اگه در خود این ErrorsController خطایی رخ بده ، یک حلقه‌ی بی پایان بین این کنترلر و رویداد Application_Error اتفاق خواهد افتاد.
public class ErrorsController : Controller
{
    [HttpGet]
    public ActionResult Http404()
    {
        Response.StatusCode = 404;
        return View();
    }

    [HttpGet]
    public ActionResult Http500()
    {
        Response.StatusCode = 500;
        return View();
    }
}

نظرات مطالب
توسعه برنامه های Cross Platform با Xamarin Forms & Bit Framework - قسمت چهارم
دو برنامه Mono Shared Runtime و Xamarin Android Support هستن. این دو، آیکون ای در Home Screen گوشی/امولاتور شما اضافه نمی‌کنند، بلکه فقط در قسمت Settings سیستم عامل، در جایی که تمامی App‌ها لیست شده اند، دیده می‌شوند. مطمئنا هر دو رو دارید که الآن تونستید دیباگ کنید. این که تغییر آنی Xaml روی امولاتور کار می‌کنه، ولی روی گوشی نه، احتمالا به خاطر این هست که گوشی و کامپیوتر تو یه شبکه نیستن، یا Firewall ای چیزی در بین هست. اعمال آنی تغییرات CSharp هم مختص UWP هست و در Android/iOS کار نمی‌کنه.
مطالب
ساخت گزارش Code coverage مربوط به تست‌ها
اگر برای پروژه‌های خود تست نویسی انجام می‌دهید، یکی از موارد مهم، Code coverage مربوط به تست‌ها می‌باشد که نشان می‌دهد چند درصد از کدهای شما تست شده، کدام قسمت از کد، تست نشده‌است و ... .
در این مطلب نحوه‌ی استفاده از ReportGenerator را برای ایجاد گزارش مربوط به Code coverage، ارائه می‌دهیم. ReportGenerator دیتاهای تولید شده‌ی توسط coverlet, OpenCover, dotCover, Visual Studio, NCover, Cobertura, JaCoCo, Clover و ... را به یک گزارش قابل درک در فرمت‌های Html, Coberura و CSV تبدیل می‌کند. در  این مطلب چند موردتست نویسی با xUnit در Asp.Net Core  را نوشته‌ام و اکنون می‌خواهیم برای تست‌های نوشته شده، Code coverage آنها را بدست آوریم.
در کد زیر، سه تست برای متد AverageUsersAge نوشته شده است:
[Fact]
public async Task AverageUsersAge_When_Repository_Return_Null_Then_Zero_Should_Be_Returned()
{
    _userRepository.Setup(a => a.GetAllUsers())
        .ReturnsAsync((List<User>)null);
    var result = await _userService.AverageUsersAge();
    result.Should().Be(0);
}
[Fact]
public async Task AverageUsersAge_When_Repository_Return_Empty_Then_Zero_Should_Be_Returned()
{
    _userRepository.Setup(a => a.GetAllUsers())
        .ReturnsAsync(new List<User>());
    var result = await _userService.AverageUsersAge();
    result.Should().Be(0);
}
[Fact]
public async Task AverageUsersAge_When_Repository_Return_List_Of_Users_Then_Average_Of_Age_Should_Be_Retunred()
{

    _userRepository.Setup(a => a.GetAllUsers())
        .ReturnsAsync(UserMockData.People);
    var result = await _userService.AverageUsersAge();
    var expected = (UserMockData.AdultUser.Age + UserMockData.ChildUser.Age + UserMockData.InfantUser.Age) / 3;
    result.Should().Be(expected);
}
اکنون اگر بخواهیم Code coverage مربوط به این تست‌ها را مشاهده کنیم، ابتدا باید پکیج coverlet.collector  را در پروژه‌ی تست نصب کنیم. سپس باید دستور زیر را برای نصب ReportGenerator اجرا کنید؛ درون powershell:
dotnet tool install -g dotnet-reportgenerator-globaltool
اکنون میتوانیم Code coverage تست‌ها را تولید کنیم و با استفاده از ReportGenerator، آن‌را به قالب HTML تبدیل کنیم. اگر به مسیر پروژه تست خود بروید و دستور زیر را وارد کنید:
dotnet test --collect:"XPlat Code Coverage"
در پروژه‌ی تست شما، یک پوشه به نام TestResults ایجاد می‌شود و همچنین یک فایل xml با عنوان coverage.cobertura ایجاد شده‌است که نتیجه‌ی Code coverage مربوط به تست‌های شما در آن قرار دارد. 
اکنون می‌توانید با استفاده از ReportGenerator گزارش تست‌های خود را از فایل ایجاد شده، تهیه کنید. برای این کار باید دستور زیر را در مسیر پروژه تست، در powershell  وارد کنید:
reportgenerator -reports:"C:\Users\Farhad\source\repos\xUnitExample\xUnitExample.Tests\TestResults\*\coverage.cobertura.xml" -targetdir:"coveragereport" -reporttypes:Html
در پارامتر reports باید مسیر فایل xml ایجاد شده مربوط به Code coverage را قرار دهید (با هر بار اجرای دستور "dotnet test --collect:"XPlat Code Coverage یک فایل xml جدید ایجاد می‌شود و ممکن است در سیستم شما مقدار GUID ایجاد شده، تفاوت داشته باشد). 
درون پوشه TestResults یک پوشه دیگر قرار دارد که نام آن پوشه، یک GUID می‎‌باشد و به همین دلیل به جای استفاده از GUID از * استفاده کرده‌ایم.
 اگر دستور بالا را اجرا کنید باید خروجی زیر را مشاهده کنید:
2021-11-18T14:05:02: Arguments
2021-11-18T14:05:02:  -reports:C:\Users\Farhad\source\repos\xUnitExample\xUnitExample.Tests\TestResults\*\coverage.cobertura.xml
2021-11-18T14:05:02:  -targetdir:coveragereport
2021-11-18T14:05:02:  -reporttypes:Html
2021-11-18T14:05:02: Writing report file 'coveragereport\index.html'
2021-11-18T14:05:02: Report generation took 0.3 seconds
اکنون اگر در مسیری که دستور بالا را اجرا کردید بروید، یک پوشه به نام coveragereport مشاهده می‌کنید که یک فایل index.html دارد و اگر آنرا در مرورگر مشاهده کنید، Code coverage مربوط به تست‌های نوشته شده را مشاهده میکنید:


در عکس ارسال شده، تست‌های نوشته شده برای تمامی لایه‌ها به صورت جدا ایجاد شده‌است.
تست مربوط به UserService:

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

برای مثال در عکس زیر مشخص شده‌است که برای فایل ApplicationConfiguration هیچ تستی نوشته نشده‌است.


اگر از CICD استفاده میکنید، می‌توانید در قسمت CI پروژه، هربار دستورات بالا را اجرا کنید و Code coverage مربوط به هر Build را به صورت جداگانه در کنار فایل‌های آپلود شده داشته باشد.
نکته: برای اجرای دستورات بالا و ساخت گزارش باید ورژن SDK نصب شده بر روی سیستم شما برابر 2.2.401 یا بیشتر باشد و ورژن Microsoft.NET.Test.Sdk برابر 16.5.0 یا بیشتر باشد.

مطالب
بالا بردن سرعت بارگذاری اولیه NHibernate

در زمان اولین بارگذاری NHibernate ، ساخت تمام نگاشت‌ها صورت گرفته و همچنین session factory ایجاد می‌گردد. به همین جهت به کمک الگوی thread safe singleton نسبت به کش کردن آن در طول عمر یک برنامه استفاده می‌گردد.
در برنامه‌ای که در یک محیط کاری مورد استفاده قرار می‌گیرد این زمان اصلا مهم نیست، زیرا تنها یکبار باید انجام شود. اما به عنوان یک برنامه نویس شاید در طول روز صدها بار نیاز به باز و بسته کردن برنامه جهت آزمودن آن داشته باشیم و این مورد پس از مدتی تبدیل به عذاب می‌شود! خوشبختانه امکان serialize نمودن تنظیمات تولیدی session factory به فایل و سپس خواندن از آن نیز وجود دارد که این امر در حین توسعه‌ی برنامه بسیار ارزشمند است.
جهت مطالعه بیشتر می‌توان به مطالب زیر مراجعه کرد:

و حاصل تمام این مقالات در پروژه‌ی Effectus، فایل Effectus\Infrastructure\BootStrapper.cs آن گردآوری شده است.

مطالب
#Defensive Code in C - قسمت اول

Defensive Coding به معنی است که شما با انجام یکسری کار‌ها و در نظر گرفتن یکسری زیر ساخت‌ها در توسعه‌ی نرم افزار خود، به اهداف ذیل دست پیدا کنید:

1. Quality (کیفیت)

2. Comprehensible (جامعیت)

3. Predictable  (قابلیت پیش بینی)

دستیابی به هر کدام از این اهداف و روش‌های اعمال آنها بر روی یک پروژه‌ی نرم افزاری، در ادامه بحث خواهند شد. 

1. Clean Code

یکی از اهداف Defensive Coding که در ابتدای مقاله بحث شد جامعیت یا Comprehension بود. برای رسید به این هدف از مفهومی به نام Clean Code  استفاده می‌شود. Clean Code علاوه بر این مسئله، در پی ساده کردن ساختار بندی پشتیبانی و کاهش باگ‌های نرم افزار نیز هست. ویژگی‌های Clean Code در بالا با  توجه به شکل ذیل تشریح می‌شوند: 

· Easy to read

یک کد Clean  قابلیت خوانایی بالایی دارد. بسیاری از برنامه نویسان در سطوح مختلف با اهمیت این مسئله در توسعه نرم افزار آشنایی دارند. ولی بسیاری از همین برنامه نویسان این اصول را رعایت نمی‌کنند و سعی نمی‌کنند با اصول پیاده سازی آن در نرم افزارآشنا شوند.

اگر قابلیت خوانایی یک کد بالا باشد:

§ شما می‌توانید Pattern ‌های موجود در کد خود را که می‌توانید به عنوان نامزدهایی جهت Refactoring  هستند، تشخیص دهید.

§ برنامه نویسان دیگر به راحتی قصد و اهداف ( intent ) شما را از نوشتن یک کد خاص درک خواهند کرد و در طول زمان با خطا‌های زیادی روبرو نمی‌شوند.

§ توسعه‌ی راحت‌تر و در شرایط وجود فشار، ایجاد سریع یک قابلیت جدید در نرم افزار.

· Clear intent

یک کد Clear دارای اهداف روشن و قابل فهمی می‌باشد.

· Simple

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

· Minimal

کد باید به گونه‌ای باشد که تنها یک چیز را انجام داده و آن را به درستی انجام دهد. همچنین وابستگی بین اجزای کد باید در کمترین حد ممکن باشند.

· Thoughtful

یک کد Clean  کدی است که ساختار آن متفکرانه طراحی شده باشد. از نحوه‌ی طراحی یک کلاس گرفته تا layering و Tiering پروژه باید کاملا هوشمندانه و با توجه به پارامتر‌های موجود باشند. همچنین خطا‌های خطرناک و استثناء‌ها باید کاملا هندل شوند. 

همه‌ی ما با دیدن کد بالا سریعا مفهوم اسپاگتی کد به ذهنمان خطور می‌کند. تغییر، توسعه و پشتیبانی نرم افزارهایی که کد آنها به این صورت نوشته شده است، بسیار سخت و پر هزینه می‌باشد. در این حالت تغییر هر یک از اجزاء ممکن است بر سایر قسمت‌های دیگر تاثیرات مختلفی داشته باشد. راه کاری که در این حالت ارائه می‌شود، Refactoring می‌باشد. در این روش کد را به کلاس‌ها و متدهایی بر حسب عملکرد تقسیم خواهیم کرد. در نهایت کد تولید شده دارای کمترین تاثیر بر سایر قسمت‌ها خواهد بود. توجه داشته باشید که با انجام این کار، قدمی به سوی SOC یا Separation Of Concern برداشته‌اید.

1. Testable Code & Unit Test

یکی دیگر از اهداف Defensive Coding افزایش کیفیت یا Quality می‌باشد که برای رسیدن به این هدف از مفهوم Testable Code & Unit Test استفاده می‌شود. بسیاری از ویژگی‌های Testable Code و Clean Code با هم مشابه می‌باشند. برای مثال Refactor کردن هر متد به متد‌های کوچکتر، تست آن را ساده‌تر خواهند کرد. در نتیجه نوشتن کد‌های Testable ، با نوشتن کد‌های clean شروع می‌شود.

در این قسمت اشاره‌ای به Unit Test شده است؛ اما این مفهوم می‌تواند به یک مفهوم گسترده‌تر به نام  Automated Code testing، تعمیم داده شود. به این دلیل که تست فقط به Unit Testing محدود نمی‌شود و می‌تواند شامل سایر انواع تست‌ها مانند  integration test نیز باشد.

برای مثال شکل ذیل را در نظر بگیرید. در انتهای این سناریو یک Page جدید اضافه شده است. خوب؛ برای تست کد اضافه شده، مجبورید برنامه را اجرا کنید، login کنید، داده‌های مورد نظر را در فرم وارد کرده و در نهایت شرایط لازم را جهت تست، فراهم کنید تا بتوانید کد جدید را تست کنید. در این بین با خطایی مواجه می‌شوید. پس برنامه را متوقف می‌کنید و تغییرات لازم را اعمال می‌کنید. حال فرض کنید این خطا به این زودی‌ها رفع نشود. در این حالت باید فرآیند بالا را چندین و چند بار انجام دهید. نتیجه اینکه این روش بسیار زمان بر و پر هزینه خواهد بود. البته میزان هزینه و زمان رابطه‌ی نزدیکی با وسعت تغییرات دارند. برای رفع مسائلی از این دست مایکروسافت زیرساختی به نام MS Test ارائه داده است که می‌توان با آن سناریوهای تست متفاوتی را پیاده سازی و اجرا نمود. متاسفانه این مسئله در بسیار از جوامع توسعه نرم افزار رعایت نمی‌شود و در بسیاری از این جوامع، نیروی انسانی، این فرآیند و فرآیندهایی از این دست را انجام می‌دهند. درحالیکه چنین فرآیندهایی به راحتی توسط ابزارهای ارائه شده‌ی توسط شرکت‌های مختلف قابل مدیریت است.

 


1. Predictability

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

برای رسیدن به این هدف باید اصل Trust but Verify را دنبال کنیم. برای مثال این اصل به ما می‌گوید که در هنگام تعریف متد‌های public باید یکسری موارد را در نظر بگیریم. یک متد باید از یکسری قرارداد‌ها پیروی کند. یک متد قرارداد می‌کند که یکسری پارامتر‌ها را با یک data type خاص به عنوان ورودی دریافت کند. قرارداد می‌کند که یک مقدار خاص با یک data type خاص را به عنوان نوع بازگشتی بازگرداند یا اینکه هیچ مقداری را باز نگرداند و در نهایت یک متد متعهد می‌شود که یکسری Exception ‌تعریف شده و پیش بینی شده را صادر کند. اما برای اینکه مطمئن شویم یک application واقعا قابل پیش بینی است و این اصل را به درستی پیاده سازی کرده است، اعتماد می‌کنیم اما Verify را هم انجام می‌دهیم. برای verify کردن باید پارامترها، دیتا‌های متغیر، مقادیر بازگشتی و استثناء‌ها به گونه‌ای بررسی شوند که مطمئن شویم انتظارت ما را برآورده کرده‌اند. 

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