مطالب
استفاده از افزونه Typeahead مجموعه Twitter Bootstrap در ASP.NET MVC
با تعدادی از کامپوننت‌های Bootstrap در مطلب «نگاهی به اجزای تعاملی Twitter Bootstrap» آشنا شدید. یکی دیگر از این افزونه‌ها، Typeahead نام دارد که در حقیقت نوعی Autocomplete text box است. در ادامه قصد داریم نحوه استفاده از آن‌را در ASP.NET MVC بررسی کنیم.

استفاده‌ی استاتیک از افزونه Typeahead

منظور از استفاده‌ی استاتیک، مشخص بودن آرایه عناصر و هچنین درج آن به صورت html encoded در صفحه است. برای این منظور، کنترلر برنامه چنین شکلی را خواهد داشت:
using System.Web.Mvc;
using System.Web.Script.Serialization;

namespace Mvc4TwitterBootStrapTest.Controllers
{
    public class HomeController : Controller
    {
        [HttpGet]
        public ActionResult Index()
        {
            var array = new[]
                {
                     "Afghanistan",
                     "Albania",
                     "Algeria",
                     "American Samoa",
                     "Andorra",
                     "Angola",
                     "Anguilla",
                     "Antarctica",
                     "Antigua and/or Barbuda"
                };
            ViewBag.JsonString = new JavaScriptSerializer().Serialize(array);
            return View();
        }
    }
}
در اینجا یک آرایه با تعداد عناصر مشخص، تبدیل به رشته JSON معادل آن شده و توسط ViewBag.JsonString به View ارسال می‌شود.
View متناظر با آن به نحو ذیل با مشخص سازی نوع data-provide (تا به کتابخانه‌ی جاوا اسکریپتی همراه bootstrap اعلام کند از چه افزونه‌ای در اینجا قرار است استفاده شود)، منبع داده data-source و حداکثر تعداد آیتم ظاهر شونده data-items، می‌تواند طراحی شود:
@{
    ViewBag.Title = "Index";    
}
<h2>
    Typeahead</h2>
@Html.TextBox("search", null, htmlAttributes:
                              new
                              {
                                  autocomplete = "off",
                                  data_provide = "typeahead",
                                  data_items = 8,
                                  data_source = @ViewBag.JsonString
                              })

به این ترتیب، یک چنین خروجی در صفحه درج می‌شود:
<input autocomplete="off" data-items="8" data-provide="typeahead" 
data-source="[&quot;Afghanistan&quot;,&quot;Albania&quot;,&quot;Algeria&quot;,&quot;American Samoa&quot;,&quot;Andorra&quot;,&quot;Angola&quot;,&quot;Anguilla&quot;,&quot;Antarctica&quot;,&quot;Antigua and/or Barbuda&quot;]" 
id="search" name="search" type="text" value="" />
همانطور که ملاحظه می‌کنید دقیقا data-source تهیه شده مطابق نیاز خاص این افزونه، html encoded است. به علاوه هر جایی در htmlAttributes صفحه از under line استفاده شده، در این سمت به صورت خودکار به - ترجمه گردیده است.
اگر هم بخواهیم برای آن یک Html Helper درست کنیم، می‌توان به نحو ذیل عمل کرد:
        public static MvcHtmlString TypeaheadFor<TModel, TValue>(
                this HtmlHelper<TModel> htmlHelper,
                Expression<Func<TModel, TValue>> expression,
                IEnumerable<string> source,
                int items = 8)
        {
            var jsonString = new JavaScriptSerializer().Serialize(source);
            return htmlHelper.TextBoxFor(
                expression,
                new
                {
                    autocomplete = "off",
                    data_provide = "typeahead",
                    data_items = items,
                    data_source = jsonString
                }
            );
        }


استفاده پویا و Ajax ایی از افزونه Typeahead

اگر بخواهیم data-source را به صورت پویا، هربار از بانک اطلاعاتی دریافت و ارائه دهیم، نیاز به کمی اسکریپت نویسی خواهد بود:
using System;
using System.Linq;
using System.Web.Mvc;
using System.Web.Script.Serialization;

namespace Mvc4TwitterBootStrapTest.Controllers
{
    public class HomeController : Controller
    {
        [HttpGet]
        public JsonResult GetNames(string term)
        {
            var array = new[]
                {
                     "Afghanistan",
                     "Albania",
                     "Algeria",
                     "American Samoa",
                     "Andorra",
                     "Angola",
                     "Anguilla",
                     "Antarctica",
                     "Antigua and/or Barbuda"
                };

            var results = array.Where(n =>
                n.StartsWith(term, StringComparison.OrdinalIgnoreCase));

            return Json(results.ToArray(), JsonRequestBehavior.AllowGet);
        }
    }
}
در این حالت، کدهای اکشن متدی که یک عبارت، یا قسمتی از آن را از طریق پارامتر term دریافت و خروجی JSON مناسبی را ارائه می‌کند، همانند متد GetNames فوق خواهد بود.
سپس در تعاریف View، قسمت data-source مرتبط با TextBox حذف و از طریق فراخوانی مستقیم کدهای افزونه typeahead مقدار دهی می‌گردد:
@{
    ViewBag.Title = "Index";
    var url = Url.Action("GetNames", "Home");
}
<h2>
    Typeahead</h2>
@Html.TextBox("search", null, htmlAttributes:
                              new
                              {
                                  autocomplete = "off",
                                  data_provide = "typeahead",
                                  data_items = 8
                              })
