مطالب
نکاتی در مورد استفاده از توابع تجمعی در Entity framework
استفاده از Aggregate functions یا توابع تجمعی چه در زمان SQL نویسی مستقیم و یا در حالت استفاده از LINQ to Entities نیاز به ملاحظات خاصی دارد که عدم رعایت آن‌ها سبب کرش برنامه در زمان موعد خواهد شد. در ادامه تعدادی از این موارد را مرور خواهیم کرد.

ابتدا مدل‌های برنامه را در نظر بگیرید که از یک صورتحساب، به همراه ریز قیمت‌های آیتم‌های مرتبط با آن تشکیل شده است:
    public class Bill
    {
        public int Id { set; get; }
        public string Name { set; get; }

        public virtual ICollection<Transaction> Transactions { set; get; }
    }

    public class Transaction
    {
        public int Id { set; get; }
        public DateTime AddDate { set; get; }
        public int Amount { set; get; }

        [ForeignKey("BillId")]
        public virtual Bill Bill { set; get; }
        public int BillId { set; get; }
    }
در ادامه این کلاس‌ها را در معرض دید EF Code first قرار می‌دهیم:
    public class MyContext : DbContext
    {
        public DbSet<Bill> Bills { get; set; }
        public DbSet<Transaction> Transactions { get; set; }
    }
همچنین تعدادی رکورد اولیه را نیز جهت انجام آزمایشات به بانک اطلاعاتی متناظر، اضافه خواهیم کرد:
    public class Configuration : DbMigrationsConfiguration<MyContext>
    {
        public Configuration()
        {
            AutomaticMigrationsEnabled = true;
            AutomaticMigrationDataLossAllowed = true;
        }

        protected override void Seed(MyContext context)
        {
            var bill1 = new Bill { Name = "bill-1" };
            context.Bills.Add(bill1);

            for (int i = 0; i < 11; i++)
            {
                context.Transactions.Add(new Transaction
                {
                    AddDate = DateTime.Now.AddDays(-i),
                    Amount = 1000000000 + i,
                    Bill = bill1
                });
            }
            base.Seed(context);
        }
    }
در اینجا به عمد از ارقام بزرگ استفاده شده است تا نمایانگر عملکرد یک سیستم واقعی در طول زمان باشد.


اولین مثال: یک جمع ساده

    public static class Test
    {
        public static void RunTests()
        {
            Database.SetInitializer(new MigrateDatabaseToLatestVersion<MyContext, Configuration>());
            using (var context = new MyContext())
            {
                var sum = context.Transactions.Sum(x => x.Amount);
                Console.WriteLine(sum);
            }
        }
    }
ساده‌ترین نیازی را که در اینجا می‌توان مدنظر داشت، جمع کل تراکنش‌‌های سیستم است. به نظر شما خروجی کوئری فوق چیست؟
خروجی SQL کوئری فوق به نحو زیر است:
SELECT 
         [GroupBy1].[A1] AS [C1]
         FROM ( SELECT 
                    SUM([Extent1].[Amount]) AS [A1]
                    FROM [dbo].[Transactions] AS [Extent1]
                    )  AS [GroupBy1]
و خروجی واقعی آن استثنای زیر می‌باشد:
 Arithmetic overflow error converting expression to data type int.
بله. محاسبه ممکن نیست؛ چون جمع حاصل از بازه اعداد صحیح خارج شده است.

راه حل:
نیاز است جمع را بر روی Int64 بجای Int32 انجام دهیم:
var sum2 = context.Transactions.Sum(x => (Int64)x.Amount);

SELECT 
      [GroupBy1].[A1] AS [C1]
         FROM ( SELECT 
                    SUM( CAST( [Extent1].[Amount] AS bigint)) AS [A1]
                    FROM [dbo].[Transactions] AS [Extent1]
               )  AS [GroupBy1]                  


مثال دوم: سیستم باید بتواند با نبود رکوردها نیز صحیح کار کند
برای نمونه کوئری زیر را بر روی بازه‌ا‌ی که سیستم عملکرد نداشته است، در نظر بگیرید:
var date = DateTime.Now.AddDays(10);
var sum3 = context.Transactions
                  .Where(x => x.AddDate > date)  
                  .Sum(x => (Int64)x.Amount);               
یک چنین خروجی SQL ایی دارد:
SELECT 
     [GroupBy1].[A1] AS [C1]
        FROM ( SELECT 
                    SUM( CAST( [Extent1].[Amount] AS bigint)) AS [A1]
                    FROM [dbo].[Transactions] AS [Extent1]
                    WHERE [Extent1].[AddDate] > @p__linq__0
              )  AS [GroupBy1]
اما در سمت کدهای ما با خطای زیر متوقف می‌شود:
The cast to value type 'Int64' failed because the materialized value is null.
Either the result type's generic parameter or the query must use a nullable type.
راه حل: استفاده از نوع‌های nullable در اینجا ضروری است:
var date = DateTime.Now.AddDays(10);
var sum3 = context.Transactions
                  .Where(x => x.AddDate > date)
                  .Sum(x => (Int64?)x.Amount) ?? 0;
به این ترتیب، خروجی صفر را بدون مشکل، دریافت خواهیم کرد.

مثال سوم: حالت‌های خاص استفاده از خواص راهبری
کوئری زیر را در نظر بگیرید:
 var sum4 = context.Bills.First().Transactions.Sum(x => (Int64?)x.Amount) ?? 0;
در اینجا قصد داریم جمع تراکنش‌های صورتحساب اول را بدست بیاوریم که از طریق استفاده از خاصیت راهبری Transactions کلاس Bill، به نحو فوق میسر شده است. به نظر شما خروجی SQL آن به چه صورتی است؟
SELECT 
     [Extent1].[Id] AS [Id], 
     [Extent1].[AddDate] AS [AddDate], 
     [Extent1].[Amount] AS [Amount], 
     [Extent1].[BillId] AS [BillId]
   FROM [dbo].[Transactions] AS [Extent1]
   WHERE [Extent1].[BillId] = @EntityKeyValue1
بله! در اینجا خبری از Sum نیست. ابتدا کل اطلاعات دریافت شده و سپس جمع و منهای نهایی در سمت کلاینت بر روی آن‌ها انجام می‌شود؛ که بسیار ناکارآمد است. (قرار است این مورد ویژه، در نگارش‌های بعدی بهبود یابد)
راه حل کنونی:
var entry = context.Bills.First();
var sum5 = context.Entry(entry).Collection(x => x.Transactions).Query().Sum(x => (Int64?)x.Amount) ?? 0;
در اینجا باید از روش خاصی که مشاهده می‌کنید جهت کار با خواص راهبری استفاده کرد و نکته اصلی آن استفاده از متد Query است. حاصل کوئری LINQ فوق اینبار SQL مطلوب زیر است که سمت سرور عملیات جمع را انجام می‌دهد و نه سمت کلاینت:
SELECT 
    [GroupBy1].[A1] AS [C1]
     FROM ( SELECT 
               SUM( CAST( [Extent1].[Amount] AS bigint)) AS [A1]
                   FROM [dbo].[Transactions] AS [Extent1]
                    WHERE [Extent1].[BillId] = @EntityKeyValue1
            )  AS [GroupBy1]                  


نکاتی که در اینجا ذکر شدند در مورد تمام توابع تجمعی مانند Sum، Count، Max و Min و غیره صادق هستند و باید به آن‌ها نیز دقت داشت.
نظرات مطالب
PersianDatePicker یک DatePicker شمسی به زبان JavaScript که از تاریخ سرور استفاده می‌کند
برای استفاده از این برای مقدار دهی به یک پروپرتی از یک مدل تو mvc تایپ اون فیلد رو چی باید در نظر بگیرم؟
   public DateTime deliveryDate { get; set; }
الان وقتی با این دستور اسکریپت رو فراخوانی می‌کنم فقط قالب datepicker رو می‌بینم روزا رو نمی‌بینم. اگر میشه راهنماییم کنید. ممنون
 @Html.TextBoxFor(model => model.deliveryDate,
 new { @id = "TextBox1",onclick = "PersianDatePicker.Show(this,'PersianDateTime.Now.now.ToString(PersianDateTimeFormat.Date)')" })
مطالب
ساخت یک بلاگ ساده با Ember.js، قسمت سوم
پس از ایجاد کنترلرها، در این قسمت سعی خواهیم کرد تا آرایه‌ای ثابت از مطالب و نظرات را در سایت نمایش دهیم. همچنین امکان ویرایش اطلاعات را نیز به این آرایه‌های جاوا اسکریپتی مدل، اضافه خواهیم کرد.


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

فایل جدید Scripts\App\store.js را اضافه کرده و محتوای آن‌را به نحو ذیل تغییر دهید:
var posts = [
  {
      id: '1',
      title: "Getting Started with Ember.js",
      body: "Bla bla bla 1."
  },
  {
      id: '2',
      title: "Routes and Templates",
      body: "Bla bla bla 2."
  },
  {
      id: '3',
      title: "Controllers",
      body: "Bla bla bla 3."
  }
];
 
