مطالب
معرفی سرویس‌های ارائه شده توسط شرکت‌های گوگل، آمازون و مایکروسافت در قالب رایانش ابری - قسمت دوم
همانطور که که در قسمت اول اشاره گردید، شرکت گوگل به ارائه سرویس‌های متنوعی بر اساس فناوری رایانش ابری پرداخته است. در این بخش به معرفی سرویس‌های ابری ارائه شده توسط شرکت آمازون پرداخته می‌شود. 
وب سایت این شرکت برای پوشش ترافیک در تمام طول سال به میزان بالایی زیرساخت نرم افزاری و سخت افزاری خود را گسترش داده است. بر همین اساس، این شرکت به منظور جلوگیری از اتلاف منابع ایجاد شده و کسب منافع مالی قابل توجه، به مرور امکان استفاده از منابع شبکه­‌اش را برای کاربران مهیا ساخته است. آمازون در سال 2006 سکوی وب سرویس خود را به عنوان مدل مصرفی در دسترس توسعه دهندگان قرار داد. این شرکت از طریق مجازی سازی سخت افزار بر روی Xen Hypervisor می­تواند سرورهای مجازی ایجاد کند. وب سرویس­های آمازون (Amazon Web Services -AWS) چیزی که اصولاً ظرفیت استفاده نشده زیر ساخت شبکه آمازون است را می­گیرد و آن را به تجارتی سودمند تبدیل می­کند.
سرویس‌های آمازون بی تردید نمایانگر بزرگترین IaaS محض در دنیای امروز هستند. ابر محاسباتی توسعه پذیر آمازون(Amazon Elastic Compute Cloud - EC2) که بزرگترین مولفه محصولات آمازون است در سال 2009 بالغ بر 220 میلیون دلار درآمد داشته است و تخمین زده می‌شود که EC2 بر روی بیش از چهل هزار سرور جهانی که در شش نقطه جهان تقسیم شده اند، اجرا می‌گردد.

صفحه اصلی وب سرویس‌های آمازون

سرویس‌ها و اجزای وب سرویس آمازون:

وب سرویس­های آمازون دارای اجزای زیادی می­باشند. تعدادی از این سرویس­ها برای ارائه خدمات پردازشی و تعداد دیگری برای ارائه فضای ذخیره­سازی، عرضه شده‌­اند. در ادامه گروهی از این سرویس­ها معرفی می­گردد: 

  1. ابر محاسباتی توسعه پذیر آمازون (EC2)
این سرویس، استفاده و مدیریت سرورهای اختصاصی مجازی که سیستم عامل­های لینوکس یا ویندوز را بر روی Xen Hypervisor  اجرا می­کنند، میسر کرده است. نمونه­‌های ماشین با توان­های پردازشی مختلف موجود می­باشد و بر اساس محاسبات/ساعت اجاره می­شوند. برنامه­‌های مستقر بر روی این ماشین­ها بسیار توسعه پذیر و با تحمل پذیری بالای خطا می­باشند. ذکر تفاوت میان یک نمونه ماشین و یک تصویر ماشین می­تواند به درک مفاهیم موجود در سرویس آمازون کمک کند. به طور کلی نمونه ماشین در واقع تقلید یا همسان­سازی(Emulation) سکوی سخت­افزاری مانند x86 و غیره بر روی لایه نرم­افزار مجازی Xen می­باشد. در حالی که تصویر ماشین، نرم افزار و سیستم عاملی است که در سطح یک نمونه ماشین اجرا می­شود و می­توان به محتویات یک درایو راه‌­انداز تشبیه نمود. تعدادی از ابزارهایی که برای پشتیبانی سرویس­های EC2 استفاده می­شوند به شرح زیر است:

  • سرویس صف ساده آمازون(Simple Queue Service):  یک صف پیام یا سیستم تراکنش برای برنامه­‌های مبتنی بر اینترنت توزیع شده می­باشد. این سرویس تضمین می­کند که پیام­ها حتی در زمانی که مؤلفه‌ای موجود نیست، گم نشود و برای انتقال پیام میان مؤلفه‌های مختلف که هرکدام کار جداگانه‌­ای را انجام می­دهند، بسیار مناسب است.
  • سرویس آگاه سازی ساده آمازون(Simple Notification Service):  ): وب سرویسی است که می­تواند پیام یک برنامه را منتشر کند و آن­ها را به برنامه­‌ها یا مشترکین دیگر منتقل کند. SNS  متدی را برای راه­‌اندازی فعالیت­ها ارائه می­نماید که برنامه­‌ها را قادر می­سازد تا در مورد اطلاعات جدید یا تغییر یافته از آن‌ها نظرسنجی شود یا به روز رسانی­‌ها را انجام دهند.
  • سرویس نظارت ابر آمازون(Amazon Cloud Watch):  کنسولی را فراهم می­کند که در آن مصرف منابع، شاخص­‌های کلیدی عملکرد سایت و نشانگرهای عملیاتی برای عواملی همچون تقاضای پردازشگر، مصرف دیسک و ورودی و خروجی شبکه را ارائه می­دهد.  نتایج معیارهایی که توسط آن کسب ­می­شود برای فعال‌سازی قابلیتی به نام Auto Scaling  مورد استفاده قرار می­گیرد که به صورت خودکار می­تواند یک سایت EC2 را بر مبنای مجموعه‌­ای از قوانین که توسعه دهنده ایجاد می­کند، توسعه دهد.
  •   توازن بار منعطف(Elastic Load Balancing): نمونه­‌های ماشین آمازون(Amazon Machine Image) با استفاده از این قابلیت، دارای امکان توازن بار ترافیکی می­شوند. این قابلیت هنگامی که نمونه‌­ای دچار شکست می­شود آن را کشف کرده و ترافیک را به یک نمونه سالم حتی نمونه‌­ای در محیط­‌های دیگر AWS  مسیریابی مجدد می­کند.
    2.  سیستم ذخیره سازی ساده آمازون (Amazon Simple Storage Service - S3)
یک سیستم ذخیره­سازی و پشتیبان گیری آنلاین است و دارای قابلیت انتقال سریع داده به نام  AWS Import/Export  می­باشد و داده را با استفاده از شبکه داخلی آمازون از AWS به دستگاه­‌های ذخیره­‌سازی قابل حمل منتقل می­نماید. این سیستم دسترسی به واحدهای اطلاعاتی را از طریق API وب S3 به کمک استانداردهای SOAP یا REST فراهم می‌کند. از آنجایی که دسترسی به داده با پهنای باند پایین میسر است، از این نوع حافظه بیشتر برای کارهای غیر عملیاتی مانند آرشیو و بازیابی یا پشتیبان گیری از دیسک استفاده می­شود.
    3.  انبار بلوک بسط پذیر آمازون (Amazon Elastic Block Store - EBS)
سیستمی است برای ساخت دیسک‌­های مجازی یا دستگاه­‌های ذخیره­سازی بلوکی که برای نمونه­‌های ماشین آمازون در EC2  مورد استفاده قرار می­گیرند. مزیت این سیستم این است دارای عملکرد بالاتر و قابل اعتماد‌تر از آمازون S3 است به همین دلیل یک واسط ذخیره سازی داده عملیاتی بسیار ارزشمند برای AWS  است. همچنین هزینه ایجاد EBS  مناسب‌تر از مشابه S3 می‌باشد. هر EBS پس از ایجاد بر روی یک نمونه مشخص سوار یا نصب می­شود و تنها برای آن نمونه قابل دسترسی خواهد بود. از این‌رو اشتراک آن­ها بین نمونه­‌ها امکان پذیر نمی­باشد. این سرویس بر اساس فضای ذخیره سازی مصرفی، مدت زمان استفاده و تعداد تقاضاهای ورودی/خروجی قیمت گزاری می­شود.
    4.  پایگاه داده ساده آمازون (Amazon Relational Database Service - RDS) 
این سرویس نمونه­‌های پایگاه داده MySQL را برای پشتیبانی از وب سایت و سایر برنامه‌­هایی که متکی بر سرویس‌­های داده محور(Data Driven) می­باشند، ایجاد می­کند. این سرویس برنامه­‌های پایگاه داده­‌ای که قبلاً در محیط دیگری ساخته شده­‌ا‌ند را پشتیبانی می­نماید و هر برنامه­‌ای که با پایگاه داده MySQL کار می‌کند با RDS نیز کار خواهد کرد. یکی از ویژگی­‌های مهم RDS سیستم پشتیبان گیری خودکار برای داده‌­های درون پایگاه و گزارشات تراکنش MySQL می­باشد. فایل­های پشتیبان به مدت 8 روز ذخیره می­شوند و علاوه بر آن امکان تصویر برداری از پایگاه داده نیز وجود دارد.

مدل قیمت گذاری:

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

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

  • نمونه نقطه­‌ای: این متد برای قیمت گذاری بر روی ظرفیت استفاده نشده EC2 بر مبنای قیمت نقطه فعلی است. این قابلیت، قیمت­‌های بسیار پایین را به همراه خواهد داشت اما در زمان­‌های مختلف فرق می­کند یا در زمانی که ظرفیت مازادی نباشد، در دسترس نخواهد بود. 

در جدول زیر  مشخصات سخت افزاری انواع نمونه ماشین­‌های آمازون ذکر شده‌­اند و با توجه به قیمت گذاری نمونه‌ها بر اساس موقعیت جغرافیایی که در آن قرار گرفته‌­اند، بسیار متنوع است، از ذکر این موارد اجتناب نموده و علاقه‌مندان به کسب اطلاعات بیشتر به وب سایت شرکت آمازون ارجاع داده می­شوند. همچنین ذکر این نکته ضروری است که شرکت آمازون به منظور تست و توسعه سرویس‌های ارائه شده، اکانت یکساله رایگان با امکان استفاده از سرویس‌ها به صورت محدود، ارائه می‌نماید.
نوع 
موتور محاسبه    حافظه اصلی(GB)    ذخیره سازی(GB) سکو   
 ریز نمونه   تا دو واحد محاسباتی در انفجار بار    0.613   EBS    32 یا 64 بیتی 
 نمونه کوچک   یک واحد محاسباتی    1.7    160   32 بیتی   
 نمونه بزرگ   چهار واحد محاسباتی    7.5    850    64 بیتی 
 نمونه بسیار بزرگ   هشت واحد محاسباتی    15    1690    64 بیتی 
   
   
مطالب دوره‌ها
آشنایی با نحوه ایجاد یک IoC Container
قبل از اینکه وارد بحث استفاده از کتابخانه‌های بسیار غنی IoC Container موجود شویم، بهتر است یک نمونه ساده آن‌ها را طراحی کنیم تا بهتر بتوان با عملکرد و ساختار درونی آن‌ها آشنا شد.


IoC Container چیست؟

IoC Container، فریم ورکی است برای انجام تزریق وابستگی‌ها. در این فریم ورک امکان تنظیم اولیه وابستگی‌های سیستم وجود دارد. برای مثال زمانیکه برنامه از یک IoC Container، نوع اینترفیس خاصی را درخواست می‌کند، این فریم ورک با توجه به تنظیمات اولیه‌اش، کلاسی مشخص را بازگشت خواهد داد.
IoC Containerهای قدیمی‌تر، برای انجام تنظیمات اولیه خود از فایل‌های کانفیگ استفاده می‌کردند. نمونه‌های جدیدتر آن‌ها از روش‌های Fluent interfaces برای مشخص سازی تنظیمات خود بهره می‌برند.