@section JavaScript
{
    <script type="text/javascript">
            $(function () {
                $('#search').typeahead({
                    source: function (term, process) {
                        return $.getJSON('@url', { term: term }, function (data) { return process(data); });
                    }
                });
            });
    </script>
}
در اینجا توسط متد getJSON کتابخانه jQuery، مقدار عبارت وارد شده در TextBox جستجو، به آدرس اکشن متد GetNames ارسال و سپس حاصل به source افزونه typeahead انتساب داده می‌شود.
مطالب
نحوه ایجاد فیلدهای محاسباتی در PdfReport
در گزارشات، گاهی از اوقات نیاز خواهد شد تا تعدادی ستون جدید را بر اساس مقادیر فیلدهای موجود در منبع داده گزارش، به صورت پویا محاسبه و تولید کنیم (ایجاد ستون‌هایی که در منبع داده وجود خارجی ندارند). در ادامه با نحوه انجام اینکار در PdfReport آشنا خواهیم شد.
در ابتدا کدهای کامل گزارش این قسمت را در ادامه ملاحظه خواهید کرد (در این کلاس از دو کلاس User و AppPath قسمت قبل نیز استفاده شده است):
using System;
using System.Collections.Generic;
using PdfReportSamples.Models;
using PdfRpt.Core.Contracts;
using PdfRpt.Core.Helper;
using PdfRpt.FluentInterface;

namespace PdfReportSamples.CalculatedFields
{
    public class CalculatedFieldsPdfReport
    {
        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" });
            })
             .DefaultFonts(fonts =>
             {
                 fonts.Path(AppPath.ApplicationPath + "\\fonts\\irsans.ttf",
                            Environment.GetEnvironmentVariable("SystemRoot") + "\\fonts\\verdana.ttf");
             })
             .PagesFooter(footer =>
             {
                 footer.DefaultFooter(printDate: DateTime.Now.ToString("MM/dd/yyyy"));
             })
             .PagesHeader(header =>
             {
                 header.DefaultHeader(defaultHeader =>
                 {
                     defaultHeader.ImagePath(AppPath.ApplicationPath + "\\Images\\01.png");
                     defaultHeader.Message("گزارش جدید ما");
                 });
             })
             .MainTableTemplate(template =>
             {
                 template.BasicTemplate(BasicTemplate.RainyDayTemplate);
             })
             .MainTablePreferences(table =>
             {
                 table.ColumnsWidthsType(TableColumnWidthType.Relative);
             })
             .MainTableDataSource(dataSource =>
             {
                 var listOfRows = new List<User>();
                 for (int i = 0; i < 220; i++)
                 {
                     listOfRows.Add(new User { Id = i, LastName = "نام خانوادگی " + i, Name = "نام " + i, Balance = i + 1000 });
                 }
                 dataSource.StronglyTypedList<User>(listOfRows);
             })
             .MainTableEvents(events =>
             {
                 events.DataSourceIsEmpty(message: "رکوردی یافت نشد.");
             })
             .MainTableSummarySettings(summary =>
             {
                 summary.OverallSummarySettings("جمع");
                 summary.PreviousPageSummarySettings("نقل از صفحه قبل");
                 summary.PageSummarySettings("جمع صفحه");
             })
             .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<User>(x => x.Id);
                     column.CellsHorizontalAlignment(HorizontalAlignment.Center);
                     column.IsVisible(true);
                     column.Order(1);
                     column.Width(2);
                     column.HeaderCell("شماره");
                 });

                 columns.AddColumn(column =>
                 {
                     column.PropertyName<User>(x => x.Name);
                     column.CellsHorizontalAlignment(HorizontalAlignment.Center);
                     column.IsVisible(true);
                     column.Order(2);
                     column.Width(2);
                     column.HeaderCell("نام");
                 });

                 columns.AddColumn(column =>
                 {
                     column.PropertyName<User>(x => x.LastName);
                     column.CellsHorizontalAlignment(HorizontalAlignment.Center);
                     column.IsVisible(true);
                     column.Order(3);
                     column.Width(3);
                     column.HeaderCell("نام خانوادگی");
                 });

                 columns.AddColumn(column =>
                 {
                     column.PropertyName("CF1");
                     column.CalculatedField(true,
                         list =>
                         {
                             if (list == null) return string.Empty;
                             var name = list.GetSafeStringValueOf<User>(x => x.Name);
                             var lastName = list.GetSafeStringValueOf<User>(x => x.LastName);
                             return name + " - " + lastName;
                         });
                     column.HeaderCell("ف.م.");
                     column.Width(3);
                     column.CellsHorizontalAlignment(HorizontalAlignment.Center);
                     column.IsVisible(true);
                     column.Order(4);
                 });

                 columns.AddColumn(column =>
                 {
                     column.PropertyName<User>(x => x.Balance);
                     column.HeaderCell("موجودی");
                     column.ColumnItemsTemplate(template =>
                     {
                         template.TextBlock();
                         template.DisplayFormatFormula(obj => obj == null ? string.Empty : string.Format("{0:n0}", obj));
                     });
                     column.Width(2);
                     column.AggregateFunction(aggregateFunction =>
                     {
                         aggregateFunction.NumericAggregateFunction(AggregateFunction.Sum);
                         aggregateFunction.DisplayFormatFormula(obj => obj == null ? string.Empty : string.Format("{0:n0}", obj));
                     });
                     column.CellsHorizontalAlignment(HorizontalAlignment.Center);
                     column.IsVisible(true);
                     column.Order(5);
                 });

                 columns.AddColumn(column =>
                 {
                     column.PropertyName("CF2");
                     column.HeaderCell("ف.م.");
                     column.Width(3);
                     column.AggregateFunction(aggregateFunction =>
                     {
                         aggregateFunction.NumericAggregateFunction(AggregateFunction.Sum);
                         aggregateFunction.DisplayFormatFormula(obj => obj == null ? string.Empty : string.Format("{0:n0}", obj));
                     });
                     column.CellsHorizontalAlignment(HorizontalAlignment.Center);
                     column.ColumnItemsTemplate(template =>
                     {
                         template.TextBlock();
                         template.DisplayFormatFormula(obj => obj == null ? string.Empty : string.Format("{0:n0}", obj));
                     });
                     column.CalculatedField(true,
                         list =>
                         {
                             if (list == null) return string.Empty;
                             var balance = list.GetValueOf<User>(x => x.Balance);
                             return (long)balance * 3;
                         });
                     column.IsVisible(true);
                     column.Order(6);
                 });
             })
             .Export(export =>
             {
                 export.ToExcel(footer: "Footer text", header: "&24&U&\"Arial,Regular Bold\" New rpt.", pageLayoutView: true);
                 export.ToXml();
             })
             .Generate(data => data.AsPdfFile(AppPath.ApplicationPath + "\\Pdf\\RptCalculatedFieldsSample.pdf"));
        }
    }
}

