مطالب
استفاده از چندین Context در EF 6 Code first
در نگارش قبلی EF Code first به ازای یک پروژه تنها یک سیستم Migration قابل تعریف بود و این سیستم مهاجرت، تنها با یک DbContext کار می‌کرد. در نگارش ششم این کتابخانه، سیستم مهاجرت Code first آن از چندین DbContext، به ازای یک پروژه که به یک بانک اطلاعاتی اشاره می‌کنند، پشتیبانی می‌کند. مزیت اینکار اندکی بهبود در نگهداری تنها کلاس DbContext تعریف شده است. برای مثال می‌توان یک کلاس DbContext مخصوص قسمت ثبت نام را ایجاد کرد. یک کلاس DbContext مخصوص کلیه جداول مرتبط با مقالات را و همینطور الی آخر. نهایتا تمام این Contextها سبب ایجاد یک بانک اطلاعاتی واحد خواهند شد.
اگر در یک پروژه EF Code first چندین Context وجود داشته باشد و دستور enable-migrations را بدون پارامتری فراخوانی کنیم، پیغام خطای More than one context type was found in the assmbly xyz را دریافت خواهیم کرد.
الف) اما در EF 6 می‌توان با بکار بردن سوئیچ جدید ContextTypeName، به ازای هر Context، مهاجرت مرتبط با آن‌را تنظیم نمود:
 enable-migrations -ContextTypeName dbContextName1 -MigrationDirectory DataContexts\Name1Migrations
همچنین در اینجا نیز می‌توان با استفاده از سوئیچ MigrationDirectory، فایل‌های تولید شده را در پوشه‌های مجزایی ذخیره کرد.

ب) در مرحله بعد، نیاز به فراخوانی دستور add-migration است:
 add-migration -ConfigurationTypeName FullNameSpaceCtx1.Configuration "InitialCreate"
با اجرای دستور enable-migrations یک کلاس Configuration جهت DbContext مشخص شده، ایجاد می‌شود. سپس آدرس کامل این کلاس را به همراه ذکر دقیق فضای نام آن در اختیار دستور add-migration قرار می‌دهیم.
ذکر کامل فضای نام، از این جهت مهم است که کلاس Configuration به ازای Contextهای مختلف ایجاد شده، یک نام را خواهد داشت؛ اما در فضاهای نام متفاوتی قرار می‌گیرد.
با اجرای دستور add-migration، کدهای سی شارپ مورد نیاز جهت اعمال تغییرات بر روی ساختار بانک اطلاعاتی تولید می‌شوند. در مرحله بعد، این کدها تبدیل به دستورات SQL متناظری شده و بر روی بانک اطلاعاتی اجرا خواهند شد.
بدیهی است اگر دو Context در برنامه تعریف کرده باشید، دوبار باید دستور enable-migrations و دوبار دستور add-migration را با پارامترهای اشاره کننده به Conetxtهای مدنظر اجرا کرد.

ج) سپس برای اعمال این تغییرات، باید دستور update-database را اجرا کرد.
 update-database  -ConfigurationTypeName FullNameSpaceCtx1.Configuration
اینبار دستور update-database نیز بر اساس نام کامل کلاس Configuration مدنظر باید اجرا گردد و به ازای هر Context موجود، یکبار نیاز است اجرا گردد.
نهایتا اگر به بانک اطلاعاتی مراجعه کنید، تمام جداول و تعاریف را یکجا در همان بانک اطلاعاتی می‌توانید مشاهده نمائید.


داشتن چندین Context در برنامه و مدیریت تراکنش‌ها

در EF، هر DbContext معرف یک واحد کار است. یعنی تراکنش‌ها و چندین عمل متوالی مرتبط انجام شده، درون یک DbContext معنا پیدا می‌کنند. متد SaveChanges نیز بر همین اساس است که کلیه اعمال ردیابی شده در طی یک واحد کار را در طی یک تراکنش به بانک اطلاعاتی اعمال می‌کند. همچنین مباحثی مانند lazy loading نیز در طی یک Context مفهوم دارند. به علاوه دیگر امکان join نویسی بین دو Context وجود نخواهد داشت. باید اطلاعات را از یکی واکشی و سپس این اطلاعات درون حافظه‌ای را به دیگری ارسال کنید.

یک نکته
می‌توان یک DbSet را در چندین ‍Context تعریف کرد. یعنی اگر بحث join نویسی مطرح است، با تکرار تعریف DbSetها اینکار قابل انجام است اما این مساله اساس جداسازی Contextها را نیز زیر سؤال می‌برد.
 

داشتن چندین Context در برنامه و مدیریت رشته‌های اتصالی

در EF Code first روش‌های مختلفی برای تعریف رشته اتصالی به بانک اطلاعاتی وجود دارند. اگر تغییر خاصی در کلاس مشتق شده از DbContext ایجاد نکنیم، نام کلید رشته اتصالی تعریف شده در فایل کانفیگ باید به نام کامل کلاس Context برنامه اشاره کند. اما با داشتن چندین Context به ازای یک دیتابیس می‌توان از روش ذیل استفاده کرد:

  public class Ctx1 : DbContext
    {
        public Ctx1()
            : base("DefaultConnection")
        {
            //Database.Log = sql => Debug.Write(sql);
        }
    }

  public class Ctx2 : DbContext
    {
        public Ctx2()
            : base("DefaultConnection")
        {
            //Database.Log = sql => Debug.Write(sql);
        }
    }

  <connectionStrings>
    <add name="DefaultConnection" connectionString="…." providerName="System.Data.SqlClient" />
  </connectionStrings>
در اینجا در سازنده کلاس‌های Context تعریف شده، نام کلید رشته اتصالی موجود در فایل کانفیگ برنامه ذکر شده است. به این ترتیب این دو Context به یک بانک اطلاعاتی اشاره خواهند کرد.


چه زمانی بهتر است از چندین Context در برنامه استفاده کرد؟
عموما در طراحی‌های سازمانی SQL Server، تمام جداول از schema مدیریتی به نام dbo استفاده نمی‌کنند. جداول فروش از schema خاص خود و جداول کاربران از schema دیگری استفاده خواهند کرد. با استفاده از چندین Context می‌توان به ازای هر کدام از schemaهای متفاوت موجود، «یک ناحیه ایزوله» را ایجاد و مدیریت کرد.
  public class Ctx2 : DbContext
    {
        public Ctx2()
            : base("DefaultConnection")
        {
            //Database.Log = sql => Debug.Write(sql);
        }

         protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            modelBuilder.HasDefaultSchema("sales");
            base.OnModelCreating(modelBuilder);
        }
    }
در این حالت در EF 6 می‌توان DefaultSchema کلی یک Context را در متد بازنویسی شده OnModelCreating به نحو فوق تعریف و مدیریت کرد. در این مثال به صورت خودکار کلیه DbSetهای Ctx2 از schema ایی به نام sales استفاده می‌کنند.
مطالب
تبدیل HTML فارسی به PDF با استفاده از افزونه‌ی XMLWorker کتابخانه‌ی iTextSharp
پیشتر مطلبی را در مورد «تبدیل HTML به PDF با استفاده از کتابخانه‌ی iTextSharp» در این سایت مطالعه کرده‌اید. این مطلب از افزونه HTMLWorker کتابخانه iTextSharp استفاده می‌کند که ... مدتی است توسط نویسندگان این مجموعه منسوخ شده اعلام گردیده و دیگر پشتیبانی نمی‌شود.
کتابخانه جایگزین آن‌را افزونه XMLWorker معرفی کرده‌اند که توانایی پردازش CSS و HTML بهتر و کاملتری را نسبت به HTMLWorker ارائه می‌دهد. این کتابخانه نیز همانند HTMLWorker پشتیبانی توکاری از متون راست به چپ و یونیکد فارسی، ندارد و نیاز است برای نمایش صحیح متون فارسی در آن، نکات خاصی را اعمال نمود که در ادامه بحث آن‌ها را مرور خواهیم کرد.

ابتدا برای دریافت آخرین نگارش‌های iTextSharp و افزونه XMLWorker آن به آدرس‌های ذیل مراجعه نمائید:

تهیه یک UnicodeFontProvider

Encoding پیش فرض قلم‌ها در XMLWorker مساوی BaseFont.CP1252 است؛ که از حروف یونیکد پشتیبانی نمی‌کند. برای رفع این نقیصه نیاز است یک منبع تامین قلم سفارشی را برای آن ایجاد نمود:
    public class UnicodeFontProvider : FontFactoryImp
    {
        static UnicodeFontProvider()
        {
            // روش صحیح تعریف فونت   
            var systemRoot = Environment.GetEnvironmentVariable("SystemRoot");
            FontFactory.Register(Path.Combine(systemRoot, "fonts\\tahoma.ttf"));
            // ثبت سایر فونت‌ها در اینجا
            //FontFactory.Register(Path.Combine(Environment.CurrentDirectory, "fonts\\irsans.ttf"));
        }

        public override Font GetFont(string fontname, string encoding, bool embedded, float size, int style, BaseColor color, bool cached)
        {
            if (string.IsNullOrWhiteSpace(fontname))
                return new Font(Font.FontFamily.UNDEFINED, size, style, color);
            return FontFactory.GetFont(fontname, BaseFont.IDENTITY_H, BaseFont.EMBEDDED, size, style, color);
        }
    }
قلم‌های مورد نیاز را در سازنده کلاس به نحوی که مشاهده می‌کنید، ثبت نمائید.
مابقی مسایل آن خودکار خواهد بود و هر زمانیکه نیاز به قلم خاصی از طرف XMLWorker وجود داشت، به متد GetFont فوق مراجعه کرده و اینبار قلمی با BaseFont.IDENTITY_H را دریافت می‌کند. IDENTITY_H در استاندارد PDF، جهت مشخص ساختن encoding قلم‌هایی با پشتیبانی از یونیکد بکار می‌رود.


تهیه منبع تصاویر

در XMLWorker اگر تصاویر با http شروع نشوند (دریافت تصاویر وب آن خودکار است)، آن تصاویر را از مسیری که توسط پیاده سازی کلاس AbstractImageProvider مشخص خواهد شد، دریافت می‌کند که نمونه‌ای از پیاده سازی آن‌را در ذیل مشاهده می‌کنید:
    public class ImageProvider : AbstractImageProvider
    {
        public override string GetImageRootPath()
        {
            var path = Environment.GetFolderPath(Environment.SpecialFolder.MyPictures);
            return path + "\\"; // مهم است که این مسیر به بک اسلش ختم شود تا درست کار کند
        }
    }