زمانیکه از یک IOC Container در کدهای خود استفاده می‌کنید، مراحلی چند رخ خواهند داد:
الف) کد فراخوان، از IOC Container، یک شیء مشخص را درخواست می‌کند. عموما اینکار با درخواست یک اینترفیس صورت می‌گیرد؛ هرچند محدودیتی نیز نداشته و امکان درخواست یک کلاس از نوعی مشخص نیز وجود دارد.
ب) در ادامه IOC Container به لیست اشیاء قابل ارائه توسط خود نگاه کرده و در صورت وجود، وهله سازی شیء درخواست شده را انجام و نهایتا شیء مطلوب را بازگشت خواهد داد.
در این بین زنجیره‌ی وابستگی‌های مورد نیاز نیز وهله سازی خواهند شد. برای مثال اگر وابستگی اول به وابستگی دوم برای وهله سازی نیاز دارد، کار وهله سازی وابستگی‌های وابستگی دوم نیز به صورت خودکار انجام خواهند شد. (این موردی است که بسیاری از تازه واردان به این بحث تا یکبار آن‌را امتحان نکنند باور نخواهند کرد!)
ج) سپس کد فراخوان وهله دریافتی را مورد پردازش قرار داده و سپس شروع به استفاده از متدها و خواص آن خواهد نمود.


در تصویر فوق محل قرارگیری یک IOC Container را مشاهده می‌کنید. یک IOC Container در مورد تمام وابستگی‌های مورد نیاز، اطلاعات لازم را دارد. همچنین این فریم ورک در مورد کلاسی که قرار است از وابستگی‌های سیستم استفاده نماید نیز مطلع است؛ به این ترتیب می‌تواند به صورت خودکار در زمان وهله سازی آن، نوع‌های وابستگی‌های مورد نیاز آن‌را در اختیارش قرار دهد.
برای مثال در اینجا MyClass، وابستگی مشخص شده در سازنده خود را به نام IDependency از IOC Container درخواست می‌کند. سپس این IOC Container بر اساس تنظیمات اولیه خود، یکی از وابستگی‌های A یا B را بازگشت خواهد داد.


آغاز به کار ساخت یک IOC Container نمونه

در ابتدا کدهای آغازین مثال بحث جاری را در نظر بگیرید:
using System;

namespace DI01
{
    public interface ICreditCard
    {
        string Charge();
    }

    public class Visa : ICreditCard
    {
        public string Charge()
        {
            return "Charging with the Visa!";
        }
    }

    public class MasterCard : ICreditCard
    {
        public string Charge()
        {
            return "Swiping the MasterCard!";
        }
    }

    public class Shopper
    {
        private readonly ICreditCard creditCard;

        public Shopper(ICreditCard creditCard)
        {
            this.creditCard = creditCard;
        }

        public void Charge()
        {
            var chargeMessage = creditCard.Charge();
            Console.WriteLine(chargeMessage);
        }
    }    
}
در اینجا وابستگی‌های کلاس خریدار از طریق سازنده آن که متداول‌ترین روش تزریق وابستگی‌ها است، در اختیار آن قرار خواهد گرفت. یک اینترفیس کردیت کارت تعریف شده‌است به همراه دو پیاده سازی نمونه آن مانند مسترکارت و ویزا کارت. ساده‌ترین نوع فراخوانی آن نیز می‌تواند مانند کدهای ذیل باشد (تزریق وابستگی‌های دستی):
 var shopper = new Shopper(new Visa());
shopper.Charge();
در ادامه قصد داریم این فراخوانی‌ها را اندکی هوشمندتر کنیم تا بتوان بر اساس تنظیمات برنامه، کار تزریق وابستگی‌ها صورت گیرد و به سادگی بتوان اینترفیس‌های متفاوتی را در اینجا درخواست و مورد استفاده قرار داد. اینجا است که به اولین IoC Container خود خواهیم رسید:
using System;
using System.Collections.Generic;
using System.Linq;

namespace DI01
{
    public class Resolver
    {
        //کار ذخیره سازی و نگاشت از یک نوع به نوعی دیگر در اینجا توسط این دیکشنری انجام خواهد شد
        private Dictionary<Type, Type> dependencyMap = new Dictionary<Type, Type>();

        /// <summary>
        /// یک نوع خاص از آن درخواست شده و سپس بر اساس تنظیمات برنامه، کار وهله سازی
        /// نمونه معادل آن صورت خواهد گرفت
        /// </summary>
        public T Resolve<T>()
        {
            return (T)Resolve(typeof(T));
        }

        private object Resolve(Type typeToResolve)
        {
            Type resolvedType;

            // ابتدا بررسی می‌شود که آیا در تنظیمات برنامه نگاشت متناظری برای نوع درخواستی وجود دارد؟
            if (!dependencyMap.TryGetValue(typeToResolve, out resolvedType))
            {
                //اگر خیر، کار متوقف خواهد شد
                throw new Exception(string.Format("Could not resolve type {0}", typeToResolve.FullName));
            }

            var firstConstructor = resolvedType.GetConstructors().First();
            var constructorParameters = firstConstructor.GetParameters();
            // در ادامه اگر این نوع، دارای سازنده‌ی بدون پارامتری است
            // بلافاصله وهله سازی خواهد شد
            if (!constructorParameters.Any())
                return Activator.CreateInstance(resolvedType);


            var parameters = new List<object>();
            foreach (var parameterToResolve in constructorParameters)
            {
                // در اینجا یک فراخوانی بازگشتی صورت گرفته است برای وهله سازی
                // خودکار پارامترهای مختلف سازنده یک کلاس
                parameters.Add(Resolve(parameterToResolve.ParameterType));
            }            
            return firstConstructor.Invoke(parameters.ToArray());
        }

        public void Register<TFrom, TTo>()
        {
            dependencyMap.Add(typeof(TFrom), typeof(TTo));
        }
    }
}
در اینجا کدهای کلاس Resolver یا همان IoC Container ابتدایی بحث را مشاهده می‌کنید. توضیحات قسمت‌های مختلف آن به صورت کامنت ارائه شده‌اند.
 var resolver = new Resolver();
//تنظیمات اولیه
resolver.Register<Shopper, Shopper>();
resolver.Register<ICreditCard, Visa>();
//تزریق وابستگی‌ها و وهله سازی
var shopper = resolver.Resolve<Shopper>();
shopper.Charge();
در ادامه نحوه استفاده از IoC Container ایجاد شده را مشاهده می‌کنید.
ابتدا کار تعاریف نگاشت‌های اولیه انجام می‌شود. در این صورت زمانیکه متد Resolve فراخوانی می‌گردد، نوع درخواستی آن به همراه سازنده دارای آرگومانی از نوع ICreditCard وهله سازی شده و بازگشت داده خواهد شد. سپس با در دست داشتن یک وهله آماده، متد Charge آن‌را فراخوانی خواهیم کرد.


بررسی نحوه استفاده از Microsoft Unity به عنوان یک IoC Container

Unity چیست؟

Unity یک فریم ورک IoC Container تهیه شده توسط مایکروسافت می‌باشد که آن‌را به عنوان جزئی از Enterprise Library خود قرار داده است. بنابراین برای دریافت آن یا می‌توان کل مجموعه Enterprise Library را دریافت کرد و یا به صورت مجزا به عنوان یک بسته نیوگت نیز قابل تهیه است.
برای این منظور در خط فرمان پاورشل نیوگت در VS.NET دستور ذیل را اجرا کنید:
 PM> Install-Package Unity

پیاده سازی مثال خریدار توسط Unity

همان مثال قسمت قبل را درنظر بگیرید. قصد داریم اینبار بجای IoC Container دست سازی که تهیه شد، پیاده سازی آن‌را به کمک MS Unity انجام دهیم.
using Microsoft.Practices.Unity;

namespace DI02
{
    class Program
    {
        static void Main(string[] args)
        {
            var container = new UnityContainer();

            container.RegisterType<ICreditCard, MasterCard>();

            var shopper = container.Resolve<Shopper>();
            shopper.Charge();
        }
    }
}
همانطور که ملاحظه می‌کنید، API آن بسیار شبیه به کلاس دست سازی است که در قسمت قبل تهیه کردیم.
مطابق کدهای فوق، ابتدا تنظیمات IoC Container انجام شده است. به آن اعلام کرده‌ایم که در صورت نیاز به ICreditCard، نوع MasterCard را یافته و وهله سازی کن. با این تفاوت که Unity هوشمند‌تر بوده و سطر مربوط به ثبت کلاس Shoper ایی را که در قسمت قبل انجام دادیم، در اینجا حذف شده است.
سپس به این IoC Container اعلام کرده‌ایم که نیاز به یک وهله از کلاس خریدار داریم. در اینجا Unity کار وهله سازی‌های خودکار وابستگی‌ها و تزریق آن‌ها را در سازنده کلاس خریدار انجام داده و نهایتا یک وهله قابل استفاده را در اختیار ادامه برنامه قرار خواهد داد.

یک نکته:
به صورت پیش فرض کار تزریق وابستگی‌ها در سازنده کلاس‌ها به صورت خودکار انجام می‌شود. اگر نیاز به Setter injection و مقدار دهی خواص کلاس وجود داشت می‌توان به نحو ذیل عمل کرد:
 container.RegisterType<ICreditCard, MasterCard>(new InjectionProperty("propertyName", 5));
نام خاصیت و مقدار مورد نظر به عنوان پارامتر متد RegisterType باید تعریف شوند.


مدیریت طول عمر اشیاء در Unity

توسط یک IoC Container می‌توان یک وهله معمولی از شیءایی را درخواست کرد و یا حتی طول عمر این وهله را به صورت Singleton معرفی نمود (یک وهله در طول عمر کل برنامه). در Unity اگر تنظیم خاصی اعمال نشود، هربار که متد Resolve فراخوانی می‌گردد، یک وهله جدید را در اختیار ما قرار خواهد داد. اما اگر پارامتر متد RegisterType را با وهله‌ای از ContainerControlledLifetimeManager مقدار دهی کنیم:
 container.RegisterType<ICreditCard, MasterCard>(new ContainerControlledLifetimeManager());
از این پس با هربار فراخوانی متد Resolve، در صورت نیاز به وابستگی از نوع ICreditCard، تنها یک وهله مشترک از MasterCard ارائه خواهد شد.
حالت پیش فرض مورد استفاده، بدون ذکر پارامتر متد RegisterType، مقدار TransientLifetimeManager می‌باشد.
مطالب
ساخت فرم های generic در WPF و Windows Application
آیا می‌توان در یک پروژه های  Windows App یا WPF، یک فرم پایه به صورت generic تعریف کنیم و سایر فرم‌ها بتوانند از آن ارث ببرند؟ در این پست به تشریح و بررسی این مسئله  خواهیم پرداخت.
در پروژه هایی به صورت Smart UI کد نویسی شده اند و یا حتی قصد انجام پروژه با تکنولوژی‌های WPF یا Windows Application را دارید و نیاز دارید که فرم‌های خود را به صورت generic بسازید این مقاله به شما کمک خواهد کرد.
#Windows Application
یک پروژه از نوع Windows Application ایجاد می‌کنیم و یک فرم به نام FrmBase در آن خواهیم داشت. یک Label در فرم قرار دهید  و مقدار Text آن را فرم اصلی قرار دهید.
در فرم مربوطه، فرم را به صورت generic تعریف کنید. به صورت زیر:
public partial class FrmBase<T> : Form where T : class 
    {
        public FrmBase()
        {
            InitializeComponent();
        }
    }

بعد باید همین تغییرات را در فایل FrmBase.designer.cs هم اعمال کنیم:
partial class FrmBase<T> where T : class 
    {
        /// <summary>
        /// Required designer variable.
        /// </summary>
        private System.ComponentModel.IContainer components = null;

        /// <summary>
        /// Clean up any resources being used.
        /// </summary>
        /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
        protected override void Dispose( bool disposing )
        {
            if ( disposing && ( components != null ) )
            {
                components.Dispose();
            }
            base.Dispose( disposing );
        }

        #region Windows Form Designer generated code

        /// <summary>
        /// Required method for Designer support - do not modify
        /// the contents of this method with the code editor.
        /// </summary>
        private void InitializeComponent()
        {
            this.label1 = new System.Windows.Forms.Label();
            this.SuspendLayout();
            // 
            // label1
            // 
            this.label1.AutoSize = true;
            this.label1.Location = new System.Drawing.Point(186, 22);
            this.label1.Name = "label1";
            this.label1.Size = new System.Drawing.Size(51, 13);
            this.label1.TabIndex = 0;
            this.label1.Text = "فرم اصلی";
            // 
            // FrmBase
            // 
            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
            this.ClientSize = new System.Drawing.Size(445, 262);
            this.Controls.Add(this.label1);
            this.Name = "FrmBase";
            this.Text = "Form1";
            this.ResumeLayout(false);
            this.PerformLayout();

        }

        #endregion

        private System.Windows.Forms.Label label1;
    }