توضیحات:
- در این مثال از یک قلم سفارشی به نام Iranian Sans استفاده شده که در پوشه bin\fonts سورس به روز شده پروژه، قابل دریافت است.
- برخلاف قسمت قبل، از متد table.NumberOfDataRowsPerPage استفاده نشده است. به این ترتیب تعداد ردیف‌ها به صورت خودکار بر اساس اندازه صفحه محاسبه می‌شود.
- منبع داده تعریف شده توسط  dataSource.StronglyTypedList، بسیار مناسب است جهت کار با انواع و اقسام ORMها. تفاوتی نمی‌کند از چه ORM ایی استفاده می‌کنید؛ همینقدر که خروجی کار شما یک List باشد، در اینجا قابل استفاده خواهد بود. حتی نیازی هم به بانک اطلاعاتی نیست و در این مثال از یک منبع داده درون حافظه‌ای استفاده شده است.
- توضیحاتی در مورد ColumnsWidthsType :
برای تعیین عرض ستون‌ها، چهار حالت بر اساس مقادیر enum ایی به نام TableColumnWidthType میسر است:
الف) Relative : عرض نسبی. به این معنا که اگر سه ستون با عرض‌های 2, 1, 1 تعریف کنید، کل عرض صفحه به 4 قسمت تقسیم می‌شود. از این 4 قسمت، 2 قسمت به ستون اول و یک قسمت به ستون دوم و همچنین یک قسمت به ستون سوم اختصاص خواهد یافت.
ب) Absolute : در این حالت باید عرض ستون‌ها را دقیقا بر اساس user space units مشخص کنید.
ج) FitToContent : سعی خواهد کرد بر اساس طول محتوای یک سلول، عرض بهینه‌ای را محاسبه کند. در این حالت نیازی به قید column.Width نیست.
د) EquallySized : به صورت خودکار عرض تمام ستون‌ها را یکسان محاسبه می‌کند. در این حالت نیازی به قید column.Width نیست.
- اولین فیلد محاسباتی که در PdfReport به صورت توکار و خودکار در اختیار شما است، فیلد شماره ردیف می‌باشد که به صورت زیر مشخص می‌شود:
 column.IsRowNumber(true);
بنابراین نیازی نیست تا منبع داده شما شامل یک ستون اضافی به نام ردیف باشد. PdfReport این مورد را به صورت خودکار تولید خواهد کرد.
- سپس دو فیلد و ستون محاسباتی در گزارش فوق قابل مشاهده هستند:
                 columns.AddColumn(column =>
                 {
                     column.PropertyName("CF1");
                     column.CalculatedField(true,
                         list =>
                         {
                             if (list == null) return string.Empty;
                             var name = list.GetSafeStringValueOf<User>(x => x.Name);
                             var lastName = list.GetSafeStringValueOf<User>(x => x.LastName);
                             return name + " - " + lastName;
                         });
                     column.HeaderCell("ف.م.");
                     column.Width(3);
                     column.CellsHorizontalAlignment(HorizontalAlignment.Center);
                     column.IsVisible(true);
                     column.Order(4);
                 });
برای اینکه یک فیلد پویا را به مجموعه فیلدهای مهیا شده توسط منبع داده اضافه کنیم، از متد CalculatedField با پارامتر اول مساوی true استفاده خواهیم کرد. سپس نیاز است نحوه محاسبه این فیلد را مشخص کنیم. امضای متد CalculatedField به صورت زیر است:
 public void CalculatedField(bool isCalculatedField, Func<IList<CellData>, object> calculatedFieldFormula)
به این معنا که توسط آرگومان دوم آن، لیست کلیه مقادیر ردیف جاری در اختیار شما قرار خواهد گرفت. در این بین فرصت خواهیم داشت بر این اساس، فیلد و مقدار جدیدی را تولید کرده و بازگشت دهیم؛ که نمونه‌ای از اینکار را در فیلد محاسبانی CF1 فوق مشاهده می‌کنید.
باید دقت داشت که نام خواص (column.PropertyName) باید منحصربفرد باشند و گرنه برنامه با یک استثناء متوقف خواهد شد. اگر ستون معرفی شده متناظر است با یک فیلد یا خاصیت منبع داده، باید PropertyName با رعایت کوچکی و بزرگی حروف، معادل فیلد متناظر باشد. اگر ستون تعریف شده یک فیلد محاسباتی است، تنها کافی است یک نام دلخواه غیرتکراری را ذکر کرد.
همچنین جهت سهولت کار، در فضای نام PdfRpt.Core.Helper، تعدادی متد برای کار با لیستی از CellDataها تدارک دیده شده‌اند؛ که نمونه‌ای از آن‌را در اینجا با استفاده از متدهای GetSafeStringValueOf ملاحظه می‌کنید.