نحوه تعریف یک فایل CSS خارجی

    public static class XMLWorkerUtils
    {
        /// <summary>
        /// نحوه تعریف یک فایل سی اس اس خارجی
        /// </summary>
        public static ICssFile GetCssFile(string filePath)
        {
            using (var stream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
            {
                return XMLWorkerHelper.GetCSS(stream);
            }
        }
    }
برای مسیردهی یک فایل CSS در کتابخانه XMLWorker می‌توان از کلاس فوق استفاده کرد.


تبدیل المان‌های HTML پردازش شده به یک لیست PDF ایی

تهیه مقدمات فارسی سازی و نمایش راست به چپ اطلاعات در کتابخانه XMLWorker از اینجا شروع می‌شود. در حالت پیش فرض کار آن، المان‌های HTML به صورت خودکار Parse شده و به صفحه اضافه می‌شوند. به همین دلیل دیگر فرصت اعمال خواص RTL به المان‌های پردازش شده دیگر وجود نخواهد داشت و به صورت توکار نیز این مسایل درنظر گرفته نمی‌شود. به همین دلیل نیاز است که در حین پردازش المان‌های HTML و تبدیل آن‌ها به معادل المان‌های PDF، بتوان آن‌ها را جمع آوری کرد که نحوه انجام آن‌را با پیاده سازی اینترفیس IElementHandler در ذیل مشاهده می‌کنید:
    /// <summary>
    /// معادل پی دی افی المان‌های اچ تی ام ال را جمع آوری می‌کند
    /// </summary>
    public class ElementsCollector : IElementHandler
    {
        private readonly Paragraph _paragraph;

        public ElementsCollector()
        {
            _paragraph = new Paragraph
            {
                Alignment = Element.ALIGN_LEFT  // سبب می‌شود تا در حالت راست به چپ از سمت راست صفحه شروع شود
            };
        }

        /// <summary>
        /// این پاراگراف حاوی کلیه المان‌های متن است
        /// </summary>
        public Paragraph Paragraph
        {
            get { return _paragraph; }
        }

        /// <summary>
        /// بجای اینکه خود کتابخانه اصلی کار افزودن المان‌ها را به صفحات انجام دهد
        /// قصد داریم آن‌ها را ابتدا جمع آوری کرده و سپس به صورت راست به چپ به صفحات نهایی اضافه کنیم
        /// </summary>
        /// <param name="htmlElement"></param>
        public void Add(IWritable htmlElement)
        {
            var writableElement = htmlElement as WritableElement;
            if (writableElement == null)
                return;

            foreach (var element in writableElement.Elements())
            {
                fixNestedTablesRunDirection(element);
                _paragraph.Add(element);
            }
        }

        /// <summary>
        /// نیاز است سلول‌های جداول تو در توی پی دی اف نیز راست به چپ شوند
        /// </summary>        
        private void fixNestedTablesRunDirection(IElement element)
        {
            var table = element as PdfPTable;
            if (table == null)
                return;

            table.RunDirection = PdfWriter.RUN_DIRECTION_RTL;
            foreach (var row in table.Rows)
            {
                foreach (var cell in row.GetCells())
                {
                    cell.RunDirection = PdfWriter.RUN_DIRECTION_RTL;
                    foreach (var item in cell.CompositeElements)
                    {
                        fixNestedTablesRunDirection(item);
                    }
                }
            }
        }
    }
این کلاس کلیه المان‌های دریافتی را به یک پاراگراف اضافه می‌کند. همچنین اگر به جدولی در این بین برخورد، مباحث RTL آن‌را نیز اصلاح خواهد نمود.


یک مثال کامل از نحوه کنار هم قرار دادن پیشنیازهای تهیه شده

خوب؛ تا اینجا یک سری پیشنیاز را تهیه کردیم، اما XMLWorker از وجود آن‌ها بی‌خبر است. برای معرفی آن‌ها باید به نحو ذیل عمل کرد:
            using (var pdfDoc = new Document(PageSize.A4))
            {
                var pdfWriter = PdfWriter.GetInstance(pdfDoc, new FileStream("test.pdf", FileMode.Create));
                pdfWriter.RgbTransparencyBlending = true;
                pdfDoc.Open();


                var html = @"<span style='color:blue; font-family:tahoma;'><b>آزمایش</b></span>   
                                    کتابخانه <i>iTextSharp</i> <u>جهت بررسی فارسی نویسی</u>
                            <table style='color:blue; font-family:tahoma;' border='1'><tr><td>eeمتن</td></tr></table>
                            <code>This is a code!</code>
                            <br/>
                            <img src='av-13489.jpg' />
                            ";

                var cssResolver = new StyleAttrCSSResolver();
                // cssResolver.AddCss(XMLWorkerUtils.GetCssFile(@"c:\path\pdf.css"));
                cssResolver.AddCss(@"code 
                                     {
                                        padding: 2px 4px;
                                        color: #d14;
                                        white-space: nowrap;
                                        background-color: #f7f7f9;
                                        border: 1px solid #e1e1e8;
                                     }",
                                     "utf-8", true);

                // کار جمع آوری المان‌های ترجمه شده به المان‌های پی دی اف را انجام می‌دهد
                var elementsHandler = new ElementsCollector();

                var htmlContext = new HtmlPipelineContext(new CssAppliersImpl(new UnicodeFontProvider()));
                htmlContext.SetImageProvider(new ImageProvider());
                htmlContext.CharSet(Encoding.UTF8);
                htmlContext.SetAcceptUnknown(true).AutoBookmark(true).SetTagFactory(Tags.GetHtmlTagProcessorFactory());
                var pipeline = new CssResolverPipeline(cssResolver,
                                                       new HtmlPipeline(htmlContext, new ElementHandlerPipeline(elementsHandler, null)));
                var worker = new XMLWorker(pipeline, parseHtml: true);
                var parser = new XMLParser();
                parser.AddListener(worker);
                parser.Parse(new StringReader(html));

                // با هندلر سفارشی که تهیه کردیم تمام المان‌های اچ تی ام ال به المان‌های پی دی اف تبدیل شدند
                // الان تنها کافی کافی است تا این‌ها را در یک جدول راست به چپ محصور کنیم تا درست نمایش داده شوند
                var mainTable = new PdfPTable(1) { WidthPercentage = 100, RunDirection = PdfWriter.RUN_DIRECTION_RTL };
                var cell = new PdfPCell
                {
                    Border = 0,
                    RunDirection = PdfWriter.RUN_DIRECTION_RTL,
                    HorizontalAlignment = Element.ALIGN_LEFT
                };
                cell.AddElement(elementsHandler.Paragraph);
                mainTable.AddCell(cell);

                pdfDoc.Add(mainTable);
            }

            Process.Start("test.pdf");
نحوه تعریف inline css یا نحوه افزودن یک فایل css خارجی را نیز در ابتدای این مثال مشاهده می‌کنید.
UnicodeFontProvider باید به HtmlPipelineContext شناسانده شود.
ImageProvider توسط متد SetImageProvider به HtmlPipelineContext معرفی می‌شود.
ElementsCollector سفارشی ما در قسمت CssResolverPipeline باید به سیستم تزریق شود.
پس از آن XMLWorker را وادار می‌کنیم تا HTML را Parse کرده و معادل المان‌های PDF ایی آن‌را تهیه کند؛ اما آن‌ها را به صورت خودکار به صفحات فایل PDF نهایی اضافه نکند. در این بین ElementsCollector ما این المان‌ها را جمع آوری کرده و در نهایت، پاراگراف کلی حاصل از آن‌را به یک جدول با RUN_DIRECTION_RTL اضافه می‌کنیم. حاصل آن نمایش صحیح متون فارسی است.

کدهای مثال فوق را از آدرس ذیل نیز می‌توانید دریافت کنید:
XMLWorkerRTLsample.cs


به روز رسانی
کلیه نکات مطلب فوق را به همراه بهبودهای مطرح شده در نظرات آن، در پروژه‌ی ذیل می‌توانید به صورت یکجا دریافت و بررسی کنید:
XMLWorkerRTLsample.zip
مطالب
پروسیجرها و شنود پارامترها در SQL Server
در اس کیو ال سرور 2016، قابلیت غیر فعال نمودن parameter sniffing در سطح بانک اطلاعاتی مهیا شده است. اما چرا؟


قبل از پاسخگویی به سؤال بالا، به یک سری مقدمات نیاز است:

وقتی یک کوئری به اس کیو ال ارسال می‌شود، چه اتفاقی رخ می‌دهد؟
وقتی یک کوئری ارسال می‌شود، تعدادی از پروسس‌ها بر روی کوئری شروع به فعالیت‌هایی مانند مهیا نمودن داده‌های بازگشتی، یا ذخیره سازی و ... می‌کنند.
 پروسس‌ها به دو دسته زیر تقسیم می‌شوند:
  1. پروسس‌هایی که در relational engine رخ می‌دهند
  2. پروسس‌هایی که در storage engine رخ می‌دهند

در relational engine، هر کوئری pars شده و سپس بوسیله query optimizer پردازش و پلن اجرایی (execution plan) آن که بفرمت باینری است، ایجاد می‌شود و به storage engine ارسال می‌گردد. در storage engine پروسس‌هایی مانند قفل گذاری، نگهداری ایندکس‌ها و تراکنش‌ها رخ می‌دهد. هنگامیکه اس کیو ال سرور کوئری را دریافت می‌نمایند، آن را بلافاصله به relational engine ارسال می‌کند. سپس نحو (syntax) آن بررسی می‌شود؛ این عمل  query parsing نامیده می‌شود. خروجی عملیات پارسر، یک ساختار درختی (query tree) است. این ساختار درختی مشخص کننده مراحل لازم جهت اجرای کوئری ارائه شده می‌باشد.
اگر یک کوئری شامل DML نباشد (مانند ساخت جدول)، علمیات بهبود برروی آن صورت نخواهد گرفت. ولی در صورتیکه کوئری ارسالی، DML باشد، درخت اشاره شده در بالا به algebrizer فرستاده می‌شود که وظیفه آن تفسیر و بررسی کلیه نام اشیاء، جداول و ستون‌های اشاره شده در متن کوئری است. فرآیند algebrizer بسیار مهم و حیاتی است؛ بدلیل اینکه در کوئری ممکن است اشاره کننده‌هایی به اشیایی باشند که در بانک اطلاعاتی موجود نیست. خروجی algebrizer یک query processor tree باینری است که به بهبود دهنده کوئری ارسال می‌گردد. 

معرفی Query Optimizer (بهبود دهنده پرس و جو)

بهبود دهنده، بهترین مسیر اجرای کوئری را مشخص می‌کند. این بهبود دهنده است که مشخص می‌کند که اطلاعات بوسیله ایندکس دریافت شوند، یا اینکه از چه اتصالی استفاده شود و الی آخر. این تصمیمات براساس محاسبات هزینه‌های (میزان پردازش لازم cpu و I/O) پلن اجرایی صورت خواهد پذیرفت. بهمین دلیل به پلن cost-based نیز شناخته می‌شود.
هنگامیکه کوئری ساده‌ای مانند دریافت اطلاعات از یک جدول، که بر روی آن ایندکس گذاری انجام نشده‌است، ارسال شود، بهبود دهنده بجای مشخص نمودن یک پلن مناسب بهینه، از یک پلن ساده (trivial) استفاده می‌کند. ولی برعکس در صورتیکه کوئری trivial نباشد (یعنی مثلا کوئری به گونه‌ای باشد که از ایندکس‌ها به شکل صحیحی استفاده شده باشند)، بهبود دهنده یک پلن مناسب را براساس اطلاعات آماری مهیا شده در اس کیو ال سرور، تولید و انتخاب می‌نماید.

اطلاعات آماری از ستون‌ها و ایندکس‌ها جمع آوری می‌شود. این اطلاعات شامل نحوه توزیع داده، یکتایی و انتخاب شوندگی است. این اطلاعات توسط یک histogram ارائه می‌شود. اگر اطلاعات آماری برای یک ستون و یا ایندکس وجود داشته باشد، بهبود دهنده از آن‌ها برای محاسبات خود استفاده خواهد کرد. اطلاعات آماری بصورت خودکار برای تمام ایندکس‌ها و یا هر ستونی که بشود بر روی آن‌ها where یا join نوشت، فراهم خواهد شد.
بهبود دهنده با مقایسه پلن‌ها براساس بررسی تفاوت‌های انواع joinها، چیدمان مجدد ترتیب join و بررسی ایندکس‌های مختلف و سایر فعالیت‌های دیگر، پلن مناسب را انتخاب و از آن استفاده می‌کند. در طی هر کدام از فعالیت‌های اشاره شده، زمان اجرای آن‌ها نیز تخمین زده (estimated cost) خواهد شد و در پایان، زمان کل تخمینی بدست خواهد آمد و بهبود دهنده از این زمان برای انتخاب پلن مناسب بهره خواهد برد. باید توجه داشت که این زمان تقریبی است. زمانیکه بهبود دهنده پلن اجرایی انتخاب می‌کند، یک actual plan را ایجاد و در حافظه ذخیره می‌شود؛ بنام plan cache. البته درصورتیکه پلن مشابه و بهینه‌تری وجود نداشته باشد. 

استفاده مجدد از پلن ها

تولید پلن هزینه بر است. به‌همین دلیل اس کیوال سرور اقدام به ذخیره سازی و نگهداری آن‌ها می‌کند تا بتواند از آن‌ها مجددا استفاده نماید؛ البته تا جایی که مقدور باشد. هنگامیکه آن‌ها تولید می‌شوند، در قسمتی از حافظه بنام plan cache ذخیره می‌شوند. به این عمل procedure cache نیز گفته می‌شود.

هنگامیکه کوئری به سرور ارسال می‌شود، بوسیله بهبود دهنده، یک estimated plan ایجاد خواهد شد و قبل از اینکه به storage engine ارسال شود، بهبود دهنده estimated plan را با actual execution planهای موجود در plan cache مقایسه می‌کند. در صورتیکه یک actual plan را مطابق با estimated plan پیدا نماید، از آن مجدد استفاده خواهد کرد. این استفاده مجدد به عدم تحمیل سربار اضافه‌ای به سرور جهت کوئری‌های بزرگ و پیچیده که در زمان واحد، هزاران بار اجرا خواهند شد، منجر می‌شود.
هر پلن فقط یکبار در حافظه ذخیره خواهد شد. ولی در مواقعی با تشخیص بهبود دهنده و هزینه پلن، یک کوئری می‌تواند پلن دیگری نیز داشته باشد. بنابراین پلن دوم نیز با مجموعه عملیاتی متفاوت، جهت اجرای موازی (parallel execution) برای یک کوئری ایجاد و در حافظه ذخیره می‌شود.
پلن‌های اجرایی برای همیشه در حافظه باقی نخواهند ماند. پلن‌های اجرایی دارای طول عمری طبق فرمول حاصل ضرب هزینه، در تعداد دفعات می‌باشند. مثلاً پلنی با هزینه 10 و تعداد دفعات اجرای 5، طول عمر 50 را خواهد داشت. پروسس lazywriter که یک پروسس داخلی است وظیفه آزاد سازی تمام انواع کش‌ها، از جمله پلن کش را دارد. این پروسس در بازه‌های مشخص، تمام اشیاء درون حافظه را بررسی کرده و یک واحد از طول عمر آن‌ها می‌کاهد.
در موارد زیر، یک پلن از حافظه پاک خواهد شد:
1. به حافظه بیشتری نیاز باشد
2. طول عمر پلن صفر شده باشد 

حال فرض کنید شما یک پروسیجر یا یک کوئری پارامتری دارید (پارامتر ورودی: شناسه سفارش یا نال) که کلیه محصولات سفارش داده شده یا محصولات یک سفارش خاص را نمایش می‌دهد. هنگامی که SQL Server optimizer پلن این کوئری را ایجاد می‌کند و یا آن را کامپایل می‌کند، به پارامترهای ورودی این پروسیجر گوش می‌دهد (نال یا یک شناسه سفارش). optimizer بوسیله column statistics از تعداد رکوردهایی که بازگشت داده می‌شود، برآوردی می‌کند (مثلا 40 رکورد). سپس یک پلن مناسب را انتخاب می‌کند و آن را برای اجرا ارسال می‌کند و پلن را ذخیره می‌نماید.
جمله آخر، معمولا باعث ایجاد مشکل می‌شوند.
اگر optimizer تکست کوئری مشابهی را مشاهده نماید، ولی با پارامترهای متفاوت، به کش پلن مراجعه کرده و اگر در آن جا قرار داشت، از آن مجددا استفاده می‌نماید. این استفاده مجدد خوب است؛ اما  درصورتیکه پارامتر ارسالی نال باشد چه اتفاقی رخ می‌دهد؟ جدول سفارشات محصول بسیار حجیم است و متاسفانه از پلنی که برای بازگشت 40 رکورد قبلا ایجاد شده، برای بازگشت این حجم بالای از رکوردها استفاده می‌شود که این کشنده است.
هیچ تضمینی وجود ندارد که از وقوع این اتفاق جلوگیری نمایید؛ اما می‌توانید در هنگام توسعه، پروسیجر را شناسایی و نسبت به رفع آنها اقدام نمایید. ابتدا کش پلن را خالی نمایید و سپس پروسیجر را با مقادیر متفاوت، اجرا نمایید. در صورتیکه پلن‌های متفاوتی مشاهده نمودید، این یک علامت هشدار است و می‌بایست نسبت به رفع آن‌ها اقدام فوری نمایید. 
مطالب
ساخت تم سفارشی در انگیولار متریال ۲ - بخش دوم

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

ساخت تم اضافی در کنار تم اصلی

ساخت تم اضافی در انگیولار متریال ۲ بسیار ساده است. شما می‌توانید با استفاده مجدد از تابع angular-material-theme داخل یک کلاس CSS، صاحب یک تم اضافی دیگر شوید. برای نمونه در اینجا فایل my-custom-theme.scss را باز کرده و به شکل زیر تغییر می‌دهیم.

@import '~@angular/material/theming';
@include mat-core();
$my-app-primary: mat-palette($mat-teal);
$my-app-accent: mat-palette($mat-amber, 500, A100, A400);
$my-app-warn: mat-palette($mat-deep-orange);

$my-app-theme: mat-light-theme($my-app-primary, $my-app-accent, $my-app-warn);

@include angular-material-theme($my-app-theme);

/*تعریف تم اضافی در کنار تم اصلی*/
$alternate-primary: mat-palette($mat-light-blue);
$alternate-accent: mat-palette($mat-yellow, 500);
$alternate-warn: mat-palette($mat-red, 500);
$alternate-theme: mat-dark-theme($alternate-primary, $alternate-accent);
.alternate-theme {
  @include angular-material-theme($alternate-theme);
}

با این‌کار در کنار تم روشن اصلی، یک تم مشکی به صورت اضافی داخل کلاس CSS به نام alternate-theme تعریف کرده‌ایم. در این حالت تمامی کامپوننتهایی که داخل المنت با کلاس alternate-theme قرار گرفته‌اند، از تم مشکی تعریف شده استفاده خواهند کرد.

با تغییر فایل app.component.html به شکل زیر: 

<md-card>
  <md-card-header>
    <md-card-title>تم اصلی</md-card-title>
  </md-card-header>
  <button md-raised-button color="primary">
    Primary
  </button>
  <button md-raised-button color="accent">
    Accent
  </button>
  <button md-raised-button color="warn">
    Warning
  </button>
</md-card>

<div>
  <md-card>
    <md-card-header>
      <md-card-title>تم اضافی</md-card-title>
    </md-card-header>
    <md-card-content>
      <button md-raised-button color="primary">
        Primary
      </button>
      <button md-raised-button color="accent">
        Accent
      </button>
      <button md-raised-button color="warn">
        Warning
      </button>
    </md-card-content>
  </md-card>
</div>

تصویر زیر را در خروجی خواهید داشت.

 

به همین روش می‌توانید تعداد دلخواهی از تم‌ها را بسازید. همچنین می‌توانید هر تم اضافی را در یک فایل Sass تعریف کنید و از این طریق تم‌های مختلف را از هم جدا کنید. در این حالت به این نکته توجه داشته باشید که نباید mat-core@ در سرتاسر برنامه بیش از یکبار بارگذاری شده باشد. 

ساخت تم به ازای هر کامپوننت

با استفاده از mixin به نام angular-material-theme خروجی تولید شده بر روی تمامی کامپوننت‌های انگیولار متریال ۲ اعمال خواهد شد. اگر از تمامی کامپوننت‌های انگیولار متریال ۲ استفاده نمی‌کنید، می‌توانید برای کاهش حجم فایل CSS تولید شده از mixin مخصوص به هر کامپوننت استفاده کنید. همچنین برای ساخت تم‌های متفاوت به ازای هر کامپوننت نیز می‌توانید از این روش استفاده کنید. 

برای این کار تمامی مراحلی که برای ساخت تم مورد نیاز بود، باید طی شود. فقط به جای استفاده از mixin به نام angular-material-theme بایستی به طریق زیر عمل شود. 

اول: بارگذاری mixin با نام mat-core-them. این mixin تمامی استایل‌های مشترک رفتاری (مانند موج (ripple) در هنگام کلیک) برای کامپوننت‌ها را در بر دارد. این mixin خروجی تابع mat-light-theme یا mat-dark-theme را به عنوان ورودی دریافت می‌کند.

دوم: بارگذاری mixin مربوط به هر کامپوننت. برای مثال برای دکمه از mixin به نام mat-button-theme و برای checkbox از mixin به نام mat-checkbox-theme می‌توانید استفاده کنید. در زیر لیست mixin‌ها به ازای کامپوننت‌های مختلف ذکر شده است. 

mat-autocomplete-theme
mat-button-theme
mat-button-toggle-theme
mat-card-theme
mat-checkbox-theme
mat-chips-theme
mat-datepicker-theme
mat-dialog-theme
mat-grid-list-theme
mat-icon-theme
mat-input-theme
mat-list-theme
mat-menu-theme
mat-progress-bar-theme
mat-progress-spinner-theme
mat-radio-theme
mat-select-theme
mat-sidenav-theme
mat-slide-toggle-theme
mat-slider-theme
mat-tabs-theme
mat-toolbar-theme
mat-tooltip-theme

در مثال زیر می‌خواهیم تمامی کامپوننت‌ها به جز کامپوننت دکمه، تم سبز(در گروه Primary) و دکمه‌ها نیز تم آبی داشته باشند. کافی است کدهای زیر را در فایل Sass خود وارد کنید. 

@import '~@angular/material/theming';
@include mat-core();
$my-app-primary: mat-palette($mat-teal);
$my-app-accent: mat-palette($mat-amber, 500, A100, A400);
$my-app-warn: mat-palette($mat-deep-orange);
$my-app-theme: mat-light-theme($my-app-primary, $my-app-accent, $my-app-warn);
@include mat-material-theme($my-app-theme);

/* تعریف تم برای دکمه */
$button-primary: mat-palette($mat-light-blue);
$button-accent: mat-palette($mat-yellow, 500);
$button-warn: mat-palette($mat-red, 500);
$button-theme: mat-light-theme($button-primary, $button-accent);
@include mat-button-theme($button-theme);

با توجه به اینکه mat-material-theme در داخل خود mat-button-theme را بارگذاری می‌کند دو نتیجه زیر را می‌توان گرفت.

اول: اگر mat-material-theme بعد از هر کدام از mixinهای مربوط به کامپوننت‌ها نوشته شود، تمامی Cssهای تولید شده به ازای کامپوننت را دوباره نویسی کرده و عملا هیچ کدام کارایی نخواهند داشت. برای مثال کافی است فایل Sass خود را به شکل زیر تغییر دهید. در این صورت تم مربوط به دکمه کاریی نخواهد داشت. 

@import '~@angular/material/theming';
@include mat-core();

/* تعریف تم برای دکمه */
$button-primary: mat-palette($mat-light-blue);
$button-accent: mat-palette($mat-yellow, 500);
$button-warn: mat-palette($mat-red, 500);
$button-theme: mat-ligth-theme($button-primary, $button-accent);
@include mat-button-theme($button-theme);

$my-app-primary: mat-palette($mat-teal);
$my-app-accent: mat-palette($mat-amber, 500, A100, A400);
$my-app-warn: mat-palette($mat-deep-orange);
$my-app-theme: mat-light-theme($my-app-primary, $my-app-accent, $my-app-warn);
@include mat-material-theme($my-app-theme);

دوم: همانطور که گفتیم mat-button-theme در mat-material-theme قبلا بارگذاری شده است. با بارگذاری دوباره توسط mat-button-theme کدهای CSS که قبلا برای دکمه تولید شده‌اند را از نو دوباره می‌نویسد و این باعث بزرگ شدن حجم فایل Css تولید شده خواهد شد. پس بهتر است هنگام استفاده از mixinهای مختص کامپوننت‌ها از mat-material-theme استفاده نکنیم. 

  جهت بهبود کدهای قبلی بهتر است از کدهای زیر استفاده کنیم. 
@import '~@angular/material/theming';
@include mat-core();
$my-app-primary: mat-palette($mat-teal);
$my-app-accent: mat-palette($mat-amber, 500, A100, A400);
$my-app-warn: mat-palette($mat-deep-orange);
$my-app-theme: mat-light-theme($my-app-primary, $my-app-accent, $my-app-warn);

$button-primary: mat-palette($mat-light-blue);
$button-accent: mat-palette($mat-yellow, 500);
$button-warn: mat-palette($mat-red, 500);
$button-theme: mat-light-theme($button-primary, $button-accent);

@include mat-core-theme($my-app-theme);
@include mat-autocomplete-theme($my-app-theme);

@include mat-button-theme($button-theme);

@include mat-button-toggle-theme($my-app-theme);
@include mat-card-theme($my-app-theme);
@include mat-checkbox-theme($my-app-theme);
@include mat-chips-theme($my-app-theme);
@include mat-datepicker-theme($my-app-theme);
@include mat-dialog-theme($my-app-theme);
@include mat-grid-list-theme($my-app-theme);
@include mat-icon-theme($my-app-theme);
@include mat-input-theme($my-app-theme);
@include mat-list-theme($my-app-theme);
@include mat-menu-theme($my-app-theme);
@include mat-progress-bar-theme($my-app-theme);
@include mat-progress-spinner-theme($my-app-theme);
@include mat-radio-theme($my-app-theme);
@include mat-select-theme($my-app-theme);
@include mat-sidenav-theme($my-app-theme);
@include mat-slide-toggle-theme($my-app-theme);
@include mat-slider-theme($my-app-theme);
@include mat-tabs-theme($my-app-theme);
@include mat-toolbar-theme($my-app-theme);
@include mat-tooltip-theme($my-app-theme);

تعویض تم از طریق کد

فرض کنید یک تم پیش فرض و یک تم اضافی به نام alternate-theme دارید. برای تعویض تم از طریق کد کافی است کلاس المنت پدر در صفحه html خود را از طریق [ngClass] با نام تم، مقدار دهی کنید. کدهای داخل app.component.ts را به شکل زیر تغییر می‌دهیم. 

import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  themes = [
    {value: 'alternate-theme', text: 'تم مشکی'},
    {value: '', text: 'تم سفید'},
  ];
  activeTheme = '';
}

