مطالب
آموزش فریم ورک Vuetify قسمت اول - نصب و بررسی ساختار grid؛ بخش اول
برای طراحی ui برنامه‌هایی که با فریم ورک vue.js توسعه داده می‌شوند، داشتن یک css framework مناسب، جهت زیبا سازی برنامه، یکی از انتخابهای مهم می‌باشد. در این سری از آموزش‌ها، نحوه‌ی بکارگیری فریم ورک Vuetify را یاد خواهیم گرفت.

چرا vuetify ؟

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


روش نصب vuetify

پس از ایجاد یک برنامه Vuejs  با دستور زیر، فریم ورک Vuetify را اضافه می‌کنیم. 
vue add vuetify


ساختار grid

grid  برای طرح بندی، یا بخش بندی محتوای برنامه استفاده می‌شود .vuetify همانند bootstrap، از سیستم بخش بندی 12 تایی برای تقسیم بندی صفحه استفاده می‌کند. در این روش ما به 5 حالت مختلف می‌توانیم صفحه را بخش بندی کنیم:


طریقه‌ی استفاده

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

ساختار برنامه ما باید دارای یک v-container باشد تا به درستی کار کند. این کامپوننت در بر گیرنده‌ی تمام صفحه‌ی برنامه است. هر کامپوننت می‌تواند تنظیمات خاص خود را داشته باشد.

برای مثال در کد پایین، تنظیم fluid باعث می‌شود تا کامپوننت v-container تمام عرض صفحه‌ی ما را در بر بگیرد.

<v-container fluid></v-container>

کامپوننت v-layout برای کار با ردیفها مورد استفاده قرار میگیرد که تنظیمات مخصوص به خود را دارد.

برای بخش بندی هر ستون در صفحه می‌توان از کامپوننت v-flex استفاده کرد.

نکته: در توضیح کد پایین، در قسمت تعریف v-layout به وسیله row مشخص میکنیم که می‌خواهیم یک ردیف را ایجاد کنیم و در قسمت تعریف v-flex به وسیله md6 مشخص می‌کنیم که 6 خانه از 12 خانه موجود در ردیف را می‌خواهیم در اختیار داشته باشیم:

<v-container>
  <v-layout row>
    <v-flex md6>
      ...
    </v-flex>
    <v-flex md6>
      ...
    </v-flex>
  </v-layout>
</v-container>


نتیجه‌ی قطعه کد بالا بدین ترتیب است:

Two elements in row

هر کدام از کامپوننت‌های گفته شده، properties مختص به خود را دارد که در برخی موارد مشابه یکدیگر هستند. این properties مشابه flexbox در css عمل میکنند.
نکته: تمامی این property‌ها از نوع داده boolean بوده و مقدار اولیه آنها false می‌باشد. 

breakpoint‌ها : 
به وسیله breakpoint‌ها می‌توانیم مشخص کنیم که هر المان درون هر مدیا (موبایل، تبلت، کامپیوتر و ...) به چه صورتی نمایش داده شود. در حالت کلی پنج نوع breakpoint وجود دارند که به ترتیب شامل:
xs جهت نمایش درون دستگاه‌هایی است که اندازه‌ی صفحه‌ی آنها از 600 پیکسل کمتر باشد.
sm جهت نمایش درون دستگاه‌هایی است که اندازه‌ی آنها از 600 پیکسل بزرگتر و از 960 پیکسل کوچکتر باشد.
md جهت نمایش درون دستگاه‌هایی است که اندازه‌ی آنها از 960 پیکسل بزرگتر و از 1264 پیکسل کوچکتر باشد.
lg جهت نمایش درون دستگاه‌هایی است که اندازه‌ی آنها از 1264 پیکسل بزرگتر و از 1904 پیکسل کوچکتر باشد.
xl جهت نمایش درون دستگاه‌هایی است که اندازه‌ی آنها بیش از 1904 پیکسل باشد.

هر یک از این breakpointها می‌توانند به وسیله‌ی کامپوننت v-flex، حاوی مقداری بین 1 تا 12 باشند تا درون هر مدیا به صورت مشخصی نمایش داده شوند.
نکته: ما میتوانیم برای هر کدام از این مدیا‌ها به صورت جداگانه این تنظیمات را اعمال کنیم.
نکته: اگر برای کوچکترین حالت، یعنی xs، یک مقدار را مشخص کنیم و برای حالت‌های دیگر مقداری را تنظیم نکنیم، به این معنا است که حالتهای دیگر نیز با مقداری که برای xs تنظیم کرده‌ایم، تنظیم می‌شوند.

برای مثال: در این قطعه کد ما برای xs، مقدار 6 و برای sm، مقدار 5 را در نظر گرفته‌ایم. یعنی در دستگاه‌های Extra small، این پارامتر تعداد 6 خانه از 12 خانه در دسترس را اشغال میکند؛ اما درون دستگاه‌های small، تعداد 5 خانه از 12 خانه را اشغال می‌نماید. باید بدانیم به صورت خودکار برای md  وlg ،xl نیز مقدار 5 در نظر گرفته می‌شود. 
<v-flex xs6 sm5>
</v-flex>

به این مثال توجه کنید: در این مثال ما برای مدیاهای xs، مقدار 6 و برای مدیاهای md، مقدار 8 را در نظر گرفته‌ایم. نکته قابل توجه اینجاست که در چنین حالتی برای مدیهای sm، مقدار 6 به صورت خودکار در نظر گرفته میشود و برای مدیاهای lg و xl نیز به صورت خودکار مقدار 8 در نظر گرفته می‌شوند.
<v-flex xs5 md8>
</v-flex>

Offset
به وسیله‌ی offset می‌توانیم مشخص کنیم که هر کامپوننت v-flex از چه موقعیتی در v-layout شروع به گرفتن فضا کند. در پایین یک مثال کاربردی آورده شده‌است تا به صورت بهتر کارکرد offset‌ها را درک کنیم. در توضیح کد پایین در خط پنجم، مقدار offset-xs1 در نظر گرفته شده است. بدین معنا که با توجه به بخش بندی 12 تایی که داریم، کلاس offset-xs1 باعث می‌شود برای گرفتن  فضای تعریف شده توسط xs10، ستون اول را نادیده گرفته و از ستون دوم فضا را اشغال کند. همانطور که قبل‌تر نیز گفته شد، به دلیل اینکه برای xs مقدار 10 تنظیم شده‌است، درون تمامی مدیاها v-flex، تعداد 10 خانه از 12 خانه در دسترس را اشغال می‌کند.
<div id="app">
  <v-app id="inspire">
    <v-container grid-list-xl text-xs-center>
      <v-layout row wrap>
        <v-flex xs10 offset-xs1>
          <v-card dark color="purple">
            <v-card-text>xs10 offset-xs1</v-card-text>
          </v-card>
        </v-flex>
        <v-flex xs7 offset-xs5 offset-md2 offset-lg5>
          <v-card dark color="secondary">
            <v-card-text>xs7 offset-(xs5 | md2 | lg5)</v-card-text>
          </v-card>
        </v-flex>
        <v-flex xs12 sm5 md3>
          <v-card dark color="primary">
            <v-card-text>(xs12 | sm5 | md3)</v-card-text>
          </v-card>
        </v-flex>
        <v-flex xs12 sm5 md5 offset-xs0 offset-lg2>
          <v-card dark color="green">
            <v-card-text>(xs12 | sm5 | md5) offset-(xs0 | lg2)</v-card-text>
          </v-card>
        </v-flex>
      </v-layout>
    </v-container>
  </v-app>
</div>

نتیجه‌ی قطعه کد بالا بدین صورت است:


مطالب
DbContext pooling در EF Core 2.0
روش متداول تنظیمات EF Core در برنامه‌های ASP.NET Core، به صورت معرفی یک DbContext سفارشی، به سیستم تزریق وابستگی‌های آن است و سپس می‌توان به وهله‌ای از این Context، توسط تزریق آن به سازنده‌های کلاس‌های مختلف برنامه، دسترسی یافت. به این معنا که به ازای هر درخواست رسیده، یک وهله‌ی جدید از DbContext ایجاد خواهد شد. در نگارش 2، روش جدیدی برای ثبت DbContext برنامه معرفی شده‌است که در صورت بکارگیری آن، بجای وهله سازی مجدد Contextها، ابتدا استخر موجود Contextها بررسی می‌شود و در صورت مهیا بودن نمونه‌ای، بجای نمونه سازی از صفر آن، از این نمونه‌ی موجود، استفاده‌ی مجدد خواهد شد. در پایان کار درخواست، تنها وضعیت این Context به حالت اولیه برگردانده شده و سپس به استخر Contextها برای استفاده‌ی مجدد بازگشت داده می‌شود. این مفهوم درحقیقت پیاده سازی مفهوم connection pooling موجود در ADO.NET است. به این ترتیب هزینه‌ی ساخت و ایجاد اتصالات به بانک اطلاعاتی به شدت کاهش خواهد یافت.


نحوه‌ی معرفی DbContext pooling

اینبار بجای روش قبلی و استفاده از متد AddDbContext
services.AddDbContext<BloggingContext>(
   options => options.UseSqlServer(connectionString));
از متد جدید AddDbContextPool استفاده می‌شود:
services.AddDbContextPool<BloggingContext>(
   options => options.UseSqlServer(connectionString));


محدودیت‌های روش DbContext pooling