- فیلد محاسباتی دیگری نیز در این گزارش قابل مشاهده است:
                 columns.AddColumn(column =>
                 {
                     column.PropertyName("CF2");
                     column.HeaderCell("ف.م.");
                     column.Width(3);
                     column.AggregateFunction(aggregateFunction =>
                     {
                         aggregateFunction.NumericAggregateFunction(AggregateFunction.Sum);
                         aggregateFunction.DisplayFormatFormula(obj => obj == null ? string.Empty : string.Format("{0:n0}", obj));
                     });
                     column.CellsHorizontalAlignment(HorizontalAlignment.Center);
                     column.ColumnItemsTemplate(template =>
                     {
                         template.TextBlock();
                         template.DisplayFormatFormula(obj => obj == null ? string.Empty : string.Format("{0:n0}", obj));
                     });
                     column.CalculatedField(true,
                         list =>
                         {
                             if (list == null) return string.Empty;
                             var balance = list.GetValueOf<User>(x => x.Balance);
                             return (long)balance * 3;
                         });
                     column.IsVisible(true);
                     column.Order(6);
                 });
در اینجا در متد  column.CalculatedField، مقدار فیلد موجودی (Balance) ردیف جاری دریافت شده و سپس مقدار دلخواه جدیدی تولید و بازگشت داده شده است.
همچنین توسط متد column.AggregateFunction، مشخص کرده‌ایم که این ستون جدید نیاز به جمع پایین صفحه هم دارد. به علاوه توسط متد column.ColumnItemsTemplate، با مشخص سازی DisplayFormatFormula، پیش از نمایش اطلاعات فیلد جاری، مقدار آن‌را دریافت و فرمت کرده‌ایم؛ که در اینجا سه رقم جداکننده اعداد اضافه شده است.
ذکر متد template.TextBlock اختیاری است و حالت پیش فرض می‌باشد. قالب‌های دیگری نیز در اینجا تعریف شده‌اند که در مثال‌های قسمت‌های بعدی آن‌ها را بررسی خواهیم کرد (امکان نمایش تصویر، لینک، بارکد و غیره).

 
نظرات مطالب
خلاص شدن از شر deep null check
متاسفانه روش فوق کد نویسی را تا حد زیادی تحت تاثیر قرار می‌دهد، مگر این که روش استفاده از متد الحاقی شما را به خوبی متوجه نشده باشم
به مثال زیر دقت کنید:
public class Customer
    {
        public CustomerInfo Info { get; set; }
        public Int32 GetNameLength()
        {
            return this.IfNotDefault(city => city.Info)
                .IfNotDefault(info => info.CityInfo)
                .IfNotDefault(cityInfo => cityInfo.Name)
                .IfNotDefault(name => name.Length);
        }
    }
    public class CustomerInfo
    {
        public CustomerCityInfo CityInfo { get; set; }
    }
    public class CustomerCityInfo
    {
        public String Name { get; set; }
    }
و برای استفاده داریم:
            Customer customer = new Customer();
            String cityName = customer
                .IfNotDefault(cust => cust.Info)
                .IfNotDefault(info => info.CityInfo)
                .IfNotDefault(city => city.Name);
            Int32 length = customer.GetNameLength();
در حالی که با متد الحاقی زیر داریم
public static TValue GetValue<TObj, TValue>(this TObj obj, Func<TObj, TValue> member, TValue defaultValueOnNull = default(TValue))
        {
            if (member == null)
                throw new ArgumentNullException("member");

            if (obj == null)
                throw new ArgumentNullException("obj");

            try
            {
                return member(obj);
            }
            catch (NullReferenceException)
            {
                return defaultValueOnNull;
            }
        }  
            تعریف ساده‌تر کلاس
   public class Customer
    {
        public CustomerInfo Info { get; set; }

        public Int32 GetNameLength()
        {
            return this.Info.CityInfo.Name.Length;
        }
    }

    public class CustomerInfo
    {
        public CustomerCityInfo CityInfo { get; set; }
    }

    public class CustomerCityInfo
    {
        public String Name { get; set; }
    }  
و سادگی در استفاده
             Customer customer = new Customer();

            String cityName = customer.GetValue(cust => cust.Info.CityInfo.Name, "Not Selected");

            Int32 i = customer.GetValue(cust => cust.GetNameLength());    
شاید بگویید استفاده از Try-Catch سیستم را کند می‌کند، البته نه در آن حدی که فکر می‌کنید، و اگر قسمتی از کد شما به تعداد زیادی در بازه‌ی زمانی کوتاه فراخوانی می‌شود، می‌توانید آنرا به صورت کاملا عادی بنویسید، چون واقعا تعداد این شرایط زیاد نیست و این مورد سناریوی فراگیری نیست، در عوض خوانایی کد بسیار بسیار بالاتر از حالات عادی است.
در ضمن دقت کنید که تا زمانی که خطای NullReference رخ ندهد، سرعت سیستم در حد همان حداقل نیز کاهش نمی‌یابد، بدین جهت که بسیاری از افراد فکر می‌کنند Try-Catch نوشتن به خودی خود برنامه را کند می‌کند، ولی این رخ دادن خطا و جمع آوری StackTrace و ... است که برنامه را کند می‌کند، که شاید در خیلی از موارد اصلا رخ ندهد.
البته کدهای نوشته صرفا نمونه کد است، به هیچ وجه اصول طراحی در آن رعایت نشده است، بلکه سعی کرده ام مثال واضح‌تری بزنم
موفق و پایدار باشید
مطالب
ویژگی های کمتر استفاده شده در NET. - بخش پنجم

Nullable<T>.GetValueOrDefault Method

با استفاده از متد GetValueOrDefault مقدار فعلی یک شیء Nullable و یا مقدار پیش فرض آن را می‌توان بدست آورد. این متد از عملگر ?? سریع‌تر است.
float? yourSingle = -1.0f;
Console.WriteLine( yourSingle.GetValueOrDefault() );

yourSingle = null;
Console.WriteLine( yourSingle.GetValueOrDefault() );