یک فرم جدید بسازید و نام آن را FrmTest بگذارید. این فرم باید از FrmBase ارث ببرد. خب این کار را به صورت زیر انجام می‌دهیم:
public partial class FrmTest : FrmBase<String>
    {
        public FrmTest()
        {
            InitializeComponent();
        }
    }
پروژه را اجرا کنید. بدون هیچ گونه مشکلی برنامه اجرا می‌شود وفرم مربوطه را در حالت اجرا مشاهده خواهید کرد. اما اگر قصد باز کردن فرم FrmTest را در حالت design داشته باشید با خطای زیر مواجه خواهید شد:

با این که برنامه به راحتی اجرا می‌شود و خروجی آن قابل مشاهده است ولی امکان نمایش فرم در حالت design وجود ندارد. متاسفانه در Windows App‌ها برای تعریف فرم‌ها به صورت generic یا این مشکل روبرور هستیم. تنها راه موجود برای حل این مشکل استفاده از یک کلاس کمکی است. به صورت زیر:

    public partial class FrmTest : FrmTestHelp
    {
        public FrmTest()
        {
            InitializeComponent();
        }
    }

    public class FrmTestHelp : FrmBase<String>
    {
    }
مشاهده می‌کنید که بعد از اعمال تغییرات بالا فرم FrmTest به راحتی Load می‌شود و در حالت designer هم می‌تونید از آن استفاده کنید.
#WPF
در پروژه‌های WPF ، راه حلی برای این مشکل در نظر گرفته شده است. در WPF، برای Window یا UserControl پایه نمی‌توان Designer داشت. ابتدا باید فرم پایه را به صورت زیر ایجاد کنیم:
public class WindowBase<T> : Window where T : class
{
}
در این مرحله یک Window بسازید که از WindowBase ارث ببرد:
public partial class MainWindow: WindowBase<String>
    {
        public MainWindow()
        {
            InitializeComponent();
        }
    }
در WPF باید تعاریف موجود برای Xaml و Code Behind یکی باشد. در نتیجه باید تغییرات زیر را در فایل Xaml نیز اعمال کنید:
<local:WindowBase x:Class="GenericWindows.MainWindow" 
x:TypeArguments="sys:String" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:GenericWindows" xmlns:sys="clr-namespace:System;assembly=mscorlib" Title="MainWindow" Height="350" Width="525"> <Grid> </Grid> </local:WindowBase>
همان طور که می‌بیینید در ابتدای فایل به جای Window از local:WindowBase استفاده شده است. این نشان دهنده این است که فرم پایه برای این Window از نوع WindowBase است. برای مشخص کردن نوع generic هم می‌تونید از x:TypeArguments استفاده کنید که در این جا نوع آن را String انتخاب کردم.
مطالب
آشنایی با NHibernate - قسمت پنجم

استفاده از LINQ جهت انجام کوئری‌ها توسط NHibernate

نگارش نهایی 1.0 کتابخانه‌ی LINQ to NHibernate اخیرا (حدود سه ماه قبل) منتشر شده است. در این قسمت قصد داریم با کمک این کتابخانه، اعمال متداول انجام کوئری‌ها را بر روی دیتابیس قسمت قبل انجام دهیم.
توسط این نگارش ارائه شده، کلیه اعمال قابل انجام با criteria API این فریم ورک را می‌توان از طریق LINQ نیز انجام داد (NHibernate برای کار با داده‌ها و جستجوهای پیشرفته بر روی آن‌ها، HQL : Hibernate Query Language و Criteria API را سال‌ها قبل توسعه داده است).

جهت دریافت پروایدر LINQ مخصوص NHibernate به آدرس زیر مراجعه نمائید:


پس از دریافت آن، به همان برنامه کنسول قسمت قبل، دو ارجاع را باید افزود:
الف) ارجاعی به اسمبلی NHibernate.Linq.dll
ب) ارجاعی به اسمبلی استاندارد System.Data.Services.dll دات نت فریم ورک سه و نیم

در ابتدای متد Main برنامه قصد داریم تعدادی مشتری را به دیتابیس اضافه نمائیم. به همین منظور متد AddNewCustomers را به کلاس CDbOperations برنامه کنسول قسمت قبل اضافه نمائید. این متد لیستی از مشتری‌ها را دریافت کرده و آن‌ها را در طی یک تراکنش به دیتابیس اضافه می‌کند:

public void AddNewCustomers(params Customer[] customers)
{
using (ISession session = _factory.OpenSession())
{
using (ITransaction transaction = session.BeginTransaction())
{
foreach (var data in customers)
session.Save(data);

session.Flush();

transaction.Commit();
}
}
}
در اینجا استفاده از واژه کلیدی params سبب می‌شود که بجای تعریف الزامی یک آرایه از نوع مشتری‌ها، بتوانیم تعداد دلخواهی پارامتر از نوع مشتری را به این متد ارسال کنیم.

پس از افزودن این ارجاعات، کلاس جدیدی را به نام CLinqTest به برنامه کنسول اضافه نمائید. ساختار کلی این کلاس که قصد استفاده از پروایدر LINQ مخصوص NHibernate را دارد باید به شکل زیر باشد (به کلاس پایه NHibernateContext دقت نمائید):

using System.Collections.Generic;
using System.Linq;
using NHibernate;
using NHibernate.Linq;
using NHSample1.Domain;

namespace ConsoleTestApplication
{
class CLinqTest : NHibernateContext
{ }
}
اکنون پس از مشخص شدن context یا زمینه، نحوه ایجاد یک کوئری ساده LINQ to NHibernate به صورت زیر می‌تواند باشد:

using System.Collections.Generic;
using System.Linq;
using NHibernate;
using NHibernate.Linq;
using NHSample1.Domain;

namespace ConsoleTestApplication
{
class CLinqTest : NHibernateContext
{
ISessionFactory _factory;

public CLinqTest(ISessionFactory factory)
{
_factory = factory;
}

public List<Customer> GetAllCustomers()
{
using (ISession session = _factory.OpenSession())
{
var query = from x in session.Linq<Customer>() select x;
return query.ToList();
}
}
}
}
ابتدا علاوه بر سایر فضاهای نام مورد نیاز، فضای نام NHibernate.Linq به پروژه افزوده می‌شود. سپس از extension متدی به نام Linq بر روی اشیاء ISession از نوع یکی از موجودیت‌های تعریف شده در برنامه در قسمت‌های قبل، می‌توان جهت تهیه کوئری‌های Linq مورد نظر بهره برد.
در این کوئری، لیست تمامی مشتری‌ها بازگشت داده می‌شود.

سپس جهت استفاده و بررسی آن در متد Main برنامه خواهیم داشت:

static void Main(string[] args)
{
using (ISessionFactory session = Config.CreateSessionFactory(
MsSqlConfiguration
.MsSql2008
.ConnectionString("Data Source=(local);Initial Catalog=HelloNHibernate;Integrated Security = true")
.ShowSql()
))
{

var customer1 = new Customer()
{
FirstName = "Vahid",
LastName = "Nasiri",
AddressLine1 = "Addr1",
AddressLine2 = "Addr2",
PostalCode = "1234",
City = "Tehran",
CountryCode = "IR"
};

var customer2 = new Customer()
{
FirstName = "Ali",
LastName = "Hasani",
AddressLine1 = "Addr..1",
AddressLine2 = "Addr..2",
PostalCode = "4321",
City = "Shiraz",
CountryCode = "IR"
};

var customer3 = new Customer()
{
FirstName = "Mohsen",
LastName = "Shams",
AddressLine1 = "Addr...1",
AddressLine2 = "Addr...2",
PostalCode = "5678",
City = "Ahwaz",
CountryCode = "IR"
};

CDbOperations db = new CDbOperations(session);
db.AddNewCustomers(customer1, customer2, customer3);

CLinqTest lt = new CLinqTest(session);
foreach (Customer customer in lt.GetAllCustomers())
{
Console.WriteLine("Customer: LastName = {0}", customer.LastName);
}
}

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

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

مهمترین مزیت استفاده از LINQ در این نوع کوئری‌ها نسبت به روش‌های دیگر، استفاده از کدهای strongly typed دات نتی تحت نظر کامپایلر است، نسبت به رشته‌های معمولی SQL که کامپایلر کنترلی را بر روی آن‌ها نمی‌تواند داشته باشد (برای مثال اگر نوع یک ستون تغییر کند یا نام آن‌، در حالت استفاده از LINQ بلافاصله یک خطا را از کامپایلر جهت تصحیح مشکلات دریافت خواهیم کرد که این مورد در زمان استفاده از یک رشته معمولی صادق نیست). همچنین مزیت فراهم بودن Intellisense را حین نوشتن کوئری‌هایی از این دست نیز نمی‌توان ندید گرفت.

مثالی دیگر:
لیست تمام مشتری‌های شیرازی را نمایش دهید:
ابتدا متد GetCustomersByCity را به کلاس CLinqTest فوق اضافه می‌کنیم:

public List<Customer> GetCustomersByCity(string city)
{
using (ISession session = _factory.OpenSession())
{
var query = from x in session.Linq<Customer>()
where x.City == city
select x;
return query.ToList();
}
}
سپس برای استفاده از آن، چند سطر ساده زیر به ادامه متد Main اضافه می‌شوند:

foreach (Customer customer in lt.GetCustomersByCity("Shiraz"))
{
Console.WriteLine("Customer: LastName = {0}", customer.LastName);
}
یکی دیگر از مزایای استفاده از LINQ to NHibernate ، امکان بکارگیری LINQ بر روی تمامی دیتابیس‌های پشتیبانی شده توسط NHibernate است؛ برای مثال مای اس کیوال، اوراکل و ....
لیست کامل دیتابیس‌های پشتیبانی شده توسط NHibernate را در این آدرس می‌توانید مشاهده نمائید. (البته به نظر لیست آن، آنچنان هم به روز نیست؛ چون در نگارش آخر NHibernate ، پشتیبانی از اس کیوال سرور 2008 هم اضافه شده است)


نکته:
در کوئری‌های مثال‌های فوق همواره باید session.Linq<T> را ذکر کرد. اگر علاقمند بودید شبیه به روشی که در LINQ to SQL موجود است مثلا db.TableName بجای session.Linq<T> در کوئری‌ها ذکر گردد، می‌توان اصلاحاتی را به صورت زیر اعمال کرد:
یک کلاس جدید را به نام SampleContext به برنامه کنسول جاری با محتویات زیر اضافه نمائید:

using System.Linq;
using NHibernate;
using NHibernate.Linq;
using NHSample1.Domain;

namespace ConsoleTestApplication
{
class SampleContext : NHibernateContext
{
public SampleContext(ISession session)
: base(session)
{ }

public IOrderedQueryable<Customer> Customers
{
get { return Session.Linq<Customer>(); }
}

public IOrderedQueryable<Employee> Employees
{
get { return Session.Linq<Employee>(); }
}

public IOrderedQueryable<Order> Orders
{
get { return Session.Linq<Order>(); }
}

public IOrderedQueryable<OrderItem> OrderItems
{
get { return Session.Linq<OrderItem>(); }
}

public IOrderedQueryable<Product> Products
{
get { return Session.Linq<Product>(); }
}
}
}
در این کلاس به ازای تمام موجودیت‌های تعریف شده در پوشه domain برنامه اصلی خود (همان NHSample1 قسمت‌های اول و دوم)، یک متد از نوع IOrderedQueryable را باید تشکیل دهیم که پیاده سازی آن‌را ملاحظه می‌نمائید.
سپس بازنویسی متد GetCustomersByCity بر اساس SampleContext فوق به صورت زیر خواهد بود که به کوئری‌های LINQ to SQL بسیار شبیه است:

using System.Collections.Generic;
using System.Linq;
using NHibernate;
using NHSample1.Domain;

namespace ConsoleTestApplication
{
class CSampleContextTest
{
ISessionFactory _factory;

public CSampleContextTest(ISessionFactory factory)
{
_factory = factory;
}

public List<Customer> GetCustomersByCity(string city)
{
using (ISession session = _factory.OpenSession())
{
using (SampleContext db = new SampleContext(session))
{
var query = from x in db.Customers
where x.City == city
select x;
return query.ToList();
}
}
}
}
}
دریافت سورس برنامه تا این قسمت

و در تکمیل این بحث، می‌توان به لیستی از 101 مثال LINQ ارائه شده در MSDN اشاره کرد که یکی از بهترین و سریع ترین مراجع یادگیری مبحث LINQ است.


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


مطالب
بررسی خطای cycles or multiple cascade paths و یا cyclical reference در EF Code first
ابتدا مثال کامل این قسمت را با شرح زیر درنظر بگیرید؛ در اینجا هر کاربر، یک کارتابل می‌تواند داشته باشد (رابطه یک به صفر یا یک) و تعدادی سند منتسب به او (رابطه یک به چند).  همچنین روابط بین کارتابل و اسناد نیز چند به چند است:
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Data.Entity;
using System.Data.Entity.Migrations;
using System.Data.Entity.ModelConfiguration;

namespace EF_General.Models.Ex18
{
    public class UserProfile
    {
        public int UserProfileId { set; get; }
        public string UserName { set; get; }

        [ForeignKey("CartableId")]
        public virtual Cartable Cartable { set; get; } // one-to-zero-or-one
        public int? CartableId { set; get; }

        public virtual ICollection<Doc> Docs { set; get; } // one-to-many
    }

    public class Doc
    {
        public int DocId { set; get; }
        public string Title { set; get; }
        public string Body { set; get; }

        [ForeignKey("UserProfileId")]
        public virtual UserProfile UserProfile { set; get; }
        public int UserProfileId { set; get; }

        public virtual ICollection<Cartable> Cartables { set; get; } // many-to-many
    }

    public class Cartable
    {
        public int CartableId { set; get; }

        [ForeignKey("UserProfileId")]
        public virtual UserProfile UserProfile { set; get; }
        public int UserProfileId { set; get; }

        public virtual ICollection<Doc> Docs { set; get; } // many-to-many
    }

    public class UserProfileMap : EntityTypeConfiguration<UserProfile>
    {
        public UserProfileMap()
        {
            this.HasOptional(x => x.Cartable)
                .WithRequired(x => x.UserProfile)
                .WillCascadeOnDelete();
        }
    }

    public class MyContext : DbContext
    {
        public DbSet<UserProfile> UserProfiles { get; set; }
        public DbSet<Doc> Docs { get; set; }
        public DbSet<Cartable> Cartables { get; set; }

        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            modelBuilder.Configurations.Add(new UserProfileMap());
            base.OnModelCreating(modelBuilder);
        }
    }

    public class Configuration : DbMigrationsConfiguration<MyContext>
    {
        public Configuration()
        {
            AutomaticMigrationsEnabled = true;
            AutomaticMigrationDataLossAllowed = true;
        }
    }

    public static class Test
    {
        public static void RunTests()
        {
            Database.SetInitializer(new MigrateDatabaseToLatestVersion<MyContext, Configuration>());
            using (var context = new MyContext())
            {
                var user = context.UserProfiles.Find(1);
                if (user != null)
                    Console.WriteLine(user.UserName);
            }
        }
    }
}
اگر این مثال را اجرا کنیم، به خطای ذیل برخواهیم خورد:
Introducing FOREIGN KEY constraint 'FK_DocCartables_Cartables_Cartable_CartableId' 
on table 'DocCartables' may cause cycles or multiple cascade paths. Specify 
ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints.
Could not create constraint. See previous errors.
علت اینجا است که EF به صورت پیش فرض ویژگی cascade delete را برای حالات many-to-many و یا کلیدهای خارجی غیرنال پذیر اعمال می‌کند.
این دو مورد در کلاس‌های Doc و Cartable با هم وجود دارند که در نهایت سبب بروز circular cascade delete (حذف آبشاری حلقوی) می‌شوند و بیشتر مشکل SQL Server است تا EF؛ از این لحاظ که SQL Server در این حالت نمی‌تواند در مورد نحوه حذف خودکار رکوردهای وابسته درست تصمیم‌گیری و عمل کند. برای رفع این مشکل تنها کافی است کلید خارجی تعریف شده در دو کلاس Doc و کارتابل را nullable تعریف کرد تا cascade delete اضافی پیش فرض را لغو کند:
public int? UserProfileId { set; get; }
راه دیگر، استفاده از تنظیمات Fluent و تنظیم WillCascadeOnDelete به false است که به صورت پیش فرض در حالات ذکر شده (روابط چند به چند و یا کلید خارجی غیرنال پذیر)، true است.

شبیه به همین خطا نیز زمانی رخ خواهد داد که در یک کلاس حداقل دو کلید خارجی تعریف شده باشند:
The referential relationship will result in a cyclical reference that is not allowed. [ Constraint name =  ]
در اینجا نیز با نال پذیر تعریف کردن این کلیدهای خارجی، خطای cyclical reference برطرف خواهد شد.
مطالب دوره‌ها
ایندکس‌ها در RavenDB
RavenDB یک Document database است و در این نوع بانک‌های اطلاعاتی، اسکیما و ساختار مشخصی وجود ندارد. شاید اینطور به نظر برسد، زمانیکه با دات نت کلاینت RavenDB کار می‌کنیم، یک سری کلاس مشخص دات نتی داشته و این‌ها ساختار اصلی کار را مشخص می‌کنند. اما در عمل RavenDB چیزی از این کلاس‌ها و خواص نمی‌داند و این کلاس‌های دات نتی صرفا کمکی هستند جهت سهولت اعمال Serialization و Deserialization اطلاعات. زمانیکه اطلاعاتی را در RavenDB ذخیره می‌کنیم، هیچ نوع قیدی در مورد ساختار نوع سندی که در حال ذخیره است، اعمال نمی‌شود.
خوب؛ اکنون این سؤال مطرح می‌شود که RavenDB چگونه اطلاعاتی را در این اسناد بدون اسکیما جستجو می‌کند؟ اینجا است که مفهوم و کاربرد ایندکس‌ها مطرح می‌شوند. ما در قسمت قبل که کوئری نویسی مقدماتی را بررسی کردیم، عملا ایندکس خاصی را به صورت دستی جهت انجام جستجو‌ها ایجاد نکردیم؛ از این جهت که خود RavenDB به کمک امکانات dynamic indexing آن، پیشتر اینکار را انجام داده است. برای نمونه به سطر ارسال کوئری به سرور، که در قسمت قبل ارائه شد، دقت کنید. در اینجا ارسال کوئری به indexes/dynamic کاملا مشخص است:
Request #   2: GET     - 3,818 ms - <system>   - 200 - /indexes/dynamic/Questions?&query=Title%3ARaven*&pageSize=128

Dynamic Indexes یا ایندکس‌های پویا

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


Static indexes یا ایندکس‌های ایستا

ایندکس‌های پویا به دلیل وقفه ابتدایی که برای تولید آن‌ها وجود خواهد داشت، شاید آنچنان مطلوب به نظر نرسند. اینجا است که مفهوم ایندکس‌های ایستا مطرح می‌شوند. در این حالت ما به RavenDB خواهیم گفت که چه چیزی را ایندکس کند. برای تولید ایندکس‌های ایستا، از مفاهیم Map/Reduce که در پیشنیازهای دوره جاری در مورد آن بحث شد، استفاده می‌گردد. خوشبختانه تهیه Map/Reduceها در RavenDB پیچیده نبوده و کل عملیات آن توسط کوئری‌های LINQ قابل پیاده سازی است.
تهیه ایندکس‌های پویا نیز در تردهای پس‌زمینه انجام می‌شوند. از آنجائیکه RavenDB برای اعمال Read، بهینه سازی شده است، با ارسال یک کوئری به آن، این بانک اطلاعاتی، کلیه اطلاعات آماده را در اختیار شما قرار خواهد داد؛ صرفنظر از اینکه کار تهیه ایندکس تمام شده است یا خیر.


چگونه یک ایندکس ایستا را ایجاد کنیم؟

اگر به کنسول مدیریتی سیلورلایت RavenDB مراجعه کنیم، حاصل کوئری‌های LINQ قسمت قبل را در برگه‌ی ایندکس‌های آن می‌توان مشاهده کرد:


در اینجا بر روی دکمه Edit کلیک نمائید، تا با نحوه تهیه این ایندکس پویا آشنا شویم:


این ایندکس، یک نام داشته به همراه قسمت Map از پروسه Map/Reduce که توسط یک کوئری LINQ تهیه شده است. کاری که در اینجا انجام شده، ایندکس کردن کلیه سؤالات، بر اساس خاصیت عنوان آن‌ها است.
اکنون اگر بخواهیم همین کار را با کدنویسی انجام دهیم، به صورت زیر می‌توان عمل کرد:
using System;
using System.Linq;
using Raven.Client.Document;
using RavenDBSample01.Models;
using Raven.Client;
using Raven.Client.Linq;
using Raven.Client.Indexes;

namespace RavenDBSample01
{
    class Program
    {
        static void Main(string[] args)
        {
            using (var store = new DocumentStore
            {
                Url = "http://localhost:8080"
            }.Initialize())
            {
                store.DatabaseCommands.PutIndex(
                name: "Questions/ByTitle",
                indexDef: new IndexDefinitionBuilder<Question>
                {
                    Map = questions => questions.Select(question => new { Title = question.Title } )
                });
            }
        }
    }
}
کار با شیء DatabaseCommands یک DocumentStore شروع می‌شود. سپس توسط متد PutIndex آن می‌توان یک ایندکس جدید را تعریف کرد. این متد نیاز به نام ایندکس ایجاد شده و همچنین حداقل، متد Map آن‌را دارد. برای این منظور از شیء IndexDefinitionBuilder برای تعریف نحوه جمع آوری اطلاعات ایندکس کمک خواهیم گرفت. در اینجا خاصیت Map آن‌را باید توسط یک کوئری LINQ که فیلدهای مدنظر را بازگشت می‌دهد، مقدار دهی کنیم.
برنامه را اجرا کرده و سپس به کنسول مدیریتی تحت وب RavenDB، قسمت ایندکس‌های آن مراجعه کنید. در اینجا می‌توان ایندکس جدید ایجاد شده را مشاهده کرد:


هرچند همین اعمال را در کنسول مدیریتی نیز می‌توان انجام داد، اما مزیت آن در سمت کدها، دسترسی به intellisense و نوشتن کوئری‌های strongly typed است.

روش استفاده از store.DatabaseCommands.PutIndex اولین روش تولید Index در RavenDB با کدنویسی است. روش دوم، بر اساس ارث بری از کلاس AbstractIndexCreationTask شروع می‌شود و مناسب است برای حالتیکه نمی‌خواهید کدهای تولید ایندکس، با کدهای سایر قسمت‌های برنامه مخلوط شوند:
    public class QuestionsByTitle : AbstractIndexCreationTask<Question>
    {
        public QuestionsByTitle()
        {
            Map = questions => questions.Select(question => new { Title = question.Title });
        }
    }