var comments = [
    {
        id: '1',
        postId: '3',
        text: 'Thanks!'
    },
    {
        id: '2',
        postId: '3',
        text: 'Good to know that!'
    },
    {
        id: '3',
        postId: '1',
        text: 'Great!'
    }
];
در اینجا دو آرایه ثابت از اشیاء مطالب و نظرات را مشاهده می‌کنید.
سپس جهت استفاده از آن، تعریف مدخل آن‌را به فایل index.html، پیش از تعاریف کنترلرها اضافه خواهیم کرد:
 <script src="Scripts/App/store.js" type="text/javascript"></script>


ویرایش قالب مطالب برای نمایش لیستی از عناوین ارسالی

قالب فعلی Scripts\Templates\posts.hbs صرفا دارای یک سری عنوان درج شده به صورت مستقیم در صفحه است. اکنون قصد داریم آن‌را جهت نمایش لیستی از آرایه مطالب تغییر دهیم.


همانطور که در تصویر ملاحظه می‌کنید، با درخواست آدرس صفحه‌ی مطالب، router آن مسیریابی متناظری را یافته و سپس بر این اساس، template، کنترلر و مدلی را انتخاب می‌کند. به صورت پیش فرض، قالب و کنترلر انتخاب شده، مواردی هستند همنام با مسیریابی جاری. اما مقدار پیش فرضی برای model وجود ندارد و باید آن‌را به صورت دستی مشخص کرد.
برای این منظور فایل Scripts\Routes\posts.js را به پوشه‌ی routes با محتوای ذیل اضافه کنید:
Blogger.PostsRoute = Ember.Route.extend({
    controllerName: 'posts',
    renderTemplare: function () {
        this.render('posts');
    },
    model: function () {
        return posts;
    }
});
در اینجا صرفا جهت نمایش پیش فرض‌ها و نحوه‌ی کار یک route، دو خاصیت controllerName و renderTemplare آن نیز مقدار دهی شده‌اند. این دو خاصیت به صورت پیش فرض، همنام مسیریابی جاری مقدار دهی می‌شوند و نیازی به ذکر صریح آن‌ها نیست. اما خاصیت model یک مسیریابی است که باید دقیقا مشخص شود. در اینجا مقدار آن‌را به آرایه posts تعریف شده در فایل Scripts\App\store.js تنظیم کرده‌ایم. به این ترتیب مدل تعریف شده در اینجا، به صورت خودکار در کنترلر posts و قالب متناظر با آن، قابل استفاده خواهد بود.
همچنین اگر به خاطر داشته باشید، در پوشه‌ی کنترلرها فایل posts.js تعریف نشده‌است. اگر اینکار صورت نگیرد، ember.js به صورت خودکار کنترلر پیش فرضی را ایجاد خواهد کرد. در کل، یک قالب هیچگاه به صورت مستقیم با مدل کار نمی‌کند. این کنترلر است که مدل را در اختیار یک قالب قرار می‌دهد.
سپس مدخل تعریف این فایل را به فایل index.html، پس از تعاریف کنترلرها اضافه نمائید:
 <script src="Scripts/Routes/posts.js" type="text/javascript"></script>