آرایه‌ای جهت نمایش در کامپوننت md-select با دو مقدار تم پیش فرض و تم با نام 'alternate-theme تعریف می‌کنیم. همچنین متغیری با نام activeTheme را تعریف می‌کنیم. این متغیر در هر لحظه نام تم اعمال شده را در خود نگهداری می‌کند. مقدار اولیه این متغیر تم اصلی است.

کامپوننت md-select را به شکل زیر به فایل app.component.html به تگ main اضافه می‌کنیم. 

<md-select dir="rtl" [(ngModel)]="activeTheme" placeholder="تعویض تم">
    <md-option *ngFor="let theme of themes" [value]="theme.value">
            {{ theme.text }}
    </md-option>
</md-select>
حالا کافی است کل تگهای موجود در app.component.html را داخل یک تگ div به شکل زیر قرار دهیم. 
<div [ngClass]="activeTheme">
بعد از اجرای برنامه می‌توانید با تعویض تم از طریق کامپوننت md-select، تم صفحه را تعویض کنید. 

کدهای این قسمت را، از اینجا دریافت کنید: ساخت تم سفارشی در انگولار متریال ۲ - بخش دوم
مطالب
چگونه پروژه‌های Angular ی سبکی داشته باشیم - قسمت اول
یکی از عیب‌هایی که برنامه نویسان front-end و گاها بعضی از مدیران از Angular می‌گیرند، حجم زیاد صفحاتی است که با Angular کار می‌شود. در نتیجه‌ی جستجوی مشکل ذکر شده، با تعدادی پاسخ مشابه ^ و ^ روبرو می‌شویم که هیچ کدام روش صحیحی را برای رفع مشکل ذکر شده عنوان نکرده‌اند. در ادامه پروژه‌ی Angular ای را شروع می‌کنیم و حجم صفحات خروجی را مورد بررسی قرار می‌دهیم. سپس نحوه‌ی بهینه سازی و کم کردن حجم صفحات خروجی را بررسی می‌کنیم. 