در حالت استفاده‌ی از روش AddDbContextPool، دیگر متد OnConfiguring کلاس Context سفارشی شما فراخوانی نخواهد شد. بنابراین تمام تنظیمات ابتدایی برنامه را باید به همان کلاس آغازین برنامه منتقل کنید و کلاس Context، این تنظیمات را به صورت ذیل از طریق سازنده‌ی آن دریافت می‌کند:
public class BloggingContext : DbContext
{
   public BloggingContext(DbContextOptions<BloggingContext> options) : base(options){}

همچنین باید درنظر داشت که استفاده‌ی مجدد از یک Context به معنای حفظ مقادیر فیلدهای private کلاس Context سفارشی شما نیز می‌شود. در اینجا پس از پایان هر درخواست، تنها وضعیت Context از دیدگاه EF به حالت اول بازگشت داده می‌شود؛ اما حالت شیء Context و تمام اطلاعات فیلدهای خصوصی آن در همان حالت قبلی (و همان وهله‌ی موجود پیشین و اصلی) رها می‌شوند. چون وهله سازی مجددی از آن صورت نخواهد گرفت.


یک مثال: بررسی بهبود کارآیی برنامه در حالت استفاده‌ی از DbContext pooling

کدهای کامل این مثال را برای اجرا می‌توانید از اینجا دریافت کنید: ContextPooling.zip

در اینجا یکبار حالت متداول AddDbContext
        public static void RunWithoutContextPooling()
        {
            Console.WriteLine("\nRun Without ContextPooling");
            var serviceProvider = new ServiceCollection()
                .AddEntityFrameworkSqlServer()
                .AddDbContext<BloggingContext>(
                    c => c.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=Demo.ContextPooling;Trusted_Connection=True;ConnectRetryCount=0;"))
                .BuildServiceProvider();

            new RunTests().Start(serviceProvider);
        }
و سپس روش جدید AddDbContextPool
        public static void RunWithContextPooling()
        {
            Console.WriteLine("\nRun With ContextPooling");
            var serviceProvider = new ServiceCollection()
                .AddEntityFrameworkSqlServer()
                .AddDbContextPool<BloggingContext>(
                    c => c.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=Demo.ContextPooling;Trusted_Connection=True;ConnectRetryCount=0;"),
                    poolSize: 16)
                .BuildServiceProvider();

            new RunTests().Start(serviceProvider);
        }
بررسی و اجرا شده‌اند. نتیجه‌ی نهایی به صورت ذیل است:
Run Without ContextPooling
[10:49:30.728] Context creations: 637 | Requests per second: 597
[10:49:31.746] Context creations: 1069 | Requests per second: 1050
[10:49:32.765] Context creations: 1088 | Requests per second: 1067
[10:49:33.784] Context creations: 1139 | Requests per second: 1119
[10:49:34.802] Context creations: 1138 | Requests per second: 1117
[10:49:35.831] Context creations: 1153 | Requests per second: 1120
[10:49:36.845] Context creations: 1126 | Requests per second: 1111
[10:49:37.873] Context creations: 1014 | Requests per second: 987
[10:49:38.898] Context creations: 1139 | Requests per second: 1111
[10:49:39.918] Context creations: 1086 | Requests per second: 1065

Total context creations: 10592
Requests per second:     1034

Run With ContextPooling
[10:49:40.982] Context creations: 32 | Requests per second: 1388
[10:49:41.991] Context creations: 0 | Requests per second: 1691
[10:49:43.014] Context creations: 0 | Requests per second: 1684
[10:49:44.031] Context creations: 0 | Requests per second: 1702
[10:49:45.049] Context creations: 0 | Requests per second: 1694
[10:49:46.067] Context creations: 0 | Requests per second: 1401
[10:49:47.075] Context creations: 0 | Requests per second: 1510
[10:49:48.107] Context creations: 0 | Requests per second: 1669
[10:49:49.127] Context creations: 0 | Requests per second: 1679
[10:49:50.147] Context creations: 0 | Requests per second: 1688

Total context creations: 32
Requests per second:     1610
همانطور که ملاحظه می‌کنید، در حالت ContextPooling، تعداد وهله سازی‌های صورت گرفته به شدت کاهش یافته‌است و همچنین قابلیت پاسخ‌دهی برنامه به علت کاهش سربار اتصال به بانک اطلاعاتی نیز حدود 55 درصد بهبود یافته‌است.
مطالب
ایجاد یک HtmlHelper سفارشی با پشتیبانی از UnobtrusiveValidationAttributes
همانطور که می‌دانید، در MVC برای اعتبارسنجی داده‌ها در سمت کلاینت از کتابخانه‌ی jquery استفاده می‌شود. مایکروسافت از طریق jquery.validate.unobtrusive و گسترش کتابخانه‌ی jquery.validate توانسته منطق خود را برای اعتبارسنجی داده‌ها در سمت کلاینت پیاده سازی کند. 
برای این منظور MVC به کنترلهایی که باید اعتبارسنجی شوند، خصوصیاتی را از طریق Data Attribute اضافه می‌کند. برای مثال اگر در مدل خود فیلد ایمیل را به شکل زیر امضاء کرده باشید:
[Display(Name = "رایانامه")]
[Required(AllowEmptyStrings = false, ErrorMessage = "رایانامه خود را وارد کنید.")]
[RegularExpression("\\w+([-+.']\\w+)*@\\w+([-.]\\w+)*\\.\\w+([-.]\\w+)*", ErrorMessage = "نشانی رایانامه پذیرفتنی نمی‌باشد.")]
[ExistField(Action = "EmailExist", Namespace = "Parsnet.Controllers", Controller = "Account", ErrorMessage = "این رایانامه پیشتر به کار گرفته شده است.")]
        public string Email { get; set; }
و در View مورد نظر از Htmlhlper مربوطه به شکل زیر استفاده کرده باشید:
@Html.TextBoxFor(m => m.Email, new { @class = "form-control en", placeholder = @Html.DisplayNameFor(m => m.Email) })
در نهایت، Html خروجی در سمت کلاینت به شکل زیر خواهد بود:
<input data-val="true" data-val-existfiledvalidator="این رایانامه پیشتر به کار گرفته شده است." data-val-existfiledvalidator-url="/account/emailexist" data-val-regex="نشانی رایانامه پذیرفتنی نمی‌باشد." data-val-regex-pattern="\w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*" data-val-required="رایانامه خود را وارد کنید." id="Email" name="Email" placeholder="رایانامه" value="" type="text">
و در اینجا کتابخانه‌ی اعتبارسنجی MVC با استفاده از همین خصوصیات *-data، اطلاعات مورد نیاز را جهت نمایش، اعتبارسنجی، تنظیم و بکارگیری، مورد استفاده قرار می‌دهد.
در یکی از پروژه‌هایی که در حال کار کردن بر روی آن هستم لازم شد تا این اطلاعات اعتبارسنجی به یک تگ span اعمال شوند. سناریوی مورد نظر به این صورت است که در بخش پروفایل کاربر، کاربر می‌تواند اطلاعات خود را بصورت inline ویرایش کنید. برای اینکار از کتابخانه X-editable استفاده کردم که از این لینک قابل دریافت است.
ابتدا اطلاعات موردنیاز در یک تگ span نمایش داده می‌شوند و در ادامه کاربر پس از کلیک بر روی آیکن ویرایش، امکان تغییر آن فیلد را دارد. برای اعتبارسنجی داده‌ها لازم بود تا تمامی اطلاعات مورد نیاز اعتبارسنجی در سمت کلاینت را به شکلی در اختیار داشته باشم و به ذهنم رسید تا با ایجاد یک Helper سفارشی، خصوصیات موردنظر را به تگ span اعمال کنم و سپس در سمت کلاینت از آن استفاده کنم. در واقع با اینکار با استفاده از همان کلاس مدل و این Helper سفارشی، از وارد کردن دستی داده‌ها و خصوصیات اجتناب کنم. (تصور کنید چیزی حدود 30 فیلد که هرکدام حداقل 4 خصوصیت دارند)
با نگاهی به سورس MVC دیدم پیاده سازی این قابلیت چندان سخت نیست و به راحتی با ایجاد یک Helper سفارشی، منطق خود را پیاده سازی و اعتبارسنجی در سمت کلاینت را به راحتی اعمال کردم.
برای ایجاد این Helper سفارشی ابتدا یک کلاس استاتیک ایجاد کنید و با استفاده از extension Method‌ها یک helper جدید را ایجاد کنید:
namespace Parsnet
{
     public static MvcHtmlString SpanFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, object htmlAttributes)
        {
            
            var sb = new StringBuilder();

            var span = new TagBuilder("span");

            var metadata = ModelMetadata.FromLambdaExpression<TModel, TProperty>(expression, htmlHelper.ViewData);
            var name = ExpressionHelper.GetExpressionText(expression);
            var fullName = htmlHelper.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(name);
            var value = "";

            if (metadata.Model != null && metadata.Model.GetType() == typeof(List<IdentityProvider.IdentityRole>))
            {
                var modelList = (List<IdentityProvider.IdentityRole>)metadata.Model;
                value = String.Join("، ", modelList.Select(r => r.Name));
            }
            else
            {
                value = htmlHelper.FormatValue(metadata.Model, null);
            }

            span.MergeAttributes<string, object>(((IDictionary<string, object>)HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes)));

            var fieldName = fullName.Split('.')[1];
            span.MergeAttribute("data-name", fieldName, true);
            span.MergeAttributes<string, object>(htmlHelper.GetUnobtrusiveValidationAttributes(name, metadata));

            sb.Append(span.ToString(TagRenderMode.StartTag));
            sb.Append(value);
            sb.Append(span.ToString(TagRenderMode.EndTag));

            return new MvcHtmlString(sb.ToString());
        }
    }
}
ما در این helper سفارشی از عبارت‌های لامبدا استفاده می‌کنیم و با استفاده از این عبارات، فیلد مورد نظر مدل خود را به helper معرفی می‌کنیم. آرگومان htmlAttributes در متد helper نیز برای دریافت خصوصیات اضافی helper است؛ خصوصیاتی مانند class، id, style و غیره.
با استفاده از کلاس TagBuilder تگ مورد نظر خود را ایجاد می‌کنیم. در اینجا من تگ span را ایجاد کرده‌ام که شما می‌توانید هر تگ دلخواه دیگری را نیز ایجاد کنید. اولین مرحله، استخراج اطلاعات موردنیاز از metadata مدل است که در خط زیر با پردازش عبارت لامبدا اینکار صورت می‌گیرد:
var metadata = ModelMetadata.FromLambdaExpression<TModel, TProperty>(expression, htmlHelper.ViewData);
سپس نام فیلد مورد نظر را از مدل استخراج می‌کنیم:
var name = ExpressionHelper.GetExpressionText(expression);
var fullName = htmlHelper.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(name);
کدهای فوق نام فیلد جاری (در اینجا Email) را از MetaData برای ما استخراج می‌کند. متغیر value برای نگهداری مقدار این فیلد از مدل است. مرحله بعد استخراج مقدار فیلد و انتساب آن به متغیر value است.
در سناریوی من کاربر می‌تواند زمینه‌ی فعالیت خود را انتخاب کند که به صورت IdentityRole پیاده سازی شده است. من در اینجا چک می‌کنیم که اگر نوع داده‌ای این فیلد List<IdentityProvider.IdentityRole> بود زمینه فعالیت کاربر را از طریق "،" از هم جدا کرده و به صورت یک رشته تبدیل می‌کنم. در غیر اینصورت همان مقدار عادی فیلد را بکار می‌گیرم.
if (metadata.Model != null && metadata.Model.GetType() == typeof(List<IdentityProvider.IdentityRole>))
            {
                var modelList = (List<IdentityProvider.IdentityRole>)metadata.Model;
                value = String.Join("، ", modelList.Select(r => r.Name));
            }
            else
            {
                value = htmlHelper.FormatValue(metadata.Model, null);
            }