در اینجا با ایجاد یک کلاس جدید و ارث بری از کلاس AbstractIndexCreationTask کار شروع می‌شود. سپس در سازنده این کلاس، خاصیت Map را مقدار دهی می‌کنیم. مقدار آن نیز یک کوئری LINQ است که کار Select فیلدهای شرکت دهنده در کار تهیه ایندکس را انجام می‌دهد.
اکنون برای معرفی آن به برنامه باید از متد IndexCreation.CreateIndexes استفاده کرد. این متد، نیاز به دریافت اسمبلی محل تعریف کلاس‌های تولید ایندکس را دارد. به این ترتیب تمام کلاس‌های مشتق شده از AbstractIndexCreationTask را یافته و ایندکس‌های متناظری را تولید می‌کند.
            using (var store = new DocumentStore
            {
                Url = "http://localhost:8080"
            }.Initialize())
            {
                IndexCreation.CreateIndexes(typeof(QuestionsByTitle).Assembly, store);
            }
این روش، قابلیت نگهداری و نظم بهتری دارد.


استفاده از ایندکس‌های ایستای ایجاد شده

تا اینجا موفق شدیم ایندکس‌های ایستای خود را با کد نویسی ایجاد کنیم. در ادامه قصد داریم از این ایندکس‌ها در کوئری‌های خود استفاده نمائیم.
            using (var store = new DocumentStore
            {
                Url = "http://localhost:8080"
            }.Initialize())
            {
                using (var session = store.OpenSession())
                {
                    var questions = session.Query<Question>(indexName: "QuestionsByTitle")
                                           .Where(x => x.Title.StartsWith("Raven")).Take(128);
                    foreach (var question in questions)
                    {
                        Console.WriteLine(question.Title);
                    }
                }
            }
استفاده از ایندکس تعریف شده نیز بسیار ساده می‌باشد. تنها کافی است نام آن‌را به متد Query ارسال نمائیم. اینبار اگر به خروجی کنسول سرور RavenDB دقت کنیم، از ایندکس indexes/QuestionsByTitle بجای ایندکس‌های پویا استفاده کرده است:
Request # 147: GET     -    58 ms - <system>   - 200 - /indexes/QuestionsByTitle?&query=Title%3ARaven*&pageSize=128
        Query: Title:Raven*
        Time: 7 ms
        Index: QuestionsByTitle
        Results: 2 returned out of 2 total.
روش مشخص سازی نام ایندکس با استفاده از رشته‌ها، با هر دو روش store.DatabaseCommands.PutIndex و استفاده از AbstractIndexCreationTask سازگار است. اما اگر ایندکس‌های خود را با ارث بری از AbstractIndexCreationTask ایجاد کرده‌ایم، می‌توان نام کلاس مشتق شده را به صورت یک آرگومان جنریک دوم به متد Query به شکل زیر ارسال کرد تا از مزایای تعریف strongly typed آن نیز بهره‌مند شویم:
                    var questions = session.Query<Question, QuestionsByTitle>()
                                           .Where(x => x.Title.StartsWith("Raven")).Take(128);

ایجاد ایندکس‌های پیشرفته با پیاده سازی Map/Reduce

حالتی را در نظر بگیرید که در آن قصد داریم تعداد عنوان‌های سؤالات مانند هم را بیابیم (یا تعداد مطالب گروه‌های مختلف یک وبلاگ را محاسبه کنیم). برای انجام اینکار با سرعت بسیار بالا، می‌توانیم از ایندکس‌هایی با قابلیت محاسباتی در RavenDB استفاده کنیم. کار با ارث بری از کلاس AbstractIndexCreationTask شروع می‌شود. آرگومان جنریک اول آن، نام کلاسی است که در تهیه ایندکس شرکت خواهد داشت و آرگومان دوم (و اختیاری) ذکر شده، نتیجه عملیات Reduce است:
    public class QuestionsCountByTitleReduceResult
    {
        public string Title { set; get; }
        public int Count { set; get; }
    }

    public class QuestionsCountByTitle : AbstractIndexCreationTask<Question, QuestionsCountByTitleReduceResult>
    {
        public QuestionsCountByTitle()
        {
            Map = questions => questions.Select(question =>
                                                    new
                                                    {
                                                        Title = question.Title,
                                                        Count = 1
                                                    });
            Reduce = results => results.GroupBy(x => x.Title)
                                       .Select(g =>
                                                   new
                                                   {
                                                       Title = g.Key,
                                                       Count = g.Sum(x => x.Count)
                                                   });
        }
    }
در اینجا یک ایندکس پیشرفته را تعریف کرده‌ایم که در آن در قسمت Map، کار ایندکس کردن تک تک عنوان‌ها انجام خواهد شد. به همین جهت مقدار Count در این حالت، عدد یک است. در قسمت Reduce، بر روی نتیجه قسمت Map کوئری LINQ دیگری نوشته شده و تعداد عنوان‌های همانند، با گروه بندی اطلاعات، شمارش گردیده است.
اکنون برای استفاده از این ایندکس، ابتدا توسط متد IndexCreation.CreateIndexes، کار معرفی آن به RavenDB صورت گرفته و سپس متد Query سشن باز شده، دو آرگومان جنریگ را خواهد پذیرفت. اولین آرگومان، همان نتیجه Map/Reduce است و دومین آرگومان نام کلاس ایندکس جدید تعریف شده می‌باشد:
            using (var store = new DocumentStore
            {
                Url = "http://localhost:8080"
            }.Initialize())
            {
                IndexCreation.CreateIndexes(typeof(QuestionsCountByTitle).Assembly, store);

                using (var session = store.OpenSession())
                {
                    var result = session.Query<QuestionsCountByTitleReduceResult, QuestionsCountByTitle>()
                                         .FirstOrDefault(x => x.Title == "Raven") ?? new QuestionsCountByTitleReduceResult();
                    Console.WriteLine(result.Count);
                }
            }
در کوئری فوق چون عملیات بر روی نتیجه نهایی باید صورت گیرد از FirstOrDefault استفاده شده است. این کوئری در حقیقت بر روی قسمت Reduce پیشتر محاسبه شده، اجرا می‌شود.
مطالب
بررسی تغییرات Blazor 8x - قسمت دوم - بررسی حالت رندر سمت سرور
در قسمت قبل، حالت‌های مختلف رندر کامپوننت‌ها را در Blazor 8x معرفی کردیم. در این قسمت می‌خواهیم نحوه‌ی کارکرد دو حالت InteractiveServer و StreamRendering را به همراه چند مثال بررسی کنیم.


معرفی قالب‌های جدید شروع پروژه‌های Blazor در دات نت 8

پس از نصب SDK دات نت 8، دیگر خبری از قالب‌های قدیمی پروژه‌های blazor server و blazor wasm نیست! در اینجا در ابتدا باید مشخص کرد که سطح تعاملی برنامه در چه حدی است. در ادامه 4 روش شروع پروژه‌های Blazor 8x را مشاهده می‌کنید که توسط پرچم interactivity--، نوع رندر برنامه در آن‌ها مشخص شده‌است:

اجرای قسمت‌های تعاملی برنامه بر روی سرور:
dotnet new blazor --interactivity Server

اجرای قسمت‌های تعاملی برنامه در مرورگر، توسط فناوری وب‌اسمبلی:
dotnet new blazor --interactivity WebAssembly

برای اجرای قسمت‌های تعاملی برنامه، ابتدا حالت Server فعالسازی می‌شود تا فایل‌های WebAssembly دریافت شوند، سپس فقط از WebAssembly استفاده می‌کند:
dotnet new blazor --interactivity Auto

فقط از حالت SSR یا همان static server rendering استفاده می‌شود (این نوع برنامه‌ها تعاملی نیستند):
dotnet new blazor --interactivity None

سایر گزینه‌ها را با اجرای دستور dotnet new blazor --help می‌توانید مشاهده کنید.


نکته‌ی مهم! در قالب‌های آماده‌ی Blazor 8x، حالت SSR، پیش‌فرض است.

هرچند در تمام پروژه‌های فوق، انتخاب حالت‌های مختلف رندر را مشاهده می‌کنید، اما این انتخاب‌ها صرفا دو مقصود مهم را دنبال می‌کنند:
الف) تنظیم فایل Program.cs برنامه جهت افزودن وابستگی‌های مورد نیاز، به صورت خودکار.
ب) ایجاد پروژه‌ی کلاینت (علاوه بر پروژه‌ی سرور)، در صورت نیاز. برای مثال حالت‌های وب‌اسمبلی و Auto، هر دو به همراه یک پروژه‌ی کلاینت وب‌اسمبلی هم هستند؛ اما حالت‌های Server و None، خیر.

در تمام این پروژه‌ها هر صفحه و یا کامپوننتی که ایجاد می‌شود، به صورت پیش‌فرض بر اساس SSR رندر و نمایش داده خواهد شد؛ مگر اینکه به صورت صریحی این نحوه‌ی رندر را بازنویسی کنیم. برای مثال مشخص کنیم که قرار است بر اساس Blazor Server اجرا شود و یا وب‌اسمبلی و یا حالت Auto.

اما ... اگر علاقمند بودیم تا به حالت‌های پیش‌از دات نت 8 رجوع کنیم و تمام برنامه را به صورت یک‌دست تعاملی کنیم و حالت SSR پیش‌فرض نباشد، می‌توان از پرچم all-interactive-- که به انتهای دستورات فوق قابل افزوده شدن است، کمک گرفت. این پرچم، فایل App.Razor را جهت تنظیم سراسری حالت‌های رندر، ویرایش می‌کند. این مورد را در ادامه‌ی این مطلب، در قسمت «روشی ساده برای تعاملی کردن کل برنامه» بیشتر بررسی می‌کنیم.


بررسی حالت Server side rendering

برای بررسی این حالت یک پوشه‌ی جدید را ایجاد کرده و توسط خط فرمان، دستور dotnet new blazor --interactivity Server را در ریشه‌ی آن اجرا می‌کنیم. پس از ایجاد ساختار ابتدایی پروژه بر اساس این قالب انتخابی، فایل Program.cs جدید آن، چنین شکلی را دارد:
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorComponents().AddInteractiveServerComponents();

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error", createScopeForErrors: true);
    app.UseHsts();
}

app.UseHttpsRedirection();

app.UseStaticFiles();
app.UseAntiforgery();

app.MapRazorComponents<App>().AddInteractiveServerRenderMode();

app.Run();
مهم‌ترین قسمت‌های آن، متدهای AddInteractiveServerComponents و AddInteractiveServerRenderMode هستند که server-side rendering را به همراه امکان داشتن کامپوننت‌های تعاملی، میسر می‌کنند.
server-side rendering به این معنا است که برنامه‌ی سمت سرور، کل DOM و HTML نهایی را تولید کرده و به مرورگر کاربر ارائه می‌کند. مرورگر هم این DOM را نمایش می‌دهد. فقط همین! در اینجا هیچ خبری از اتصال دائم SignalR نیست و محتوای ارائه شده، یک محتوای استاتیک است. این حالت رندر، برای ارائه‌ی محتواهای فقط خواندنی غیرتعاملی، فوق العاده‌است؛ امکان از لحظه‌ای که نیاز به کلیک بر روی دکمه‌ای باشد، دیگر پاسخگو نیست. به همین جهت در اینجا امکان تعاملی کردن تعدادی از کامپوننت‌های ویژه و مدنظر نیز پیش‌بینی شده‌اند تا بتوان به ترکیبی از server-side rendering و client-side rendering رسید.
حالت پیش‌فرض در اینجا، ارائه‌ی محتوای استاتیک است. بنابراین هر کامپوننتی در اینجا ابتدا بر روی سرور رندر شده (HTML ابتدایی آن آماده شده) و به سمت مرورگر کاربر ارسال می‌شود. اگر کامپوننتی نیاز به امکانات تعاملی داشت باید آن‌را دقیقا توسط ویژگی InteractiveXYZ مشخص کند؛ مانند مثال زیر:
@page "/counter"
@rendermode InteractiveServer

<PageTitle>Counter</PageTitle>

<h1>Counter</h1>

<p role="status">Current count: @currentCount</p>
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>