برای شروع، CMD را در مسیر دلخواهی برای ساخت پروژه باز کرده  و ابتدا Angular CLI را توسط دستور زیر به آخرین نسخه‌ی موجود به‌روز رسانی می‌کنیم:
npm install -g @angular/cli
و سپس پروژه‌ی انگیولاری را  با دستور زیر می‌سازیم:
ng new HowToKeepAngularSizeSmall
پاسخ سوال ? Would you like to add Angular routing? (y/N)  را  y و scss را به عنوان فرمت stylesheet انتخاب می‌کنیم.
در ادامه پروژه را با استفاده از دستور زیر
ng serve --open --prod
اجرا می‌کنیم . سپس با استفاده از ابزار DevTools console از تب network، حجم فایل‌های لود شده را بررسی می‌کنیم:

حجم خروجی پروژه بعد از ساخت 222KB است.
حال برای آنکه پروژه‌ی جاری را به پروژه‌های واقعی نزدیک‌تر کنیم، بسته‌های npm زیر را به فایل package.json اضافه کرده و با دستور npm i بسته‌ها را نصب می‌کنیم.
"@agm/core": "^1.0.0-beta.5",
"@angular/flex-layout": "^7.0.0-beta.23",
"@angular/material": "^7.3.3",
"@angular/platform-browser": "~7.2.0",
"@asymmetrik/ngx-leaflet": "^5.0.1",
"@ngx-loading-bar/router": "1.3.1",
"@ngx-translate/core": "^11.0.1",
"@ngx-translate/http-loader": "^4.0.0",
"@progress/kendo-angular-buttons": "^4.3.3",
"@progress/kendo-angular-dateinputs": "^3.6.0",
"@progress/kendo-angular-dialog": "^3.10.1",
"@progress/kendo-angular-dropdowns": "^3.5.1",
"@progress/kendo-angular-excel-export": "^2.3.0",
"@progress/kendo-angular-grid": "^3.13.0",
"@progress/kendo-angular-inputs": "^4.2.0",
"@progress/kendo-angular-intl": "^1.7.0",
"@progress/kendo-angular-l10n": "^1.3.0",
"@progress/kendo-angular-layout": "^3.2.0",
"@progress/kendo-data-query": "^1.5.0",
"@progress/kendo-drawing": "^1.5.8",
"@progress/kendo-theme-default": "^3.3.1",
"@swimlane/ngx-datatable": "^14.0.0",
"angular-calendar": "0.23.7",
"angular-tree-component": "7.0.1",
"bootstrap": "^3.4.0",
"chart.js": "2.7.2",
"d3": "4.13.0",
"dragula": "3.7.2",
"hammerjs": "2.0.8",
"intl": "1.2.5",
"leaflet": "1.3.1",
"moment": "2.21.0",
"ng2-charts": "1.6.0",
"ng2-dragula": "1.5.0",
"ng2-file-upload": "1.3.0",
"ng2-validation": "4.2.0",
"ngx-perfect-scrollbar": "5.3.5",
"ngx-quill": "2.2.0",
"screenfull": "3.3.2",
"font-awesome": "^4.7.0",
"jalali-moment": "^3.3.1",
"jquery": "^3.3.1",
"ng-snotify": "^4.3.1",
"normalize.css": "^8.0.1",
و خطوط زیر را به styles.scss اضافه می‌کنیم:
@import "~bootstrap/dist/css/bootstrap.css";
@import "~@progress/kendo-theme-default/scss/all";
@import '@angular/material/prebuilt-themes/pink-bluegrey.css';
@import '~perfect-scrollbar/css/perfect-scrollbar.css';
@import "~ng-snotify/styles/material";
و قسمت scripts زیر را به فایل angular.json اضافه می‌کنیم:
"scripts": [
              "node_modules/jquery/dist/jquery.js",
              "node_modules/bootstrap/dist/js/bootstrap.min.js",
              "node_modules/hammerjs/hammer.min.js"
            ],