سپس خصوصیات سفارشی خود را که بصورت attribute‌های HTML هستند، در خط زیر به تگ سفارشی اعمال می‌شوند:
span.MergeAttributes<string, object>(((IDictionary<string, object>)HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes)));
مهمترین مرحله که در واقع هدف اصلی من بود، استخراج خصوصیت‌های *-data برای اعتبارسجی است که در خط زیر اینکار صورت گرفته است:
 span.MergeAttributes<string, object>(htmlHelper.GetUnobtrusiveValidationAttributes(name, metadata));
نحوه‌ی استفاده از این helper سفارشی هم خیلی ساده است:
@Html.SpanFor(m => m.Profile.Email, new { @class = "editor", data_type = "text" })
و در نهایت HTML خروجی به شکل زیر است:
<span class="editor" data-name="Email" data-type="text" data-val="true" data-val-existfiledvalidator="این رایانامه پیشتر به کار گرفته شده است." data-val-existfiledvalidator-url="/account/emailexist" data-val-regex="نشانی رایانامه پذیرفتنی نمی‌باشد." data-val-regex-pattern="\w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*" data-val-required="رایانامه خود را وارد کنید.">alireza_s_84@yahoo.com</span>
دیدن شکل‌های زیر خالی از لطف نیستند:

و پس از ویرایش:


البته برای درک بهتر این موضوع سعی خواهم کرد تا با یک مثال عملی کامل، نحوه‌ی پیاده سازی را در همینجا قرار دهم.
مطالب
روش‌هایی برای بهبود سرعت برنامه‌های مبتنی بر Entity framework
در این مطلب تعدادی از شایع‌ترین مشکلات حین کار با Entity framework که نهایتا به تولید برنامه‌هایی کند منجر می‌شوند، بررسی خواهند شد.

مدل مورد بررسی

    public class User
    {
        public int Id { get; set; }
        public string Name { get; set; }

        public virtual ICollection<BlogPost> BlogPosts { get; set; }
    }

    public class BlogPost
    {
        public int Id { get; set; }
        public string Title { get; set; }
        public string Content { get; set; }

        [ForeignKey("UserId")]
        public virtual User User { get; set; }
        public int UserId { get; set; }
    }
کوئری‌هایی که در ادامه بررسی خواهند شد، بر روی رابطه‌ی one-to-many فوق تعریف شده‌اند؛ یک کاربر به همراه تعدادی مطلب منتشر شده.


مشکل 1: بارگذاری تعداد زیادی ردیف
 var data = context.BlogPosts.ToList();
در بسیاری از اوقات، در برنامه‌های خود تنها نیاز به مشاهده‌ی قسمت خاصی از یک سری از اطلاعات، وجود دارند. به همین جهت بکارگیری متد ToList بدون محدود سازی تعداد ردیف‌های بازگشت داده شده، سبب بالا رفتن مصرف حافظه‌ی سرور و همچنین بالا رفتن میزان داده‌ای که هر بار باید بین سرور و کلاینت منتقل شوند، خواهد شد. یک چنین برنامه‌هایی بسیار مستعد به استثناهایی از نوع out of memory هستند.
راه حل:  با استفاده از Skip و Take، مباحث صفحه‌ی بندی را اعمال کنید.


مشکل 2: بازگرداندن تعداد زیادی ستون
 var data = context.BlogPosts.ToList();
فرض کنید View برنامه، در حال نمایش عناوین مطالب ارسالی است. کوئری فوق، علاوه بر عناوین، شامل تمام خواص تعریف شده‌ی دیگر نیز هست. یک چنین کوئری‌هایی نیز هربار سبب هدر رفتن منابع سرور می‌شوند.
راه حل: اگر تنها نیاز به خاصیت Content است، از Select و سپس ToList استفاده کنید؛ البته به همراه نکته 1.
 var list = context.BlogPosts.Select(x => x.Content).Skip(15).Take(15).ToList();


مشکل 3: گزارشگیری‌هایی که بی‌شباهت به حمله‌ی به دیتابیس نیستند
 foreach (var post in context.BlogPosts)
{
     Console.WriteLine(post.User.Name);
}
فرض کنید قرار است رکوردهای مطالب را نمایش دهید. در حین نمایش این مطالب، در قسمتی از آن باید نام نویسنده نیز درج شود. با توجه به رابطه‌ی تعریف شده، نوشتن post.User.Name به ازای هر مطلب، بسیار ساده به نظر می‌رسد و بدون مشکل هم کار می‌کند. اما ... اگر خروجی SQL این گزارش را مشاهده کنیم، به ازای هر ردیف نمایش داده شده، یکبار رفت و برگشت به بانک اطلاعاتی، جهت دریافت نام نویسنده یک مطلب وجود دارد.
این مورد به lazy loading مشهور است و در مواردی که قرار است با یک مطلب و یک نویسنده کار شود، شاید اهمیتی نداشته باشد. اما در حین نمایش لیستی از اطلاعات، بی‌شباهت به یک حمله‌ی شدید به بانک اطلاعاتی نیست.
راه حل: در گزارشگیری‌ها اگر نیاز به نمایش اطلاعات روابط یک موجودیت وجود دارد، از متد Include استفاده کنید تا Lazy loading لغو شود.
 foreach (var post in context.BlogPosts.Include(x=>x.User))


مشکل 4:  فعال بودن بی‌جهت مباحث ردیابی اطلاعات
 var data = context.BlogPosts.ToList();
در اینجا ما فقط قصد داریم که لیستی از اطلاعات را دریافت و سپس نمایش دهیم. در این بین، هدف، ویرایش یا حذف اطلاعات این لیست نیست. یک چنین کوئری‌هایی مساوی هستند با تشکیل dynamic proxies مخصوص EF جهت ردیابی تغییرات اطلاعات (مباحث AOP توکار). EF توسط این dynamic proxies، محصور کننده‌هایی را برای تک تک آیتم‌های بازگشت داده شده از لیست تهیه می‌کند. در این حالت اگر خاصیتی را تغییر دهید، ابتدا وارد این محصور کننده (غشاء نامرئی) می‌شود، در سیستم ردیابی EF ذخیره شده و سپس به شیء اصلی اعمال می‌گردد. به عبارتی شیء در حال استفاده، هر چند به ظاهر post.User است اما در واقعیت یک User دارای روکشی نامرئی از جنس dynamic proxy‌های EF است. تهیه این روکش‌ها، هزینه‌بر هستند؛ چه از لحاظ میزان مصرف حافظه و چه از نظر سرعت کار.
راه حل: در گزاشگیری‌ها، dynamic proxies را توسط متد AsNoTracking غیرفعال کنید:
 var data = context.BlogPosts.AsNoTracking().Skip(15).Take(15).ToList();


مشکل 5: باز کردن  تعداد اتصالات زیاد به بانک اطلاعاتی در طول یک درخواست

هر Context دارای اتصال منحصربفرد خود به بانک اطلاعاتی است. اگر در طول یک درخواست، بیش از یک Context مورد استفاده قرار گیرد، بدیهی است به همین تعداد اتصال باز شده به بانک اطلاعاتی، خواهیم داشت. نتیجه‌ی آن فشار بیشتر بر بانک اطلاعاتی و همچنین کاهش سرعت برنامه است؛ از این لحاظ که اتصالات TCP برقرار شده، هزینه‌ی بالایی را به همراه دارند.
روش تشخیص:
        private void problem5MoreThan1ConnectionPerRequest() 
        {
            using (var context = new MyContext())
            {
                var count = context.BlogPosts.ToList();
            }
        }
داشتن متدهایی که در آن‌ها کار وهله سازی و dispose زمینه‌ی EF انجام می‌شود (متدهایی که در آن‌ها new Context وجود دارد).
راه حل: برای حل این مساله باید از روش‌های تزریق وابستگی‌ها استفاده کرد. یک Context وهله سازی شده‌ی در طول عمر یک درخواست، باید بین وهله‌های مختلف اشیایی که نیاز به Context دارند، زنده نگه داشته شده و به اشتراک گذاشته شود.


مشکل 6: فرق است بین IList و IEnumerable
DataContext = from user in context.Users
                      where user.Id>10
                      select user;
خروجی کوئری LINQ نوشته شده از نوع IEnumerable است. در EF، هربار مراجعه‌ی مجدد به یک کوئری که خروجی IEnumerable دارد، مساوی است با ارزیابی مجدد آن کوئری. به عبارتی، یکبار دیگر این کوئری بر روی بانک اطلاعاتی اجرا خواهد شد و رفت و برگشت مجددی صورت می‌گیرد.
زمانیکه در حال تهیه‌ی گزارشی هستید، ابزارهای گزارشگیر ممکن است چندین بار از نتیجه‌ی کوئری شما در حین تهیه‌ی گزارش استفاده کنند. بنابراین برخلاف تصور، data binding انجام شده، تنها یکبار سبب اجرای این کوئری نمی‌شود؛ بسته به ساز و کار درونی گزارشگیر، چندین بار ممکن است این کوئری فراخوانی شود.
راه حل: یک ToList را به انتهای این کوئری اضافه کنید. به این ترتیب از نتیجه‌ی کوئری، بجای اصل کوئری استفاده خواهد شد و در این حالت تنها یکبار رفت و برگشت به بانک اطلاعاتی را شاهد خواهید بود.