// assign different default value
Console.WriteLine( yourSingle.GetValueOrDefault( -2.4f ) );

// returns the same result as the above statement
Console.WriteLine( yourSingle ?? -2.4f );

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

شما می‌توانید برای دیکشنری نیز یک متد Get امن ایجاد کنید (در صورت عدم وجود کلید، بجای پرتاب استثناء، مقدار پیش فرض بازگشت داده شود).

public static class DictionaryExtensions
{
    public static TValue GetValueOrDefault< TKey, TValue >( this Dictionary< TKey, TValue > dic,
                                                            TKey key )
    {
        TValue result;
        return dic.TryGetValue( key,
                                out result )
            ? result
            : default(TValue);
    }
}

و روش استفاده

var names = new Dictionary< int, string >
            {
                { 0, "Vahid" }
            };
Console.WriteLine( names.GetValueOrDefault( 1 ) );


ZipFile in .NET

با استفاده از کلاس ZipFile ( رفرنس به اسمبلی System.IO.Compression.FileSystem ) می‌توان عملیات بازکردن، ایجاد و استخراج فایل‌های Zip را انجام داد.
var startPath = Path.Combine( Environment.GetFolderPath( Environment.SpecialFolder.Desktop ), "Start" );
var resultPath = Path.Combine( Environment.GetFolderPath( Environment.SpecialFolder.Desktop ), "Result" );
var extractPath = Path.Combine( Environment.GetFolderPath( Environment.SpecialFolder.Desktop ), "Extract" );
Directory.CreateDirectory( startPath );
Directory.CreateDirectory( resultPath );
Directory.CreateDirectory( extractPath );

var zipPath = Path.Combine( resultPath, Guid.NewGuid() + ".zip" );
ZipFile.CreateFromDirectory( startPath, zipPath );
ZipFile.ExtractToDirectory( zipPath, extractPath );

C# Preprocessor Directives

با استفاده از  warning#  می توان یک هشدار را در یک قسمت خاص از کد تولید کرد.
#if DEBUG
#warning DEBUG is defined
#endif
و خروجی آن

با استفاده از  error#  می توان یک خطا را در یک جای خاصی از کد تولید کرد.
#if DEBUG
#error DEBUG is defined
#endif
در صورتی که کد بالا را اجرا کنید (در حال دیباگ) کامپایلر با نمایش DEBUG is defined در پنجره Error List، جلوی اجرای برنامه را می‌گیرد. اما در حالت ریلیز، برنامه بدون هیچ مشکلی اجرا می‌شود.

با استفاده از  line#  می توانید شماره خط کامپایلر و نام فایل خروجی (اختیاری) را برای خطاها و هشدارها تغییر دهید.

در مثال زیر، در صورتیکه در خط اول break point قرار دهید و با کلید F10 برنامه را اجرا کنید، مشاهده می‌کنید که دیباگر، خطی را که بعد از دستور line hidden# نوشته شده است، در نظر نمی‌گیرد (برای دیباگ) اما اجرا می‌شود و دیباگر بر روی دستور بعد از line default# قرار می‌گیرد.

    Console.WriteLine("Normal line #1."); // Set break point here.
#line hidden
    Console.WriteLine("Hidden line.");
#line default
    Console.WriteLine("Normal line #2.");


Stackalloc

کلمه کلیدی stackalloc برای اختصاص یک بلاک از حافظه در stack، در زمینه کد غیرامن (unsafe code) استفاده می‌شود.
مثال زیر 20 عدد اول دنباله فیبوناچی را تولید می‌کند. هر عدد از مجموع دو عدد قبلی به دست می‌آید. در این مثال، یک بلاک از حافظه به اندازه 20 عدد از نوع int را در stack (نه heap) اختصاص می‌دهد. (تفاوت stack با heap)
static unsafe void Fibonacci()
{
    const int arraySize = 20;
    int* fib = stackalloc int[arraySize];
    var p = fib;
    *p++ = *p++ = 1;

    for ( var i = 2; i < arraySize; ++i, ++p )
    {
        *p = p[-1] + p[-2];
    }

    for ( var i = 0; i < arraySize; ++i )
    {
        System.Console.WriteLine( fib[i] );
    }
}
آدرس بلاک حافظه در اشاره گر fib ذخیره می‌شود. این متغیر توسط GC جمع آوری نمی‌شود و طول عمر آن محدود به متدی است که در آن تعریف شده است و شما نمی‌توانید قبل از بازگشت متد، حافظه را آزاد کنید.
تنها دلیل استفاده از stackalloc، عملکرد بهتر آن است (برای محاسبات و یا ردوبدل اطلاعات). با استفاده از stackalloc به جای اختصاص دادن آرایه (heap)، فشار کمتری را بر GC وارد می‌کنید (نیاز کمتری به اجرای GC وجود دارد). در نتیجه سرعت اجرای بالاتری خواهید داشت.
توجه: برای اجرای مثال بالا باید پنجره خصوصیات پروژه را باز کنید و در بخش Build، گزینه Allow unsafe code را تیک بزنید.
پاسخ به پرسش‌ها
آیا امکان استفاده از Extension Method در زمان Select وجود دارد؟

البته یک نمونه در اینترنت پیدا کردم (البته پاسخ Copilot بوده) که متد را بصورت Generic تعریف کرده بود که برای انجام Dynamic Select روش زیر را بکاربرده بود:

IQueryable<TResult> results = query.SelectMany(x => selectProperties.Select(prop => prop.Compile()(x)));

تا اینجا هیچ مشکلی وجود ندارد ولی به محض آنکه در خط بعدی متد ToList اجرا میشه با پیغام خطا مواجه میشم:

An exception was thrown while attempting to evaluate a LINQ query parameter expression. See the inner exception for more information. To show additional information call 'DbContextOptionsBuilder.EnableSensitiveDataLogging'.