و فایل app.module.ts را نیز به صورت زیر تغییر می‌دهیم:
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';


// Import Material
import {
  MatFormFieldModule, MatInputModule,
  MatButtonModule, MatButtonToggleModule,
  MatDialogModule, MatIconModule,
  MatSelectModule, MatToolbarModule,
  MatDatepickerModule,
  DateAdapter, MAT_DATE_FORMATS, MAT_DATE_LOCALE, MatTableModule, MatCheckboxModule, MatRadioModule, MatCardModule, fadeInContent,
  MatListModule, MatProgressBarModule, MatTabsModule,
  MatSidenavModule,
  MatSlideToggleModule,
  MatMenuModule
} from '@angular/material';



// Import kendo angular ui
import { ButtonsModule } from '@progress/kendo-angular-buttons';
import { GridModule, ExcelModule } from '@progress/kendo-angular-grid';
import { DropDownsModule } from '@progress/kendo-angular-dropdowns';
import { InputsModule } from '@progress/kendo-angular-inputs';
import { DateInputsModule } from '@progress/kendo-angular-dateinputs';
import { DialogsModule } from '@progress/kendo-angular-dialog';
import { RTL } from '@progress/kendo-angular-l10n';
import { LayoutModule } from '@progress/kendo-angular-layout';
import { WindowService, WindowRef, WindowCloseResult, DialogService, DialogRef, DialogCloseResult } from '@progress/kendo-angular-dialog';

import { SnotifyModule, SnotifyService, SnotifyPosition, SnotifyToastConfig, ToastDefaults } from 'ng-snotify';


import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,

    // material 
    MatSidenavModule,
    MatSlideToggleModule,
    MatInputModule,
    MatFormFieldModule,
    MatButtonModule, MatButtonToggleModule,
    MatDialogModule, MatIconModule,
    MatSelectModule, MatToolbarModule,
    MatDatepickerModule,
    MatCheckboxModule,
    MatRadioModule,
    MatCardModule,
    MatMenuModule,
    MatListModule,
    MatProgressBarModule,
    MatTabsModule,


    // kendo-angular
    ButtonsModule,
    GridModule,
    ExcelModule,
    DropDownsModule,
    InputsModule,
    DateInputsModule,
    DialogsModule,
    LayoutModule,

    SnotifyModule,


  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

بدون اینکه component جدیدی را به پروژه‌ی جاری اضافه کنیم، پروژه را با دستور ng serve --open --prod  اجرا کرده و خروجی را بررسی می‌کنیم:

همانطور که می‌بینید بدون افزودن کامپوننت جدیدی، حجم خروجی از 222KB به 582KB رسیده‌است. معمولا در هر پروژه نیاز به تعدادی دایرکتیو و سرویس پایه نیز می‌باشد که کم کم به حجم خروجی صفحات می‌افزاید. در نظر بگیرید که هنوز هیچ قالب خاصی برای صفحه مورد نظرمان استفاده نشده و به حجم 582KB رسیده‌ایم. برای نمونه می‌توانیم سری به سایت madewithangular.com بزنیم و حجم خروجی تعدادی از سایت‌های نوشته شده‌ی با انگیولار را بررسی کنیم. سایت‌های زیر خروجی بالای 1.5MB دارند. همچنین سایتی را که خودم تقریبا یک سال پیش شروع کرده بودم، حجم خروجی آن  2.7MB است:

دلیل بالا رفتن حجم خروجی، اضافه شدن فایل‌های JavaScript و style-sheet به bundle اصلی پروژه است. برای مثال حجم فایل main.js را در نمونه‌های ذکر شده بررسی کنید. 

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

مطالب
ارتقاء به ASP.NET Core 1.0 - قسمت 11 - بررسی بهبودهای Razor
زبان Razor نیز در ASP.NET Core به همراه بهبودها و اضافات قابل توجهی است که در این قسمت تعدادی از آن‌ها را مانند امکان ارث بری و تزریق وابستگی‌ها، بررسی خواهیم کرد.

نحوه‌ی سفارشی سازی کلاس پایه‌ی تمام Viewهای برنامه و معرفی inherits@

در نگارش‌های پیشین ASP.NET MVC، امکان تعویض کلاس پایه‌ی Viewها، در فایل web.config واقع در پوشه‌ی ریشه‌ی Views وجود داشت. با حذف این فایل و ساده سازی و محول کردن مسئولیت‌های آن به فایل جدید view imports، اینبار برای تعریف کلاس پایه‌ی viewها می‌توان به صورت ذیل عمل کرد:
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.Razor;
 
namespace Core1RtmEmptyTest.StartupCustomizations
{
    public abstract class MyCustomBaseView<TModel> : RazorPage<TModel>
    {
        public bool IsAuthenticated()
        {
            return Context.User.Identity.IsAuthenticated;
        }
 
#pragma warning disable 1998
        public override async Task ExecuteAsync()
        {
        }
#pragma warning restore 1998
    }
}
به صورت پیش فرض تمام viewهای برنامه از کلاس <RazorPage<T ارث بری می‌کنند؛ که در اینجا T، نوع مدلی است که توسط model@ تنظیم می‌شود. اگر نیاز به سفارشی سازی این کلاس وجود داشت، برای مثال بجای اینکه هربار در viewها مقدار Context.User.Identity.IsAuthenticated را جهت نمایش قسمتی از صفحه، به کاربران اعتبارسنجی شده بررسی کنیم، می‌توان این قطعه کد را به یک کلاس پایه‌ی سفارشی منتقل و از آن در تمام Viewها استفاده کرد که نمونه‌ای از آن‌را در کدهای فوق مشاهده می‌کنید.
پس از تعریف این کلاس، برای ثبت و معرفی آن به فایل ViewImports.cshtml_ مراجعه کنید و این یک سطر را به ابتدای آن اضافه نمائید:
@inherits Core1RtmEmptyTest.StartupCustomizations.MyCustomBaseView<TModel>
inherits@ از تازه‌های razor بوده و جهت تعریف ارث بری‌ها کاربرد دارد. البته ممکن است در حین تعریف فوق، TModel را قرمز رنگ مشاهده کنید که مهم نیست و بیشتر مشکل ReSharper است و برنامه بدون مشکل اجرا می‌شود.
برای نمونه پس از سفارشی سازی صفحه‌ی پایه‌ی تمام Viewها، اکنون یک سطر ذیل را در هر view ایی می‌توان تعریف و استفاده کرد:
 Is Current User Authenticated? @IsAuthenticated()


معرفی functions@

دایرکتیو جدید functions@، بسیار شبیه است به دایرکتیو قدیمی و حذف شده‌ی helper@، که در نگارش‌های پیشین Razor معرفی شده بود:
@functions
 {
    public string Test()
    {
        return message;
    }
 
    readonly string message = "test";
}
ASP.NET Core در حین پردازش یک View، کدهای آن‌را تبدیل به یک کلاس می‌کند و در اینجا تمام کدهای داخل بدنه‌ی functions@ را نیز به صورت اعضای این کلاس تعریف خواهد کرد. به این ترتیب یک چنین فراخوانی‌هایی در View میسر می‌شوند:
 @Test()
<br />
@message


معرفی inject@

توسط دایرکتیو جدید inject@، یک خاصیت عمومی به ASP.NET Core اعلام می‌شود و سپس مقدار دهی آن بر اساس تنظیمات IoC Container برنامه به صورت خودکار صورت خواهد گرفت. برای مثال زمانیکه می‌خواهیم به سرویس توکار HostingEnvironment در یک  View دسترسی پیدا کنیم، می‌توان در ابتدای آن نوشت:
 @inject Microsoft.AspNetCore.Hosting.IHostingEnvironment Host;