مشکل 7: فرق است بین IQueryable و IEnumerable

خروجی IEnumerable، یعنی این عبارت را محاسبه کن. خروجی IQueryable یعنی این عبارت را درنظر داشته باش. اگر نیاز است نتایج کوئری‌ها با هم ترکیب شوند، مثلا بر اساس رابط کاربری برنامه، کاربر بتواند شرط‌های مختلف را با هم ترکیب کند، باید از ترکیب IQueryableها استفاده کرد تا سبب رفت و برگشت اضافی به بانک اطلاعاتی نشویم.


مشکل 8: استفاده از کوئری‌های Like دار
 var list = context.BlogPosts.Where(x => x.Content.Contains("test"))
این نوع کوئری‌ها که در نهایت به Like در SQL ترجمه می‌شوند، سبب full table scan خواهند شد که کارآیی بسیار پایینی دارند. در این نوع موارد توصیه شده‌است که از روش‌های full text search استفاده کنید.


مشکل 9: استفاده از Count بجای Any

اگر نیاز است بررسی کنید مجموعه‌ای دارای مقداری است یا خیر، از Count>0 استفاده نکنید. کارآیی Any و کوئری SQL ایی که تولید می‌کند، به مراتب بیشتر و بهینه‌تر است از Count>0.


مشکل 10: سرعت insert پایین است

ردیابی تغییرات را خاموش کرده و از متد جدید AddRange استفاده کنید. همچنین افزونه‌هایی برای Bulk insert نیز موجود هستند.


مشکل 11: شروع برنامه کند است

می‌توان تمام مباحث نگاشت‌های پویای کلاس‌های برنامه به جداول و روابط بانک اطلاعاتی را به صورت کامپایل شده در برنامه ذخیره کرد. این مورد سبب بالا رفتن سرعت شروع برنامه خصوصا در حالتیکه تعداد جداول بالا است می‌شود.
مطالب
رده‌ها و انواع مختلف بانک‌های اطلاعاتی NoSQL
4 رده و گروه عمده بانک‌های اطلاعاتی NoSQL وجود دارند؛ شامل:
الف) Key-Value stores که پایه بانک‌های اطلاعاتی NoSQL را تشکیل داده و اهدافی عمومی را دنبال می‌کنند.
ب) Wide column stores که در شرکت‌های بزرگ اینترنتی بیشتر مورد استفاده قرار گرفته‌اند.
ج) Document stores یا بانک‌های اطلاعاتی NoSQL سندگرا.
د) Graph databases که بیشتر برای ردیابی ارتباطات بین موجودیت‌ها بکار می‌روند.

و در تمام این گروه‌ها، مکانیزم‌های Key-Value به شدت مورد استفاده‌اند.


الف) Key-Value stores
Key-Value stores یکی از عمومی‌ترین و پایه‌ای‌ترین گروه‌های بانک‌های اطلاعاتی NoSQL را تشکیل می‌دهند. البته این مورد بدین معنا نیست که این رده، جزو محبوب‌ترین‌ها نیز به‌شمار می‌روند.


این نوع بانک‌های اطلاعاتی شامل جداولی از اطلاعات هستند. هر جدول نیز شامل تعدادی ردیف است؛ چیزی همانند بانک‌های اطلاعاتی رابطه‌ای. اما در هر ردیف، یک Dictionary یا آرایه‌ای از اطلاعات key-value شکل را شاهد خواهید بود. در اینجا ساختار و اسکیمای ردیف‌ها می‌توانند نسبت به یکدیگر کاملا متفاوت باشند (دید لیبرال نسبت به اسکیما، که در قسمت قبل به آن پرداخته شد). در این بین، تنها تضمین خواهد شد که هر ردیف، Id منحصربفردی دارد.
از این نوع بانک‌های اطلاعاتی، در سکوهای کاری ابری زیاد استفاده می‌شود. دو مثال مهم در اینباره شامل Amazon SimpleDB و Azure Table Storage هستند.
سایر نمونه‌های مهم دیگری از بانک‌های اطلاعاتی NoSQL که بر مبنای مفهوم Key-Value stores کار می‌کنند، عبارتند از MemcacheDB و Voldemort. به علاوه در Amazon web services بانک اطلاعاتی دیگری به نام DynamoDB به عنوان یک سرویس عمومی در دسترس است. همچنین Dynomite نیز به عنوان نمونه سورس باز Dynamo مطرح است.
Redis و Riak نیز جزو بانک‌های اطلاعاتی Key-Value store بسیار معروف به‌شمار می‌روند.

همانطور که در تصویر فوق ملاحظه می‌کنید، Key-Value stores دارای بانک‌های اطلاعاتی شامل جداول مختلف هستند. در اینجا همچنین ساختار ردیف‌هایی از اطلاعات این جداول نیز مشخص شده‌اند. هر ردیف، یک کلید دارد به همراه تعدادی جفت کلید-مقدار. در این جداول، اسکیما ثابت نگه داشته شده است و از ردیفی به ردیف دیگر متفاوت نیست؛ اما این مساله اختیاری است. برای مثال می‌توان در ردیف اطلاعات یک مشتری خاص، کلید-مقدارهایی خاص او را نیز درج کرد که لزوما در سایر ردیف‌ها، نیازی به وجود آن‌ها نیست.
به علاوه باید به خاطر داشت که هرچند به ظاهر last_orderها به شماره Id سفارشات مرتبط هستند، اما مفاهیمی مانند کلیدهای خارجی بانک‌های اطلاعاتی رابطه‌ای، در اینجا وجود خارجی ندارند. بیشتر در اینجا هدف سهولت جستجوی اطلاعات است.


ب) Wide column stores
Wide column stores دارای جداولی است که درون آن‌ها ستون‌هایی قابل تعریف است. درون این ستون‌ها که یادآور بانک‌های اطلاعاتی رابطه‌ای هستند، اطلاعات به شکل key-value با ساختاری متفاوت، قابل ذخیره سازی هستند. در اینجا هر ستون، می‌تواند شامل گروهی از ستون‌ها که بر اساس مفاهیم جفت‌های key-value کار می‌کنند، باشد.
این نوع بانک‌های اطلاعاتی عموما در سایت‌های اینترنتی بسیار بزرگ و برنامه‌های «Big data» استفاده می‌شوند. برای مثال:


- BigTable گوگل که یک محصول اختصاصی و غیرعمومی است؛ اما جزئیات آن را به عنوان مقالات علمی منتشر کرده است.
- دنیای سورس باز به رهبری Yahoo، نمونه سورس باز BigTable را به نام Hbase ارائه داده است.
- در فیس بوک، از بانک اطلاعاتی دیگری به نام Cassandra استفاده می‌کنند. در اینجا به گروهی از ستون‌ها super columns و جداول super column families گفته می‌شود.

در اینجا نیز جداول و ردیف‌ها وجود دارند و هر ستون باید عضوی از خانواده یک super column باشد. ساختار ردیف‌ها در این تصویر یکسان درنظر گرفته شده‌اند، اما اگر نیاز بود، برای مثال می‌توان در ردیفی خاص، ساختار را تغییر داد و مثلا middle name را نیز بر اساس نیاز، به ردیفی اضافه کرد.


ج) Document stores
Document stores بجای جداول، دارای بانک‌های اطلاعاتی مختلفی هستند و در اینجا بجای ردیف‌ها، سند یا document دارند. ساختار سندها نیز عموما بر مبنای اشیاء JSON تعریف می‌گردد (که البته این مورد الزامی نبوده و از هر محصول، به محصول دیگری ممکن است متفاوت باشد؛ اما عمومیت دارد). بنابراین هر سند دارای تعدادی خاصیت است (چون اشیاء JSON به این نحو تعریف می‌گردند) که دارای مقدار هستند. در نگاه اول، شاید این نوع اسناد، بسیار شبیه به key-value stores به نظر برسند. اما در حین تعریف اشیاء JSON، یک مقدار می‌تواند خود یک شیء کامل دیگر باشد و نه صرفا یک مقدار ساده. به همین جهت عده‌ای به این نوع بانک‌های اطلاعاتی، بانک‌های اطلاعاتی Key-value store سفارشی و خاص نیز می‌گویند.
این نوع ساختار منعطف، برای ذخیره سازی اطلاعات اشیاء تو در تو و درختی بسیار مناسب است. همچنین این اسناد می‌توانند حاوی پیوست‌هایی نیز باشد؛ مانند پیوست یک فایل به یک سند.
در Document stores، نگارش‌های قدیمی اسناد نیز نگهداری می‌گردند. به همین جهت این نوع بانک‌های اطلاعاتی برای ایجاد برنامه‌های مدیریت محتوا نیز بسیار مطلوب می‌باشند.
با توجه به مزایایی که برای این رده از بانک‌های اطلاعاتی NoSQL ذکر گردید، Document stores در بین برنامه نویس‌ها بسیار محبوب و پرکاربرد هستند.
از این دست بانک‌های اطلاعاتی NoSQL، می‌توان به CouchDB ، MongoDB و RavenDB اشاره کرد.
سایر مزایای Document stores که به پرکاربرد شدن آن‌ها کمک کرده‌اند به شرح زیر هستند:
- هر سند را می‌توان با یک URI آدرس دهی کرد.
- برای نمونه CouchDB از یک full REST interface برای دسترسی و کار با اسناد پشتیبانی می‌کند (چیزی شبیه به ASP.NET WEB API در دات نت). در اینجا با استفاده از یک وب سرور توکار و بکارگیری HTTP Verbs مانند Put، Delete، Get و غیره، امکان کار با اسناد وجود دارد.
- اغلب بانک‌های اطلاعاتی Document stores از JavaScript به عنوان native language خود بهره می‌برند (جهت سهولت کار با اشیاء JSON).


در اینجا دو دیتابیس، بجای دو جدول وجود دارند. همچنین در مقایسه با بانک‌های اطلاعاتی key-value، برای نمونه، مقدار خاصیت آدرس، خود یک شیء است که از دو خاصیت تشکیل شده است. به علاوه هر خاصیت Most_Recent یک Order، به سند دیگری در بانک اطلاعاتی Orders لینک شده است.