اکنون فایل Scripts\Templates\posts.hbs را گشوده و به نحو ذیل، جهت نمایش عناوین مطالب، ویرایش کنید:
<h2>Emeber.js blog</h2>
<ul>
    {{#each post in model}}
    <li>{{post.title}}</li>
    {{/each}}
</ul>
در این قالب، حلقه‌ای بر روی عناصر model تشکیل شده و سپس خاصیت title هر عضو نمایش داده می‌شود.




نمایش لیست آخرین نظرات ارسالی

در ادامه قصد داریم تا آرایه comments ابتدای بحث را در صفحه‌ای جدید نمایش دهیم. بنابراین نیاز است تا ابتدا مسیریابی آن تعریف شود. بنابراین فایل Scripts\App\router.js را گشوده و مسیریابی جدید recent-comments را به آن اضافه کنید:
Blogger.Router.map(function () {
    this.resource('posts', { path: '/' });
    this.resource('about');
    this.resource('contact', function () {
        this.resource('email');
        this.resource('phone');
    });
    this.resource('recent-comments');
});
سپس جهت تعیین مدل این مسیریابی جدید نیاز است تا فایل Scripts\Routes\recent-comments.js را در پوشه‌ی routes با محتوای ذیل اضافه کرد:
Blogger.RecentCommentsRoute = Ember.Route.extend({
    model: function () {
        return comments;
    }
});
در اینجا آرایه comments بازگشتی، همان آرایه‌ای است که در ابتدای بحث در فایل Scripts\App\store.js تعریف کردیم.
همچنین نیاز است تا تعریف مدخل این فایل جدید را نیز به انتهای تعاریف مداخل فایل index.html اضافه کنیم:
 <script src="Scripts/Routes/recent-comments.js" type="text/javascript"></script>

اکنون قالب application واقع در فایل Scripts\Templates\application.hbs را جهت افزودن منوی مرتبط با این مسیریابی جدید، به نحو ذیل ویرایش خواهیم کرد:
<div class='container'>
    <nav class='navbar navbar-default' role='navigation'>
        <ul class='nav navbar-nav'>
            <li>{{#link-to 'posts'}}Posts{{/link-to}}</li>
            <li>{{#link-to 'recent-comments'}}Recent comments{{/link-to}}</li>
            <li>{{#link-to 'about'}}About{{/link-to}}</li>
            <li>{{#link-to 'contact'}}Contact{{/link-to}}</li>
        </ul>
    </nav>
    {{outlet}}
</div>
و در آخر قالب جدید Scripts\Templates\recent-comments.hbs را برای نمایش لیست آخرین نظرات، با محتوای زیر اضافه می‌کنیم:
<h1>Recent comments</h1>
<ul>
  {{#each comment in model}}
    <li>{{comment.text}}</li>
  {{/each}}
</ul>
برای فعال شدن آن نیاز است تا تعریف این قالب جدید را به template loader برنامه، در فایل index.html اضافه کنیم:
<script type="text/javascript">
    EmberHandlebarsLoader.loadTemplates([
       'posts', 'about', 'application', 'contact', 'email', 'phone',
       'recent-comments'
    ]);
</script>



نمایش مجزای هر مطلب در یک صفحه‌ی جدید

تا اینجا در صفحه‌ی اول سایت، لیست عناوین مطالب را نمایش دادیم. در ادامه نیاز است تا بتوان هر عنوان را به صفحه‌ی متناظر و اختصاصی آن لینک کرد؛ برای مثال لینکی مانند http://localhost:25918/#/posts/3 به سومین مطلب ارسالی اشاره می‌کند. Ember.js به عدد 3 در اینجا، یک dynamic segment می‌گوید. از این جهت که مقدار آن بر اساس شماره مطلب درخواستی، متفاوت خواهد بود. برای پردازش این نوع آدرس‌ها نیاز است مسیریابی ویژه‌ای را تعریف کرد. فایل Scripts\App\router.js را گشوده و سپس مسیریابی post را به نحو ذیل به آن اضافه نمائید:
Blogger.Router.map(function () {
    this.resource('posts', { path: '/' });
    this.resource('about');
    this.resource('contact', function () {
        this.resource('email');
        this.resource('phone');
    });
    this.resource('recent-comments');
    this.resource('post', { path: 'posts/:post_id' });
});
قسمت پویای مسیریابی با یک : مشخص می‌شود.
با توجه به اینکه این مسیریابی جدید post نام گرفت (جهت نمایش یک مطلب)، به صورت خودکار، کنترلر و قالبی به همین نام را بارگذاری می‌کند. همچنین مدل خود را نیز باید از مسیریابی خاص خود دریافت کند. بنابراین فایل جدید Scripts\Routes\post.js را در پوشه‌ی routes با محتوای ذیل اضافه کنید:
Blogger.PostRoute = Ember.Route.extend({
    model: function (params) {
        return posts.findBy('id', params.post_id);
    }
});
در اینجا مدل مسیریابی post بر اساس پارامتری به نام params تعیین می‌شود. این پارامتر حاوی مقدار متغیر پویای post_id که در مسیریابی جدید post مشخص کردیم می‌باشد. در ادامه از آرایه posts تعریف شده در ابتدای بحث، توسط متد findBy که توسط Ember.js اضافه شده‌است، عنصری را که خاصیت id آن مساوی post_id دریافتی است، انتخاب کرده و به عنوان مقدار مدل بازگشت می‌دهیم.
برای مثال، جهت آدرس http://localhost:25918/#/posts/3، مقدار post_id به صورت خودکار به عدد 3 تنظیم می‌شود.

پس از آن نیاز است مدخل این فایل جدید را در صفحه‌ی index.html نیز اضافه کنیم:
 <script src="Scripts/Routes/post.js" type="text/javascript"></script>

در ادامه برای نمایش اطلاعات مدل نیاز است قالب جدید Scripts\Templates\post.hbs را با محتوای زیر اضافه کنیم:
 <h1>{{title}}</h1>
<p>{{body}}</p>
و template loader صفحه‌ی index.html را نیز باید از وجود آن باخبر کرد:
<script type="text/javascript">
    EmberHandlebarsLoader.loadTemplates([
       'posts', 'about', 'application', 'contact', 'email', 'phone',
       'recent-comments', 'post'
    ]);
</script>

اکنون به قالب Scripts\Templates\posts.hbs مراجعه کرده و هر عنوان را به مطلب متناظر با آن لینک می‌کنیم:
<h2>Emeber.js blog</h2>
<ul>
    {{#each post in model}}
    <li>{{#link-to 'post' post.id}}{{post.title}}{{/link-to}}</li>
    {{/each}}
</ul>
همانطور که ملاحظه می‌کنید، link-to امکان پذیرش id یک مطلب را به صورت متغیر نیز دارا است که سبب خواهد شد تا عناوین، به مطالب متناظر لینک شوند:


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



افزودن امکان ویرایش مطالب

می‌خواهیم در صفحه‌ی نمایش جزئیات یک مطلب، امکان ویرایش آن‌را نیز فراهم کنیم. بنابراین فایل Scripts\Templates\post.hbs را گشوده و محتوای آن‌را به نحو ذیل ویرایش کنید:
<h2>{{title}}</h2>
{{#if isEditing}}
<form>
    <div class="form-group">
        <label for="title">Title</label>
        {{input value=title id="title" class="form-control"}}
    </div>
    <div class="form-group">
        <label for="body">Body</label>
        {{textarea value=body id="body" class="form-control" rows="5"}}
    </div>
    <button class="btn btn-primary" {{action 'save' }}>Save</button>
</form>
{{else}}
<p>{{body}}</p>
<button class="btn btn-primary" {{action 'edit' }}>Edit</button>
{{/if}}
شبیه به این if و else را در قسمت قبل حین ایجاد صفحات about و یا contact نیز مشاهده کرده‌اید. در اینجا اگر خاصیت isEditing مساوی true باشد، فرم ویرایش اطلاعات ظاهر می‌شود و اگر خیر، محتوای مطلب جاری نمایش داده خواهد شد.
در فرم تعریف شده، المان‌های ورودی اطلاعات از handlebar helper‌های ویژه‌ی input و textarea استفاده می‌کنند؛ بجای المان‌های متداول HTML. همچنین value یکی به title و دیگری به body تنظیم شده‌است (خواص مدل ارائه شده توسط کنترلر متصل به قالب). این مقادیر نیز داخل '' قرار ندارند؛ به عبارتی در یک handlebar helper به عنوان متغیر در نظر گرفته می‌شوند. به این ترتیب اطلاعات کنترلر جاری، به این المان‌های ورودی اطلاعات به صورت خودکار bind می‌شوند و برعکس. اگر کاربر مقادیر آن‌ها را تغییر دهد، تغییرات نهایی به صورت خودکار به خواص متناظری در کنترلر جاری منعکس خواهند شد (two-way data binding).
دو دکمه نیز تعریف شده‌اند که به اکشن‌های save و edit متصل هستند.
بنابراین نیاز به یک کنترلر جدید، به نام post داریم تا بتوان رفتار قالب post را کنترل کرد. برای این منظور فایل جدید Scripts\Controllers\post.js را با محتوای ذیل ایجاد کنید:
Blogger.PostController = Ember.ObjectController.extend({
    isEditing: false,
    actions: {
        edit: function () {
            this.set('isEditing', true);
        },
        save: function () {
            this.set('isEditing', false);
        }
    }
});
همچنین مدخل تعریف آن‌را نیز به فایل index.html اضافه نمائید (پس از تعاریف کنترلرهای موجود):
 <script src="Scripts/Controllers/post.js" type="text/javascript"></script>

اگر به کدهای این کنترلر دقت کرده باشید، اینبار زیرکلاسی از ObjectController ایجاد شده‌است و نه Controller، مانند مثال‌های قبل. ObjectController تغییرات رخ داده بر روی خواص مدل را که توسط کنترلر در معرض دید قالب قرار داده‌است، به صورت خودکار به مدل مرتبط نیز منعکس می‌کند (Ember.ObjectController.extend)؛ اما Controller خیر (Ember.Controller.extend). در اینجا مدل کنترلر، تنها «یک» شیء است که بر اساس id آن انتخاب شده‌است. به همین جهت از ObjectController برای ارائه two-way data binding کمک گرفته شد.
در ember.js، یک قالب تنها با کنترلر خودش دارای تبادل اطلاعات است. اگر این کنترلر از نوع ObjectController باشد، تغییرات خاصیتی در یک قالب، ابتدا به کنترلر آن منعکس می‌شود و سپس این کنترلر، در صورت یافتن معادلی از این خاصیت در مدل، آن‌را به روز خواهد کرد. در حالت استفاده از Controller معمولی، صرفا تبادل اطلاعات بین قالب و کنترلر را شاهد خواهیم بود و نه بیشتر.

در ابتدای کار مقدار خاصیت isEditing مساوی false است. این مورد سبب می‌شود تا در بار اول بارگذاری اطلاعات یک مطلب انتخابی، صرفا عنوان و محتوای مطلب نمایش داده شوند؛ به همراه یک دکمه‌ی edit. با کلیک بر روی دکمه‌ی edit، مطابق کدهای کنترلر فوق، تنها خاصیت isEditing به true تنظیم می‌شود و در این حالت، بدنه‌ی اصلی شرط if isEditing در قالب post، رندر خواهد شد.

برای مثال در ابتدا مطلب شماره یک را انتخاب می‌کنیم:


با کلیک بر روی دکمه‌ی edit، فرم ویرایش ظاهر خواهد شد:


نکته‌ی جالب آن، مقدار دهی خودکار المان‌های ویرایش اطلاعات است. در این حالت سعی کنید، عنوان مطلب جاری را اندکی ویرایش کنید:


با ویرایش عنوان، می‌توان بلافاصله مقدار تغییر یافته را در برچسب عنوان مطلب نیز مشاهده کرد. این مورد دقیقا مفهوم two-way data binding و اتصال مقادیر value هر کدام از handlebar helper‌های ویژه‌ی input و textarea را به عناصر مدل ارائه شده توسط کنترلر post، بیان می‌کند.
در این حالت در کدهای متد save، تنها کافی است که خاصیت isEditing را به false تنظیم کنیم. زیرا کلیه مقادیر ویرایش شده توسط کاربر، در همان لحظه در برنامه منتشر شده‌اند و نیاز به کار بیشتری برای اعمال تغییرات نیست.


اضافه کردن دکمه‌ی مرتب سازی بر اساس عناوین، در صفحه‌ی اول سایت

Ember.ObjectController.extend برای data bindg یک شیء کاربرد دارد. اگر قصد داشته باشیم با آرایه‌ای از اشیاء کار کنیم می‌توان از ArrayController استفاده کرد. فرض کنید در صفحه‌ی اول سایت می‌خواهیم امکان مرتب سازی مطالب را بر اساس عنوان آن‌ها اضافه کنیم. فایل Scripts\Templates\posts.hbs را گشوده و لینک Sort by title را به انتهای آن اضافه کنید:
<h2>Emeber.js blog</h2>
<ul>
    {{#each post in model}}
    <li>{{#link-to 'post' post.id}}{{post.title}}{{/link-to}}</li>
    {{/each}}
</ul>
 
<a href="#" class="btn btn-primary" {{action 'sortByTitle'}}>Sort by title</a>
در اینجا چون قصد تغییر رفتار قالب posts را توسط اکشن جدید sortByTitle داریم، نیاز است کنترلر متناظر با آن‌را نیز اضافه کنیم. برای این منظور فایل جدید Scripts\Controllers\posts.js را به پوشه‌ی کنترلرها اضافه کنید؛ با محتوای ذیل:
Blogger.PostsController = Ember.ArrayController.extend({
    sortProperties: ['id'],// مقادیر پیش فرض مرتب سازی
    sortAscending: false,
    actions: {
        sortByTitle: function () {
            this.set('sortProperties', ['title']);
            this.set('sortAscending', !this.get('sortAscending'));
        }
    }
});
sortProperties جزو خواص کلاس پایه ArrayController است. اگر مانند سطر اول به صورت مستقیم مقدار دهی شود، خاصیت یا خواص پیش فرض مرتب سازی را مشخص می‌کند. اگر مانند اکشن sortByTitle توسط متد set مقدار دهی شود، امکان مرتب سازی تعاملی و با فرمان کاربر را فراهم خواهد کرد.

در ادامه، تعریف مدخل این کنترلر جدید را نیز باید به فایل index.html، اضافه کرد:
 <script src="Scripts/Controllers/posts.js" type="text/javascript"></script>

اگر برنامه را در این حالت اجرا کرده و بر روی دکمه‌ی Sort by title کلیک کنید، اتفاقی رخ نمی‌دهد. علت اینجا است که ArrayController خروجی تغییر یافته خودش را توسط خاصیتی به نام arrangedContent در اختیار قالب خود قرار می‌دهد. بنابراین نیاز است فایل قالب Scripts\Templates\posts.hbs را به نحو ذیل ویرایش کرد:
<h2>Emeber.js blog</h2>
<ul>
    {{#each post in arrangedContent}}
    <li>{{#link-to 'post' post.id}}{{post.title}}{{/link-to}}</li>
    {{/each}}
</ul>
 
<a href="#" class="btn btn-primary" {{action 'sortByTitle'}}>Sort by title</a>
اینبار کلیک بر روی دکمه‌ی مرتب سازی بر اساس عناوین، هربار لیست موجود را به صورت صعودی و یا نزولی مرتب می‌کند.



یک نکته: حلقه‌ی ویژه‌ای به نام each

اگر قالب Scripts\Templates\posts.hbs را به نحو ذیل، با یک حلقه‌ی each ساده بازنویسی کنید:
<h2>Ember.js blog</h2>
<ul>
    {{#each}}
    <li>{{#link-to 'post' id}}{{title}}{{/link-to}}</li>
    {{/each}}
</ul>
 
<a href="#" class="btn btn-primary" {{action 'sortByTitle'}}>Sort by title</a>
هم در حالت نمایش معمولی و هم در حالت استفاده از ArrayController برای نمایش اطلاعات مرتب شده، بدون مشکل کار می‌کند و نیازی به تغییر نخواهد داشت.



کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید:
EmberJS03_03.zip
مطالب
NHibernate 3.0 و خواص تنبل (lazy properties)!

احتمالا مطلب "دات نت 4 و کلاس Lazy" را پیشتر مطالعه کرده‌اید. هر چند NHibernate 3.0 بر اساس دات نت فریم ورک 3 و نیم تهیه شده، اما شبیه به این مفهوم را در مورد بارگذاری به تاخیر افتاده‌ی مقادیر خواص یک کلاس که به ندرت مورد استفاده قرار می‌گیرند، پیاده سازی کرده است. Lazy را در اینجا تنبل، به تعویق افتاده، با تاخیر و شبیه به آن می‌توان ترجمه کرد؛ خواص معوقه!
برای مثال فرض کنید یکی از خواص کلاس مورد استفاده، متن، تصویر یا فایلی حجیم است. در مکانی هم که قرار است وهله‌ای از این کلاس مورد استفاده قرار گیرد نیازی به این اطلاعات حجیم نیست؛ با سایر خواص آن سر و کار داریم و نیازی به اشغال حافظه و منابع سیستم در این مورد خاص نیست.

سؤال: چگونه آن‌را تعریف کنیم؟
اگر از NHibernate سنتی استفاده می‌کنید (یا به عبارتی فایل‌های hbm.xml را دستی تهیه می‌کنید)، ویژگی Lazy را به صورت زیر می‌توان مشخص کرد:
<property name="Text" lazy="true"/>
اگر از Fluent NHibernate استفاده می‌کنید (و فایل‌های hbm.xml به صورت خودکار از کلاس‌های شما تهیه خواهند شد)، روش کار به صورت زیر است (فراخوانی متد LazyLoad روی خاصیت مورد نظر):
public class Post
{
public virtual int Id { set; get; }
public virtual string PostText { set; get; }
}

public class PostMappings : ClassMap<Post>
{
public PostMappings()
{
Id(p => p.Id, "PostId").GeneratedBy.Identity();
Map(p => p.PostText).LazyLoad();
//…
Table("Posts");
}
}
در این حالت در پشت صحنه در مورد خاصیت PostText چنین نگاشتی تعریف خواهد شد:
<property name="PostText" type="System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" lazy="true" />

سؤال: چه زمانی نباید از این روش استفاده کرد؟
فرض کنید در شرایطی دیگر نیاز است تا اطلاعات تمام رکوردهای جدول مذکور به همراه مقدار PostText نمایش داده شود. در این حالت بسته به تعداد رکوردها، ممکن است هزاران هزار کوئری به دیتابیس ایجاد شود که مطلوب نیست (به ازای هربار دسترسی به خاصیت PostText یک کوئری تولید می‌شود).

البته امکان لغو موقت این روش تنها در حین استفاده از HQL (یکی دیگر از روش‌های دسترسی به داده‌ها در NHibernate) میسر است. اطلاعات بیشتر: (+)

نظرات مطالب
چک لیست تهیه یک برنامه ASP.NET MVC
فرض کنید که در UI فقط به نام کاربر و آدرس ایمیل کاربر احتیاج است،کلاس کاربر در در Domain به شرح زیر است:
public class User
{
   public int UserId {get;set;}
   public string Name {get;set;}
   public string Family {get;set;}
   public string Web {get;set;}
   public string Email {get;set;}
   public DateTime RegisterDate {get;set;}
}
حالا در یک کنترلر فقط به نام کاربر و آدرس ایمیل کاربر احتیاج است،حالا میایم یک کلاس تعریف میکنیم که شامل فیلدهای مورد نیاز است:
public class UserInfoViewModel
{
   public string Name {get;set;}
   public string Family {get;set;}
   public string Email {get;set;}
}
و لایه سرویس یک متد منحصر بفرد مختص این کار می نویسیم ،مثلاً:
public UserInfoViewModel GetMemberByUserName(string username)
{
   var result = from u in _users
                   where u.UserName == username
                   select new UserInfoViewModel() {Name = u.Name,Family=u.Family,Email=u.Email};

   return result;
}
1-اگه این روش درسته،حالا:
  • - برای هر متد که نیازه یه سری فیلد مورد نیاز رو برگردونه،یه کلاس جداگانه باید تعریف کرد؟(در اینجا UserInfoViewModel
  • - این کلاس UserInfoViewModel باید جز ViewModel‌های لایه UI باشه و در لایه Models قرار بگیره؟
      در اینصورت (کلاس UserInfoViewModel باید جز ViewModel‌های لایه UI باشه )،لایه سرویس   وابسته به لایه UI نمیشه؟
      اگر جواب منفیه،
      کلاس UserInfoViewModel تو کدوم لایه باید قرار بگیره؟
      دیگه نباید پسوند ViewModel رو به این کلاس اضافه کرد،درسته؟
2-اگه این روش کلاً اشتباهه،راه حل شما دقیقاً چیه؟
مطالب دوره‌ها
اصول طراحی یک سیستم افزونه پذیر به کمک StructureMap
قصد داریم سیستمی را طراحی کنیم که افزونه‌های خود را در زمان اجرا از مسیری خاص خوانده و سپس وهله‌های آن‌ها‌را جهت استفاده در دسترس قرار دهد. برنامه‌ای که در اینجا مورد بررسی قرار می‌گیرد، یک برنامه‌ی WinForms ساده است؛ به نام WinFormsWithPluginSupport. اما اصول کلی مطرح شده، در تمام فناوری‌های دیگر دات نتی نیز کاربرد دارد و یکسان است.


تهیه قرارداد

یک پروژه‌ی Class library به نام PluginsBase را به Solution جاری اضافه کنید. به آن اینترفیس قرار داد پلاگین‌های برنامه خود را اضافه نمائید. برای مثال:
namespace PluginsBase
{
    public interface IPlugin
    {
        string Name { get; }
        void Run();
    }
}
هر پلاگین دارای یک نام یا توضیح خاص خود خواهد بود به همراه متدی برای اجرای منطق مرتبط با آن.


تهیه سه پلاگین جدید

به Solution جاری سه پروژه‌ی مجزای Class library با نام‌های plugin1 تا 3 را اضافه کنید. در ادامه به هر پلاگین، ارجاعی را به اسمبلی PluginsBase، برای دریافت قرارداد پیاده سازی منطق پلاگین، اضافه نمائید. هدف این است که اینترفیس IPlugin، در این اسمبلی‌ها قابل دسترسی شود.
هر پلاگین هم دارای برای مثال کدهایی مانند کد ذیل خواهد بود که در آن صرفا نام آن‌ها به 2 و 3 تنظیم می‌شود.
using PluginsBase;

namespace Plugin1
{
    public class Plugin1Main : IPlugin
    {
        public string Name
        {
            get { return "Test 1"; }
        }

        public void Run()
        {
            // todo: ...
        }
    }
}


کپی خودکار پلاگین‌ها به پوشه‌ی مخصوص آن‌ها

به پروژه‌ی WinFormsWithPluginSupport مراجعه کنید. در پوشه‌ی bin\debug آن یک پوشه‌ی جدید به نام Plugins ایجاد نمائید. بدیهی است هربار که پلاگین‌های برنامه تغییر کنند نیاز است اسمبلی‌های نهایی آن‌ها را به این پوشه کپی نمائیم. اما راه بهتری نیز وجود دارد. به خواص هر کدام از پروژه‌های پلاگین مراجعه کرده و برگه‌ی Build events را باز کنید.


در اینجا قسمت post-build event را به نحو ذیل تغییر دهید:
 Copy "$(ProjectDir)$(OutDir)$(TargetName).*" "$(SolutionDir)WinFormsWithPluginSupport\bin\debug\Plugins"
این کار را برای هر سه پلاگین تکرار کنید.
به این ترتیب هربار که پلاگین جاری کامپایل شود، پس از آن به صورت خودکار به پوشه‌ی plugins تعیین شده، کپی می‌شود و دیگر نیازی به کپی دستی نخواهد بود.
تنظیم فوق، تنها اسمبلی اصلی پروژه را به پوشه‌ی bin\debug\plugins کپی می‌کند. اگر می‌خواهید تمام فایل‌ها کپی شوند، از تنظیم ذیل استفاده کنید:
 Copy "$(ProjectDir)$(OutDir)*.*" "$(SolutionDir)WinFormsWithPluginSupport\bin\debug\Plugins"


اضافه کردن وابستگی‌های اصلی پروژه‌ی WinForms

در ادامه بسته‌ی نیوگت StructureMap را به پروژه‌ی WinForms از طریق دستور ذیل اضافه کنید:
 PM> install-package structuremap
همچنین این پروژه تنها نیاز دارد ارجاع مستقیمی را به اسمبلی PluginsBase ابتدای مطلب داشته باشد. از آن، جهت تنظیمات اولیه یافتن افزونه‌ها استفاده می‌کنیم.


تعریف محل ثبت پلاگین‌ها

روش‌های متفاوتی برای کار با StructureMap وجود دارد. یکی از آن‌ها تعریف کلاسی است مشتق شده از کلاس Registry آن به نحو ذیل:
using System.IO;
using System.Windows.Forms;
using PluginsBase;
using StructureMap.Configuration.DSL;
using StructureMap.Graph;

namespace WinFormsWithPluginSupport.Core
{
    public class PluginsRegistry : Registry
    {
        public PluginsRegistry()
        {
            this.Scan(scanner =>
            {
                scanner.AssembliesFromPath(
                    path: Path.Combine(Application.StartupPath, "plugins"),
                    // یک اسمبلی نباید دوبار بارگذاری شود
                    assemblyFilter: assembly =>
                    {
                        return !assembly.FullName.Equals(typeof(IPlugin).Assembly.FullName);
                    });
                scanner.AddAllTypesOf<IPlugin>().NameBy(item => item.FullName);
            });
        }
    }
}
در اینجا مشخص کرده‌ایم که اسمبلی‌های پوشه plugins را که یک سطح پایین‌تر از پوشه‌ی اجرایی برنامه قرار می‌گیرند، خوانده و در این بین آن‌هایی را که پیاده سازی از اینترفیس IPlugin دارند، در دسترس قرار دهد.

یک نکته‌ی مهم
در قسمت assemblyFilter تعیین کرده‌ایم که اسمبلی تکراری PluginBase بارگذاری نشود. چون این اسمبلی هم اکنون به برنامه‌ی WinForms ارجاع دارد. رعایت این نکته جهت رفع تداخلات آتی بسیار مهم است. همچنین این فایل در پوشه‌ی Plugins نیز نباید حضور داشته باشد وگرنه شاهد بارگذاری افزونه‌ها نخواهید بود.


سپس نیاز به وهله سازی Container آن و معرفی این کلاس PluginsRegistry می‌باشد:
using System;
using System.Threading;
using StructureMap;

namespace WinFormsWithPluginSupport
{
    public static class IocConfig
    {
        private static readonly Lazy<Container> _containerBuilder =
            new Lazy<Container>(defaultContainer, LazyThreadSafetyMode.ExecutionAndPublication);

        public static IContainer Container
        {
            get { return _containerBuilder.Value; }
        }

        private static Container defaultContainer()
        {
            return new Container(x =>
            {
                x.AddRegistry<PluginsRegistry>();
            });
        }
    }
}


تنظیمات ابتدایی WinForms برای دسترسی به امکانات StructureMap

به فرم اصلی برنامه مراجعه کرده و به سازنده‌ی آن IContainer را اضافه کنید. از این اینترفیس جهت دسترسی به پلاگین‌های برنامه استفاده خواهیم کرد.
using System.Windows.Forms;
using StructureMap;

namespace WinFormsWithPluginSupport
{
    public partial class FrmMain : Form
    {
        private readonly IContainer _container;

        public FrmMain(IContainer container)
        {
            _container = container;
            InitializeComponent();
        }
    }
}
اکنون برنامه دیگر کامپایل نخواهد شد؛ چون در فایل Program.cs وهله سازی مستقیمی از FrmMain وجود دارد. این وهله سازی را اکنون به StructureMap محول می‌کنیم تا مشکل برطرف شود:
using System;
using System.Windows.Forms;

namespace WinFormsWithPluginSupport
{
    static class Program
    {
        [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(IocConfig.Container.GetInstance<FrmMain>());
        }
    }
}
زمانیکه از متد IocConfig.Container.GetInstance استفاده می‌شود، تا هر تعداد سطحی که تعریف شده، سازنده‌های کلاس‌های مرتبط وهله سازی می‌شوند. در اینجا نیاز است سازنده‌ی کلاس FrmMain وهله سازی شود. چون IContainer اینترفیس اصلی خود StructureMap است، آن‌را شناخته و به صورت خودکار وهله سازی می‌کند. اگر اینترفیس دیگری را ذکر کنید، نیاز است مطابق معمول آن‌را در کلاس IocConfig و متد defaultContainer آن معرفی نمائید.


بارگذاری و اجرای افزونه‌ها

دو دکمه‌ی Run و ReLoad را به فرم اصلی برنامه با کدهای ذیل اضافه کنید:
using System.Linq;
using System.Windows.Forms;
using PluginsBase;
using StructureMap;
using WinFormsWithPluginSupport.Core;

namespace WinFormsWithPluginSupport
{
    public partial class FrmMain : Form
    {
        private readonly IContainer _container;

        public FrmMain(IContainer container)
        {
            _container = container;
            InitializeComponent();
        }

        private void BtnRun_Click(object sender, System.EventArgs e)
        {
            var plugins = _container.GetAllInstances<IPlugin>().ToList();
            foreach (var plugin in plugins)
            {
                plugin.Run();
            }
        }

        private void BtnReload_Click(object sender, System.EventArgs e)
        {
            _container.EjectAllInstancesOf<IPlugin>();
            _container.Configure(x =>
                x.AddRegistry<PluginsRegistry>()
            );
        }
    }
}
در اینجا توسط متد container.GetAllInstances می‌توان به تمامی وهله‌های پلاگین‌های بارگذاری شده، دسترسی یافت و سپس آن‌ها را اجرا کرد.
همچنین در متد ReLoad نحوه‌ی بارگذاری مجدد این پلاگین‌ها را در صورت نیاز مشاهده می‌کنید.
اگر برنامه را اجرا کردید و پلاگینی بارگذاری نشد، به دنبال اسمبلی‌های تکراری بگردید. برای مثال PluginsBase نباید هم در پوشه‌ی اصلی اجرایی برنامه حضور داشته باشد و هم در پوشه‌ی پلاگین‌ها.


کدهای کامل این مثال را از اینجا می‌توانید دریافت کنید
WinFormsWithPluginSupport.zip
 
مطالب
تهیه گزارشات Crosstab به کمک LINQ

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

مثال اول) تهیه گزارش Crosstab جمع هزینه‌های واحدهای مختلف به تفکیک ماه

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

using System;

namespace Pivot.Sample1
{
public class Expense
{
public DateTime Date { set; get; }
public string Department { set; get; }
public decimal Expenses { set; get; }
}
}

با توجه به این کلاس، یک منبع داده آزمایشی جهت تهیه گزارشات، می‌تواند به صورت زیر باشد:

using System;
using System.Collections.Generic;

namespace Pivot.Sample1
{
public class ExpenseDataSource
{
public static IList<Expense> ExpensesDataSource()
{
return new List<Expense>
{
new Expense { Date = new DateTime(2011,11,1), Department = "Computer", Expenses = 100 },
new Expense { Date = new DateTime(2011,11,1), Department = "Math", Expenses = 200 },
new Expense { Date = new DateTime(2011,11,1), Department = "Physics", Expenses = 150 },

new Expense { Date = new DateTime(2011,10,1), Department = "Computer", Expenses = 75 },
new Expense { Date = new DateTime(2011,10,1), Department = "Math", Expenses = 150 },
new Expense { Date = new DateTime(2011,10,1), Department = "Physics", Expenses = 130 },

new Expense { Date = new DateTime(2011,9,1), Department = "Computer", Expenses = 90 },
new Expense { Date = new DateTime(2011,9,1), Department = "Math", Expenses = 95 },
new Expense { Date = new DateTime(2011,9,1), Department = "Physics", Expenses = 100 }
};
}
}
}

و اگر این لیست را به همین شکلی که هست نمایش دهیم، خروجی زیر را خواهیم داشت:


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


یعنی اگر سه ماه را در نظر بگیریم با هر تعداد رکورد، فقط سه ردیف به ازای هر ماه باید حاصل شود و ستون‌های دیگر هم نام بخش‌ها یا واحدهای موجود باشند.
برای رسیدن به این خروجی Crosstab، می‌توان کوئری LINQ زیر را به کمک امکانات گروه بندی اطلاعات آن تهیه کرد:

using System.Collections;
using System.Linq;

namespace Pivot.Sample1
{
public class PivotTable
{
public static IList ExpensesCrossTab()
{
return ExpenseDataSource
.ExpensesDataSource()
.GroupBy(t =>
new
{
Year = t.Date.Year,
Month = t.Date.Month
})
.Select(myGroup =>
new
{
//Year = myGroup.Key.Year,
Month = myGroup.Key.Month,
ComputerDepartment = myGroup.Where(x => x.Department == "Computer").Sum(x => x.Expenses),
MathDepartment = myGroup.Where(x => x.Department == "Math").Sum(x => x.Expenses),
PhysicsDepartment = myGroup.Where(x => x.Department == "Physics").Sum(x => x.Expenses)
})
.ToList();
}
}
}

که اینبار خروجی زیر را تولید می‌کند.


اگر علاقمند باشید که مثال فوق را در برنامه‌ی LINQPad آزمایش کنید، این فایل را دریافت نموده و در آن برنامه باز نمائید.


مثال دوم) تهیه لیست Crosstab حضور و غیاب افراد در طول یک هفته

کلاس StudentStat را جهت ثبت اطلاعات حضور یک دانشجو، می‌توان به شکل زیر تعریف کرد:

using System;

namespace Pivot.Sample2
{
public class StudentStat
{
public int Id { set; get; }
public string Name { set; get; }
public DateTime Date { set; get; }
public bool IsPresent { set; get; }
}
}

و بر همین اساس یک منبع داده فرضی جهت انجام گزارشات می‌تواند به نحو زیر تهیه شود:

using System;
using System.Collections.Generic;

namespace Pivot.Sample2
{
public class StudentsStatDataSource
{
public static IList<StudentStat> CreateMonthlyReportDataSource()
{
var result = new List<StudentStat>();
var rnd = new Random();

for (int day = 1; day < 6; day++)
{
for (int student = 1; student < 6; student++)
{
result.Add(new StudentStat
{
Id = student,
Date = new DateTime(2011, 11, day),
IsPresent = rnd.Next(-1, 1) == 0 ? true : false,
Name = "student " + student
});
}
}

return result;
}
}
}

خروجی این گزارش هم در این حالت ساده با 5 دانشجو و فقط 5 روز، 25 رکورد خواهد بود:


که ... این هم آنچنان از لحاظ آماری مطلوب و مفهوم نیست. می‌خواهیم سطرهای این گزارش همانند لیست واقعی حضورغیاب، فقط از نام افراد تشکیل شود و همچنین ستون‌ها مثلا شماره یا نام روزهای یک هفته یا ماه باشند. مثلا به شکل زیر:


برای رسیدن به این خروجی Crosstab، مثلا می‌توان از کوئری LINQ زیر کمک گرفت که بر اساس شماره دانشجویی اطلاعات را گروه بندی کرده است:

using System.Collections;
using System.Linq;

namespace Pivot.Sample2
{
public class PivotTable
{
public static IList StudentsStatCrossTab()
{
return StudentsStatDataSource
.CreateWeeklyReportDataSource()
.GroupBy(x =>
new
{
x.Id
})
.Select(myGroup =>
new
{
myGroup.Key.Id,
Name = myGroup.First().Name,
Day1IsPresent = myGroup.Where(x => x.Date.Day == 1).First().IsPresent,
Day2IsPresent = myGroup.Where(x => x.Date.Day == 2).First().IsPresent,
Day3IsPresent = myGroup.Where(x => x.Date.Day == 3).First().IsPresent,
Day4IsPresent = myGroup.Where(x => x.Date.Day == 4).First().IsPresent,
Day5IsPresent = myGroup.Where(x => x.Date.Day == 5).First().IsPresent,
PresentsCount = myGroup.Where(x => x.IsPresent).Count(),
AbsentsCount = myGroup.Where(x => !x.IsPresent).Count()
})
.ToList();
}
}
}

و این کوئری خروجی زیر را تولید می‌کند که از هر لحاظ نسبت به لیست قبلی مفهوم‌تر است:


فایل LINQPad این مثال را می‌توانید از اینجا دریافت کنید.

مطالب
PowerShell 7.x - قسمت سیزدهم - ساخت یک Static Site Generator ساده توسط PowerShell و GitHub Actions
در این مطلب میخواهیم یک مثال دیگر از PowerShell را به همراه GitHub Actions را بررسی کنیم. هدف ایجاد یک Static Site Generator و در نهایت پابلیش خروجی استاتیک بر روی GitHub Pages است. روالی که در ادامه بررسی میکنیم صرفاً یک مثال از ترکیب این تکنولوژی‌ها است و قاعدتاً روش‌های ساده‌تر و سرراست‌تری نیز برای اینکار وجود دارد. به عنوان مثال میتوانید از Jekyll که یک SSG مبتنی بر Ruby است نیز استفاده کنید که GitHub Pages، به صورت پیش‌فرض از آن پشتیبانی میکند. در اینحالت به محض پوش کردن سایت بر روی ریپوزیتوری (با فرض اینکه این امکان را فعال کرده باشید) به صورت خودکار سایت بیلد شده و خروجی بر روی یک برنچ دیگر قرار خواهد گرفت و در نهایت برنچ بیلد شده توسط GitHub Pages میزبانی خواهد شد (البته امکان تغییر برنچ پیش‌فرض را نیز دارید). اما اگر بخواهیم کل فرآیند بیلد را به صورت سفارشی انجام دهیم، میتوانیم از GitHub Actions استفاده کنیم؛ یعنی مشابه کاری که Jekyll انجام میدهد. به محض پوش کردن محتوا، یک اسکریپت PowerShell برای اینکار فراخوانی شود و خروجی نهایی بر روی یک برنچ دیگر منتشر شود. خروجی نهایی این چنین قالبی خواهد داشت:

نکته: در اینجا از فونت آقای راستی‌کردار استفاده شده است؛ با آرزوی بهبودی و سلامتی ایشان.

ساختار پروژه
ساختاری که برای پروژه در نظر گرفته‌ام به صورت زیر است:
├── _layout
│   ├── _footer.html
│   ├── _header.html
│   ├── _nav.html
│   └── main.html
├── build
├── img
├── posts
└── set-posts.ps1
  • دایرکتوری layout_: درون این دایرکتوری، ساختار اصلی بلاگ را قرار داده‌ایم. در ادامه محتویات هر فایل را مشاهده خواهید کرد: 
<!--main.html-->
<!DOCTYPE html>
<html dir="rtl">

{{header}}

<body>
    {{nav}}
    <main>
        {{content}}
    </main>
    {{footer}}
</body>



<!--_header.html-->
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>{{title}}</title>
    <link href="https://cdn.jsdelivr.net/gh/rastikerdar/samim-font@v4.0.5/dist/font-face.css" rel="stylesheet"
        type="text/css" />
    <link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.16/dist/tailwind.min.css" rel="stylesheet">
    <style>
        * {
        }
    </style>
</head>

<!--_nav.html-->
<header>
    <nav>
        <div>
            <div>
                <a href="#">بلاگ من</a>
            </div>
            <div>
                <ul>
                    {{nav}}
                </ul>
            </div>
        </div>
    </nav>
</header>


<!--_footer.html-->
<footer>
    <div>
        <div>
            <p>
                تمامی حقوق محفوظ است
            </p>
        </div>
    </div>
</footer>
  • دایرکتوری build: درون این دایرکتوری، خروجی‌های HTML که قرار است توسط اسکریپت PowerShell جنریت شوند، قرار خواهند گرفت. این پوشه در واقع قرار است توسط GitHub Pages میزبانی شود.
  • دایرکتوری img: درون این دایرکتوری، تصاویر مربوط به هر بلاگ‌پست را قرار خواهیم داد.
  • دایرکتوری posts: درون این دایرکتوری، مطالب‌مان را با فرمت Markdown، قرار خواهیم داد. به عنوان مثال در ادامه یک نمونه از آن را مشاهده خواهید کرد (در کد زیر از Front Matter برای اضافه کردن یکسری متادیتای موردنیاز که حین بیلد شدن ضروری هستند استفاده شده‌است) 
---
title: اولین پست من
slug: hello
date: 2023-04-26
author: سیروان عفیفی
tags: [tag1, tag2, tag3]
excerpt: این یک پست تستی است در مورد اینکه چطور میتوانیم از این قالب استفاده کنیم
---

# اولین پست من

## اولین پست من

### اولین پست من

#### اولین پست من

##### اولین پست من

###### اولین پست من

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

<img src="/img/graphql.jpg"/>
همانطور که مشاهده میکنید، مسیر تصویر استفاده شده در بلاگ‌پست، از دایرکتوری img خوانده شده‌است.
  • فایل set_post.ps1: موتور اصلی جنریت کردن صفحات HTML این فایل میباشد. در ادامه محتویات آن را مشاهده خواهید کرد. سپس هر کدام از توابع استفاده شده را یکی‌یکی توضیح خواهیم داد: 
Function Get-Layouts {
    $headerLayout = Get-Content -Path ./_layout/_header.html -Raw
    $homeLayout = Get-Content -Path ./_layout/main.html -Raw
    $footerLayout = Get-Content -Path ./_layout/_footer.html -Raw

    Return @{
        Header = $headerLayout
        Home   = $homeLayout
        Footer = $footerLayout
    }
}

Function Get-PostFrontMatter($postContent) {
    $frontMatter = [regex]::Match($postContent, "(---(?:\r?\n(?!--|\s*$).*)*)\s*((?:\r?\n(?!---).*)*\r?\n---)")
    Return $frontMatter
}

Function Set-Headings($postHtml) {
    Return $postHtml -Replace '<h(\d) id="(.*)">', {
        $level = $_.Groups[1].Value
        $id = $_.Groups[2].Value
        $class = Switch ($level) {
            '1' { 'text-4xl font-bold mb-2' }
            '2' { 'text-3xl font-bold mb-2' }
            '3' { 'text-2xl font-bold mb-2' }
            '4' { 'text-xl font-bold mb-2' }
            '5' { 'text-lg font-bold mb-2' }
            '6' { 'text-base font-bold mb-2' }
        }
        "<h$level class='$class' id='$id'>"
    }
}

Function ConvertTo-Slug {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [string]$String
    )
    process {
        $slug = $String -replace '[^\w\s-]', '' # remove non-word characters except hyphens
        $slug = $slug -replace '\s+', '-' # replace whitespace with a single hyphen
        $slug = $slug -replace '^-+|-+$', '' # remove leading/trailing hyphens
        $slug = $slug.ToLower() # convert to lowercase
        Write-Output $slug
    }
}

Function Get-Posts {
    $markdownPosts = Get-ChildItem -Path ./posts -Filter *.md
    $posts = @()
    Foreach ($post in $markdownPosts) {
        $postContent = Get-Content -Path $post.FullName -Raw
        $frontMatter = Get-PostFrontMatter $postContent
        $frontMatterObject = $frontMatter | ConvertFrom-Yaml

        $slug = $frontMatterObject.slug ?? (ConvertTo-Slug "$($frontMatterObject.date)-$($frontMatterObject.title)")
        $body = $postContent.Replace($frontMatter.Value, "") | ConvertFrom-Markdown
        $postHtml = $layouts.Home -replace '{{header}}', $layouts.Header `
            -replace '{{title}}', $frontMatterObject.title `
            -replace '{{nav}}', (Set-Navs) `
            -replace '{{content}}', $body.Html `
            -replace '{{footer}}', $layouts.Footer

        $postHtml = Set-Headings $postHtml
        $postHtml | Out-File -FilePath ./build/$slug.html

        $posts += @{
            title   = $frontMatterObject.title
            slug    = $slug
            excerpt = $frontMatterObject.excerpt
            date    = $frontMatterObject.date
            author  = $frontMatterObject.author
            body    = $body.Html
        }
    }
    Return $posts
}

Function Set-Archive {
    $posts = Get-Posts
    $archive = @()
    $archive = @"
        <ul>
            $($posts | ForEach-Object { "<li><a href='$($_.slug).html'>$($_.title)</a></li>" })
        </ul>
"@
    Return $archive -join "`r`n"
}

Function Copy-ToBuild {
    $layouts = Get-Layouts
    $latestPosts = Get-Posts | ForEach-Object { @"
    <div>
        <img src="https://via.placeholder.com/300x200" alt="$($_.title)">
        <h2>$($_.title)</h2>
        <p>$($_.excerpt)</p>
            <a href="$($_.slug).html">ادامه مطلب</a>
        </div>
"@
    }

    $homeLayout = $layouts.Home -replace '{{header}}', $layouts.Header `
        -replace '{{nav}}', (Set-Navs) `
        -replace '{{title}}', 'بلاگ من' `
        -replace '{{content}}', ('<div>' + $latestPosts + '</div>') `
        -replace '{{footer}}', $layouts.Footer

    $homeLayout | Out-File -FilePath ./build/index.html

    Copy-Item -Path ./img -Destination ./build -Recurse -Force

}

Function Set-Navs {
    $navs = @(
        @{
            title = "صفحه اصلی"
            url   = "/sample"
        },
        @{
            title = "درباره ما"
            url   = "/sample/about.html"
        },
        @{
            title = "تماس با ما"
            url   = "/sample/contact.html"
        }
    )
    $navLayout = Get-Content -Path ./_layout/_nav.html -Raw
    $navLayout -replace '{{nav}}', ($navs | ForEach-Object { "<li><a href=""$($_.url)""text-gray-700 hover:text-gray-800 m-2"">$($_.title)</a></li>" })
}


Copy-ToBuild
همانطور که در کد فوق مشاهده میکنید، تابع Copy-ToBuild فراخوانی شده است. درون این تابع، ابتدا لی‌اوت‌های موردنیاز برای تولید صفحات HTML را درون یک متغییر با نام layouts قرار داده‌ایم. درون لی‌اوت‌ها یکسری placeholder برای قرارگیری قسمت‌های مختلف سایت تعریف کرده‌ایم که قرار است توسط تابع عنوان شده، جایگزین شوند. در ادامه توسط تابع Get-Posts، تمامی مطالب درون دایرکتوری posts را واکشی کرده و برای هر کدام، صفحه‌ی HTML معادل آن را تولید کرده‌ایم. برای پارز کردن قسمت Front Matter هر بلاگ‌پست نیز از پکیج powershell-yaml استفاده شده‌است. درون تابع Get-Posts باید هر مطلب را همراه با لی‌اوت اصلی سایت، به HTML تبدیل کنیم. بنابراین یکسری عملیات string replacement درون تابع عنوان شده انجام گرفته است. در نهایت، مطالب همراه با لی‌اوت مناسبی درون دایرکتوری build ذخیره خواهند شد و سپس یک لیست از مطالب پارز شده به عنوان خروجی تابع برگردانده خواهد شد. در نهایت درون تابع Copy-ToBuild، یکسری card برای نمایش آخرین مطالب بلاگ تهیه شده، سپس خروجی نهایی درون فایل index.html همراه با قالب اصلی ذخیره خواهد شد.

ایجاد GitHub Actions Workflow
در ادامه برای ساختن workflow نهایی باید با کمک GitHub Actions، اسکریپت PowerShellی را که ساختیم، اجرا کنیم. این اسکریپت ابتدا پروژه را clone کرده، سپس وابستگی موردنیاز را نصب کرده و در نهایت اسکریپت را اجرا خواهد کرد: 
name: Deploy static content to Pages

on:
  push:
    branches:
      - main
  workflow_dispatch:

permissions:
  contents: read
  pages: write
  id-token: write

concurrency:
  group: "pages"
  cancel-in-progress: false

jobs:
  build-and-deploy:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout Repository
        uses: actions/checkout@v2

      - name: Install powershell-yaml module
        shell: pwsh
        run: |
          Set-PSRepository PSGallery -InstallationPolicy Trusted
          Install-Module powershell-yaml -ErrorAction Stop

      - name: Setup Pages
        uses: actions/configure-pages@v3

      - name: Build Static Site
        shell: pwsh
        run: |
          . ./set-posts.ps1

      - name: Upload Static Site Artifact
        uses: actions/upload-pages-artifact@v1
        with:
          path: build

      - name: Deploy to GitHub Pages
        id: deployment
        uses: actions/deploy-pages@v2

نحوه انتشار یک مطلب جدید
  • درون دایرکتوری posts، مطلب موردنظر را به همراه Front Matter زیر ایجاد کرده و سپس محتویات مطلب را بعد از آن وارد کنید:
---
title: اولین پست من
slug: hello
date: 2023-04-26
author: سیروان عفیفی
tags: [tag1, tag2, tag3]
excerpt: این یک پست تستی است در مورد اینکه چطور میتوانیم از این قالب استفاده کنیم
---

content
  • تغییرات ایجاد شده را کامیت و سپس پوش کنید. به محض پوش کردن تغییرات، GitHub Actions پروسه بیلد را انجام خواهد داد و بلافاصله میتوانید تغییرات را مشاهده نمائید.
نکته: قابلیت GitHub Pages به صورت پیش‌فرض فعال نیست. برای فعال کردن آن ابتدا باید به قسمت تنظیمات ریپوزیتوری GitHubتان مراجعه کرده و سپس از تب Pages، فیلد Source را بر روی GitHub Actions قرار دهید:


بنابراین بعد از پوش کردن تغییرات workflowایی که ایجاد کردیم توسط Source شناسایی خواهد شد و سپس وب‌سایت از طریق آدرس https://<username>.github.io/<repository> در دسترس قرار خواهد گرفت:

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

مطالب
آموزش WAF (مشاهده تغییرات خواص ViewModel در Controller)
قصد داریم در مثال پست قبلی برای Command مورد نظر، عملیات اعتبارسنجی را فعال کنیم. اگر با الگوی MVVM آشنایی داشته باشید می‌دانید که می‌توان برای Command‌ها اکشنی به عنوان CanExecute تعریف کرد و در آن عملیات اعتبارسنجی را انجام داد. اما از آن جا که پیاده سازی این روش زمانی مسیر است که تغییرات خواص ViewModel در دسترس باشد در نتیجه در WAF مکانیزمی جهت ردیابی تغییرات خواص ViewModel در کنترلر فراهم شده است. در نسخه‌های قبلی WAF (قبل از نسخه 3) هر کنترلر از کلاس پایه ای به نام Controller ارث می‌برد که متد هایی جهت ردیابی تغییرات در آن در نظر گرفته شده بود به صورت زیر:
public class MyController : Controller
    {
        [ImportingConstructor]
        public MyController(MyViewModel viewModel)
        {
            ViewModelCore = viewModel;
        }

        public MyViewModel ViewModelCore 
        {
            get; 
            private set; 
        }

        public void Run()
        {
            AddWeakEventListener(ViewModelCore , ViewModelCoreChanged)
        }

        private void ViewModelCoreChanged(object sender , PropertyChangedEventArgs e)
        {
            if(e.PropertyName=="CurrentItem")
            {
                
            }
        }
    }
همان طور که مشاهده می‌کنید با استفاده از متد AddWeakEventListener توانستیم تمامی تغییرات خواص ViewModel مورد نظر را از طریق متد ViewModelCoreChanged ردیابی کنیم. این متد بر مبنای الگوی WeakEvent پیاده سازی شده است. البته این تغییرات فقط زمانی قابل ردیابی هستند که  در ViewModel متد RaisePropertyChanged برای متد set خاصیت فراخوانی شده باشد.
از آنجا که در دات نت 4.5 یک پیاده سازی خاص از الگوی WeakEvent در کلاس PropertyChangedEventManager موجود در اسمبلی WindowsBase و فضای نام System.ComponentModel انجام شده است در نتیجه توسعه دهندگان این کتابخانه نیز تصمیم به استفاده از این روش گرفتند که نتیجه آن  Obsolete شدن کلاس پایه کنترلر در نسخه‌های 3 به بعد آن است. در روش جدید کافیست به صورت زیر عمل نمایید:
 [Export]
    public class BookController
    {
        [ImportingConstructor]
        public BookController(BookViewModel viewModel)
        {
            ViewModelCore = viewModel;
        }
        
        public BookViewModel ViewModelCore
        {
            get;
            private set;
        }

        public DelegateCommand RemoveItemCommand 
        { 
            get; 
            private set;
        }

        private void ExecuteRemoveItemCommand()
        {
            ViewModelCore.Books.Remove(ViewModelCore.CurrentItem);
        }

        private bool CanExecuteRemoveItemCommand()
        {
            return ViewModelCore.CurrentItem != null;
        }
        private void Initialize()
        {
            RemoveItemCommand = new DelegateCommand(ExecuteRemoveItemCommand , CanExecuteRemoveItemCommand);
            ViewModelCore.RemoveItemCommand = RemoveItemCommand;
        }

        public void Run()
        {
            var result = new List<Book>();
            result.Add(new Book { Code = 1, Title = "Book1" });
            result.Add(new Book { Code = 2, Title = "Book2" });
            result.Add(new Book { Code = 3, Title = "Book3" });

            Initialize();
            ViewModelCore.Books = new ObservableCollection<Models.Book>(result);

            PropertyChangedEventManager.AddHandler(ViewModelCore, ViewModelChanged, "CurrentItem");
            
            (ViewModelCore.View as IBookView).Show();
        }

        private void ViewModelChanged(object sender,PropertyChangedEventArgs e)
        {
            if(e.PropertyName == "CurrentItem")
            {
                RemoveItemCommand.RaiseCanExecuteChanged();
            }
        }
    }
تغییرات:
»ابتدا متدی به نام CanExecuteRemoveItemCommand ایجاد کردیم و کد‌های اعتبارسنجی را در آن قرار دادیم؛
»هنگام تعریف Command مربوطه متد بالا را به DelegateCommand رجیستر کردیم:
  RemoveItemCommand = new DelegateCommand(ExecuteRemoveItemCommand , CanExecuteRemoveItemCommand);
در این حالت بعد از اجرای برنامه همواره دکمه RemoveItem غیر فعال خواهد بود. دلیل آن این است که بعد از انتخاب آیتم مورد نظر از لیست باید کنترلر را متوجه تغییر در مقدار خاصیت CurrentItem نماییم. بدین منظور کد زیر را به متد Run اضافه کردم:
     PropertyChangedEventManager.AddHandler(ViewModelCore, ViewModelChanged, "CurrentItem");
دستور بالا دقیقا معادل دستور AddWeakEventListener موجود در نسخه‌های قدیمی WAF است. سپس در صورتی که نام خاصیت مورد نظر CurrentItem بود با استفاده از دستور RaiseCanExecuteChanged در کلاس DelegateCommand کنترلر را ملزم به اجرای دوباره متد CanExecuteRemoveItemCommand می‌کنیم.
اجرای برنامه:
ابتدا دکمه RemoveItem غیر فعال است:

بعد از انتخاب یکی از گزینه و فراخوانی مجدد متد CanExecuteRemoveItemCommand دکمه مورد نظر فعال می‌شود:

و در نهایت دکمه RemoveItem فعال خواهد شد:


دانلود سورس پروژه
مطالب
آشنایی با mocking frameworks (چارچوب‌های تقلید) - قسمت اول

این مطلب در ادامه‌ی مطالب آزمو‌ن‌های واحد یا unit testing است.
نوشتن آزمون واحد برای کلاس‌هایی که با یک سری از الگوریتم‌ها ، مسایل ریاضی و امثال آن سر و کار دارند، ساده است. عموما این نوع کلاس‌ها وابستگی خارجی آنچنانی ندارند؛ اما در عمل کلاس‌های ما ممکن است وابستگی‌های خارجی بسیاری پیدا کنند؛ برای مثال کار با دیتابیس، اتصال به یک وب سرویس، دریافت فایل از اینترنت، خواندن اطلاعات از انواع فایل‌ها و غیره.
مطابق اصول آزمایشات واحد، یک آزمون واحد خوب باید ایزوله باشد. نباید به مرزهای سیستم‌های دیگر وارد شده و عملکرد سیستم‌های خارج از کلاس را بررسی کند.
این مثال ساده را در نظر بگیرید:
فرض کنید برنامه شما قرار است از یک وب سرویس لیستی از آدرس‌های IP یک کشور خاص را دریافت کند و در یک دیتابیس محلی آن‌ها را ذخیره نماید. به صورت متداول این کلاس باید اتصالی را به وب سرویس گشوده و اطلاعات را دریافت کند و همچنین آن‌ها را خارج از مرز کلاس در یک دیتابیس ثبت کند. نوشتن آزمون واحد برای این کلاس مطابق اصول مربوطه غیر ممکن است. اگر کلاس آزمون واحد آن‌را تهیه نمائید، این آزمون، integration test نام خواهد گرفت زیرا از مرزهای سیستم باید عبور نماید.

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

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

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

public static string GetComparisonText(IComparable a, IComparable b)
{
if (a.CompareTo(b) == 1)
return "a is bigger";
if (a.CompareTo(b) == -1)
return "b is bigger";
return "same";
}
در این مثال چون از IComparable استفاده شده، متد ما از هر نوع داده‌ای جهت مقایسه می‌تواند استفاده کند. تنها موردی که برای آن مهم خواهد بود این است که a راهی را برای مقایسه با b ارائه دهد.

اکنون با توجه به این توضیحات، برای ایزوله کردن ارتباط با دیتابیس و وب سرویس در مثال فوق، می‌توان اینترفیس‌های زیر را تدارک دید:
    public interface IEmailSource
{
IEnumerable<string> GetEmailAddresses();
}

public interface IEmailDataStore
{
void SaveEmailAddresses(IEnumerable<string> emailAddresses);

}
در اینجا استفاده و تعریف اینترفیس‌ها چندین خاصیت را به همراه خواهد داشت :
الف) به این صورت تنها مشخص می‌شود که چه کاری را قصد داریم انجام دهیم و کاری به پیاده سازی آن نداریم.
ب) ساخت کلاس بدون وجود یا دسترسی به یک دیتابیس میسر می‌شود. این مورد خصوصا در یک کار تیمی که قسمت‌های مختلف کار به صورت همزمان در حالت پیشرفت و تهیه است حائز اهمیت می‌شود.
ج) با توجه به اینکه در اینجا به پیاده سازی توجهی نداریم، می‌توان از این اینترفیس‌ها جهت تقلید دنیای واقعی استفاده کنیم. (که در اینجا mocking نام گرفته است)

جهت تقلید رفتار و عملکرد این دو اینترفیس، به کلاس‌های تقلید زیر خواهیم رسید:

public class MockEmailSource : IEmailSource
{
public IEnumerable<string> EmailAddressesToReturn { get; set; }
public IEnumerable<string> GetEmailAddresses()
{
return EmailAddressesToReturn;
}
}

public class MockEmailDataStore : IEmailDataStore
{
public IEnumerable<string> SavedEmailAddresses { get; set; }
public void SaveEmailAddresses(IEnumerable<string> emailAddresses)
{
SavedEmailAddresses = emailAddresses;
}

}
تا اینجا اولین قدم در مورد ایزوله سازی کلاس‌هایی که به مرز سیستم‌های دیگر وارد می‌شوند، برداشته شد. اما به مرور زمان مدیریت این اینترفیس‌ها و افزودن رفتارهای جدید به کلاس‌های مشتق شده از آن‌ها مشکل می‌شود. به همین جهت تا حد ممکن از پیاده سازی دستی آن‌ها خودداری شده و روش پیشنهادی استفاده از mocking frameworks است.

ادامه دارد ....