@code {
    private int currentCount = 0;

    private void IncrementCount()
    {
        currentCount++;
    }
}
همانطور که مشاهده می‌کنید، این کامپوننت از روش رندر InteractiveServer استفاده می‌کند. برای درک نحوه‌ی کارکرد آن، همین سطر را حذف، یا کامنت کنید و سپس برنامه را اجرا کنید. در حین مشاهده‌ی این صفحه، همه چیز به خوبی نمایش داده می‌شود و حتی دکمه‌ی Click me هم مشخص است. اما ... با کلیک بر روی آن اتفاقی رخ نمی‌دهد! علت اینجا است که اکنون این صفحه، یک صفحه‌ی کاملا استاتیک است و دیگر تعاملی نیست.
در ادامه، مجددا سطر کامنت شده را به حالت عادی برگردانید و سپس برنامه را اجرا کنید. پیش از باز کردن صفحه‌ی Counter، ابتدا developer tools مرورگر خود را گشوده و برگه‌ی network آن‌را انتخاب و سپس صفحه‌ی Counter را باز کنید. در این لحظه‌است که مشاهده می‌کنید یک اتصال وب‌سوکت برقرار شد. این اتصال است که قابلیت‌های تعاملی صفحه را برقرار کرده و مدیریت می‌کند (این اتصال دائم SignalR است که این صفحه را همانند برنامه‌های Blazor Web Server پیشین مدیریت می‌کند).

یک نکته: در برنامه‌های Blazor Server سنتی، امکان فعالسازی قابلیتی به نام prerender نیز وجود دارد. یعنی سرور، ابتدا صفحه را رندر کرده و محتوای استاتیک آن‌را به سمت مرورگر کاربر ارسال می‌کند و سپس اتصال SignalR برقرار می‌شود. در دات نت 8، این حالت، حالت پیش‌فرض است. اگر آن‌را نمی‌خواهید باید به نحو زیر غیرفعالش کنید:
@rendermode InteractiveServerRenderModeWithoutPrerendering

@code{
  static readonly IComponentRenderMode InteractiveServerRenderModeWithoutPrerendering = 
        new InteractiveServerRenderMode(false);
}


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

اگر می‌خواهید رفتار برنامه را همانند Blazor Server سابق کنید و نمی‌خواهید به ازای هر کامپوننت، نحوه‌ی رندر آن‌را به صورت سفارشی انتخاب کنید، فقط کافی است فایل App.razor را باز کرده:
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <base href="/" />
    <link rel="stylesheet" href="bootstrap/bootstrap.min.css" />
    <link rel="stylesheet" href="app.css" />
    <link rel="stylesheet" href="MyApp.styles.css" />
    <link rel="icon" type="image/png" href="favicon.png" />
    <HeadOutlet />
</head>

<body>
    <Routes />
    <script src="_framework/blazor.web.js"></script>
</body>

</html>
و قسمت‌های HeadOutlet و مسیریابی آن‌را به صورت زیر تغییر دهید تا به کل برنامه اعمال شود:
<HeadOutlet @rendermode="@InteractiveServer" />
...
<Routes @rendermode="@InteractiveServer" />
این مورد دقیقا همان کاری است که پرچم all-interactive-- در هنگام ایجاد پروژه‌های جدید Blazor 8x به کمک NET CLI.، انجام می‌دهد. یک چنین گزینه‌ای در ویژوال استودیو نیز هنگام ایجاد پروژه‌ها‌ی جدید Blazor وجود دارد و به شما این امکان را می‌دهد که بین حالت‌های تعاملی Per page/component و Global، یکی را انتخاب کنید. در حین استفاده‌ی از CLI، نیازی به ذکر حالت تعاملی Per page/component نیست؛ چون حالت پیش‌فرض، یا همان SSR است. حالت Global هم فقط فایل App.Razor را به صورت فوق، ویرایش و تنظیم می‌کند.


در این حالت دیگر نیازی نیست تا به ازای هر کامپوننت و صفحه، نحوه‌ی رندر را مشخص کنیم؛ چون این نحوه، از بالاترین سطح، به تمام زیرکامپوننت‌ها به ارث می‌رسد (درباره‌ی این نکته در قسمت قبل، توضیحاتی ارائه شد).


بررسی حالت Streaming Rendering

در اینجا مثال پیش‌فرض Weather.razor قالب پیش‌فرض مورد استفاده‌ی جاری را کمی تغییر داده‌ایم که کدهای نهایی آن به صورت زیر است (2 قسمت forecasts.AddRange_ را اضافه‌تر دارد):

@page "/weather"
@attribute [StreamRendering(prerender: true)]

<PageTitle>Weather</PageTitle>

<h1>Weather</h1>

<p>This component demonstrates showing data.</p>

@if (_forecasts == null)
{
    <p>
        <em>Loading...</em>
    </p>
}
else
{
    <table class="table">
        <thead>
        <tr>
            <th>Date</th>
            <th>Temp. (C)</th>
            <th>Temp. (F)</th>
            <th>Summary</th>
        </tr>
        </thead>
        <tbody>
        @foreach (var forecast in _forecasts)
        {
            <tr>
                <td>@forecast.Date.ToShortDateString()</td>
                <td>@forecast.TemperatureC</td>
                <td>@forecast.TemperatureF</td>
                <td>@forecast.Summary</td>
            </tr>
        }
        </tbody>
    </table>
}

@code {
    private List<WeatherForecast>? _forecasts;

    protected override async Task OnInitializedAsync()
    {
    // Simulate asynchronous loading to demonstrate streaming rendering
        await Task.Delay(500);

        var startDate = DateOnly.FromDateTime(DateTime.Now);
        var summaries = new[]
                        {
                            "Freezing",
                            "Bracing",
                            "Chilly",
                            "Cool",
                            "Mild",
                            "Warm",
                            "Balmy",
                            "Hot",
                            "Sweltering",
                            "Scorching",
                        };
        _forecasts = GetWeatherForecasts(startDate, summaries).ToList();
        StateHasChanged();

    // Simulate asynchronous loading to demonstrate streaming rendering
        await Task.Delay(1000);
        _forecasts.AddRange(GetWeatherForecasts(startDate, summaries));
        StateHasChanged();

        await Task.Delay(1000);
        _forecasts.AddRange(GetWeatherForecasts(startDate, summaries));
    }

    private static IEnumerable<WeatherForecast> GetWeatherForecasts(DateOnly startDate, string[] summaries)
    {
        return Enumerable.Range(1, 5)
                         .Select(index => new WeatherForecast
                                          {
                                              Date = startDate.AddDays(index),
                                              TemperatureC = Random.Shared.Next(-20, 55),
                                              Summary = summaries[Random.Shared.Next(summaries.Length)],
                                          });
    }

    private class WeatherForecast
    {
        public DateOnly Date { get; set; }
        public int TemperatureC { get; set; }
        public string? Summary { get; set; }
        public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
    }

}
برای بررسی این مثال، ابتدا سطر ویژگی StreamRendering را حذف و یا کامنت کرده و سپس برنامه را اجرا کنید. پس از اجرای برنامه، با انتخاب مشاهده‌ی صفحه‌ی Weather، هیچگاه قسمت loading که در حالت forecasts == null_ قرار است ظاهر شود، نمایش داده نمی‌شود؛ چون در این حالت (حذف نوع رندر)، صفحه‌ی نهایی که به کاربر ارائه خواهد شد، یک صفحه‌ی استاتیک کاملا رندر شده‌ی در سمت سرور است و کاربر باید تا زمان پایان این رندر در سمت سرور، منتظر بماند و سپس صفحه‌ی نهایی را دریافت و مشاهده کند. در این حالت امکانات تعاملی Blazor server وجود خارجی ندارند.
در ادامه مجددا سطر ویژگی StreamRendering را به حالت قبلی برگردانید و برنامه را اجرا کنید. در این حالت ابتدا قسمت loading ظاهر می‌شود و سپس در طی چند مرحله با توجه به Task.Delay‌های قرار داده شده، صفحه رندر شده و تکمیل می‌شود.
اتفاقی که در اینجا رخ می‌دهد، استفاده از فناوری  HTML Streaming است که مختص به مایکروسافت هم نیست. در حالت Streaming، هربار قطعه‌ای از HTML ای که قرار است به کاربر ارائه شود، به صورت جریانی به سمت مرورگر ارسال می‌شود و مرورگر این قطعه‌ی جدید را بلافاصله نمایش می‌دهد. نکته‌ی جالب این روش، عدم نیاز به اتصال SignalR و یا اجرای WASM درون مرورگر است.

Streaming rendering حالت بینابین رندر کامل در سمت سرور و رندر کامل در سمت کلاینت است. در حالت رندر سمت سرور، کل HTML صفحه ابتدا توسط سرور تهیه و بازگشت داده می‌شود و کاربر باید تا پایان عملیات تهیه‌ی این HTML نهایی، منتظر باقی بماند و در این بین چیزی را مشاهده نخواهد کرد. در حالت Streaming rendering، هنوز هم همان حالت تهیه‌ی HTML استاتیک سمت سرور برقرار است؛ به همراه تعدادی محل جایگذاری اطلاعات جدید. به محض پایان یک عمل async سمت سرور که سه نمونه‌ی آن را در مثال فوق مشاهده می‌کنید، برنامه، جریان قطعه‌ای از اطلاعات استاتیک را به سمت مرورگر کاربر ارسال می‌کند تا در مکان‌هایی از پیش تعیین شده، درج شوند.
در حالت SSR، فقط یکبار شانس ارسال کل اطلاعات به سمت مرورگر کاربر وجود دارد؛ اما در حالت Streaming rendering، ابتدا می‌توان یک قالب HTML ای را بازگشت داد و سپس مابقی محتوای آن‌را به محض آماده شدن در طی چند مرحله بازگشت داد. در این حالت نمایش گزارشی از اطلاعاتی که ممکن است با تاخیر در سمت سرور تهیه شوند، ساده‌تر می‌شود. یعنی می‌توان هربار قسمتی را که تهیه شده، برای نمایش بازگشت داد و کاربر تا مدت زیادی منتظر نمایش کل صفحه باقی نخواهد ماند.


روش نهایی معرفی نحوه‌ی رندر صفحات

بجای استفاده از ویژگی‌های RenderModeXyz جهت معرفی نحوه‌ی رندر کامپوننت‌ها و صفحات (که تا پیش از نگارش RTM معرفی شده بودند و چندبار هم تغییر کردند)، می‌توان از دایرکتیو جدیدی به نام rendermode@ با سه مقدار InteractiveServer، InteractiveWebAssembly و InteractiveAuto استفاده کرد. برای سهولت تعریف این موارد باید سطر ذیل را به فایل Imports.razor_ اضافه نمود:
@using static Microsoft.AspNetCore.Components.Web.RenderMode
در این حالت تعریف قدیمی سطر ذیل:
@attribute [RenderModeInteractiveServer]
به صورت زیر:
@rendermode InteractiveServer
تبدیل می‌شود.

اگر هم قصد سفارشی سازی آن‌ها را دارید، برای مثال می‌خواهید prerender را در آن‌ها false کنید، روش کار به صورت زیر است:
@rendermode renderMode

@code {
    static IComponentRenderMode renderMode = new InteractiveWebAssemblyRenderMode(prerender: false);
}
مطالب
React 16x - قسمت 22 - ارتباط با سرور - بخش 1 - برپایی تنظیمات
هر برنامه‌ی وب، دارای یک frontend و یک backend است. تا اینجا، تمام تمرکز این سری، بر روی پیاده سازی frontend بود و هیچکدام از برنامه‌هایی را که تکمیل کردیم، تبادل اطلاعاتی را با وب سرویس‌های backend نداشتند؛ اما به عنوان یک توسعه دهنده‌ی React، نیاز است با نحوه‌ی ارتباط با سرور آشنایی داشت که در طی چند قسمت به آن می‌پردازیم.


ایجاد برنامه‌ی backend ارائه دهنده‌ی REST API