د) Graph databases
Graph databases نوع خاصی از بانک‌های اطلاعاتی NoSQL هستند که جهت ردیابی ارتباطات بین اطلاعات طراحی شده‌اند و برای برنامه‌های شبکه‌های اجتماعی بسیار مفید هستند.
در واژه نامه این بانک‌های اطلاعاتی Nodes و Edges (اتصال دهنده‌های نودها) تعریف شده‌اند. در اینجا نودها می‌توانند دارای خاصیت‌ها و مقادیر متناظر با آن‌ها باشند.
یکی از معروفترین Graph databases مورد استفاده، Neo4j نام دارد.


در اینجا یک شخص را که دارای رابطه آدرس با شیء آدرس ذکر شده است را مشاهده می‌کنید. همچنین این شخص دارای رابطه دوستی با سه شخص دیگر است.
مطالب
آشنایی با Catel MVVM Frameowork
در این مقاله به بررسی اولیه فریمورک Catel و برخی ویژگی‌های آن خواهیم پرداخت.
همانطور که می‌دانید فریمورک‌های متعددی برای MVVM به وجود آمده اند، مانند MVVM Light یا Caliburn و Chinch و ... که هر کدام از آن‌ها دارای ویژگی هایی می‌باشند اما Catel تنها یک فریمورک برای MVVM نیست بلکه دارای قسمت‌های دیگری مانند کنترل‌های اختصاصی و سرویس‌های متعدد و پرکاربرد و Extension‌‌های مفید و ... نیز می‌باشد که کار توسعه یک برنامه MVVM را فوق العاده لذتبخش می‌کند.
برای شروع کار با این فریمورک ابتدا بایستی قالب پروژه را از این آدرس دریافت نمایید. بعد از دریافت و نصب آن یک زیرگروه جدید به نام Catel به قسمت افزودن پروژه جدید اضافه خواهد شد که شامل قالب پروژه برای WPF و Silverlight و Windows Phone و Windows Store می باشد. در این قسمت گزینه WPF Application with Catel را انتخاب نمایید و پروژه را ایجاد کنید. بعد از ایجاد پروژه نوبت به نصب بسته های nuget مورد نیاز Catel می رسد. تنها بسته مورد نیاز Catel.Extensions.Controls می باشد که به صورت خودکار بسته های Catel.MVVM و Catel.Core را نیز نصب خواهد کرد. البته بسته‌های دیگری مانند Catel.Extensions.Prism, Catel.Extensions.FluentValidation و Catel.Extensions.Data و Catel.Fody و ... نیز برای این فریمورک وجود دارد که در این مطلب به آن‌ها نیازی نداریم.
اکنون ساختار اصلی پروژه ما ایجاد شده است. در این ساختار پوشه‌های Models ،Views و ViewModels به صورت پیش فرض وجود دارند. Catel برای برقراری ارتباط بین View و ViewModel از IViewLocator، IViewModelLocator و یکسری قواعد نام گذاری پیروی میکند تا نیاز به رجیستر کردن تک تک ویوها و ویومدل‌ها به صورت دستی نباشد که البته این قواعد قابل تغییر و شخصی سازی هستند. قرارداد پیش فرض برای پروژه‌های کوچک ممکن است مناسب باشد ولی در پروژه‌های بزرگ نیاز به سفارشی سازی دارد که در قسمت‌های بعد به آن خواهیم پرداخت. 
View و ViewModel:

برای ایجاد یک ViewModel جدید، باید از منوی Add New Item قسمت Catel گزینه (ViewModel (Catel را انتخاب نمایید. با توجه به code snippet های تهیه شده برای این فریمورک، کار تهیه ViewModel‌ها فوق العاده سریع انجام می‌شود. به عنوان مثال برای اضافه کردن یک Command در ویومدل، از vmcommand و یا vmcommandwithcanexecute و برای ایجاد پروپرتی هم از vmprop و vmpropchanged میتوان استفاده نمود. همانطور که ملاحظه می‌کنید نام این snippet‌‌ها کاملا واضح می‌باشد و نیاز به توضیح اضافی ندارند.
همینطور برای ایجاد یک View گزینه (DataWindow (WPF with Catel را انتخاب نمایید. ViewModel‌‌‌ها در Catel از کلاس پایه ViewModelBase و View‌‌ها نیز از کلاس DataWindow مشتق می‌شوند.
DataWindow یک Window پیشرفته با قابلیت هایی مانند افزودن خودکار دکمه‌های Ok / Cancel یا Ok / Cancel / Apply یا Close می‌باشد که می‌تواند باعث تسریع روند ایجاد Window‌های تکراری شود. اما اگر به هیچ کدام از این دکمه‌های ذکر شده نیاز نداشتید DataWindowMode.Custom را انتخاب می‌کنید. نشان دادن Validation در بالای پنجره به صورت popup نیز یکی دیگر از قابلیت‌های این Window پیشرفته است. البته DataWindow دارای overload‌‌های مختلفی است که می‌توانید به کمک آن ویژگی‌های ذکر شده را فعال یا غیر فعال کنید.
حال برای درک بهتر command‌ها و نحوه تعریف و بکارگیری آن‌ها یک command جدید در MainWindowViewModel با استفاده از vmcommand ایجاد کنید. مانند قطعه کد زیر:
public class MainWindowViewModel : ViewModelBase
    {
        public MainWindowViewModel()
            : base()
        {
            ShowPleaseWait = new Command(OnShowPleaseWaitExecute);
        }

        public override string Title { get { return "View model title"; } }

        public Command ShowPleaseWait { get; private set; }
        private void OnShowPleaseWaitExecute()
        {
            var pleaseWaitService = GetService<IPleaseWaitService>();
            pleaseWaitService.Show(() =>
            {
                Thread.Sleep(3000);
            });
        }
    }
در داخل بدنه این command از PleaseWaitService استفاده کردیم که در ادامه توضیح داده خواهد شد. در MainView نیز یک button اضافه کنید و پروپرتی Command آن را به صورت زیر تنظیم کنید:
<Button Margin="6"
                Command="{Binding ShowPleaseWait}"
                Content="Show PleaseWait!" />
اکنون با فشردن button کد داخل بدنه command اجرا خواهد شد.

سرویس ها:

کتابخانه Catel.MVVM دارای سرویس‌های مختلف و پرکاربردی می‌باشد که در ادامه به بررسی آن‌ها خواهیم پرداخت:
PleaseWaitService: از این سرویس برای نشان دادن یک loading به کاربر در حین انجام یک کار سنگین استفاده می‌شود و نحوه استفاده از آن به صورت زیر است:
var pleaseWaitService = GetService<IPleaseWaitService>();
pleaseWaitService.Show(() =>
{
        Thread.Sleep(3000);
});
UIVisualizerService: از این سرویس برای باز کردن پنجره‌های برنامه استفاده می‌شود. هر View در برنامه دارای یک ViewModel می باشد. برای باز کردن View ابتدا یک نمونه از ViewModel مربوطه را ایجاد میکنیم و با دادن viewmodel به متد Show یا ShowDialog پنجره مورد نظر را باز میکنیم.
var uiService = GetService<IUIVisualizerService>();
var viewModel = new AnotherWindowViewModel();
uiService.Show(viewModel);
OpenFileService: برای نشان دادن OpenFileDialog جهت باز کردن یک فایل در برنامه.
var openFileService = GetService<IOpenFileService>();
openFileService.Filter = "ZIP files (*.zip)|*.zip";
openFileService.IsMultiSelect = false;
openFileService.Title = "Open file";
if (openFileService.DetermineFile())
{
       // ?
}
SaveFileService: برای نشان دادن SaveFileDialog جهت ذخیره سازی.
var saveFileService = GetService<ISaveFileService>();
saveFileService.Filter = "ZIP files (*.zip)|*.zip";
saveFileService.FileName = "test";
saveFileService.Title = "Save file";
if (saveFileService.DetermineFile())
{
       // ?
}
ProcessService: برای اجرا کردن یک process. به عنوان مثال برای باز کردن ماشین حساب ویندوز به صورت زیر عمل می‌کنیم:
var processService = GetSetvice<IProcessService>();
processService.StartProcess(@"C:\Windows\System32\calc.exe");
SplashScreenService: برای نشان دادن SplashScreen در ابتدای برنامه هایی که سرعت بالا آمدن پایینی دارند.
var splashScreenService = GetService<ISplashScreenService>();
splashScreenService.Enqueue(new ActionTask("Creating the shell", OnCreateShell));
splashScreenService.Enqueue(new ActionTask("Initializing modules", OnInitializeModules));
splashScreenService.Enqueue(new ActionTask("Starting application", OnStartApplication));
MessageService: برای نشان دادن MessageBox به کاربر.
var messageService = GetService<IMessageService>();
if (messageService.Show("Are you sure?", "?", MessageButton.YesNo, MessageImage.Warning) == MessageResult.Yes)
{
       // ?
}
همانطور که ملاحظه کردید اکثر کارهای مورد نیاز یک پروژه با کمک سرویس‌های ارائه شده در این فریمورک به آسانی انجام می‌شود.
دریافت مثال و پروژه کامل این قسمت:
مطالب
فشرده سازی حجم فایل‌های PDF توسط iTextSharp
پیشتر در سایت جاری مطلبی را در مورد «بهینه سازی حجم فایل PDF تولیدی در حین کار با تصاویر در iTextSharp» مطالعه کرده‌اید. خلاصه آن به این نحو است که می‌توان در یک فایل PDF، ده‌ها تصویر را که تنها به یک فایل فیزیکی اشاره می‌کنند قرار داد. به این ترتیب حجم فایل نهایی تا حد بسیار قابل ملاحظه‌ای کاهش می‌یابد. البته آن مطلب در مورد تولید یک فایل PDF جدید صدق می‌کند. اما در مورد فایل‌های PDF موجود و از پیش آماده شده چطور؟


سؤال: آیا در فایل PDF ما تصاویر تکراری وجود دارند؟

نحوه یافتن تصاویر تکراری موجود در یک فایل PDF را به کمک iTextSharp در کدهای ذیل ملاحظه می‌کنید:
        public static int FindDuplicateImagesCount(string pdfFileName)
        {
            int count = 0;
            var pdf = new PdfReader(pdfFileName);

            var md5 = new MD5CryptoServiceProvider();
            var enc = new UTF8Encoding();
            var imagesHashList = new List<string>();

            int intPageNum = pdf.NumberOfPages;
            for (int i = 1; i <= intPageNum; i++)
            {
                var page = pdf.GetPageN(i);
                var resources = PdfReader.GetPdfObject(page.Get(PdfName.RESOURCES)) as PdfDictionary;
                if (resources == null) continue;

                var xObject = PdfReader.GetPdfObject(resources.Get(PdfName.XOBJECT)) as PdfDictionary;
                if (xObject == null) continue;

                foreach (var name in xObject.Keys)
                {
                    var pdfObject = xObject.Get(name);
                    if (!pdfObject.IsIndirect()) continue;

                    var imgObject = PdfReader.GetPdfObject(pdfObject) as PdfDictionary;
                    if (imgObject == null) continue;

                    var subType = PdfReader.GetPdfObject(imgObject.Get(PdfName.SUBTYPE)) as PdfName;
                    if (subType == null) continue;

                    if (!PdfName.IMAGE.Equals(subType)) continue;

                    byte[] imageBytes = PdfReader.GetStreamBytesRaw((PRStream)imgObject);
                    var md5Hash = enc.GetString(md5.ComputeHash(imageBytes));

                    if (!imagesHashList.Contains(md5Hash))
                    {
                        imagesHashList.Add(md5Hash);
                    }
                    else
                    {
                        Console.WriteLine("Found duplicate image @page: {0}.", i);
                        count++;
                    }
                }
            }

            pdf.Close();
            return count;
        }
در این کد، از قابلیت‌های سطح پایین PdfReader استفاده شده است. یک فایل PDF از پیش آماده، توسط این شیء گشوده شده و سپس محتویات تصاویر آن یافت می‌شوند. در ادامه هش MD5 آن‌ها محاسبه و با یکدیگر مقایسه می‌شوند. اگر هش تکراری یافت شد، یعنی تصویر یافت شده تکراری است و این فایل قابلیت بهینه سازی و کاهش حجم (قابل ملاحظه‌ای) را دارا می‌باشد.


سؤال: چگونه اشیاء تکراری یک فایل PDF را حذف کنیم؟

کلاسی در iTextSharp به نام PdfSmartCopy وجود دارد که شبیه به عملیات فوق را انجام داده و یک کپی سبک از هر صفحه را تهیه می‌کند. سپس می‌توان این کپی‌ها را کنار هم قرار داد و فایل اصلی را مجددا بازسازی کرد:
    public class PdfSmartCopy2 : PdfSmartCopy
    {
        public PdfSmartCopy2(Document document, Stream os)
            : base(document, os)
        { }

        /// <summary>
        /// This is a forgotten feature in iTextSharp 5.3.4. 
        /// Actually its PdfSmartCopy is useless without this!
        /// </summary>
        protected override PdfIndirectReference CopyIndirect(PRIndirectReference inp, bool keepStructure, bool directRootKids)
        {
            return base.CopyIndirect(inp);
        }
    }

        public static void RemoveDuplicateObjects(string inFile, string outFile)
        {
            var document = new Document();
            var copy = new PdfSmartCopy2(document, new FileStream(outFile, FileMode.Create));
            document.Open();

            var reader = new PdfReader(inFile);

            var n = reader.NumberOfPages;
            for (int page = 0; page < n; )
            {
                copy.AddPage(copy.GetImportedPage(reader, ++page));
            }
            copy.FreeReader(reader);            

            document.Close();
        }
به نظر در نگارش iTextSharp 5.3.4 نویسندگان این کتابخانه اندکی فراموش کرده‌اند که باید تعدادی متد دیگر را نیز override کنند! به همین جهت کلاس PdfSmartCopy2 را مشاهده می‌کنید (اگر از نگارش‌های پایین‌تر استفاده می‌کنید، نیازی به آن نیست).
استفاده از آن هم ساده است. در متد RemoveDuplicateObjects، ابتدا هر صفحه موجود توسط متد GetImportedPage دریافت شده و به وهله‌ای از PdfSmartCopy اضافه می‌شود. در پایان کار، فایل نهایی تولیدی، حاوی عناصر تکراری نخواهد بود. احتمالا برنامه‌های PDF compressor تجاری را در گوشه و کنار اینترنت دیده‌اید. متد RemoveDuplicateObjects دقیقا همان کار را انجام می‌دهد. 
اگر علاقمند هستید که متد فوق را آزمایش کنید یک فایل جدید PDF را به صورت زیر ایجاد نمائید:
        private static void CreateTestFile()
        {
            using (var pdfDoc = new Document(PageSize.A4))
            {
                var pdfWriter = PdfWriter.GetInstance(pdfDoc, new FileStream("Test.pdf", FileMode.Create));
                pdfDoc.Open();

                var table = new PdfPTable(new float[] { 1, 2 });
                table.AddCell(Image.GetInstance("01.png"));
                table.AddCell(Image.GetInstance("01.png"));
                pdfDoc.Add(table);
            }
        }
در این فایل دو وهله از تصویر 01.png به صفحه اضافه شده‌اند. بنابراین دقیقا دو تصویر در فایل نهایی تولیدی وجود خواهد داشت.
سپس متد RemoveDuplicateObjects را روی test.pdf تولید شده فراخوانی کنید. حجم فایل حاصل تقریبا نصف خواهد شد. از این جهت که PdfSmartCopy توانسته است بر اساس هش MD5 موجود در فایل PDF نهایی، موارد تکراری را یافته و ارجاعات را تصحیح کند.
در شکل زیر ساختار فایل test.pdf اصلی را ملاحظه می‌کنید. در اینجا img1 و img0 به دو stream متفاوت اشاره می‌کنند:


در شکل زیر همان test.pdf را پس از بکارگیری PDFSmartCopy ملاحظه می‌کنید:

اینبار دو تصویر داریم که هر دو به یک stream اشاره می‌کنند. تصاویر فوق به کمک برنامه iText RUPS تهیه شده‌اند.

مطالب
ایجاد قالب‌های سفارشی ستون‌ها و فرمت شرطی اطلاعات در PdfReport
صورت مساله:
- لیستی از حقوق کارکنان را داریم. در گزارش نهایی آن نیاز است عدد حقوق کارکنانی با مبلغ کمتر از 1000، با رنگی دیگر نمایش داده شوند.
همچنین در این گزارش هر ردیفی که در ماه 7 واقع شده نیز ظاهر عدد سلول مربوط به آن ماه، به رنگ قهوه‌ای و زمینه زرد تغییر یابد.
- در ستون مشخصات افراد این گزارش، نیاز است تصویر کارمند به همراه نام او در ذیل این تصویر (داخل یک سلول) نمایش داده شوند.

چیزی شبیه به این گزارش!


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

ابتدا سورس کامل این مثال را ملاحظه نمائید:
using System;
using iTextSharp.text;
using PdfRpt.Core.Contracts;
using PdfRpt.Core.Helper;
using PdfRpt.FluentInterface;

namespace PdfReportSamples.CustomCellTemplate
{
    public class CustomCellTemplatePdfReport
    {
        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.Compression(new CompressionSettings
                               {
                                   CompressionLevel = CompressionLevel.BestCompression,
                                   EnableCompression = true
                               });
            })
             .DefaultFonts(fonts =>
             {
                 fonts.Path(AppPath.ApplicationPath + "\\fonts\\irsans.ttf",
                            Environment.GetEnvironmentVariable("SystemRoot") + "\\fonts\\verdana.ttf");
             })
             .PagesFooter(footer =>
             {
                 footer.DefaultFooter(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.SnowyPineTemplate);
             })
             .MainTablePreferences(table =>
             {
                 table.ColumnsWidthsType(TableColumnWidthType.Relative);
                 table.MultipleColumnsPerPage(new MultipleColumnsPerPage
                 {
                     ColumnsGap = 20,
                     ColumnsPerPage = 2,
                     ColumnsWidth = 250,
                     IsRightToLeft = true,
                     TopMargin = 7
                 });
             })
             .MainTableDataSource(dataSource =>
             {
                 var table = new System.Data.DataTable("لیست حقوق");
                 table.Columns.Add("شخص", typeof(string));
                 table.Columns.Add("ماه", typeof(int));
                 table.Columns.Add("مبلغ", typeof(decimal));

                 var rnd = new Random();
                 for (int i = 0; i < 200; i++)
                     table.Rows.Add("شخص " + i, rnd.Next(1, 12), rnd.Next(400, 2000));

                 dataSource.DataTable(table);
             })
             .MainTableEvents(events =>
             {
                 events.DataSourceIsEmpty(message: "There is no data available to display.");
                 events.CellCreated(args =>
                     {
                         //change the background color of the cell based on the value
                         if (args.RowType == RowType.DataTableRow && args.Cell.RowData.Value != null && args.Cell.RowData.Value is decimal)
                         {
                             if ((decimal)args.Cell.RowData.Value <= 1000)
                                 args.Cell.BasicProperties.BackgroundColor = BaseColor.CYAN;
                         }
                     });
             })
             .MainTableSummarySettings(summary =>
             {
                 summary.OverallSummarySettings("جمع کل");
                 summary.PageSummarySettings("جمع صفحه");
                 summary.PreviousPageSummarySettings("نقل از ستون قبل");
             })
             .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("ردیف");
                 });

                 columns.AddColumn(column =>
                 {
                     column.PropertyName("شخص");
                     column.CellsHorizontalAlignment(HorizontalAlignment.Center);
                     column.IsVisible(true);
                     column.Order(1);
                     column.Width(3);
                     column.HeaderCell("شخص");
                     column.ColumnItemsTemplate(t => t.CustomTemplate(new MyCustomCellTemplate()));
                 });

                 columns.AddColumn(column =>
                 {
                     column.PropertyName("ماه");
                     column.CellsHorizontalAlignment(HorizontalAlignment.Center);
                     column.IsVisible(true);
                     column.Order(2);
                     column.Width(2);
                     column.HeaderCell("ماه");
                     column.ColumnItemsTemplate(template =>
                     {
                         template.TextBlock();
                         template.ConditionalFormatFormula(list =>
                         {
                             var cellValue = int.Parse(list.GetSafeStringValueOf("ماه", nullValue: "0"));
                             if (cellValue == 7)
                             {
                                 return new CellBasicProperties
                                 {
                                     PdfFontStyle = DocumentFontStyle.Bold | DocumentFontStyle.Underline,
                                     FontColor = new BaseColor(System.Drawing.Color.Brown),
                                     BackgroundColor = new BaseColor(System.Drawing.Color.Yellow)
                                 };
                             }
                             return new CellBasicProperties { PdfFontStyle = DocumentFontStyle.Normal };
                         });
                     });
                 });

                 columns.AddColumn(column =>
                 {
                     column.PropertyName("مبلغ");
                     column.CellsHorizontalAlignment(HorizontalAlignment.Center);
                     column.IsVisible(true);
                     column.Order(3);
                     column.Width(2);
                     column.HeaderCell("مبلغ");
                     column.ColumnItemsTemplate(template =>
                     {
                         template.TextBlock();
                         template.DisplayFormatFormula(obj => obj == null ? string.Empty : string.Format("{0:n0}", obj));
                     });
                     column.AggregateFunction(aggregateFunction =>
                     {
                         aggregateFunction.NumericAggregateFunction(AggregateFunction.Sum);
                         aggregateFunction.DisplayFormatFormula(obj => obj == null ? string.Empty : string.Format("{0:n0}", obj));
                     });
                 });
             })
             .Export(export =>
                 {
                     export.ToXml();
                     export.ToExcel();
                 })
             .Generate(data => data.AsPdfFile(AppPath.ApplicationPath + "\\Pdf\\RptDataTableSample.pdf"));
        }
    }
}
به همراه قالب سلول سفارشی آن:
using System;
using System.Collections.Generic;
using iTextSharp.text;
using iTextSharp.text.pdf;
using PdfRpt.Core.Contracts;
using PdfRpt.Core.Helper;