کد کامل Copilot:

public async Task<List<LineJoint2>> GetAsync2(
    Expression<Func<LineJoint, bool>> filter,
    List<Expression<Func<LineJoint, object>>> includes,
    List<Func<IIncludableQueryable<LineJoint, object>, IIncludableQueryable<LineJoint, object>>> thenIncludes,
    Expression<Func<IQueryable<LineJoint>, IOrderedQueryable<LineJoint>>> orderBy,
    int pageSize,
    int pageNumber,
    bool trackChanges = true,
    CancellationToken cancellationToken = default)
{
    var query = context.LineJoints.AsQueryable();

    // Apply filter
    if (filter != null)
        query = query.Where(filter);

    // Include related entities
    foreach (var include in includes)
        query = query.Include(include);

    // Apply thenIncludes
    foreach (var thenInclude in thenIncludes)
        query = thenInclude(query);

    // Project properties dynamically
    var projectedQuery = query.Select(x => new LineJoint2
    {
        Id = x.Id,
        JointNo = x.JointNo
        // Add other properties from LineJoint as needed
    });

    // Apply ordering
    if (orderBy != null)
        projectedQuery = orderBy.Compile()(projectedQuery);

    // Apply pagination
    var pagedQuery = projectedQuery.Skip((pageNumber - 1) * pageSize).Take(pageSize);

    // Execute the query
    return await (trackChanges ? pagedQuery : pagedQuery.AsNoTracking()).ToListAsync(cancellationToken);
}
نظرات مطالب
Implementing second level caching in EF code first
سلام و خسته نباشید
به این نتیجه رسیدم که اگر متد ToCacheableList()  را در انتها اضافه نکنیم چیزی شبیه به این
var list2 = context.Products
        .OrderBy(one => one.ProductNumber)
        .Where(x => x.IsActive == isActive && x.ProductName == name);
روی کش هیچ تاثیری نداره یعنی نه چیزی رو کش می‌کنه و نه چیزی رو از کش می‌خونه و نه کش رو پاک می‌کنه ولی list3 دوباره اطلاعات رو از کش می‌خونه آیا این موضوع صحیح است ؟
با تشکر 
مطالب
افزایش کارآیی گزارش‌ها در Stimulsoft
به صورت پیش فرض، تمام گزارشات Stimulsoft، قبل از رندر باید به صورت اسمبلی کامپایل شوند. در برنامه‌های NET. در صورتیکه اسمبلی در حافظه بارگذاری شده باشد، تا زمانیکه برنامه اصلی از حافظه خارج نشود، فایل اسمبلی قفل خواهد بود و امکان حذف و یا تغییر آن وجود ندارد.
در هر بار کامپایل گزارش، فایل اسمبلی کامپایل شده، در پوشه %Temp% ذخیره و در حافظه بارگذاری می‌شود. به هرحال، بارگذاری اسمبلی گزارش، سبب قفل شدن و اختصاص حافظه‌ای برای آن می‌شود.

همانطور که مشاهده می‌کنید در هر بار کامپایل گزارش، این فایل‌ها ساخته می‌شوند.
چندین راه حل برای رفع این مشکل وجود دارد:
  1. استفاده گزارشات به صورت کلاس‌های #C در برنامه شما (با استفاده از Designer و Save as آن به صورت کلاس #C). در این صورت گزارش همزمان با کامپایل برنامه شما، کامپایل شده و در زمان فراخوانی، دیگر نیاز به کامپایل مجدد آن نیست.
    اشکال: در صورتیکه قالب گزارش شما نیاز به تغییر داشته باشد، برنامه‌ی شما باید با کلاس‌های جدید گزارش مجددا کامپایل شود.
  2. می‌توان گزارش را از طریق برنامه Designer به صورت اسمبلی ( dll ) ذخیره کرد.
    اشکال: این روش برای مدیریت گزارش‌ها در طول ارتقاء آن‌ها مفید نمی‌باشد.
  3. از گزارش کامپایل شده استفاده نکنید و به جای آن از تفسیر عبارات در گزارش استفاده کنید. برای فعالسازی این حالت، خصوصیت Calculation Mode گزارش را در پنجره خصوصیت‌ها در برنامه Designer بر روی Interpretation قرار دهید.
    اشکال: این روش به این معنی است که تمامی اصطلاحات زبان C# / VB.NET تفسیر نمی‌شوند. همچنین استفاده از رخدادهای مربوط به اجزاء گزارش غیرممکن می‌باشد.
  4. کامپایل و رندر گزارش، در دامنه‌ی برنامه‌ی دیگری انجام شود. در این مورد می‌توان پس از رندر گزارش، اسمبلی را در دامنه‌ی برنامه جاری بارگذاری کرد و فایل قفل شده‌ی آن، بعدا حذف خواهد شد. با استفاده از متد ()CreateReportInNewAppDomain از کلاس StiReport می‌توان گزارش را ایجاد کرد و با استفاده از متد ()UnloadReportAppDomain آن را بارگذاری کرد.
    اشکال در این روش متدهای RegData و RegBusinessObject با سرعت خیلی کمی اجرا می‌شوند؛ چرا که هدایت داده‌ها به دامنه‌ی دیگری استفاده شده است. ایجاد و بارگذاری در دامنه‌ی برنامه نیازمند زمان می‌باشد.
  5. کارآمدترین روش: کامپایل گزارش تنها در زمان اولین فراخوانی انجام شود و هر زمان که درخواست فراخوانی گزارش انجام می‌شود، گزارش را از اسمبلی کامپایل شده، در حافظه بارگذاری می‌کنیم. در این مورد تنها یک کپی از گزارش در حافظه ذخیره می‌شود و تنها یک اسمبلی در درایو وجود دارد. پوشه مربوط به گزارش‌های کامپایل شده می‌تواند قبل از استفاده از گزارشات حذف شود، چرا که در اولین درخواست گزارش، دوباره ایجاد خواهد شد.
    مثال:
var report = new StiReport();
report.Load( "Report.mrt" );

var folder = Environment.GetFolderPath( Environment.SpecialFolder.LocalApplicationData );
folder = Path.Combine( folder, "Stimulsoft\\CompiledReports" );
folder = Path.Combine( folder, System.Runtime.InteropServices.RuntimeEnvironment.GetSystemVersion() );
var compiledReportFile = Path.Combine( folder, report.GetReportAssemblyCacheName() );

if ( File.Exists( compiledReportFile ) )
    report = StiReport.GetReportFromAssembly( compiledReportFile, true );
else
{
    if ( !Directory.Exists( folder ) ) Directory.CreateDirectory( folder );
    report.Compile( compiledReportFile );
}

report.RegBusinessObject( "TestData", new { Title = "Vahid " + DateTime.Now.Millisecond } );

report.Render( false );
همانطور که در مثال بالا مشاهده کنید، یک گزارش ساخته می‌شود و در صورت عدم وجود فایل کامپایل شده و یا تغییر در فایل گزارش، مجددا فایل گزارش با نام جدیدی کامپایل شده و نمایش داده می‌شود.
نکته: حتما بارگذاری دیتا مربوط به گزارش را بعد از عملیات کامپایل قراردهید.
نظرات مطالب
سفارشی سازی ASP.NET Core Identity - قسمت اول - موجودیت‌های پایه و DbContext برنامه
سلام.امکان داره یه مثال در مورد نحوه استفاده از قطعه کد زیر که مربوط به خواص سایه ای میشه بزنید.
public static readonly Func<object, DateTimeOffset?> EFPropertyCreatedDateTime =
                                                  entity => EF.Property<DateTimeOffset?>(entity, CreatedDateTime);
به عنوان مثال سعی کردم به شکل زیر از این قطعه کد استفاده کنم ولی با پیغام
"The EF.Property<T> method may only be used within LINQ queries"
سیستم متوقف میشه.
var persons = context.Persons
                                  .Where(x => AuditableShadowProperties.EFPropertyCreatedDateTime(x) == DateTimeOffset.UtcNow)
                                  .ToList();

مطالب
ویژگی های کمتر استفاده شده در NET. - بخش دوم

Curry and Partial methods

Curry – در ریاضیات و علوم کامپیوتر، currying روشی است برای ترجمه تابعی که آرگومان‌های متعددی می‌گیرد و به صورت ارزیابی دنباله‌ای‌است از توابع که هر کدام یک آرگومان دارند.
برای پیاده سازی آن در #C، از extension methods استفاده می‌کنیم.
public static class CurryMethodExtensions
{
    public static Func< A, Func< B, Func< C, R > > > Curry< A, B, C, R >( this Func< A, B, C, R > f )
    {
        return a => b => c => f( a, b, c );
    }
}
مثالی برای استفاده از متد بالا:
Func< int, int, int, int > addNumbers = ( x, y, z ) => x + y + z;
var f1 = addNumbers.Curry();
Func< int, Func< int, int > > f2 = f1( 3 );
Func< int, int > f3 = f2( 4 );
Console.WriteLine( f3( 5 ) );
بعد از فراخوانی متد Curry می‌توان از کلمه کلیدی var در دستورات بعدی بجای تعریف نوع متغیرها استفاده کرد.
نحوه اجرای دستورات بالا را در تصویر زیر می‌توانید مشاهده کنید:


Partial – در علوم کامپیوتر، قسمتی از یک برنامه (یا قسمتی از یک تابع برنامه) است که اشاره به روند تثبیت تعدادی از آرگومان‌ها به یک تابع و تولید تعداد آرگومان‌های کمتر تابع دیگری را می‌گویند.
public static class CurryMethodExtensions
{
    public static Func< C, R > Partial< A, B, C, R >( this Func< A, B, C, R > f, A a, B b )
    {
        return c => f( a, b, c );
    }
}
مثالی برای استفاده از تابع بالا:
Func< int, int, int, int > sumNumbers = ( x, y, z ) => x + y + z;
Func< int, int > f4 = sumNumbers.Partial( 3, 4 );
Console.WriteLine( f4( 5 ) );
بعد از فراخوانی متد Curry می‌توان از کلمه کلیدی var در دستورات بعدی بجای تعریف نوع متغیرها استفاده کرد.
نحوه اجرای دستورات بالا را در تصویر زیر می‌توانید مشاهده کنید:

WeakReference

یک ارجاع ضعیف به GC اجازه می‌دهد که یک شیء را جمع آوری کند، در عین حالی که برنامه امکان دسترسی به آن را خواهد داشت. در صورتیکه نیاز به شیء‌ای داشته باشید، می‌توانید یک ارجاع قوی را از آن داشته باشید و از جمع آوری آن توسط GC جلوگیری کنید.
var obj = new WeakReferenceTest
            {
                FirstName = "Vahid"
            };
var w = new WeakReference(obj);
obj = null;
GC.Collect();
var weakReferenceTest = w.Target as WeakReferenceTest;
if ( weakReferenceTest != null )
    Console.WriteLine( weakReferenceTest.FirstName );

Lazy<T>

برای ایجاد یک شیء بزرگ، پردازش زیاد منابع و یا اجرای یک وظیفه (task) با پردازش زیاد منابع، به خصوص در زمانیکه ایجاد و یا اجرای این فرآیند در طول عمر یک برنامه، ممکن است هرگز رخ ندهد، از Lazy استفاده می‌شود.
public abstract class ThreadSafeLazyBaseSingleton< T > where T : new()
{
    static readonly Lazy< T > lazy = new Lazy< T >( () => new T() );