در اینجا یک برنامه‌ی ساده‌ی ASP.NET Core Web API را جهت تدارک سرویس‌های backend، مورد استفاده قرار می‌دهیم. هرچند این مورد الزامی نبوده و اگر علاقمند بودید که مستقل از آن کار کنید، حتی می‌توانید از سرویس آنلاین JSONPlaceholder نیز برای این منظور استفاده کنید که یک Fake Online REST API است. کار آن ارائه‌ی یک سری endpoint است که به صورت عمومی از طریق وب قابل دسترسی هستند. می‌توان به این endpintها درخواست‌های HTTP خود را مانند GET/POST/DELETE/UPDATE ارسال کرد و از آن اطلاعاتی را دریافت نمود و یا تغییر داد. به هر کدام از این endpointها یک API گفته می‌شود که جهت آزمایش برنامه‌ها بسیار مناسب هستند. برای نمونه در قسمت resources آن اگر به آدرس https://jsonplaceholder.typicode.com/posts مراجعه کنید، می‌توان لیستی از مطالب را با فرمت JSON مشاهده کرد. کار آن ارائه‌ی آرایه‌ای از اشیاء جاوا اسکریپتی قابل استفاده‌ی در برنامه‌های frontend است. بنابراین زمانیکه یک HTTP GET را به این endpoint ارسال می‌کنیم، آرایه‌ای از اشیاء مطالب را دریافت خواهیم کرد. همین endpoint، امکان تغییر این اطلاعات را توسط برای مثال HTTP Delete نیز میسر کرده‌است.

اگر علاقمندید بودید می‌توانید از JSONPlaceholder استفاده کنید و یا در ادامه دقیقا ساختار همین endpoint ارائه‌ی مطالب آن‌را با ASP.NET Core Web API نیز پیاده سازی می‌کنیم (برای مطالعه‌ی قسمت «ارتباط با سرور» اختیاری است و از هر REST API مشابهی که توسط nodejs یا PHP و غیره تولید شده باشد نیز می‌توان استفاده کرد):

مدل مطالب
namespace sample_22_backend.Models
{
    public class Post
    {
        public int Id { set; get; }
        public string Title { set; get; }
        public string Body { set; get; }

        public int UserId { set; get; }
    }
}
ساختار این مدل، با ساختار مدل مطالب JSONPlaceholder یکی درنظر گرفته شده‌است، تا مطلب قابلیت پیگیری بیشتری را پیدا کند.


منبع داده‌ی فرضی مطالب

برای ارائه‌ی ساده‌تر برنامه، یک منبع داده‌ی درون حافظه‌ای را به همراه یک سرویس، در اختیار کنترلر مطالب، قرار می‌دهیم:
using System;
using System.Collections.Generic;
using System.Linq;
using sample_22_backend.Models;

namespace sample_22_backend.Services
{
    public interface IPostsDataSource
    {
        List<Post> GetAllPosts();
        bool DeletePost(int id);
        Post AddPost(Post post);
        bool UpdatePost(int id, Post post);
        Post GetPost(int id);
    }

    /// <summary>
    /// هدف صرفا تهیه یک منبع داده آزمایشی ساده تشکیل شده در حافظه است
    /// </summary>
    public class PostsDataSource : IPostsDataSource
    {
        private readonly List<Post> _allPosts;

        public PostsDataSource()
        {
            _allPosts = createDataSource();
        }

        public List<Post> GetAllPosts()
        {
            return _allPosts;
        }

        public Post GetPost(int id)
        {
            return _allPosts.Find(x => x.Id == id);
        }

        public bool DeletePost(int id)
        {
            var item = _allPosts.Find(x => x.Id == id);
            if (item == null)
            {
                return false;
            }

            _allPosts.Remove(item);
            return true;
        }

        public Post AddPost(Post post)
        {
            var id = 1;
            var lastItem = _allPosts.LastOrDefault();
            if (lastItem != null)
            {
                id = lastItem.Id + 1;
            }

            post.Id = id;
            _allPosts.Add(post);
            return post;
        }

        public bool UpdatePost(int id, Post post)
        {
            var item = _allPosts
                .Select((pst, index) => new { Item = pst, Index = index })
                .FirstOrDefault(x => x.Item.Id == id);
            if (item == null || id != post.Id)
            {
                return false;
            }

            _allPosts[item.Index] = post;
            return true;
        }

        private static List<Post> createDataSource()
        {
            var list = new List<Post>();
            var rnd = new Random();
            for (var i = 1; i < 10; i++)
            {
                list.Add(new Post { Id = i, UserId = rnd.Next(1, 1000), Title = $"Title {i} ...", Body = $"Body {i} ..." });
            }
            return list;
        }
    }
}
در این سرویس، نیازمندی‌های کنترلر مطالب مانند ارائه لیست تمام مطالب، نمایش اطلاعات یک مطلب، به روز رسانی، ایجاد و حذف یک مطلب، تدارک دیده شده‌اند. سپس از این سرویس در کنترلر زیر استفاده می‌کنیم:


کنترلر Web API برنامه‌ی backend

using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;
using sample_22_backend.Models;
using sample_22_backend.Services;

namespace sample_22_backend.Controllers
{
    [ApiController]
    [Route("api/[controller]")]
    public class PostsController : ControllerBase
    {
        private readonly IPostsDataSource _postsDataSource;

        public PostsController(IPostsDataSource postsDataSource)
        {
            _postsDataSource = postsDataSource;
        }

        [HttpGet]
        public ActionResult<List<Post>> GetPosts()
        {
            return _postsDataSource.GetAllPosts();
        }

        [HttpGet("{id}")]
        public ActionResult<Post> GetPost(int id)
        {
            var post = _postsDataSource.GetPost(id);
            if (post == null)
            {
                return NotFound();
            }
            return Ok(post);
        }

        [HttpDelete("{id}")]
        public ActionResult DeletePost(int id)
        {
            var deleted = _postsDataSource.DeletePost(id);
            if (deleted)
            {
                return Ok();
            }
            return NotFound();
        }

        [HttpPost]
        public ActionResult<Post> CreatePost([FromBody]Post post)
        {
            post = _postsDataSource.AddPost(post);
            return CreatedAtRoute(nameof(GetPost), new { post.Id }, post);
        }

        [HttpPut("{id}")]
        public ActionResult<Post> UpdatePost(int id, [FromBody]Post post)
        {
            var updated = _postsDataSource.UpdatePost(id, post);
            if (updated)
            {
                return Ok(post);
            }
            return NotFound();
        }
    }
}
این کنترلر که در مسیر شروع شده‌ی با https://localhost:5001/api قرار می‌گیرد، جهت پشتیبانی از افعال مختلف HTTP مانند Get/Post/Delete/Update طراحی شده‌است که در ادامه، در برنامه‌ی React خود از آن‌ها استفاده خواهیم کرد. پس از ایجاد این پروژه‌ی web api، یک نمونه خروجی آن در مسیر https://localhost:5001/api/posts، به صورت زیر خواهد بود:


البته نمایش فرمت شده‌ی JSON در مرورگر کروم، نیاز به نصب این افزونه را دارد.


ایجاد ساختار ابتدایی برنامه‌ی ارتباط با سرور