در این حالت کد فوق از دیدگاه ASP.NET Core به صورت ذیل تفسیر می‌شود:
 [Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public Microsoft.AspNetCore.Hosting.IHostingEnvironment Host { get; private set; }
این خاصیت عمومی نیز با توجه به از پیش ثبت شده بودن سرویس IHostingEnvironment  و مشخص شدن توسط RazorInjectAttribute، توسط IoC Container آن شناسایی شده و تامین می‌شود.
اکنون برای استفاده‌ی از آن خواهیم داشت:
 <div>
 Running in @Host.EnvironmentName
</div>
البته استفاده‌ی از inject@ شاید به نوعی سؤ استفاده‌ی از الگوی MVC به شما رود؛ از این جهت که اطلاعات مورد نیاز یک View، باید از طریق کنترلر آن تامین شود و خود View نباید به صورت مستقیم درخواست تامین آ‌ن‌ها را بدهد. اما باید دقت داشت که در نهایت View نیاز دارد تا کدها را اجرا کرده و خروجی را تولید کند و برای این منظور، در پشت صحنه سرویس‌های زیادی مانند IUrlHelper ، IViewComponentHelper ، IHtmlHelper و غیره به همین ترتیب در اختیار آن قرار می‌گیرند. به علاوه استفاده‌ی از تزریق وابستگی‌ها بهتر است از روش ارث بری صفحات پایه، از این جهت که انتخاب composition همواره مقدم است بر inheritance و سبب انعطاف پذیری بیشتری نسبت به قبل می‌گردد. داشتن یک صفحه‌ی پایه که بتواند تمام نیازهای انواع و اقسام Viewها را تامین کند، دور از انتظار و گاهی از اوقات، سبب سنگینی بیش از حد پردازش تمام Viewها خواهد شد. اما تزریق سرویس‌هایی اینچنینی جهت تامین نیازهای اولیه و تکراری یک یا چند View خاص، کل برنامه را سنگین نکرده و همچنین انعطاف پذیری بیشتری را در جهت تامین آن‌ها فراهم می‌کند.
به علاوه باید دقت داشت اگر تعریف inject@ فوق را در فایل view import قرار دهیم، این سرویس در اختیار تمام Viewهای برنامه قرار خواهد گرفت و دیگر نیازی به قرار دادن آن در یک کلاس پایه‌ی سفارشی نیست.
یکی از مفیدترین استفاده‌های از قابلیت تزریق سرویس‌ها در Viewها می‌تواند دسترسی به سرویس تامین تنظیمات برنامه باشد (که در مورد نحوه‌ی تامین آن در مطلب «ارتقاء به ASP.NET Core 1.0 - قسمت 7 - کار با فایل‌های config» بیشتر بحث شد):
 @inject IOptions<SmtpConfig> Settings;
مطالب
مروری بر Blazor (قسمت اول)

Blazer یک فریمورک جدید تحت وب هست که این امکان را به برنامه نویسان دات نت میدهد تا از طریق Open Web Standards بتوانند کدهای خود را در مرورگر اجرا و تجربه جدیدی از ساخت برنامه‌های تک صفحه‌ای را داشته باشند. در این نوشتار قصد داریم ساختار و نحوه کارکرد این فناوری را بررسی نماییم. قبل از هر چیزی به دوران قبل از ایجاد Web Assembly برمی‌گردیم :

همانطور که در شکل زیر می‌بینید، زمانی تنها جاوااسکریپت فرمانروای یک مرورگر محسوب می‌شد. در این حالت کدهای جاوااسکریپت به هر شکلی که نوشته شده باشند در اختیار parser قرار میگیرند  و یک درخت از کدهای نوشته شده ایجاد شده و از طریق یک کامپایلر، کد‌ها به سطح پایین‌تری مشابه بایت کدها تبدیل می‌گردند و سپس از طریق یک مفسر دسترسی به بخش‌های مختلف api یک مرورگر در اختیار این کدها قرار میگیرند تا کار مورد نظر انجام شود.

 

در تصویر بعدی Web Assembly به بخش مفسر تزریق میشود و از طریق آن زبان‌های مختلف باید بر اساس Web Standard، به کدهای سطح‌های پایین‌تری کامپایل شوند. در اینجا این نکته مدنظر باشد که کدهایی که به سطح پایین‌تری کامپایل میشوند، تنها در داخل مرورگر شناخته شده میباشند و در خارج از دنیای وب قابل استفاده نیستند و نمیتوانند در سطح سیستم عامل قابل اجرا باشند. به همین جهت به شکل یک sandbox مورد استفاده قرار میگیرند و از این لحاظ، مشکلات امنیتی را در خارج از مرورگر ایجاد نمی‌کنند.

 

در شکل سوم Blazor که ترکیبی از نام Browser + Razor میباشد اضافه میشود. Blazor در اینجا وظیفه دارد محتوای فایل دریافتی را که شامل کدهای  HTML و  CSS و جاوااسکریپت است، به کدهای قابل فهمی برای مرورگر تبدیل کند. سپس mono وارد کار میشود. همانطور که می‌دانید mono جهت پشتیبانی از اجرای چندسکویی پروژه‌های دات نت اضافه شده که در اینجا هم همان وظیفه را منتها برای مرورگرهای مختلف، دارد. بدین جهت مونوی کامپایل شده بر روی Web Assembly قرار میگیرد تا کدهای دریافتی را تفسیر نماید. Blazor در اینجا dll‌های لازم را در mono بارگذاری میکند و سپس mono کدها را برای Web Assembly تفسیر میکند.

 

  اگر در تصویر بالا درقت کنید دو فایل Blazor.js و mono.js نیز وجود دارند که یک ارتباط به صورت Introp layer با Web Assembly برقرار کرده‌اند. البته در حال حاضر این ارتباط توسط Web Assembly پشتیبانی نمی‌شود. در صورت پیاده سازی و پشتیبانی Web Assembly از این بخش، میتوان با جاوااسکریپت هم با آن ارتباط برقرار کرد و یک ارتباط دو طرفه‌ای بین کدهای js و دات نت برقرار نمود؛ بدین صورت میتوان در دات نت توابع js را صدا زد و در js توابع دات نت صدا زده شوند.

همچنین مایکروسافت تنها به استفاده از Web Assembly اکتفا نکرده و از طریق SignalR نیز این  بستر را فراهم کرده است. با ایجاد یک سوکت به سمت سرور، تغییرات صفحه در سمت سرور، محاسبه و سپس بازگشت داده می‌شوند. در این حالت نیازی به ارسال فایل‌های dll نسبت به روش قبل نمی‌باشد. برای استفاده از این حالت میتوانید از بین گزینه‌های موجود در ایجاد پروژه، Blazor Server-side را مورد استفاده قرار دهید. البته این روش هم مزایا و معایب خودش را دارد.

جهت مقایسه این دو بخش به بررسی نکات مثبت و منفی میپردازیم:
1- در حالت استفاده از Web Assembly، حجمی حدود نزدیک به دو مگابایت بایدجابجا شود؛ ولی در حال سمت سرور، حجم صفحه حدود 100 کیلوبایت خواهد شد.
2- در حالت سمت سرور، تغییرات به دلیل رفت و برگشت به سرور با کمی تاخیر روبرو میشوند.
3- در حالت سمت سرور کارکرد آفلاین از دست میرود.
4- در حالت سرور، به دلیل اینکه همه کارها سمت سرور انجام میشود، ترافیک سرور را بالاتر میبرند.
5- استفاده از حالت سرور، معماری ساده‌تر و پیچیدگی‌های کمتری در سمت کلاینت دارد.
مطالب
وب‌پارت اسلاید متحرک در شیرپوینت 2010
در خلال تعدادی از پروژه‌هایی که در زمینه توسعه شیرپوینت 2010 درگیر بودم، یکی از وب‌پارت‌هایی که اکثر مشتریان درخواست می‌کردند وب پارت اسلاید متحرک بود. به نظر من داشتن این وب‌پارت ظاهرا خیلی برای مشتریان مهم بنظر می‌رسید، بهمین سبب در زیر به پیاده‌سازی آن اشاره می‌کنم.

ساخت لیست سفارشی

ابتدا یک لیست سفارشی بنام ContentSlides ایجاد می‌کنیم و  ستونی از نوع Rich HTML به آن اضافه می‌کنیم.

ایجاد یک پروژه شیرپوینتی از نوع Visual Web Part

سپس یک پروژه شیرپوینت از نوع Visual Web Part در سایتی که لیست فوق در آن قرار دارد می‌سازیم.





در این مرحله نامگذاری‌های پیش‌فرض ویژوال استودیو که برای ویژوال وب‌پارت در نظر گرفته را به نام مناسب تغییر می‌دهیم.



افزودن پلاگین AnythingSlider

ابتدا پلاگین AnythingSlider را از این آدرس دریافت نمایید. سپس فایلهای anythingslider.css, anythingslider-ie.css, jquery.min.js, jquery.anythingslider.js  و "default.png " را به ویژوال وب‌پارت اضافه می‌کنیم. برای اضافه کردن فایلهای این پلاگین، ابتدا فولدر "Layouts" به پروژه اضافه می‌نماییم و سپس فایلهای این پلاگین در این فولدر قرار می‌دهیم.

سپس در خط 129 و 155 و 181 فایل anythingslider.css

background: url("../images/default.png") no-repeat; 
را به
background: url(“default.png") no-repeat;
تغییر دهید و شکل پروژه به صورت زیر خواهد شد.

در ادامه، بر روی "ContentSliderVisualWebPartUserControl.ascx" دابل کلیک کنید و کد زیر را به آن اضافه می‌کنیم.

<SharePoint:CssRegistration ID="AnythingSliderCssRegistration" runat="server" 
Name="/_layouts/ContentSliderWebPart/anythingslider.css"></SharePoint:CssRegistration>
<SharePoint:CssRegistration ID="AnythingSliderCssRegistrationIE7" runat="server" 
Name="/_layouts/ContentSliderWebPart/anythingslider.css" ConditionalExpression="lte IE 7"></SharePoint:CssRegistration>

<SharePoint:ScriptLink ID="JqueryScriptLink" runat="server" 
Name="/_layouts/ContentSliderWebPart/jquery.min.js"></SharePoint:ScriptLink>
<SharePoint:ScriptLink ID="AnythingSliderScriptLink" runat="server" 
Name="/_layouts/ContentSliderWebPart/jquery.anythingslider.js"></SharePoint:ScriptLink>

توجه کنید در کد بالا ما از کنترل‌های "SharePointCssRegistration" و "SharePointScriptLink" برای اضافه نمودن فایلهای Css و JavaScript مورد نیاز و هم چنین از خصیصه "ConditionalExpression" برای اضافه کردن فایل "anythingslider-ie.css" برای مرورگر اینترنت اکسپلورر 7 به بالا استفاده کردیم.

ایجاد کوئری برای لیست سفارشی

برای ایجاد کوئری از کتابخانه jQuery بنام SPService که از این آدرس قابل دریافت است، استفاده می‌کنیم. فایل "jquery.SPServices.min.js" موجود در این کتابخانه را همانند سایر فایل‌های اضافه شده در قسمت قبل را به پروژه اضافه می‌کنیم.

برای استفاده از کتابخانه، کد زیر را در user control قسمت فبلی و در ادامه کدهای قبلی وارد می‌کنیم.

<SharePoint:ScriptLink ID="SPServicesScriptLink" runat="server" 
Name="/_layouts/ContentSliderWebPart/jquery.SPServices.min.js"></SharePoint:ScriptLink>

و برای ایجاد کوئری کد زیر در user control وارد می‌کنیم.

<script language="javascript" type="text/javascript">

$(document).ready(function () {
$().SPServices({
operation: "GetListItems",
async: false,
listName: "ContentSlides",
CAMLViewFields: "<ViewFields<FieldRef Name='SlidesContent' /></ViewFields>",
completefunc: function (xData, Status) {
$(xData.responseXML).SPFilterNode("z:row").each(function () {
var liHtml = "<li>" + $(this).attr("ows_SlidesContent") + "</li>";
$("#slider").append(liHtml);
});
}
});

/*-- Initialize AnythingSlider plugin --*/
    $('#slider').anythingSlider();

});
</script>

<ul id="slider" />

وظیفه این کد انجام کوئری بر روی لیست "ContentSlides" و ایجاد ساختار یک لیست جهت نمایش اسلایدها می‌باشد که آیتم‌های این لیست مقادیر ستون
"Slides Content" می‌باشد، شناسه این لیست "Slider" است.

توجه کنید: ما می‌توانیم CAML Queryهای پیشرفته‌ای برای اعمال فیلتر مناسب و چیدمان اسلایدها استفاده کنیم.

در ادامه، برای مقدار دهی اولیه به پلاگین بوسیله فراخوانی تابع

 $('#slider').anythingSlider();

انجام می‌شود.

در پایان براحتی با Deploy نمودن Solution، امکان استفاده از وب پارت مهیاست.

برای دریافت کد این پروژه از این آدرس استفاده کنید.

مطالب
طراحی افزونه پذیر با ASP.NET MVC 4.x/5.x - قسمت سوم
پس از بررسی ساختار یک پروژه‌ی افزونه پذیر و همچنین بهبود توزیع فایل‌های استاتیک آن، اکنون نوبت به کار با داده‌ها است. هدف اصلی آن نیز داشتن مدل‌های اختصاصی و مستقل Entity framework code-first به ازای هر افزونه است و سپس بارگذاری و تشخیص خودکار آن‌ها در Context مرکزی برنامه.

پیشنیازها
- آشنایی با مباحث Migrations در EF Code first
- آشنایی با مباحث الگوی واحد کار
- چگونه مدل‌های EF را به صورت خودکار به Context اضافه کنیم؟
- چگونه تنظیمات مدل‌های EF را به صورت خودکار به Context اضافه کنیم؟


کدهایی را که در این قسمت مشاهده خواهید کرد، در حقیقت همان برنامه‌ی توسعه یافته «آشنایی با مباحث الگوی واحد کار» است و از ذکر قسمت‌های تکراری آن جهت طولانی نشدن مبحث، صرفنظر خواهد شد. برای مثال Context و مدل‌های محصولات و گروه‌های آن‌ها به همراه کلاس‌های لایه سرویس برنامه‌ی اصلی، دقیقا همان کدهای مطلب «آشنایی با مباحث الگوی واحد کار» است.


تعریف domain classes مخصوص افزونه‌ها

در ادامه‌ی پروژه‌ی افزونه پذیر فعلی، پروژه‌ی class library جدیدی را به نام MvcPluginMasterApp.Plugin1.DomainClasses اضافه خواهیم کرد. از آن جهت تعریف کلاس‌های مدل افزونه‌ی یک استفاده می‌کنیم. برای مثال کلاس News را به همراه تنظیمات Fluent آن به این پروژه‌ی جدید اضافه کنید:
using System.Data.Entity.ModelConfiguration;
 
namespace MvcPluginMasterApp.Plugin1.DomainClasses
{
    public class News
    {
        public int Id { set; get; }
 
        public string Title { set; get; }
 
        public string Body { set; get; }
    }
 
    public class NewsConfig : EntityTypeConfiguration<News>
    {
        public NewsConfig()
        {
            this.ToTable("Plugin1_News");
            this.HasKey(news => news.Id);
            this.Property(news => news.Title).IsRequired().HasMaxLength(500);
            this.Property(news => news.Body).IsOptional().IsMaxLength();
        }
    }
}
این پروژه برای کامپایل شدن نیاز به بسته‌ی نیوگت ذیل دارد:
 PM> install-package EntityFramework

مشکل! برنامه‌ی اصلی، همانند مطلب «آشنایی با مباحث الگوی واحد کار» دارای domain classes خاص خودش است به همراه تنظیمات Context ایی که صریحا در آن مدل‌های متناظر با این پروژه در معرض دید EF قرار گرفته‌اند:
public class MvcPluginMasterAppContext : DbContext, IUnitOfWork
{
    public DbSet<Category> Categories { set; get; }
    public DbSet<Product> Products { set; get; }
اکنون برنامه‌ی اصلی چگونه باید مدل‌ها و تنظیمات سایر افزونه‌ها را یافته و به صورت خودکار به این Context اضافه کند؟ با توجه به اینکه این برنامه هیچ ارجاع مستقیمی را به افزونه‌ها ندارد.


تغییرات اینترفیس Unit of work جهت افزونه پذیری

در ادامه، اینترفیس بهبود یافته‌ی IUnitOfWork را جهت پذیرش DbSetهای پویا و همچنین EntityTypeConfigurationهای پویا، ملاحظه می‌کنید:
namespace MvcPluginMasterApp.PluginsBase
{
    public interface IUnitOfWork : IDisposable
    {
        IDbSet<TEntity> Set<TEntity>() where TEntity : class;
        int SaveAllChanges();
        void MarkAsChanged<TEntity>(TEntity entity) where TEntity : class;
        IList<T> GetRows<T>(string sql, params object[] parameters) where T : class;
        IEnumerable<TEntity> AddThisRange<TEntity>(IEnumerable<TEntity> entities) where TEntity : class;
        void SetDynamicEntities(Type[] dynamicTypes);
        void ForceDatabaseInitialize();
        void SetConfigurationsAssemblies(Assembly[] assembly);
    }
}
متدهای جدید آن:
SetDynamicEntities : توسط این متد در ابتدای برنامه، نوع‌های مدل‌های جدید افزونه‌ها به صورت خودکار به Context اضافه خواهند شد.
SetConfigurationsAssemblies : کار افزودن اسمبلی‌های حاوی تعاریف EntityTypeConfigurationهای جدید و پویا را به عهده دارد.
ForceDatabaseInitialize: سبب خواهد شد تا مباحث migrations، پیش از شروع به کار برنامه، اعمال شوند.

در کلاس Context ذیل، نحوه‌ی پیاده سازی این متدهای جدید را ملاحظه می‌کنید:
namespace MvcPluginMasterApp.DataLayer.Context
{
    public class MvcPluginMasterAppContext : DbContext, IUnitOfWork
    {
        private readonly IList<Assembly> _configurationsAssemblies = new List<Assembly>();
        private readonly IList<Type[]> _dynamicTypes = new List<Type[]>(); 
 
        public void ForceDatabaseInitialize()
        {
            Database.Initialize(force: true);
        }
 
        public void SetConfigurationsAssemblies(Assembly[] assemblies)
        {
            if (assemblies == null) return;
 
            foreach (var assembly in assemblies)
            {
                _configurationsAssemblies.Add(assembly);
            }
        }
 
        public void SetDynamicEntities(Type[] dynamicTypes)
        {
            if (dynamicTypes == null) return;
            _dynamicTypes.Add(dynamicTypes);
        }
 
        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            addConfigurationsFromAssemblies(modelBuilder);
            addPluginsEntitiesDynamically(modelBuilder);
            base.OnModelCreating(modelBuilder);
        }
 
        private void addConfigurationsFromAssemblies(DbModelBuilder modelBuilder)
        {
            foreach (var assembly in _configurationsAssemblies)
            {
                modelBuilder.Configurations.AddFromAssembly(assembly);
            }
        }
 
        private void addPluginsEntitiesDynamically(DbModelBuilder modelBuilder)
        {
            foreach (var types in _dynamicTypes)
            {
                foreach (var type in types)
                {
                    modelBuilder.RegisterEntityType(type);
                }
            }
        }
    }
}
در متد استاندارد OnModelCreating، فرصت افزودن نوع‌های پویا و همچنین تنظیمات پویای آن‌ها وجود دارد. برای این منظور می‌توان از متدهای modelBuilder.RegisterEntityType و modelBuilder.Configurations.AddFromAssembly کمک گرفت.


بهبود اینترفیس IPlugin جهت پذیرش نوع‌های پویای EF

در قسمت اول، با اینترفیس IPlugin آشنا شدیم. هر افزونه باید دارای کلاسی باشد که این اینترفیس را پیاده سازی می‌کند. از آن جهت دریافت تنظیمات و یا ثبت تنظیمات مسیریابی و امثال آن استفاده می‌شود.
در اینجا متد GetEfBootstrapper آن کار دریافت تنظیمات EF هر افزونه را به عهد دارد.
namespace MvcPluginMasterApp.PluginsBase
{
    public interface IPlugin
    {
        EfBootstrapper GetEfBootstrapper();
        //...به همراه سایر متدهای مورد نیاز
    }
 
    public class EfBootstrapper
    {
        /// <summary>
        /// Assemblies containing EntityTypeConfiguration classes.
        /// </summary>
        public Assembly[] ConfigurationsAssemblies { get; set; }
 
        /// <summary>
        /// Domain classes.
        /// </summary>
        public Type[] DomainEntities { get; set; }
 
        /// <summary>
        /// Custom Seed method.
        /// </summary>
        public Action<IUnitOfWork> DatabaseSeeder { get; set; }
    } 
}
ConfigurationsAssemblies مشخص کننده‌ی اسمبلی‌هایی است که حاوی تعاریف EntityTypeConfigurationهای افزونه‌ی جاری هستند.
DomainEntities بیانگر لیست مدل‌ها و موجودیت‌های هر افزونه است.
DatabaseSeeder کار دریافت منطق متد Seed را بر عهده دارد. برای مثال اگر افزونه‌ای نیاز است در آغاز کار تشکیل جداول آن، دیتای پیش فرض و خاصی را در بانک اطلاعاتی ثبت کند، می‌توان از این متد استفاده کرد. اگر دقت کنید این Action یک وهله از IUnitOfWork را به افزونه ارسال می‌کند. بنابراین در این طراحی جدید، اینترفیس IUnitOfWork به پروژه‌ی MvcPluginMasterApp.PluginsBase منتقل می‌شود. به این ترتیب دیگر نیازی نیست تا تک تک افزونه‌ها ارجاع مستقیمی را به DataLayer پروژه‌ی اصلی پیدا کنند.


تکمیل متد GetEfBootstrapper در افزونه‌ها

اکنون جهت معرفی مدل‌ها و تنظیمات EF آن‌ها، تنها کافی است متد GetEfBootstrapper هر افزونه را تکمیل کنیم:
namespace MvcPluginMasterApp.Plugin1
{
    public class Plugin1 : IPlugin
    {
        public EfBootstrapper GetEfBootstrapper()
        {
            return new EfBootstrapper
            {
                DomainEntities = new[] { typeof(News) },
                ConfigurationsAssemblies = new[] { typeof(NewsConfig).Assembly },
                DatabaseSeeder = uow =>
                {
                    var news = uow.Set<News>();
                    if (news.Any())
                    {
                        return;
                    }
 
                    news.Add(new News
                    {
                        Title = "News 1",
                        Body = "news 1 news 1 news 1 ...."
                    });
 
                    news.Add(new News
                    {
                        Title = "News 2",
                        Body = "news 2 news 2 news 2 ...."
                    });
                }
            };
        }
در اینجا نحوه‌ی معرفی مدل‌های جدید را توسط خاصیت DomainEntities و تنظیمات متناظر را به کمک خاصیت ConfigurationsAssemblies مشاهده می‌کنید. باید دقت داشت که هر اسمبلی فقط باید یکبار معرفی شود و مهم نیست که چه تعداد تنظیمی در آن وجود دارند. کار یافتن کلیه‌ی تنظیمات از نوع EntityTypeConfigurationها به صورت خودکار توسط EF صورت می‌گیرد.
همچنین توسط delegate ایی به نام DatabaseSeeder، نحوه‌ی دسترسی به متد Set واحد کار و سپس استفاده‌ی از آن، برای تعریف متد Seed سفارشی نیز تکمیل شده‌است.


تدارک یک راه انداز EF، پیش از شروع به کار برنامه

در پوشه‌ی App_Start پروژه‌ی اصلی یا همان MvcPluginMasterApp، کلاس جدید EFBootstrapperStart را با کدهای ذیل اضافه کنید:
[assembly: PreApplicationStartMethod(typeof(EFBootstrapperStart), "Start")]
namespace MvcPluginMasterApp
{
    public static class EFBootstrapperStart
    {
        public static void Start()
        {
            var plugins = SmObjectFactory.Container.GetAllInstances<IPlugin>().ToList();
            using (var uow = SmObjectFactory.Container.GetInstance<IUnitOfWork>())
            {
                initDatabase(uow, plugins);
                runDatabaseSeeders(uow, plugins);
            }
        }
 
        private static void initDatabase(IUnitOfWork uow, IEnumerable<IPlugin> plugins)
        {
            foreach (var plugin in plugins)
            {
                var efBootstrapper = plugin.GetEfBootstrapper();
                if (efBootstrapper == null) continue;
 
                uow.SetDynamicEntities(efBootstrapper.DomainEntities);
                uow.SetConfigurationsAssemblies(efBootstrapper.ConfigurationsAssemblies);
            }
 
            Database.SetInitializer(new MigrateDatabaseToLatestVersion<MvcPluginMasterAppContext, Configuration>());
            uow.ForceDatabaseInitialize();
        }
 
        private static void runDatabaseSeeders(IUnitOfWork uow, IEnumerable<IPlugin> plugins)
        {
            foreach (var plugin in plugins)
            {
                var efBootstrapper = plugin.GetEfBootstrapper();
                if (efBootstrapper == null || efBootstrapper.DatabaseSeeder == null) continue;
 
                efBootstrapper.DatabaseSeeder(uow);
                uow.SaveAllChanges();
            }
        }
    }
}
در اینجا یک راه انداز سفارشی از نوع PreApplicationStartMethod تهیه شده‌است. Pre بودن آن به معنای اجرای کدهای متد Start این کلاس، پیش از آغاز به کار برنامه و پیش از فراخوانی متد Application_Start فایل Global.asax.cs است.
همانطور که ملاحظه می‌کنید، ابتدا لیست تمام افزونه‌های موجود، به کمک StructureMap دریافت می‌شوند. سپس می‌توان در متد initDatabase به متد GetEfBootstrapper هر افزونه دسترسی یافت و توسط آن تنظیمات مدل‌ها را یافته و به Context اصلی برنامه اضافه کرد. سپس با فراخوانی ForceDatabaseInitialize تمام این موارد به صورت خودکار به بانک اطلاعاتی اعمال خواهند شد.
کار متد runDatabaseSeeders، یافتن DatabaseSeeder هر افزونه، اجرای آن‌ها و سپس فراخوانی متد SaveAllChanges در آخر کار است.



کدهای کامل این سری را از اینجا می‌توانید دریافت کنید:
MvcPlugin
مطالب
از سرگیری مجدد، لغو درخواست و سعی مجدد دریافت فایل‌های حجیم توسط HttpClient
پس از آشنایی با «نکات دریافت فایل‌های حجیم توسط HttpClient»، در ادامه می‌توان سه قابلیت مهم از سرگیری مجدد، لغو درخواست و سعی مجدد دریافت فایل‌های حجیم را با HttpClient، همانند برنامه‌های download manager نیز پیاده سازی کرد.


از سرگیری مجدد درخواست ارسالی توسط HttpClient

یک نمونه از سرگیری مجدد درخواست را در مطلب «اضافه کردن قابلیت از سرگیری مجدد (resume) به HttpWebRequest» پیشتر در این سایت مطالعه کرده‌اید. اصول کلی آن نیز در اینجا صادق است. HTTP 1.1 از مفهوم range headers‌، برای دریافت پاسخ‌های جزئی پشتیبانی می‌کند. به این ترتیب در صورت پیاده سازی چنین قابلیتی در برنامه‌ی سمت سرور، می‌توان دریافت بازه‌ای از بایت‌ها را بجای دریافت فایل از ابتدا، از سرور درخواست کرد. به یک چنین قابلیتی Resume و یا از سرگیری مجدد گرفته می‌شود و درحین دریافت فایل‌های حجیم بسیار حائز اهمیت است.
var fileInfo = new FileInfo(outputFilePath);
long resumeOffset = 0;
if (fileInfo.Exists)
{
    resumeOffset = fileInfo.Length;
}
if (resumeOffset > 0)
{
    _client.DefaultRequestHeaders.Range = new RangeHeaderValue(resumeOffset, null);
}
در اینجا نحوه‌ی تنظیم یک RangeHeader را مشاهده می‌کنید. ابتدا نیاز است بررسی کنیم آیا فایل دریافتی از پیش موجود است؟ آیا قسمتی از این درخواست پیشتر دریافت شده و محتوای آن هم اکنون به صورت ذخیره شده وجود دارد؟ اگر بله، درخواست دریافت این فایل را بر اساس اندازه‌ی دریافتی فعلی آن، به سرور ارائه می‌کنیم.

یک نکته: تمام وب سرورها و یا برنامه‌های وب از یک چنین قابلیتی پشتیبانی نمی‌کنند.
روش تشخیص آن نیز به صورت زیر است:
var response = await client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead);
if (response.Headers.AcceptRanges == null && resumeOffset > 0)
{
    // resume not supported, starting over
}
پس از خواندن هدر درخواست، اگر خاصیت AcceptRanges آن نال بود، یعنی قابلیت از سرگیری مجدد را ندارد. در این حالت باید فایل موجود فعلی را حذف و یا از نو (FileMode.CreateNew) بازنویسی کرد (بجای حالت FileMode.Append).


لغو درخواست ارسالی توسط HttpClient

پس از شروع غیرهمزمان client.GetAsync می‌توان متد CancelPendingRequests آن‌را فراخوانی کرد تا کلیه درخواست‌های مرتبط با این client لغو شوند. اما این متد صرفا برای حالت پیش‌فرض client.GetAsync که دریافت هدر + محتوا است کار می‌کند (یعنی حالت HttpCompletionOption.ResponseContentRead). اگر همانند نکات بررسی شده‌ی در مطلب «دریافت فایل‌های حجیم توسط HttpClient» صرفا درخواست خواندن هدر را بدهیم (HttpCompletionOption.ResponseHeadersRead)، چون کنترل ادامه‌ی بحث را خودمان بر عهده گرفته‌ایم، لغو آن نیز به عهده‌ی خودمان است و متد CancelPendingRequests بر روی آن تاثیر نخواهد داشت.
این نکته در مورد تنظیم خاصیت TimeOut نیز صادق است. این خاصیت فقط زمانیکه دریافت کل هدر + محتوا توسط متد GetAsync مدیریت شوند، تاثیر گذار است.
بنابراین درحالتیکه نیاز به کنترل بیشتر است، هرچند فراخوانی متد CancelPendingRequests ضرری ندارد، اما الزاما سبب قطع کل درخواست نمی‌شود و باید این لغو را به صورت ذیل پیاده سازی کرد:
ابتدا یک منبع توکن لغو عملیات را به صورت ذیل ایجاد می‌کنیم:
private readonly CancellationTokenSource _cts = new CancellationTokenSource();
سپس، متد لغو برنامه، تنها کافی است متد Cancel این cts را فراخوانی کند؛ تا عملیات دریافت فایل خاتمه یابد.
پس از این فراخوانی (()cts.Cancel)، نحوه‌ی واکنش به آن به صورت ذیل خواهد بود:
var result = await client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead, _cts.Token);
using(var stream = await result.Content.ReadAsStreamAsync())
{
   byte[] buffer = new byte[80000];
   int bytesRead;
   while((bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length)) > 0 &&
         !_cts.IsCancellationRequested)
   {
      outputStream.Write(buffer, 0, bytesRead);
   }
}
در اینجا از cts.Token به عنوان پارامتر سوم متد GetAsync استفاده شده‌است. همچنین قسمت ثبت اطلاعات دریافتی، در استریم خروجی نیز به صورت یک حلقه درآمده‌است تا بتوان خاصیت IsCancellationRequested این توکن لغو را بررسی کرد و نسبت به آن واکنش نشان داد.


سعی مجدد درخواست ارسالی توسط HttpClient

یک روش پیاده سازی سعی مجدد درخواست شکست خورده، توسط کتابخانه‌ی Polly است. روش دیگر آن نیز به صورت ذیل است:
public async Task DownloadFileAsync(string url, string outputFilePath, int maxRequestAutoRetries)
{
            var exceptions = new List<Exception>();

            do
            {
                --maxRequestAutoRetries;
                try
                {
                    await doDownloadFileAsync(url, outputFilePath);
                }
                catch (TaskCanceledException ex)
                {
                    exceptions.Add(ex);
                }
                catch (HttpRequestException ex)
                {
                    exceptions.Add(ex);
                }
                catch (Exception ex) when (isNetworkError(ex))
                {
                    exceptions.Add(ex);
                }

                // Wait a bit and try again later
               if (exceptions.Any())  await Task.Delay(2000, _cts.Token);
            } while (maxRequestAutoRetries > 0 &&
                     !_cts.IsCancellationRequested);

            var uniqueExceptions = exceptions.Distinct().ToList();
            if (uniqueExceptions.Any())
            {
                if (uniqueExceptions.Count() == 1)
                    throw uniqueExceptions.First();
                throw new AggregateException("Could not process the request.", uniqueExceptions);
            }
}

private static bool isNetworkError(Exception ex)
{
    if (ex is SocketException || ex is WebException)
        return true;
    if (ex.InnerException != null)
        return isNetworkError(ex.InnerException);
    return false;
}
در اینجا متد doDownloadFileAsync، پیاده سازی همان متدی است که در قسمت «لغو درخواست ارسالی توسط HttpClient» در مورد آن بحث شد. این قسمت دریافت فایل را در یک حلقه که حداقل یکبار اجرا می‌شود، قرار می‌دهیم. متد GetAsync استثناءهایی مانند TaskCanceledException (در حین TimeOut و یا فراخوانی متد CancelPendingRequests که البته همانطور که توضیح داده شد، بر روی روش کنترل Response تاثیری ندارند)، HttpRequestException پس از فراخوانی متد response.EnsureSuccessStatusCode (جهت اطمینان حاصل کردن از دریافت پاسخی بدون مشکل از طرف سرور) و یا SocketException و WebException را درصورت بروز مشکلی در شبکه، صادر می‌کند. نیازی به بررسی سایر استثناءها در اینجا نیست.
اگر یکی از این استثناءهای یاد شده رخ‌دادند، اندکی صبر کرده و مجددا درخواست را از ابتدا صادر می‌کنیم.
در پایان این سعی‌های مجدد، اگر استثنایی ثبت شده بود و همچنین عملیات نیز با موفقیت به پایان نرسیده بود، آن‌را به فراخوان صادر می‌کنیم.