    public static T Instance => lazy.Value;
}

BigInteger

نوع داده BigInteger یک نوع تغییر ناپذیر (immutable type) و نمایانگر یک عدد صحیح بزرگ دلخواه است که مقدار آن در تئوری در هیچ حد و مرز حداقل و حداکثری نیست. این نوع، از دیگر انواع جدایی ناپذیر (integral types) در NET.، که دارای خصوصیت MinValue و  MaxValue هستند، متفاوت است.
var positiveString = "91389681247993671255433422114345532000000";
var negativeString = "-9031583741089631207100208803453423537140000";
var posBigInt = BigInteger.Parse( positiveString );
Console.WriteLine( posBigInt );
var negBigInt = BigInteger.Parse( negativeString );
Console.WriteLine( negBigInt );
نکته: از آنجایی که BigInteger یک نوع تغییر ناپذیر و بدون حد و مرز حداقل و حداکثر است، برای بعضی از عملیات‌، اگر مقدار آن را بسیار زیاد افزایش دهید خطای OutOfMemoryException رخ می‌دهد (البته من با 1024 بار ضرب متغیر positiveString در خودش هم نتوانستم این پیام خطا را ببینم).
مطالب
سرعت واکشی اطلاعات در List و Dictionary
دسترسی به داده‌ها پیش شرط انجام همه‌ی منطق‌های اکثر نرم افزار‌های تجاری می‌باشد. داده‌های ممکن در حافظه ، پایگاه داده ، فایل‌های فیزیکی و هر منبع دیگری قرار گرفته باشند.
هنگامی که حجم داده‌ها کم باشد شاید روش دسترسی و الگوریتم مورد استفاده اهمیتی نداشته باشد اما با افزایش حجم داده‌ها روش‌های بهینه‌تر تاثیر مستقیم در کارایی برنامه دارند.
در این مثال سعی بر این است که در یک سناریوی خاص تفاوت بین Dictionary و List را بررسی کنیم :
فرض کنید 2 کلاس Student  و Grade موجود است که وظیفه‌ی نگهداری اطلاعات دانش آموز و نمره را بر عهده دارند.
    public class Grade
    {
        public Guid StudentId { get; set; }
        public string Value { get; set; }

        public static IEnumerable<Grade> GetData()
        {
            for (int i = 0; i < 10000; i++)
            {
                yield return new Grade
                                 {
                                     StudentId = GuidHelper.ListOfIds[i], Value = "Value " + i
                                 };
            }
        }
    }

    public class Student
    {
        public Guid Id { get; set; }
        public string Name { get; set; }
        public string Grade { get; set; }

        public static IEnumerable<Student> GetStudents()
        {
            for (int i = 0; i < 10000; i++)
            {
                yield return new Student
                                 {
                                     Id = GuidHelper.ListOfIds[i],
                                     Name = "Name " + i
                                 };
            }
        }
    }
از کلاس GuidHelper برای تولید و نگهداری شناسه‌های یکتا برای دانش آموز کمک گرفته شده است :
    public class GuidHelper
    {
        public static List<Guid> ListOfIds=new List<Guid>();

        static GuidHelper()
        {
            for (int i = 0; i < 10000; i++)
            {
                ListOfIds.Add(Guid.NewGuid());
            }
        }
    }
سپس لیستی از دانش آموزان و نمرات را درون حافظه ایجاد کرده و با یک حلقه  نمره‌ی هر دانش آموز به Property مورد نظر مقدار داده می‌شود.

ابتدا از LINQ روی لیست برای پیدا کردن نمره‌ی مورد نظر استفاده کرده و در روش دوم برای پیدا کردن نمره‌ی هر دانش آموز از Dictionary  استفاده شده :
    internal class Program
    {
        private static void Main(string[] args)
        {
            var stopwatch = new Stopwatch();
            List<Grade> grades = Grade.GetData().ToList();
            List<Student> students = Student.GetStudents().ToList();

            stopwatch.Start();
            foreach (Student student in students)
            {
                student.Grade = grades.Single(x => x.StudentId == student.Id).Value;
            }
            stopwatch.Stop();
            Console.WriteLine("Using list {0}", stopwatch.Elapsed);
            stopwatch.Reset();
            students = Student.GetStudents().ToList();
            stopwatch.Start();
            Dictionary<Guid, string> dictionary = Grade.GetData().ToDictionary(x => x.StudentId, x => x.Value);

            foreach (Student student in students)
            {
                student.Grade = dictionary[student.Id];
            }
            stopwatch.Stop();
            Console.WriteLine("Using dictionary {0}", stopwatch.Elapsed);
            Console.ReadKey();
        }
    }
نتیجه‌ی مقایسه در سیستم من اینگونه می‌باشد :



همانگونه که مشاهده می‌شود در این سناریو خواندن نمره از روی Dictionary بر اساس 'کلید' بسیار سریع‌تر از انجام یک پرس و جوی LINQ روی لیست است.

زمانی که از LINQ on list
   student.Grade = grades.Single(x => x.StudentId == student.Id).Value;
برای پیدا کردن مقدار مورد نظر یک به یک روی اعضا لیست حرکت می‌کند تا به مقدار مورد نظر برسد در نتیجه پیچیدگی زمانی آن O n هست. پس هر چه میزان داده‌ها بیشتر باشد این روش کند‌تر می‌شود.

زمانی که از Dictonary
         student.Grade = dictionary[student.Id];
برای پیدا کردن مقدار استفاده می‌شود با اولین تلاش مقدار مورد نظر یافت می‌شود پس پیچیدگی زمانی آن O 1 می‌باشد.

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

تفاوت این 2 روش وقتی مشخص می‌شود که میزان داده‌ها زیاد باشد.

در همین رابطه (1 ، 2

DictionaryVsList.zip