در اینجا برای بررسی کار با سرور، یک پروژه‌ی جدید React را ایجاد می‌کنیم:
> create-react-app sample-22-frontend
> cd sample-22-frontend
> npm start
در ادامه توئیتر بوت استرپ 4 را نیز نصب می‌کنیم. برای این منظور پس از باز کردن پوشه‌ی اصلی برنامه توسط VSCode، دکمه‌های ctrl+` را فشرده (ctrl+back-tick) و دستور زیر را در ترمینال ظاهر شده وارد کنید:
> npm install --save bootstrap
سپس برای افزودن فایل bootstrap.css به پروژه‌ی React خود، ابتدای فایل index.js را به نحو زیر ویرایش خواهیم کرد:
import "bootstrap/dist/css/bootstrap.css";
این import به صورت خودکار توسط webpack ای که در پشت صحنه کار bundling & minification برنامه را انجام می‌دهد، مورد استفاده قرار می‌گیرد.

سپس فایل app.js را به شکل زیر تکمیل می‌کنیم:
import "./App.css";

import React, { Component } from "react";

class App extends Component {
  state = {
    posts: []
  };

  handleAdd = () => {
    console.log("Add");
  };

  handleUpdate = post => {
    console.log("Update", post);
  };

  handleDelete = post => {
    console.log("Delete", post);
  };

  render() {
    return (
      <React.Fragment>
        <button className="btn btn-primary mt-1 mb-1" onClick={this.handleAdd}>
          Add
        </button>
        <table className="table">
          <thead>
            <tr>
              <th>Title</th>
              <th>Update</th>
              <th>Delete</th>
            </tr>
          </thead>
          <tbody>
            {this.state.posts.map(post => (
              <tr key={post.id}>
                <td>{post.title}</td>
                <td>
                  <button
                    className="btn btn-info btn-sm"
                    onClick={() => this.handleUpdate(post)}
                  >
                    Update
                  </button>
                </td>
                <td>
                  <button
                    className="btn btn-danger btn-sm"
                    onClick={() => this.handleDelete(post)}
                  >
                    Delete
                  </button>
                </td>
              </tr>
            ))}
          </tbody>
        </table>
      </React.Fragment>
    );
  }
}

export default App;
که حاصل آن، یک دکمه، برای افزودن مطلبی جدید، به همراه جدولی است از مطالب که قصد داریم در ادامه، اطلاعات آن‌را از سرور دریافت کرده و حذف و یا به روز رسانی کنیم:



نگاهی به انواع و اقسام HTTP Client‌های مهیا

در ادامه نیاز خواهیم داشت تا از طریق برنامه‌های React خود، درخواست‌های HTTP را به سمت سرور (یا همان برنامه‌ی backend) ارسال کنیم، تا بتوان اطلاعاتی را از آن دریافت کرد و یا تغییری را در اطلاعات موجود، ایجاد نمود. همانطور که پیشتر نیز در این سری عنوان شد، React برای این مورد نیز راه‌حل توکاری را به همراه ندارد و تنها کار آن، رندر کردن View و مدیریت DOM است. البته شاید این مورد یکی از مزایای کار با React نیز باشد! چون در این حالت می‌توانید از کتابخانه‌هایی که خودتان ترجیح می‌دهید، نسبت به کتابخانه‌هایی که به شما ارائه/تحمیل (!) می‌شوند (مانند برنامه‌های Angular) آزادی انتخاب کاملی را داشته باشید. برای مثال هرچند Angular به همراه یک HTTP Module توکار است، اما تاکنون چندین بار بازنویسی از ابتدا شده‌است! ابتدا با یک کتابخانه‌ی HTTP مقدماتی شروع کردند. بعدی آن‌را منسوخ شده اعلام و با یک ماژول جدید جایگزین کردند. بعد در نگارشی دیگر، چون این کتابخانه وابسته‌است به RxJS و خود RxJS نیز بازنویسی کامل شد، روش کار کردن با این HTTP Module نیز مجددا تغییر پیدا کرد! بنابراین اگر با Angular کار می‌کنید، باید کارها را آنگونه که Angular می‌پسندد، انجام دهید؛ اما در اینجا خیر و آزادی انتخاب کاملی برقرار است.
بنابراین اکنون این سؤال مطرح می‌شود که در React، برای برقراری ارتباط با سرور، چه باید کرد؟ در اینجا آزاد هستید برای مثال از Fetch API جدید مرورگرها و یا روش Ajax ای مبتنی بر XML قدیمی‌تر آن‌ها، استفاده کنید (اطلاعات بیشتر) و یا حتی اگر علاقمند باشید می‌توانید از محصور کننده‌های آن مانند jQuery Ajax استفاده کنید. بنابراین اگر با jQuery Ajax راحت هستید، به سادگی می‌توانید از آن در برنامه‌های React نیز استفاده کنید. اما ... ما در اینجا از یک کتابخانه‌ی بسیار محبوب و قدرتمند HTTP Client، به نام Axios (اکسیوس/ یک واژه‌ی یونانی به معنای «سودمند») استفاده خواهیم کرد که فقط تعداد بار دانلود هفتگی آن، 6 میلیون بار است!


نصب Axios در برنامه‌ی React این قسمت

برای نصب کتابخانه‌ی Axios، در ریشه‌ی پروژه‌ی React این قسمت، دستور زیر را در خط فرمان صادر کنید:
> npm install --save axios
پس از برپایی این مقدمات، ادامه‌ی مطلب «ارتباط با سرور» را در قسمت بعدی پیگیری می‌کنیم.


کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: sample-22-frontend-part-01.zip و sample-22-backend-part-01.zip
مطالب
فقط به خاطر یک نیم فاصله!
نسخه‌ی 64 بیتی ویندوز 7 را نصب کرده‌ام و اولین مشکل، نبود صفحه کلید مطابق استاندارد 2091 برای نسخه‌های 64 بیتی ویندوز است. پروژه وب فارسی دانشگاه صنعتی شریف، سال‌ها قبل فقط یک نسخه‌ی 32 بیتی از آن را تهیه کرده و نسخه‌های 64 بیتی موجود، مطابق استاندارد 9147 هستند و من به دلایل ذیل حاضر به استفاده از آن نیستم!
  1. در استاندارد 9147 ، جای ژ و پ مطابق صفحه کلیدی که در بازار ایران فروخته می‌شود نیست (و باید به همه پاسخگو باشید که چرا اینطوری است).
  2. ی و ک در آن اصلاح شده و دیگر عربی نیست که چقدر هم خوب، اما من تعداد زیادی برنامه و همچنین تعداد قابل توجهی فایل word دارم که مطابق استاندارد 2091 تهیه شده‌اند. مشکلات ی و ک فارسی و عربی را هم در اینجا ذکر کرده‌ام و به دنبال باگ‌های جدید در برنامه‌ها نمی‌گردم.
  3. با ی و ک فارسی اگر در گوگل جستجو کنید تعداد پاسخ‌های مرتبط یافت شده بسیار بسیار کمتر از حالتی خواهد بود که با ی و ک عربی جستجو کنید.
  4. در یک سازمان باید رویه‌ا‌ی واحد در این مورد برقرار باشد. یا همه باید از 2091 استفاده کنند و یا همه از 9147.

نسخه‌ی کامل استاندارد 9147 را از سایت آقای اخگری می‌توانید دریافت نمائید:

به همین دلایل خصوصا قسمت دوم (هر چند ممکن است با آن موافق نباشید)، نیاز به صفحه کلید مطابق استاندارد 2091 نسخه‌ی 64 بیتی داشتم.

برای تهیه صفحه کلید فارسی از برنامه Microsoft Keyboard Layout Creator استفاده می‌شود که نسخه‌ی 1.4 آن‌را از آدرس زیر می‌توانید دریافت نمائید. این نسخه قابلیت تولید فایل‌های dll مرتبط 64 بیتی را هم دارد:


یکی از قابلیت‌های این برنامه بارگذاری صفحه کلید فارسی جاری سیستم است:



صفحه کلید استاندارد 2091 پروژه وب فارسی را اگر با آن باز کرده و سپس از منوی Project گزینه‌ی Build DLL and setup package را انتخاب نمائید، با خطای زیر متوقف خواهید شد:
ERROR: 'VK_SPACE' in Shift State 'Shift' must be made up of white space character(s), but is defined as '‌' (U+200c) instead.
که دقیقا مربوطه به این سطر در تعاریف صفحه کلید است (صفحه کلید را می‌شود به صورت فایلی با پسوند klc هم save as کرد):
39 SPACE  0 0020 200c 0020 -1  // SPACE, ZERO WIDTH NON-JOINER, SPACE, <none>
مفاهیم این ستون‌ها هم به شرح زیر هستند:
0 //Column 4
1 //Column 5 : Shft
2 //Column 6 :       Ctrl
3 //Column 7 : Shft  Ctrl
یعنی جهت نمایش نیم فاصله حاصل از ترکیب shift space مطابق استاندارد تایپ ایران، بجای 0x0020 یا همان فاصله، از 0x200C استفاده شده است (مطابق استاندارد تایپ ایران باید نوشت "می‌روم" و نه "می روم". به نیم فاصله و فاصله دقت بفرمائید).
اما این برنامه‌ی محترم دقیقا همین مورد را غلط گرفته و فایل dll نهایی را تولید نمی‌کند (مطابق خطایی که ذکر شد).
برنامه‌ی Microsoft Keyboard Layout Creator هم با دات نت فریم ورک نوشته شده است. اگر کمی با reflector به آنالیز آن بپردازیم به کلاس Accept و متد زیر خواهیم رسید:
private bool VerifySpaceBarIntegrity(ShiftState ss, bool fMustBeDefined)
در این متد جایی که خطای ذکر شده صادر می‌شود، مربوط به چند سطر زیر است:
if (!Utilities.IsSpacing(ss.Characters))
{
    this.WriteErrorToLogFile("SpaceKeyNeedsWhiteSpaceCharacters", new string[] { "VK_" + Utilities.VkStringOfIvk(ss.VK), this.m_stStateLabel[ss.State], ss.Characters, Utilities.FromCharacterToUPlusForm(ss.Characters) });
    flag = false; /*اینجا باید اصلاح شود!*/
}
خوب! به نظر شما ایرادی دارد که این flag همیشه true باشد؟! برای همین منظور، فایل MSKLC.exe را پچ کرده‌ام که از آدرس زیر قابل دریافت است:

  دریافت پچ شده نگارش 1.4.6000.2 : MSKLC.Patched.7z  

با استفاده از این نسخه (ابتدا برنامه اصلی را نصب کرده و سپس فایل exe را جایگزین نمائید)، به راحتی می‌توان نسخه‌ی استاندارد و اصلی 2091 را بارگذاری و مجددا کامپایل کرد (بدون توقف کامپایل به خاطر فقط یک نیم فاصله‌ی غیرمعتبر از دیدگاه نویسندگان اصلی برنامه). به این صورت فایل‌های 64 بیتی لازم هم تولید می‌شوند ( ممکن است در حین کار با برنامه‌ی patch شده، نمایش خطای کار با نیم فاصله را مجددا دریافت کنید؛ اما مهم نیست . نمونه‌ی وصله شده، بدون مشکل حاصل را کامپایل می‌کند که در نمونه‌ی اصلی اینطور نیست. یعنی پس از overwrite کردن فایل exe موجود، با نمونه patch شده، برنامه کار کامپایل را کامل می‌کند) که حاصل نهایی را از آدرس زیر می‌توانید دریافت نمائید:


این فایل‌ها دقیقا بر مبنای همان فایل‌های پروژه وب فارسی استاندارد 2091 هستند؛ با این تفاوت که نسخه‌‌های غیر 32 بیتی هم در آن لحاظ شده‌اند.

نصب این dll‌ها هم از ویندوز ویستا به بعد داستان خودش را پیدا کرده؛ ابتدا باید take ownership شود، سپس می‌توان فایل اصلی را به سادگی جایگزین کرد و سپس reboot .
برای این منظور ابتدا به پوشه‌ی system32 ویندوز مراجعه کرده و فایل KBDFA.DLL را پیدا کنید.
در ادامه به پنجره خواص آن (کلیک راست و انتخاب properties) مراجعه و برگه‌ی Security آن‌را انتخاب کنید.
در این قسمت بر روی دکمه‌ی Advanced کلیک نمائید.
در صفحه‌ی باز شده به برگه‌ی Owner مراجعه کنید.
در این قسمت بر روی دکمه‌ی Edit کلیک نموده و کاربر خودتان را اضافه نمائید.
پس از طی این مرحله (یا همان take ownership) به برگه security مراجعه کرده و به کاربر خودتان دسترسی full control بدهید.
اکنون مجوز تغییر این فایل را بدون هیچ مشکلی خواهید یافت.
در پایان reboot را فراموش نکنید.

راه دوم:
یا برنامه نصاب حاصل از برنامه‌ی Microsoft Keyboard Layout Creator، مدخل جدیدی را به صفحه کلیدهای قابل انتخاب ویندوز در کنترل پنل اضافه می‌کند که نیازی به reboot ندارد.
مطالب
فعال و غیر فعال کردن قیود
SQL Server قابلیت فعال(enable) و غیر فعال(disable) کردن دو قید کلید خارجی و check را برای ما مهیا کرده است.
ما می‌توانیم بعد از ساخت جدول و انتشار مقداری داده در آن قیدهایی را ایجاد کنیم. بطور پیشفرض اگر شرط قید ما بر قرار بود قید به طور صحیح ساخته می‌شود و اگر شرط قید ما بر قرار نباشد قید با خطای conflict مواجه خواهد شد.
بطور کلی غیر فعال کردن قیدها کار درستی نیست. ولی در برخی مواقع برای تسریع در اجرای کد می‌توانیم قید را غیر فعال کنیم. بطور مثال اگر یک میلیون داده قرار است در جدول درج شود و مطمئن هستیم که این داده‌ها جامعیت داده‌ها را حفظ می‌کنند آنگاه می‌توانیم قید را برای تسریع در عمل درج بطور موفق غیر فعال کنیم.

فعال و غیر فعال کردن از طریق DDL
با غیر فعال کردن قیود داده‌ها را در وضعیت نامناسبی قرار می‌دهیم ولی همان طور که اشاره شد بطور موفق اشکالی پیش نخواهد آمد.
در ادامه ابتدا طریقه غیرفعال کردن و مجددا فعال کردن قیود را توسط دستور alter table نشان خواهم داد سپس به سراغ امکانات ویزاردی می‌رویم. ابتدا یک جدول تک ستونه ایجاد می‌کنیم:
CREATE TABLE testTable
(column1 integer not  null);

الان هیچ قیدی روی جدول لحاظ نشده است. پس هر داده که در رنج domain ستون باشد را میتوانیم درج کنیم. پس بطور نمونه این داده‌ها را درج میکنیم:
INSERT INTO testTable
VALUES (-10), (0), (10), (20), (30), (40)

حالا تصمیم داریم قیدی روی ستون column1 بگذاریم که توسط آن تنها اعداد مثبت در جدول درج شوند. پس داریم:
ALTER TABLE testTable
WITH CHECK 
ADD CONSTRAINT NoNegative 
CHECK (column1 > 0);
ولی چون داده هایی در جدول از قبل وجود داشته اند که قید ما را نقض کنند این قید ساخته نخواهد شد و با پیغام زیر مواجه خواهیم شد:
The ALTER TABLE statement conflicted with the CHECK constraint "NoNegative".

برای ساخت این قید روی این داده‌ها تنها راه استفاده از کلید واژه‌های WITH NOCHECK است یعنی:
ALTER TABLE testTable
WITH NOCHECK 
ADD CONSTRAINT NoNegative 
CHECK (column1 > 0);

و اکنون سعی میکنیم یک مقدار منفی در جدول درج کنیم:
INSERT INTO testTable VALUES (-5)
  /*
 The INSERT statement conflicted with the CHECK constraint "NoNegative".
 */

اما قیدی که ساخته بودیم در جدول در حال اعمال شدن است. برای درج مقدار منفی باید غیر را غیر فعال کنیم.
ALTER TABLE TestTable
NOCHECK CONSTRAINT NoNegative

و حالا مقدار منفی را درج میکنیم. و برای برگرداندن وضعیت NOCHECK به وضعیت CHECK باید از کلید واژه‌های WITH NOCHECK استفاده کنیم. چرا که داده هایی در جدول درج شده اند که قید مورد نظر ما را نقض می‌کنند.
ALTER TABLE TestTable
WITH NOCHECK
CHECK CONSTRAINT NoNegative

فعال و غیر فعال کردن از طریق design
در قسمت object explorer قید مورد نظر را پیدا کرده و روی آن راست کلیک کرده و گزینه Modify را انتخاب کنید. سپس در پنجره باز شده در قسمت Table Designer تغییرات مورد نظر خود را اعمال کنید.