namespace PdfReportSamples.CustomCellTemplate
{
    public class MyCustomCellTemplate : IColumnItemsTemplate
    {
        Random _rnd = new Random();

        public void CellRendered(PdfPCell cell, Rectangle position, PdfContentByte[] canvases, CellAttributes attributes)
        {
        }

        public CellBasicProperties BasicProperties { set; get; }
        public Func<IList<CellData>, CellBasicProperties> ConditionalFormatFormula { set; get; }

        public PdfPCell RenderingCell(CellAttributes attributes)
        {
            var pdfCell = new PdfPCell();
            var table = new PdfPTable(1) { RunDirection = PdfWriter.RUN_DIRECTION_RTL };

            var filePath = AppPath.ApplicationPath + "\\Images\\" + _rnd.Next(1, 5).ToString("00") + ".png";
            var photo = PdfImageHelper.GetITextSharpImageFromImageFile(filePath);
            table.AddCell(new PdfPCell(photo, fit: false)
            {
                Border = 0,                
                VerticalAlignment = Element.ALIGN_BOTTOM,
                HorizontalAlignment = Element.ALIGN_CENTER
            });

            var name = attributes.RowData.TableRowData.GetSafeStringValueOf("شخص");
            table.AddCell(new PdfPCell(attributes.BasicProperties.PdfFont.FontSelector.Process(name))
            {
                Border = 0,
                HorizontalAlignment = Element.ALIGN_CENTER
            });

            pdfCell.AddElement(table);

            return pdfCell;
        }
    }
}

توضیحات:

- در این مثال از منبع داده‌ای از نوع DataTable استفاده شده است؛ که نحوه بکارگیری آن‌را در متد MainTableDataSource ملاحظه می‌کنید. ستون‌های تعریف شده در MainTableColumns نیز بر اساس ستون‌های DataTable مشخص شده‌اند.
- در متد DocumentPreferences، نحوه مشخص سازی فشرده سازی نهایی فایل PDF را ملاحظه می‌کنید. این مورد از مزایای استفاده از فایل‌های PDF است.

- برای اعمال فرمت شرطی اطلاعات در PdfReport دو روش وجود دارد.
الف) استفاده از متد MainTableEvents و کار کردن با رخ‌دادهای تعریف شده در آن مانند CellCreated. در اینجا می‌توان در نحوه رندر شدن یک سلول دخالت کرد:
events.CellCreated(args =>
    {
         //change the background color of the cell based on the value
         if (args.RowType == RowType.DataTableRow && args.Cell.RowData.Value != null && args.Cell.RowData.Value is decimal)
         {
              if ((decimal)args.Cell.RowData.Value <= 1000)
                   args.Cell.BasicProperties.BackgroundColor = BaseColor.CYAN;
          }
      });
برای مثال در تعاریف فوق، اگر نوع ردیف، از نوع ردیف‌های اطلاعاتی جدول باشد، مقدار آن دریافت شده و بر اساس شرطی مشخص، برای نمونه رنگ پس زمینه آن سلول تغییر داده می‌شود.
ب) همانطور که در قسمت تعریف ستون «ماه» ملاحظه می‌کنید، توسط متد template.ConditionalFormatFormula نیز، امکان فرمت شرطی اطلاعات فراهم شده است. در اینجا می‌توان به لیست اطلاعات سلول‌های ردیف جاری دسترسی یافت و سپس بر اساس آن تصمیم گیری کرد.

- جهت تعریف قالب‌های سفارشی سلول‌ها کافی است اینترفیس IColumnItemsTemplate را پیاده سازی کنیم؛ که نمونه‌ای از آن را در کدهای MyCustomCellTemplate فوق ملاحظه می‌کنید. در اینجا فرصت خواهید داشت هر شکل و طرح متنوعی را تهیه کرده و به صورت یک PdfPCell بازگشت دهید. برای نمونه در مثال فوق، یک جدول را در سلول تعریف شده قرار داده‌ایم. این جدول یک ستون دارد و هر سلولی که به آن اضافه خواهد شد، یک ردیف را تشکیل خواهد داد. در ردیف اول آن تصویر قرار گرفته و در ردیف دوم آن مقدار سلول جاری.
مطالب
WPF4 و ویندوز 7 : به خاطر سپاری لیست آخرین فایل‌های گشوده شده توسط برنامه

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




خبر خوب اینکه برای اضافه کردن این قابلیت به برنامه‌های WPF4 نیازی به کد نویسی نیست و این موارد که تحت عنوان استفاده از Jump list ویندوز 7 تعریف شده‌اند، با کمی دستکاری فایل App.Xaml برنامه، فعال می‌گردند:

<Application x:Class="WpfApplication1.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
StartupUri="MainWindow.xaml">
<Application.Resources>
</Application.Resources>
<JumpList.JumpList>
<JumpList ShowRecentCategory="True" />
</JumpList.JumpList>
</Application>
همین! از این پس هر فایلی که توسط برنامه‌ی شما با استفاده از common file dialog boxes باز شود به صورت خودکار به لیست مذکور اضافه می‌گردد (بدیهی است Jump lists جزو ویژگی‌های ویندوز 7 است و در سایر سیستم عامل‌ها ندید گرفته خواهد شد).


سؤال: من اینکار را انجام دادم ولی کار نمی‌کنه!؟

پاسخ: بله. کار نمی‌کنه! این قابلیت تنها زمانی فعال خواهد شد که علاوه بر نکته‌ی فوق، پسوند فایل یا فایل‌هایی نیز به برنامه‌ی شما منتسب شده باشد. این انتساب‌ها مطلب جدیدی نیست و در تمام برنامه‌های ویندوزی باید توسط بکارگیری API ویندوز مدیریت شود. قطعه کد زیر اینکار را انجام خواهد داد:
using System;
using System.Runtime.InteropServices;
using Microsoft.Win32;

namespace Common.Files
{
//from : http://www.devx.com/vb2themax/Tip/19554?type=kbArticle&trk=MSCP
public class FileAssociation
{
const int ShcneAssocchanged = 0x8000000;
const int ShcnfIdlist = 0;

public static void CreateFileAssociation(
string extension,
string className,
string description,
string exeProgram)
{
// ensure that there is a leading dot
if (extension.Substring(0, 1) != ".")
extension = string.Format(".{0}", extension);

try
{
if (IsAssociated(extension)) return;

// create a value for this key that contains the classname
using (var key1 = Registry.ClassesRoot.CreateSubKey(extension))
{
if (key1 != null)
{
key1.SetValue("", className);
// create a new key for the Class name
using (var key2 = Registry.ClassesRoot.CreateSubKey(className))
{
if (key2 != null)
{
key2.SetValue("", description);
// associate the program to open the files with this extension
using (var key3 = Registry.ClassesRoot.CreateSubKey(string.Format(@"{0}\Shell\Open\Command", className)))
{
if (key3 != null) key3.SetValue("", string.Format(@"{0} ""%1""", exeProgram));
}
}
}
}
}

// notify Windows that file associations have changed
SHChangeNotify(ShcneAssocchanged, ShcnfIdlist, 0, 0);
}
catch (Exception ex)
{
//todo: log ...
}
}

// Return true if extension already associated in registry
public static bool IsAssociated(string extension)
{
return (Registry.ClassesRoot.OpenSubKey(extension, false) != null);
}

[DllImport("shell32.dll")]
public static extern void SHChangeNotify(int wEventId, int uFlags, int dwItem1, int dwItem2);
}
}
و مثالی از نحوه‌ی استفاده از آن:
private static void createFileAssociation()
{
var appPath = Assembly.GetExecutingAssembly().Location;
FileAssociation.CreateFileAssociation(".xyz", "xyz", "xyz File",
appPath
);
}
لازم به ذکر است که این کد در ویندوز 7 فقط با دسترسی مدیریتی قابل اجرا است (کلیک راست و اجرا به عنوان ادمین) و در سایر حالات با خطای Access is denied متوقف خواهد شد. به همین جهت بهتر است برنامه‌ی نصاب مورد استفاده این نوع انتسابات را مدیریت کند؛ زیرا اکثر آن‌ها با دسترسی مدیریتی است که مجوز نصب را به کاربر جاری خواهند داد. اگر از فناوری Click once استفاده می‌کنید به این مقاله و اگر برای مثال از NSIS کمک می‌گیرید به این مطلب مراجعه نمائید.


سؤال: من این کارها را هم انجام دادم. الان به چه صورت از آن‌ استفاده کنم؟

زمانیکه کاربری بر روی یکی از این فایل‌های ذکر شده در لیست آخرین فایل‌های گشوده شده توسط برنامه کلیک کند، آدرس این فایل به صورت یک آرگومان به برنامه ارسال خواهد شد. برای مدیریت آن در WPF باید به فایل App.Xaml.cs مراجعه کرده و چند سطر زیر را به آن افزود:
    public partial class App
{
public App()
{
this.Startup += appStartup;
}

void appStartup(object sender, StartupEventArgs e)
{
if (e.Args.Any())
{
this.Properties["StartupFileName"] = e.Args[0];
}
}
//...

در این کد، e.Args حاوی مسیر فایل انتخابی است. برای مثال در اینجا مقدار آن به خاصیت StartupFileName انتساب داده شده است. این خاصیت در برنامه‌های WPF به صورت یک خاصیت عمومی تعریف شده است و در سراسر برنامه (مثلا در رخداد آغاز فرم اصلی آن یا هر جای دیگری) به صورت زیر قابل دسترسی است:
var startupFileName = Application.Current.Properties["StartupFileName"];

سؤال: برنامه‌ی من از OpenFileDialog برای گشودن فایل‌ها استفاده نمی‌کند. آیا راه دیگری برای افزودن مسیرهای باز شده به Jump lists ویندوز 7 وجود دارد؟

پاسخ: بله. همانطور که می‌دانید عناصر XAML با اشیاء دات نت تناظر یک به یک دارند. به این معنا که JumpList تعریف شده در ابتدای این مطلب در فایل App.XAML ، دقیقا معادل کلاسی به همین نام در دات نت فریم ورک است (تعریف شده در فضای نام System.Windows.Shell) و با کد نویسی نیز قابل دسترسی و مدیریت است. برای مثال:
var jumpList = JumpList.GetJumpList(App.Current);
var jumpPath = new JumpPath();
jumpPath.Path = "some path goes here....";
// If the CustomCategory property is null
// or Empty, the item is added to the Tasks category
jumpPath.CustomCategory = "Files";
JumpList.AddToRecentCategory(jumpPath);
jumpList.Apply();
به همین ترتیب،‌ JumpPath ذکر شده در کدهای فوق، در کدهای XAML نیز قابل تعریف است:
<Application x:Class="Win7Wpf4.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
StartupUri="MainWindow.xaml">
<Application.Resources>
</Application.Resources>
<JumpList.JumpList>
<JumpList ShowRecentCategory="True">
<JumpPath
CustomCategory="Files"
Path="Some path goes here..."
/>
</JumpList>
</JumpList.JumpList>
</Application>

مطالب
تزریق وابستگی (dependency injection) به زبان ساده

این مطلب در ادامه‌ی "آشنایی با الگوی IOC یا Inversion of Control (واگذاری مسئولیت)" می‌باشد که هر از چندگاهی یک قسمت جدید و یا کاملتر از آن ارائه خواهد شد.

==============
به صورت خلاصه ترزیق وابستگی و یا dependency injection ، الگویی است جهت تزریق وابستگی‌های خارجی یک کلاس به آن، بجای استفاده مستقیم از آن‌ها در درون کلاس.
برای مثال شخصی را در نظر بگیرید که قصد خرید دارد. این شخص می‌تواند به سادگی با کمک یک خودرو خود را به اولین محل خرید مورد نظر برساند. حال تصور کنید که 7 نفر عضو یک گروه، با هم قصد خرید دارند. خوشبختانه چون تمام خودروها یک اینترفیس مشخصی داشته و کار کردن با آن‌ها تقریبا شبیه به یکدیگر است، حتی اگر از یک ون هم جهت رسیدن به مقصد استفاده شود، امکان استفاده و راندن آن همانند سایر خودروها می‌باشد و این دقیقا همان مطلبی است که هدف غایی الگوی تزریق وابستگی‌ها است. بجای این‌که همیشه محدود به یک خودرو برای استفاده باشیم، بنابر شرایط، خودروی متناسبی را نیز می‌توان مورد استفاده قرار داد.
در دنیای نرم افزار، وابستگی کلاس Driver ، کلاس Car است. اگر موارد ذکر شده را بدون استفاده از تزریق وابستگی‌ها پیاده سازی کنیم به کلاس‌های زیر خواهیم رسید:

//Person.cs
namespace DependencyInjectionForDummies
{
class Person
{
public string Name { get; set; }
}
}

//Car.cs
using System;
using System.Collections.Generic;

namespace DependencyInjectionForDummies
{
class Car
{
List<Person> _passengers = new List<Person>();

public void AddPassenger(Person p)
{
_passengers.Add(p);
Console.WriteLine("{0} added!", p.Name);
}

public void Drive()
{
foreach (var passenger in _passengers)
Console.WriteLine("Driving {0} ...!", passenger.Name);
}
}
}

//Driver.cs
using System.Collections.Generic;

namespace DependencyInjectionForDummies
{
class Driver
{
private Car _myCar = new Car();

public void DriveToMarket(IList<Person> passengers)
{
foreach (var passenger in passengers)
_myCar.AddPassenger(passenger);

_myCar.Drive();
}
}
}

//Program.cs
using System.Collections.Generic;
using System;

namespace DependencyInjectionForDummies
{
class Program
{
static void Main(string[] args)
{
new Driver().DriveToMarket(
new List<Person>
{
new Person{ Name="Ali" },
new Person{ Name="Vahid" }
});

Console.WriteLine("Press a key ...");
Console.ReadKey();
}
}
}

توضیحات:
کلاس شخص (Person) جهت تعریف مسافرین، اضافه شده؛ سپس کلاس خودرو (Car) که اشخاص را می‌توان به آن اضافه کرده و سپس به مقصد رساند، تعریف گردیده است. همچنین کلاس راننده (Driver) که بر اساس لیست مسافرین، آن‌ها را به خودروی خاص ذکر شده هدایت کرده و سپس آن‌ها را با کمک کلاس خودرو به مقصد می‌رساند؛ نیز تعریف شده است. در پایان هم یک کلاینت ساده جهت استفاده از این کلاس‌ها ذکر شده است.
همانطور که ملاحظه می‌کنید کلاس راننده به کلاس خودرو گره خورده است و این راننده همیشه تنها از یک نوع خودروی مشخص می‌تواند استفاده کند و اگر روزی قرار شد از یک ون کمک گرفته شود، این کلاس باید بازنویسی شود.

خوب! اکنون اگر این کلاس‌ها را بر اساس الگوی تزریق وابستگی‌ها (روش تزریق در سازنده که در قسمت قبل بحث شد) بازنویسی کنیم به کلاس‌های زیر خواهیم رسید:

//ICar.cs
using System;

namespace DependencyInjectionForDummies
{
interface ICar
{
void AddPassenger(Person p);
void Drive();
}
}

//Car.cs
using System;
using System.Collections.Generic;

namespace DependencyInjectionForDummies
{
class Car : ICar
{
//همانند قسمت قبل
}
}

//Van.cs
using System;
using System.Collections.Generic;

namespace DependencyInjectionForDummies
{
class Van : ICar
{
List<Person> _passengers = new List<Person>();

public void AddPassenger(Person p)
{
_passengers.Add(p);
Console.WriteLine("{0} added!", p.Name);
}

public void Drive()
{
foreach (var passenger in _passengers)
Console.WriteLine("Driving {0} ...!", passenger.Name);
}
}
}

//Driver.cs
using System.Collections.Generic;

namespace DependencyInjectionForDummies
{
class Driver
{
private ICar _myCar;

public Driver(ICar myCar)
{
_myCar = myCar;
}

public void DriveToMarket(IList<Person> passengers)
{
foreach (var passenger in passengers)
_myCar.AddPassenger(passenger);

_myCar.Drive();
}
}
}

//Program.cs
using System.Collections.Generic;
using System;

namespace DependencyInjectionForDummies
{
class Program
{
static void Main(string[] args)
{
Driver driver = new Driver(new Van());
driver.DriveToMarket(
new List<Person>
{
new Person{ Name="Ali" },
new Person{ Name="Vahid" }
});

Console.WriteLine("Press a key ...");
Console.ReadKey();
}
}
}

توضیحات:
در اینجا یک اینترفیس جدید به نام ICar اضافه شده است و بر اساس آن می‌توان خودروهای مختلفی را با نحوه‌ی بکارگیری یکسان اما با جزئیات پیاده سازی متفاوت تعریف کرد. برای مثال در ادامه، یک کلاس ون با پیاده سازی این اینترفیس تشکیل شده است. سپس کلاس راننده‌ی ما بر اساس ترزیق این اینترفیس در سازنده‌ی آن بازنویسی شده است. اکنون این کلاس دیگر نمی‌داند که دقیقا چه خودرویی را باید مورد استفاده قرار دهد و از وابستگی مستقیم به نوعی خاص از آن‌ها رها شده است؛ اما می‌داند که تمام خودروها، اینترفیس مشخص و یکسانی دارند. به تمام آن‌ها می‌توان مسافرانی را افزود و سپس به مقصد رساند. در پایان نیز یک راننده جدید بر اساس خودروی ون تعریف شده، سپس یک سری مسافر نیز تعریف گردیده و نهایتا متد DriveToMarket فراخوانی شده است.
به این صورت به یک سری کلاس اصطلاحا loosely coupled رسیده‌ایم. دیگر راننده‌ی ما وابسته‌ی به یک خودروی خاص نیست و هر زمانی که لازم بود می‌توان خودروی مورد استفاده‌ی او را تغییر داد بدون اینکه کلاس راننده را بازنویسی کنیم.
یکی دیگر از مزایای تزریق وابستگی‌ها ساده سازی unit testing کلاس‌های برنامه توسط mocking frameworks است. به این صورت توسط این نوع فریم‌ورک‌ها می‌توان رفتار یک خودرو را تقلید کرد بجای اینکه واقعا با تمام ریز جرئیات آن‌ها بخواهیم سروکار داشته باشیم (وابستگی‌ها را به صورت مستقل می‌توان آزمایش کرد).