نظرات اشتراک‌ها
دیالوگ های بوت استراپ با استفاده از jquery
از مطلب «Ajax.BeginForm و ارسال فایل به سرور در ASP.NET MVC» ایده بگیرید. افزونه‌ی استفاده شده در آن در اصل با PHP تست شده‌است و بنابراین با تمام فناوری‌های سمت سرور کار می‌کند. یک مثال دیگر استفاده از همین افزونه: «آپلود فایل توسط فرم‌های پویای jqGrid»   
نظرات مطالب
افزودن هدرهای Content Security Policy به برنامه‌های ASP.NET
یعنی کلیه اسکری‍ت‌ها حتی اسکری‍ت هایی مانند  :
<script type="text/javascript">
        $(document).ready(function () {
            PopupForm.ShowForm({
                renderFormUrl : "/postreply/renderreplyform",
                .....   
          });
        });
</script>
اجازه‌ی اجرا ندارند؟ و باید در یک فایل جدا به صفحه تزریق شوند؟
مطالب
تبدیل 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
مطالب
React 16x - قسمت 11 - طراحی یک گرید - بخش 1 - کامپوننت صفحه بندی
در طی چند قسمت قصد داریم مثال قسمت قبل را که کار نمایش لیستی از فیلم‌ها را انجام می‌دهد، تبدیل به یک کامپوننت گرید کنیم که دارای امکانات صفحه بندی، فیلتر کردن و مرتب سازی اطلاعات است.


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

شبیه به کامپوننت Like که در قسمت قبل ایجاد کردیم، می‌خواهیم کامپوننت جدید Pagination نیز به طور کامل از اشیاء movie مستقل باشد؛ تا در آینده بتوان از آن در جاهای دیگری نیز استفاده کرد. به همین جهت فایل جدید src\components\common\pagination.jsx را ایجاد کرده و سپس با استفاده از میانبرهای imrc و cc در VSCode، ساختار ابتدایی این کامپوننت را ایجاد می‌کنیم. هرچند می‌توان این کامپوننت را به صورت «Stateless Functional Component» نیز طراحی کرد؛ چون state و متد دیگری بجز render نخواهد داشت و تمام اطلاعات خودش را از والد خود دریافت می‌کند.
سپس به کامپوننت movies مراجعه کرده و این کامپوننت خالی را import می‌کنیم:
import Pagination from "./common/pagination";
پس از آن به متد رندر کامپوننت movies مراجعه کرده و پس از بسته شدن tag المان جدول، قصد داریم این کامپوننت صفحه بندی را نمایش دهیم. به همین جهت المان آن‌را در این محل قرار می‌دهیم تا بتوانیم اینترفیس ابتدایی آن‌را پیش از پیاده سازی آن، طراحی کنیم:
<Pagination
  itemsCount={this.state.movies.length}
  pageSize={this.state.pageSize}
  onPageChange={this.handlePageChange}
/>
این کامپوننت نیاز به تعداد کل آیتم‌های فیلم‌ها را دارد؛ به علاوه‌ی اندازه‌ی صفحه، یا همان تعداد ردیفی که قرار است در هر صفحه نمایش داده شود. به این ترتیب کامپوننت صفحه بندی می‌تواند تعداد صفحات مورد نیاز را محاسبه کرده و سپس آن‌ها را رندر کند.
  state = {
    movies: getMovies(),
    pageSize: 4
  };

به همین جهت دو ویژگی itemsCount و pageSize را پیش از هرکاری به تعریف المان صفحه بندی اضافه کرده‌ایم تا داده‌های ورودی آن مشخص شوند. ویژگی itemsCount از تعداد اعضای آرایه‌ی movies حاصل می‌شود و pageSize را به عنوان یک خاصیت جدید شیء منتسب به state تعریف و با عدد 4 مقدار دهی کرده‌ایم.
همچنین در لیست صفحاتی که توسط این کامپوننت رندر می‌شود، باید با کلیک بر روی هر کدام، اطلاعات آن صفحه رندر شود. به همین جهت در اینجا ویژگی onPageChange تعریف شده و به متد جدید handlePageChange در کامپوننت movies، متصل گردیده تا عدد page را دریافت کرده و به آن واکنش نشان دهد:
  handlePageChange = page => {
    console.log("handlePageChange", page);
  };
تا اینجا اینترفیس کامپوننت صفحه بندی را پیش از پیاده سازی آن تعریف کردیم (تعیین ورودی‌ها و خروجی آن). در مرحله‌ی بعد، این کامپوننت را تکمیل می‌کنیم.


نمایش شماره صفحات گرید، در کامپوننت صفحه بندی

برای رندر کامپوننت صفحه بندی، از کلاس‌های مخصوص اینکار که در بوت استرپ تعریف شده‌اند، استفاده می‌کنیم که ساختار کلی آن به صورت زیر است و از یک المان nav که داخل آن ul ای با کلاس pagination و liهایی با کلاس page-item هستند، تشکیل می‌شود. هر li، به همراه یک anchor است؛ با کلاس page-link تا لینک به صفحه‌ای خاص را ارائه دهد که در اینجا بجای لینک، از کلیک بر روی آن‌ها استفاده خواهیم کرد:
import React, { Component } from "react";

class Pagination extends Component {
  render() {
    return (
      <nav>
        <ul className="pagination">
          <li className="page-item">
            <a className="page-link">1</a>
          </li>
        </ul>
      </nav>
    );
  }
}

export default Pagination;

تا اینجا اگر برنامه را ذخیره کرده و اجرا کنید، عدد 1 را در پایین جدول فیلم‌ها مشاهده خواهید کرد:


اکنون باید رندر این liها را بر اساس ورودی‌های این کامپوننت که پیشتر معرفی کردیم، یعنی pageSize و itemsCount، پویا کنیم. به همین جهت نیاز به آرایه‌ای داریم که بر اساس این ورودی‌ها، شماره‌ی صفحات مانند [1,2,3] را ارائه دهد تا بر روی آن متد Array.map را فراخوانی کرده و liهای مورد نیاز را به صورت پویا رندر کنیم:
class Pagination extends Component {
  // ...
  getPageNumbersArray() {
    const { itemsCount, pageSize } = this.props;
    const pagesCount = Math.ceil(itemsCount / pageSize);
    if (pagesCount === 1) {
      return null;
    }

    const pages = new Array();
    for (let i = 1; i <= pagesCount; i++) {
      pages.push(i);
    }
    return pages;
  }
}
در اینجا بر اساس ورودی‌ها، تعداد صفحات محاسبه شده و سپس بر اساس آن‌ها آرایه‌ای از این شماره صفحه‌ها تشکیل و بازگشت داده می‌شود. همچنین اگر تعداد صفحات 1 بود، می‌خواهیم این کامپوننت چیزی را رندر نکند. به همین جهت در اینجا null بازگشت داده شده‌است. دلیل استفاده‌ی از Math.ceil که کوچکترین عدد صحیح بزرگتر یا مساوی خروجی را بازگشت می‌دهد، نیز همین مورد است. توسط این متد، خروجی float دریافتی به integer تبدیل شده و سپس قابلیت مقایسه‌ی با 1 را پیدا می‌کند. برای مثال اگر تعداد ردیف‌های صفحه را به 10 تنظیم کنیم، خروجی این تقسیم در این مثال، 0.9 خواهد بود که شرط بررسی pagesCount === 1 را برآورده نمی‌کند. به همین جهت توسط متد Math.ceil، این خروجی به عدد 1 تقریب زده شده و سبب بازگشت نال از این متد خواهد شد.
سپس به کمک متد map، اعضای این آرایه را تبدیل به لیست liهای نمایش شماره صفحات می‌کنیم. در اینجا key هر li را نیز به شماره صفحه که منحصربفرد است، تنظیم کرده‌ایم:
class Pagination extends Component {
  render() {
    const pages = this.getPageNumbersArray();
    if (!pages) {
      return null;
    }

    return (
      <nav>
        <ul className="pagination">
          {pages.map(page => (
            <li key={page} className="page-item">
              <a className="page-link">{page}</a>
            </li>
          ))}
        </ul>
      </nav>
    );
  }

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


با داشتن 9 فیلم در آرایه‌ی movies و نمایش 4 فیلم به ازای هر صفحه، به 3 صفحه خواهیم رسید که به درستی در اینجا رندر شده‌است. یکبار هم برای آزمایش بیشتر، مقدار pageSize را در کامپوننت movies به 10 تنظیم کنید. در این حالت کامپوننت صفحه بندی نباید رندر شود.


مدیریت انتخاب شماره‌های صفحات

در این قسمت می‌خواهیم مدیریت onPageChange={this.handlePageChange} را که به تعریف المان صفحه بندی در کامپوننت movies اضافه کردیم، تکمیل کنیم. برای این منظور در کامپوننت صفحه بندی، قسمت anchor را به صورت زیر تغییر می‌دهیم تا با کلیک بر روی آن، رخداد onPageChange صادر شود:
<a
   onClick={() => this.props.onPageChange(page)}
   className="page-link"
   style={{ cursor: "pointer" }}
>
   {page}
</a>

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

اکنون می‌خواهیم اگر صفحه‌ای انتخاب شد، شماره‌ی آن صفحه با رنگی دیگر نمایش داده شود. در بوت استرپ برای اینکار تنها کافی است کلاس active را به className هر li اضافه کنیم و برعکس. یعنی اگر page ای مساوی صفحه‌ی جاری انتخاب شده بود (currentPage در اینجا)، آنگاه کلاس page-item active، به المان li اضافه شود. بنابراین در این کامپوننت نیاز است عدد currentPage را نیز دریافت کنیم. به همین جهت ویژگی currentPage را به تعریف المان Pagination در کامپوننت movies اضافه می‌کنیم:
<Pagination
  itemsCount={this.state.movies.length}
  pageSize={this.state.pageSize}
  onPageChange={this.handlePageChange}
  currentPage={this.state.currentPage}
/>
این ویژگی نیز مقدار خودش را از state به روز شده دریافت می‌کند:
class Movies extends Component {
  state = {
    movies: getMovies(),
    pageSize: 4,
    currentPage: 1
  };
به روز رسانی state نیز در متد handlePageChange که به ویژگی onPageChange متصل است، بر اساس page دریافتی از کامپوننت صفحه بندی، رخ می‌دهد:
  handlePageChange = page => {
    console.log("handlePageChange", page);
    this.setState({currentPage: page});
  };
بنابراین هرگاه که بر روی یک شماره صفحه در کامپوننت صفحه بندی کلیک می‌شود، رخ‌داد onPageChange متصل به تعریف المان Pagination درج شده‌ی در کامپوننت movies، روی داده و به همراه آن شماره صفحه‌ای، به متد رخ‌دادگران متصل به آن در کامپوننت movies که در اینجا handlePageChange نام دارد، ارسال می‌شود. در این متد state کامپوننت به روز شده و این امر سبب فراخوانی مجدد متد رندر می‌شود که در انتهای آن کامپوننت Pagination درج شده‌است. بنابراین به روز رسانی state، سبب رندر مجدد کامپوننت صفحه بندی با currentPage جدیدی که به آن رسیده‌است، خواهد شد.

پس از این تغییرات، به کامپوننت صفحه بندی مراجعه کرده و بر اساس currentPage دریافتی، کلاس active را به المان li اعمال می‌کنیم:
<li
  key={page}
  className={
    page === this.props.currentPage
    ? "page-item active"
    : "page-item"
  }
>
پس از اعمال این تغییرات، اکنون برنامه در مرورگر به صورت زیر به نظر می‌رسد:


در اولین بار نمایش برنامه، عدد 1 در حالت انتخاب شده قرار دارد؛ چون مقدار currentPage موجود در state، همان عدد 1 است. پس از آن با کلیک بر روی اعداد دیگر، با به روز رسانی state، مقدار currentPage تغییر کرده و کامپوننت صفحه بندی نسبت به آن واکنش نشان می‌دهد.


نمایش صفحه بندی شده‌ی اطلاعات

تا اینجا لیستی که نمایش داده می‌شود، حاوی تمام اطلاعات آرایه‌ی this.state.movies است و بر اساس شماره‌ی صفحه‌ی انتخابی، تغییر نمی‌کند. به همین جهت با استفاده از متد slice، تکه‌ای از آرایه‌ی movies را که بر اساس شماره صفحه‌ی انتخابی و تعداد ردیف‌ها در هر صفحه نیاز است نمایش داده شود، انتخاب کرده و بازگشت می‌دهیم:
  paginate() {
    const first = (this.state.currentPage - 1) * this.state.pageSize;
    const last = first + this.state.pageSize;
    return this.state.movies.slice(first, last);
  }
اکنون در متد رندر کامپوننت movies، بجای کار با کل آرایه‌ی this.state.movies، آرایه‌ی جدید slice شده را توسط متد paginate دریافت کرده:
  render() {
    const { length: count } = this.state.movies;

    if (count === 0) return <p>There are no movies in the database.</p>;

    const movies = this.paginate();
و سپس در همین متد رندر، فراخوانی this.state.movies.map را به movies.map تغییر می‌دهیم، تا تنها لیست مرتبط با هر صفحه‌ی انتخابی نمایش داده شود.

پس از ذخیره‌ی تغییرات و بارگذاری مجدد برنامه، اکنون می‌توان نمایش صفحه بندی شده‌ی اطلاعات را شاهد بود:



بررسی صحت نوع پارامترهای ارسالی به کامپوننت‌ها

تا اینجا فرض بر این است که مصرف کننده‌ی کامپوننت صفحه بندی، دقیقا همان ویژگی‌هایی را که ما طراحی کرده‌ایم، با همان نام‌ها و همان نوع‌ها را حتما به آن ارسال می‌کند. همچنین اگر افزونه‌ی eslint را هم در VSCode نصب کرده باشید، به همراه نصب وابستگی‌های زیر در خط فرمان:
> npm i -g typescript eslint tslint eslint-plugin-react-hooks jshint babel-eslint eslint-plugin-react eslint-plugin-mocha

 به ازای هر خاصیت props استفاده شده‌ی در کامپوننت صفحه بندی، اخطاری را مانند «'currentPage' is missing in props validation eslint(react/prop-types)» مشاهده خواهید کرد:



که عنوان می‌کند props validation این خاصیت استفاده شده، فراموش شده‌است.
در نگارش‌های قبلی React، امکانات بررسی نوع‌های ارسالی به کامپوننت‌ها، جزئی از بسته‌ی اصلی آن بود؛ اما از نگارش 15 به بعد، به بسته‌ی مستقلی منتقل شده‌است که باید به صورت جداگانه‌ای در ریشه‌ی پروژه نصب شود:
> npm i prop-types --save

البته اگر TypeScript را بر روی سیستم خود نصب کرده باشید، دیگر نیازی به نصب بسته‌ی npm فوق را ندارید و prop-types، جزئی از آن است که عموما در یک چنین مسیری قرار دارد و برای کار کردن با آن، تنها ذکر import مرتبط با PropType در ماژول‌های برنامه کافی بوده و برنامه در این حالت بدون مشکل کامپایل می‌شود:
 C:/Users/{username}/AppData/Local/Microsoft/TypeScript/3.6/node_modules/@types/prop-types/index

اکنون در ابتدای فایل کامپوننت صفحه بندی، تعریف زیر را اضافه می‌کنیم:
 import PropTypes from "prop-types";
سپس در انتهای این فایل، اعتبارسنجی props آن‌را تعریف خواهیم کرد:
Pagination.propTypes = {
  itemsCount: PropTypes.number.isRequired,
  pageSize: PropTypes.number.isRequired,
  currentPage: PropTypes.number.isRequired,
  onPageChange: PropTypes.func.isRequired
};

export default Pagination;
همانطور که مشاهده می‌کنید، در اینجا خاصیت جدید propTypes (دقیقا با همین نگارش؛ در غیراینصورت بررسی نوع‌ها کار نخواهد کرد)، به تعریف کلاس Pagination اضافه شده‌است (پس از تعریف کلاس کامپوننت به صورت مستقل اضافه می‌شود).
سپس مقدار این خاصیت جدید را به شیءای تنظیم می‌کنیم که نام خواص آن، دقیقا همان نام خواص و رویدادهای props استفاده شده‌ی در این کامپوننت است. در ادامه توسط PropTypes ای که در ابتدای ماژول import می‌شود، کار تعریف نوع این خواص و اجباری بودن آن‌ها را می‌توان مشخص کرد که برای مثال در اینجا سه خاصیت تعریف شده از نوع عددی و اجباری بوده و onPageChange، از نوع func است.
پس از اضافه کردن Pagination.propTypes و مقدار دهی آن، خطاهای eslint ای که در تصویر فوق مشاهده کردید، برطرف می‌شوند. همچنین اگر فراخوان کامپوننت Pagination مثلا بجای itemsCount یک رشته‌ی فرضی را وارد کند (برای آزمایش آن در کامپوننت movies، در تعریف المان Pagination، مقدار itemsCount را یک رشته وارد کنید)، چنین خطایی در مرورگر ظاهر خواهد شد که عنوان می‌کند itemsCount یک عدد را می‌پذیرد و نوع ورودی آن اشتباه است:

البته این خطا فقط در حالت development مشاهده می‌شود و در حالت توزیع برنامه، خیر.

بنابراین تعریف propTypes یک best practice ایجاد کامپوننت‌های React است که نه فقط بررسی نوع‌ها را فعال می‌کند، بلکه می‌تواند به عنوان مستندات آن نیز در جهت تعیین props مورد نیاز، همچنین نوع و اجباری بودن آن‌ها، اطلاعات کاملی را ارائه کند.


کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: sample-11.zip
مطالب
مدیریت رجیستری در #C

رجیستری یک پایگاه داده‌ی سیستمی است که برنامه‌ها، اجزای سیستم و اطلاعات پیکربندی در آن ذخیره و بازیابی می‌شود. داده‌های ذخیره شده در رجیستری مطابق با نسخه ویندوز فرق می‌کنند. نرم‌افزارها برای بازیابی، تغییر و پاک کردن رجیستری از API ‌های مختلفی استفاده می‌کنند. خوشبختانه .NET نیز امکانات لازم برای مدیریت رجیستری را فراهم کرده است.

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

ساختار رجیستری

رجیستری اطلاعات را در ساختار درختی نگاه می‌دارد. هر گره در درخت، یک کلید ( key ) نامیده می‌شود. هر کلید می‌تواند شامل چندین زیرکلید ( subkey ) و چندین مقدار ( value ) باشد. در برخی موارد، وجود یک کلید تمام اطلاعاتی است که نرم افزار بدان نیاز دارد و در برخی موارد، برنامه کلید را باز کرده و مقادیر مربوط به آن کلید را می‌خواند. یک کلید می‌تواند هر تعداد مقدار داشته باشد و مقادیر به هر شکلی می‌توانند باشند. هر کلید شامل یک یا چند کاراکتر است. نام کلیدها نمی‌توانند کاراکتر “\” را داشته باشند. نام هر زیرکلید یکتاست و وابسته به کلیدی است که در سلسله مراتب، بلافاصله بالای آن می‌آید. نام کلیدها باید انگلیسی باشند اما مقادیر را به هر زبانی می‌توان نوشت. در زیر یک نمونه از ساختار رجیستری را مشاهده می‌کنید که در نرم‌افزار registry editor به نمایش در آمده است.

هر کدام از درخت‌های زیر my computer یک کلید است. HKEY_LOCAL_MACHINE دارای زیرکلید‌هایی مثل HARDWARE ، SAM و SECURITY است. هر مقدار شامل یک اسم، نوع و داده‌های درون آن است. برای مثال MaxObjectNumber از مقادیر زیرکلید HKEY_LOCAL_MACHINE\HARDWARE\DEVICEMAP\VIDEO است. داده‌های درون هر مقدار می‌تواند از انواع باینری، رشته‌ای و عددی باشد؛ برای مثال MaxObjectNumber یک عدد ۳۲ بیتی است.

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

اطلاعات رجیستری در پیج فایل ( Page File ) ذخیره می‌شوند. پیج فایل ناحیه‌ای از حافظه RAM است که می‌تواند در زمانی که استفاده نمی‌شود به Hard منتقل شود. اندازه‌ی پیج فایل به وسیله‌ی مقدار PagedPoolSize در کلید HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Memory Management مطابق با جدول زیر تنظیم می‌گردد.

مقدار

توضیحات

0×00000000

سیستم یک مقدار بهینه را تعیین می‌کند

0x1–0x20000000

یک اندازه مشخص برحسب بایت که در این بازه باشد

0xFFFFFFFF

سیستم بیش‌ترین مقدار ممکن را تشخیص می‌دهد

کلیدهای از پیش تعریف شده

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

HKEY_CLASSES_ROOT

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

HKEY_CURRENT_USER

زیرشاخه‌های این کلید، تنظیمات مربوط به کاربر جاری را مشخص می‌کنند. این تنظیمات شامل متغیرهای محیطی، اطلاعات درباره‌ی برنامه‌ها، رنگ‌ها، پرینترها، ارتباطات شبکه و تنظیمات برنامه‌هاست. به طور مثال مایکروسافت اطلاعات مربوط به برنامه‌های خود را در کلید HKEY_CURRENT_USER\Software\Microsoft ذخیره می‌کند. هر کدام از برنامه‌ها یک زیرکلید در کلید مزبور را به خود اختصاص داده‌اند. این شاخه نیز نباید در یک سرویس یا برنامه‌ای که کاربران متعدد دارد، مورد استفاده قرار گیرد.

HKEY_LOCAL_MACHINE

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

HKEY_USERS

زیرشاخه‌های این کلید، پیکربندی کاربران پیش فرض، جدید، جاری سیستم و به طور کلی همه‌ی کاربران را مشخص می‌کند.

HKEY_CURRENT_CONFIG

زیرشاخه‌های این کلید، اطلاعاتی درباره وضعیت سخت‌افزار کامپیوتر در اختیار ما می‌گذارند. در واقع این کلید نام مستعاری برای کلید HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Hardware Profiles\Current است که در ویندوزهای قبل از ۳.۵۱ NT وجود نداشته است.

کندوهای رجیستری

یک کندو ( Hive ) یک گروه از کلیدها، زیرکلیدها و مقادیر در رجیستری است که یک مجموعه از فایل‌های پشتیبان را به همراه دارد. در هنگام بوت ویندوز، اطلاعات از این فایل‌ها استخراج می‌شوند. شما هم چنین می‌توانید با استفاده از Import در منوی فایل registry editor به صورت دستی این کار را انجام دهید. زمانی که ویندوز را خاموش می‌کنید، اطلاعات کندوها در فایل‌های پشتیبان نوشته می‌شوند. شما می‌توانید این کار را به طور دستی با Export در منوی فایل registry editor نیز انجام دهید.

فایل‌های پشتیبان همه کندوها به جز HKEY_CURRENT_USER در شاخه‌ی Windows Root\System32\config قرار دارند. فایل‌های پشتیبان HKEY_CURRENT_USER در شاخه‌ی System Root\Documents and Settings\Username قرار دارند. پسوند فایل‌ها در این شاخه‌ها، نوع داده‌هایی که در بر دارند را نشان می‌دهند. در جدول زیر برخی کندوها و فایل‌های پشتیبانشان آمده است.

کندوی رجیستری

فایل‌های پشتیبان

HKEY_CURRENT_CONFIG

System, System.alt, System.log, System.sav

HKEY_CURRENT_USER

Ntuser.dat, Ntuser.dat.log

HKEY_LOCAL_MACHINE\SAM

Sam, Sam.log, Sam.sav

HKEY_LOCAL_MACHINE\Security

Security, Security.log, Security.sav

HKEY_LOCAL_MACHINE\Software

Software, Software.log, Software.sav

HKEY_LOCAL_MACHINE\System

System, System.alt, System.log, System.sav

HKEY_USERS\.DEFAULT

Default, Default.log, Default.sav

هر زمان که یک کاربر به کامپیوتر وارد می‌شود، یک کندوی جدید با فایل‌های مجزا برای آن کاربر ساخته می‌شود که کندوی پروفایل کاربر نام دارد. یک کندوی کاربر، اطلاعاتی شامل تنظیمات برنامه‌های کاربر، تصویر زمینه، ارتباطات شبکه و پرینترها را در بر دارد. کندوهای پروفایل کاربر در کلید HKEY_USERS قرار دارند. مسیر فایل‌های پشتیبان این کندوها در کلید HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList\SID\ ProfileImagePath مشخص شده است. مقدار ProfileImagePath مسیر پروفایل کاربر و نام کاربر را مشخص می‌کند.

دسته بندی اطلاعات


قبل از قرار دادن اطلاعات در رجیستری باید آن‌ها را به دو دسته اطلاعات کامپیوتر و اطلاعات کاربر تقسیم کرد. با این تقسیم بندی، چندین کاربر می‌توانند از یک برنامه استفاده کنند و یا اطلاعات را بر روی شبکه قرار دهند. زمانی که یک برنامه نصب می‌شود، باید اطلاعات کامپیوتری خود را در شاخه فرضی
HKEY_LOCAL_MACHINE\Software\MyCompany\MyProduct\1.0 به گونه‌ای تعریف کند که نام شرکت، نام محصول و نسخه برنامه به خوبی مشخص گردند و هم چنین اطلاعات مربوط به کاربران را در شاخه فرضی HKEY_CURRENT_USER\Software\MyCompany\MyProduct\1.0 نگاه دارد.

باز کردن، ساختن و بستن کلیدها


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

کلاس‌های تعریف شده برای کار با رجیستری در فضانام Microsoft.Win32 قرار دارند. کلاس Microsoft.Win32.Registry مربوط به کلاس‌های از پیش تعریف شده و کلاس Microsoft.Win32.RegistryKey برای کار با رجیستری است. برای باز کردن یک کلید از متد RegistryKey.OpenSubKey استفاده می‌کنیم. به یاد داشته باشید که کلیدهای از پیش تعریف شده همیشه باز هستند و نیازی به باز کردن ندارند. برای ساختن یک کلید از متد RegistryKey.CreateSubKey استفاده می‌کنیم. دقت کنید زیرکلیدی که می‌خواهید بسازید، باید به یک کلید باز رجوع کند. برای خاتمه دسترسی به یک کلید، باید آن را ببندیم. برای بستن یک کلید از متد RegistryKey.Close استفاده می‌کنیم.

اکنون که با ساختار رجیستری و کلاس‌های مربوطه در .NET برای کار با رجیستری آشنا شدیم، به کدنویسی می‌پردازیم.

ساختن یک زیرکلید جدید

برای ساختن یک زیرکلید جدید از متد RegistryKey.CreateSubKey به صورت زیر استفاده می‌کنیم.

public RegistryKey CreateSubKey( string subkey);

subkey نام و مسیر کلیدی که می‌خواهید بسازید را مشخص می‌کند که معمولا به فرم فرضی key name\Company Name\Application Name\version است. این متد یک زیرکلید را برمی‌گرداند و در صورت بروز خطا مقدار null را برمی‌گرداند و یک exception را فرا می‌خواند. خطا به دلایلی چون عدم داشتن مجوز، وجود نداشتن مسیر درخواستی و غیره رخ می‌دهد. برای بررسی exception ‌ها می‌توانید از بلوک try-catch استفاده کنید.

RegistryKey MyReg = Registry .CurrentUser.CreateSubKey( "SOFTWARE\\SomeCompany\\SomeApp\\SomeVer" );
مثال فوق یک زیرکلید جدید در مسیر تعیین شده در شاخه‌ی HKEY_CURRENT_USER می‌سازد.

برای دست یابی به کلیدهای از پیش تعریف شده از کلاس Registry مطابق جدول زیر استفاده می‌کنیم.

فیلد

کلید

ClassesRoot

HKEY_CLASSES_ROOT

CurrentUser

HKEY_CURRENT_USER

LocalMachine

HKEY_LOCAL_MACHINE

Users

HKEY_USERS

CurrentConfig

HKEY_CURRENT_CONFIG

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

باز کردن زیرکلید موجود


برای باز کردن یک زیرکلید موجود از متد
RegistryKey.OpenSubKey به دو صورت استفاده می‌کنیم.
public RegistryKey OpenSubKey( string name);
public RegistryKey OpenSubKey( string name, bool writable);
صورت اول، کلید را در حالت فقط خواندنی باز می‌کند و صورت دوم، اگر writable ، true باشد کلید را در حالت ویرایش باز می‌کند و اگر false باشد کلید را در حالت فقط خواندنی باز می‌کند. در هر دو صورت name ، نام و مسیر زیرکلیدی که می‌خواهید باز کنید را مشخص می‌کند. اگر با خطا مواجه نشوید، متد زیرکلید را برمی‌گرداند، در غیر این صورت مقدار null را برمی‌گرداند.
RegistryKey MyReg = Registry .CurrentUser.OpenSubKey( "SOFTWARE\\SomeCompany\\SomeApp\\SomeVer" , true );

مثال فوق کلید مشخص شده را در شاخه‌ی HKEY_CURRENT_USER و در حالت ویرایش باز می‌کند.

خواندن اطلاعات از رجیستری

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

public object GetValue( string name);
public object GetValue( string name, object defaultValue);
صورت اول، اطلاعات درون مقداری با نام و مسیر name را برمی‌گرداند. اگر مقدار مذکور وجود نداشته باشد، مقدار null را برمی‌گرداند. درصورت دوم اگر مقدار خواسته شده وجود نداشته باشد، defaultValue را برمی‌گرداند. متد GetValue یک مقدار از نوع object را برمی‌گرداند در نتیجه شما برای استفاده، باید آن را به نوعی که می‌خواهید تبدیل کنید.

نوشتن اطلاعات در رجیستری


برای نوشتن اطلاعات در یک مقدار از متد
RegistryKey.SetValue به صورت زیر استفاده می‌کنیم.
public void SetValue( string name, object value);
رشته name ، نام مقداری که اطلاعات باید در آن ذخیره شود و value اطلاعاتی که باید در آن مقدار ذخیره شود را مشخص می‌کنند. چون value از نوع object است می‌توانید هر مقداری را به آن بدهید. Vallue به طور اتوماتیک به DWORD یا باینری یا رشته‌ای تبدیل می‌شود. البته یک پارامتر سومی نیز وجود دارد که از کلاس RegistryValueKind استفاده کرده و نوع اطلاعات را به طور دقیق مشخص می‌کند. برای ذخیره اطلاعات در مقدار پیش فرض ( Default ) کافی است که مقدار name را برابر string.Empty قرار دهید. هر کلید می‌تواند یک مقدار پیش فرض داشته باشد که باید نام آن مقدار را Default قرار دهید.

بستن یک کلید


زمانی که دیگر با کلید کاری ندارید و می‌خواهید تغییرات در رجیستری ثبت گردد باید فرآیندی به نام
flushing را انجام دهید. برای انجام این کار به راحتی از متد RegistryKey.Close استفاده کنید.
RegistryKey MyReg = Registry .CurrentUser.CreateSubKey( "SOFTWARE\\SomeCompany\\SomeApp\\SomeVer" );
int nSomeVal = ( int )MyReg.GetValue( "SomeVal" , 0);
MyReg.SetValue( "SomeValue" , nSomeVal + 1);
MyReg.Close();

پاک کردن یک کلید

برای پاک کردن یک زیرکلید از متد RegistryKey.DeleteSubKey به دو صورت استفاده می‌کنیم.

public void DeleteSubKey( string subkey);
public void DeleteSubKey( string subkey, bool throwOnMissingSubKey);
در صورت اول زیرکلید subkey را به شرطی حذف می‌کند که زیرکلید مذکور موجود باشد و زیرکلید دیگری در زیر آن نباشد. در صورت دوم نیز این شروط برقرار است با این تفاوت که اگر زیرکلید مذکور یافت نشود و throwOnMissingSubKey مقدار true داشته باشد یک exception فرا می‌خواند.

پاک کردن کل یک درخت


برای پاک کردن کل یک درخت با همه‌ی کلیدهای فرزند و مقادیر آن‌ها از متد
RegistryKey.DeleteSubKeyTree به دو صورت استفاده می‌کنیم.
public void DeleteSubKeyTree( string subkey);
public void DeleteSubKeyTree( string subkey, bool throwOnMissingSubKey);
دیگر با پارامترهای ارسالی در این متد آشنایی دارید و لازم به توضیح نیست.

پاک کردن یک مقدار


برای پاک کردن یک مقدار از متد
RegistryKey.DeleteValue به دو صورت زیر استفاده می‌کنیم.
public void DeleteValue( string name);
public void DeleteValue( string name, bool throwOnMissingValue);

لیست کردن زیرکلیدها

برای به دست آوردن یک لیست از همه زیرکلیدهای یک شیء RegistryKey از متد RegistryKey.GetSubKeyNames به صورت زیر استفاده می‌کنیم که یک آرایه رشته‌ای از نام زیرکلیدها را برمی‌گرداند.

public string [] GetSubKeyNames();
هم چنین می‌توانید برای شمردن تعداد زیرکلیدها از خصوصیت RegistryKey. SubKeyCount استفاده نمایید.

لیست کردن نام مقادیر


برای به دست آوردن یک لیست از همه مقادیری که در یک شیء
RegistryKey وجود دارند از متد RegistryKey.GetValueNames به صورت زیر استفاده می‌کنیم که یک آرایه رشته‌ای از نام مقادیر را برمی‌گرداند.
public string [] GetSubKeyNames();
هم چنین می‌توانید برای شمردن تعداد زیرکلیدها از خصوصیت RegistryKey.ValueCount استفاده نمایید.

ثبت تغییرات به صورت دستی


برای ثبت تغییرات یا به اصطلاح فلاش کردن به صورت دستی می‌توانید از متد
RegistryKey.Flush به صورت زیر استفاده نمایید. زمانی که از RegistryKey.Close استفاده می‌کنید فرآیند فلاش کردن به طور اتوماتیک انجام می‌گیرد.
public void Flush();

مطالب
آموزش Xamarin Forms - قسمت اول - نصب و راه اندازی اولین پروژه زمارین

در  مقاله قبلی ، درباره دلایل استفاده از Xamarin ، کمی صحبت کردیم. حال وقت آن رسیده که به سراغ نصب و راه اندازی اولین پروژه زمارین برویم.

اگر شما از Visual Studio 2015  به بعد استفاده کنید، موقع نصب قادر خواهید بود که با انتخاب گزینه Mobile Development و انتخاب Xamarin ، آن را نصب و همچنین تمامی فایل‌های مربوط به آن را دریافت کنید. در نظر داشته باشید که در طول این پروسه شما باید از ف.یل.تر شکن استفاده کنید، چرا که بعضی از فایل‌ها مانند Android Sdk بر روی سرور گوگل قرار دارد و گوگل آپی‌های ایران را برای برنامه نویسان فیلتر کرده است (البته اگر بسته‌ی کامل VS 2017 را دریافت کرده باشید، تمام این پیش نیازها، به همراه این بسته‌ی 21~ گیگابایتی موجود است و این مشکلات را نخواهید داشت). 

حال اگر در حال حاضر Visual Studio را بر روی سیستم خود نصب دارید و زمارین را نصب نکرده اید میتوانید از طریق خود سایت زمارین اقدام به دانلود آن بکنید. در نظر داشته باشید، وقتیکه فایل نصبی را از سایت دریافت میکنید، برنامه شروع به نصب خودکار زمارین و تمامی موارد مورد نیاز خود میکند که در این مورد هم به ف.یل.تر شکن نیاز دارید (منهای بسته‌ی کامل VS 2017 که پیشتر عنوان شد). همانطور که بالا گفته شد فایل هایی مانند Android Sdk را نمیتوان از آی پی ایران دانلود کرد (مگر اینکه از سایت‌های واسط استفاده کنید).

پس از موافقت با حقوق کپی رایت زمارین، سیستم شما را جستجو کرده و تمامی موارد مورد نیاز را که نیاز به نصب دارند، به شما نشان داده و نصب خواهد کرد.



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


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

 


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

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


در این مرحله میخواهیم اولین برنامه زمارین خود را شروع کنیم. همانطور که در مقاله قبلی  توضیح داده شد، شما میتوانید از xamarin forms و یا xamarin native استفاده کنید. Xamarin forms ساختاری برای code sharing میباشد که توسط شرکت زمارین توسعه داده شده‌است. به این ترتیب شما قادر خواهید بود که با استفاده از Xamarin Forms با نوشتن کمترین کد، نرم افزار خود را بر روی پلتفرم‌های پشتیبانی شده‌ی توسط زمارین اجرا کنید. استفاده از Xamarin Forms برای یک پروژه‌ی تجاری بسیار مناسب میباشد؛ اما در برخی موارد شما باید از Xamarin Native هم استفاده نمایید که در این صورت هم زمارین دست شما را نخواهد بست و قادر خواهید بود که به صورت ترکیبی از Xamarin Forms و Xamarin Native از آن استفاده نمایید.


حال به سراغ ساخت اولین پروژه زمارین برویم. در ابتدا برای ایجاد یک پروژه، از منوی File گزینه New و سپس گزینه Project را انتخاب کنید. در پنجره باز شده از سمت چپ گزینه C# را انتخاب و از زیر منوی Cross Platform گزینه Cross Platform App را انتخاب کنید.


پس از تایید با صفحه زیر مواجه خواهید شد.



در این قسمت همانطورکه مشاهده میکنید میتوانید از دو نوع  قالب استفاده کنید و همانطور که از نام آنها مشخص است، Master Detail دو پروژه خواهد ساخت که میتوانید از آن برای سناریو‌های خاص استفاده کنید. در قسمت پایین، UI Technology مشخص کننده‌ی شیوه پیاده سازی ظاهر برنامه است. زمارین فرم برای پیاده سازی ظاهر برنامه از Xaml استفاده میکند و شما میتوانید از طریق آن ظاهر تمامی پلت فرم‌ها را پیاده کنید. همچنین قادر خواهید بود بجای استفاده از Xaml، از زبان‌های مخصوص هر پلتفرم مانند Xml برای اندروید استفاده کرده و ظاهر نرم افزار خود را با استفاده از آن پیاده کنید.


زمارین فرم برای Code Sharing از دو روش استفاده میکند.

Shared Project

مزایا:

· شما به تمامی قسمت‌های یک پلتفرم دسترسی دارید.

· قادر خواهید بود کدهایی مخصوص به هر پلتفرم را بنویسید.

· میتوانید با استفاده از Conditional Compilation، بر اساس نوع پلتفرم، شروطی را اعمال نمایید.

معایب:

· شما با این روش نمیتوانید DLL ایی را تولید کنید و از آن در پروژه‌های دیگر استفاده نمایید.

· نوشتن Unit Testing نسبت به روش Portable پیچیده‌تر است.


Portable Class Library

مزایا:

· شما قادر خواهید بود از پروژه خود DLL تهیه کرده و در مابقی پروژه‌ها از آن استفاده کنید.

· نوشتن Unit Test به سادگی امکان پذیر است.

معایب:

· در این روش شما نمیتوانید از کد‌های مخصوص به هر پلتفرم ( Platform-Specific ) استفاده کنید (البته با استفاده از Dependency Injection امکان پذیر است). 

· فقط قادر خواهید بود که از زیر مجموعه .Net استفاده نمایید.


در ادامه پس از انتخاب Xamarin Forms و Shared Project، بر روی Ok کلیک کنید. همانطور که میبینید ویژوال استودیو شروع به ساخت پروژه‌های برنامه میکند. اگر تمامی Sdk ‌های مورد نیاز را نصب کرده باشید، ویژوال استدیو 5 پروژه را برای شما میسازد (در نظر داشته باشید که اگر SDK مربوط به Universal Windows Platform را هم نصب کرده باشید، پروژه‌ی مربوط به آن را نیز خواهید دید).



اولین پروژه، پروژه Shared است که کد‌های برنامه در این پروژه نوشته خواهد شد.

دومین پروژه همانطور که از پسوند آن مشخص است، پروژه مربوط به اندروید است. در نظر داشته باشید که پروژه‌هایی که مربوط به پلتفرم‌ها میباشند، برای نوشتن کدهای مخصوص به هر پلتفرم در دسترس میباشند و کدهای نوشته شده در این پروژه‌ها و پروژه Shared ترکیب شده و بعد کامپایل میشوند.

در زمارین برنامه‌ها از فایلی به نام App اجرا میشوند. این فایل علاوه بر دسترسی به Life Cycle اپلیکیشن، وظیفه‌ی دیگر آن مشخص کردن اولین صفحه‌ی برنامه نیز میباشد. این فایل درون پروژه Shared  قرار دارد.



همانطور که مشاهده میکنید پس از باز کردن فایل App، در متد سازنده آن صفحه اصلی، به صفحه‌ای به نام MainPage که یک فایل Xaml میباشد، تنظیم شده‌است. پس از باز کردن فایل MainPage با کدهای زیر مواجه میشویم. 

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:PreviewerTest"
             x:Class="PreviewerTest.MainPage">

<Label Text="Welcome to Xamarin Forms!" 
           VerticalOptions="Center" 
           HorizontalOptions="Center" />
</ContentPage>


که مشخص کننده یک لیبل با متن Welcome to Xamarin Forms! میباشد. پس از اجرای برنامه میتوانیم کد اجرا شده را مشاهده کنیم. 

مطالب
تغییرات متدهای بازگشت فایل‌ها به سمت کلاینت در ASP.NET Core
اگر خروجی return File را در اکشن متدهای ASP.NET Core همانند ASP.NET MVC 5.x مورد استفاده قرار دهید و در آن مسیرکامل فایل را برای بازگشت قید کرده باشید، پیام یافت نشدن فایل را دریافت خواهید کرد؛ هرچند این فایل بر روی سرور و در مسیر ذکر شده وجود خارجی دارد. علت آن‌را در تصویر ذیل می‌توانید مشاهده کنید:



روش‌های مختلف بازگشت فایل‌ها به سمت کلاینت در ASP.NET Core

در ASP.NET Core، نوع‌های کاملتری از Action Result‌های مرتبط با بازگشت فایل‌ها تدارک دیده شده‌اند که نحوه‌ی طراحی آن‌ها را در شکل فوق ملاحظه می‌کنید. در اینجا FileResult والد تمام حالت‌های بازگشت فایل است که شامل موارد ذیل می‌شود:
FileContentResult: از آن برای بازگشت آرایه‌ای از بایت‌ها استفاده می‌شود:
//returns the file content as an array of bytes
public FileContentResult FileContentActionResult()
{
   var file = System.IO.File.ReadAllBytes(@"C:\path\dir1\HomeController.cs");
   return File(file, "text/plain", "HomeController.cs");
}
زمانیکه Controller جاری از کلاس پایه Controller ارث بری می‌کند، متد File در این کلاس پایه قرار دارد. به همین جهت مانند مثال فوق به سادگی می‌توان به آن، بدون ذکر new دسترسی یافت. روش دیگر دسترسی به FileContentResult به صورت ذیل است که معادل قطعه کد فوق می‌باشد:
public IActionResult TestFileContentActionResult()
{
   var file = System.IO.File.ReadAllBytes(@"C:\path\dir1\HomeController.cs");
   return new FileContentResult(file, "text/plain") { FileDownloadName = "HomeController.cs" };
}

FileStreamResult: این Action Result قابلیت Streaming بازگشت فایل‌ها را مهیا می‌کند:
//return the file as a stream
public FileStreamResult FileStreamActionResult()
{
   //var file = System.IO.File.ReadAllBytes(@"C:\path\dir1\HomeController.cs");
   //var stream = new MemoryStream(file, writable:true);

   var fileStream = new FileStream(@"C:\path\dir1\HomeController.cs", FileMode.Open, FileAccess.Read);
   return File(fileStream, "text/plain", "HomeController.cs");
}
در اینجا برای مثال می‌توان یک MemoryStream و یا یک FileStream را به سمت کاربر ارسال کرد. این روش نسبت به خواندن فایل‌ها در آرایه‌ای از بایت‌ها و سپس ارسال یکجای آن، بهینه‌تر است و حافظه‌ی کمتری را مصرف می‌کند.
اگر خواستیم مستقیما با FileStreamResult کار کنیم، روش کار به صورت ذیل است:
public IActionResult TestFileStreamActionResult()
{
   //var file = System.IO.File.ReadAllBytes(@"C:\path\dir1\HomeController.cs");
   //var stream = new MemoryStream(file, writable:true);
   var fileStream = new FileStream(@"C:\path\dir1\HomeController.cs", FileMode.Open, FileAccess.Read);
   return new FileStreamResult(fileStream, "text/plain") { FileDownloadName = "HomeController.cs" };
}

VirtualFileResult: در این مورد آدرسی را که ارائه می‌دهید، باید به فایلی درون پوشه‌ی wwwroot اشاره کند (علت اصلی بروز مشکلی که در مقدمه‌ی بحث عنوان شد). در اینجا آدرس کامل فایل مدنظر نیست.
//returns a file specified with a virtual path
public VirtualFileResult VirtualFileActionResult()
{
   return File("/css/site.css", "text/plain", "site.css");
}
و یا معادل همین قطعه کد با استفاده از VirtualFileResult اصلی به صورت ذیل است:
public IActionResult TestVirtualFileActionResult()
{
   return new VirtualFileResult("/css/site.css", "text/plain") { FileDownloadName = "site.css" };
}

PhysicalFileResult: اگر قصد دارید آدرس کامل فایلی را مشخص کنید (بجای مسیر نسبی آن که از wwwroot شروع می‌شود؛ مانند حالت قبل)، اینبار باید از متد PhysicalFile استفاده کرد:
//returns the specified file on disk, that is it's physical address
public PhysicalFileResult PhysicalFileActionResult()
{
   return PhysicalFile(@"C:\path\dir1\HomeController.cs", "text/plain", "HomeController.cs");
}
این قطعه کد نیز بر اساس استفاده‌ی مستقیم از PhysicalFileResult شکل زیر را می‌تواند پیدا کند:
public IActionResult TestPhysicalFileActionResult()
{
   return new PhysicalFileResult(@"C:\path\dir1\HomeController.cs", "text/plain")
   {
      FileDownloadName = "HomeController.cs"
   };
}

در این متدها و کلاس‌ها، اگر FileDownloadName حاوی حروف اسکی نباشد، به صورت خودکار encoding از نوع RFC5987 بر روی آن اعمال خواهد شد.
مطالب دوره‌ها
نصب و راه اندازی مقدماتی Full Text Search
با استفاده از امکانات ابتدایی T-SQL مانند like می‌توان جستجوهایی را برای یافتن موارد مشابه با عبارتی خاص انجام داد، اما این جستجوها بسیار هزینه‌بر و کند هستند. در SQL Server برای مدیریت جستجوهای سریع و پیشرفته بر روی متون، افزونه‌های توکاری مانند Full text search، Semantic search، Term extraction و Term lookup تدارک دیده شده‌اند. Semantic search از نگارش 2012 آن افزوده شده‌است و مابقی در نگارش‌های پیشین آن نیز وجود داشته‌اند.


بررسی‌های مقدماتی

ابتدای کار نیاز است بررسی کنیم آیا افزونه‌ی Full Text Search، به همراه SQL Server نصب شده‌است یا خیر. برای این منظور کوئری ذیل را اجرا کنید:
 select SERVERPROPERTY('IsFullTextInstalled');
اگر خروجی این کوئری عدد 1 بود، یعنی FTS نصب شده‌است؛ اگر خیر، مجددا برنامه‌ی نصاب SQL Server را اجرا کرده و زمانیکه به قسمت feature selection رسیدید، گزینه‌ی ذیل را باید انتخاب کنید:
 instance features -> database engine services -> Full Text



راه اندازی سرویس Full Text Search

پیش از ادامه‌ی بحث، به کنسول سرویس‌های ویندوز مراجعه کرده و مطمئن شوید که سرویس SQL Full-text Filter Daemon Launcher MSSQLSERVER نیز در حال اجرا است. در غیراینصورت با خطای ذیل مواجه خواهید شد:
 SQL Server encountered error 0x80070422 while communicating with full-text filter daemon host (FDHost) process.
اگر این سرویس در حال اجرا است و باز هم خطای فوق ظاهر شد، مجددا به کنسول سرویس‌های ویندوز مراجعه کرد، در برگه‌ی  خواص سرویس SQL Full-text Filter Daemon Launcher MSSQLSERVER، گزینه‌ی logon را یافته و آن‌را به local system account تغییر دهید. سپس سرویس را ری استارت کنید. پس از آن نیاز است دستور ذیل را نیز اجرا کنید:
 sp_fulltext_service 'restart_all_fdhosts'



چه نوع داده‌هایی را می‌توان توسط FTS ایندکس کرد؟

با استفاده از امکانات FTS می‌توان کلیه ستون‌هایی را که دارای نوع‌های ذیل باشند، ایندکس کرد:
 char, nchar, varchar, nvarchar, text, ntext, image, xml, varbinary(max)
البته نوع باینری را که ملاحظه می‌کنید مانند image و varbinary max، نیاز به یک ستون اضافی، برای ذخیره سازی پسوند فایل‌های ذخیره شده در آن‌ها مانند docx، pdf ، xlsx و امثال آن نیز دارند. برای مثال ابتدا یک فایل word را در ستونی از نوع varbinary max ذخیره می‌کنید و سپس نیاز است در همانجا در ستونی دیگر، پسوند این فایل را نیز قید نمائید.
همچنین FTS برای پردازش این فایل‌های باینری و ایندکس کردن اطلاعات آن‌ها، نیاز به افزونه‌هایی به نام IFilters دارد. کار این فیلترها استخراج متن بدون فرمت، از فایل‌های باینری مرتبط و ارائه‌ی آن‌ها به موتور FTS می‌باشد.


نصب فیلترهای مخصوص FTS آفیس

اگر علاقمند هستید که بدانید در حال حاضر چه تعداد فیلترهای FTS بر روی سیستم شما نصب شده‌است، کوئری ذیل را اجرا نمائید:
 exec sys.sp_help_fulltext_system_components 'filter';
برای نمونه اگر آفیس بر روی سیستم شما نصب باشد، در حاصل کوئری فوق، فیلتری مانند offfilt.dll را نیز مشاهده خواهید کرد که به پسوندهایی مانند doc، ppt، xls و امثال آن انتساب داده شده‌است.
فیلترهای آفیس را جداگانه نیز می‌توانید دریافت و نصب کنید (بدون نیاز به نصب کامل آفیس بر روی سرور):
این فیلترها تا نگارش 2013 آفیس را نیز پشتیبانی می‌کنند و آگر آپدیت ویندوز نیز روشن باشد، سرویس پک 2 آن را نیز دریافت خواهید کرد.

پس از اینکه فیلترها را نصب کردید، باید آن‌ها را در وهله‌ی جاری SQL Server ثبت کرد:
 exec sys.sp_fulltext_service 'load_os_resources', 1;
EXEC sp_fulltext_service 'update_languages';
EXEC sp_fulltext_service 'restart_all_fdhosts';
اکنون اگر مجددا کوئری sys.sp_help_fulltext_system_components یاد شده را اجرا کنید. خروجی آن حدودا 50 سطر خواهد بود؛ این اطلاعات را از کوئری ذیل نیز می‌توان بدست آورد:
 select * from sys.fulltext_document_types;
اگر پس از نصب و همچنین ثبت و معرفی فیلترهای آفیس 2010 به بعد، هنوز تعداد 50 ردیف را ملاحظه می‌کنید (اکنون باید بیشتر از 160 مورد باشند)، نیاز است یکبار وهله‌ی جاری SQL Server را ری استارت کنید. برای اینکار در management studio بر روی وهله‌ی جاری، کلیک راست کرده و گزینه‌ی Restart را انتخاب کنید.

فیلترهای فوق علاوه بر اینکه امکان FTS را بر روی کلیه فایل‌های مجموعه آفیس میسر می‌کنند، امکان جستجو FTS را بر روی خواص ویژه اضافی آن‌ها، مانند نام نویسنده، واژه‌های کلیدی، تاریخ ایجاد و امثال آن نیز به همراه دارند.


FTS چگونه کار می‌کند؟

زبان‌های پشتیبانی شده توسط FTS را توسط کوئری ذیل می‌توانید مشاهده کنید:
 select lcid, name from sys.fulltext_languages order by name;
کار FTS با word-breakers و stemmers شروع می‌شود. این‌ها کار آنالیز متن را بر اساس زبانی مشخص انجام می‌دهند. اگر زبان مدنظر توسط FTS پشتیبانی نمی‌شود، می‌توان از زبان انگلیسی و یا همچنین Neutral نیز برای آنالیز آن استفاده کرد. زبان Neutral جزو خروجی کوئری فوق با شماره آی دی صفر است.
word-breakers تک تک کلمات را (که به آن‌ها token نیز گفته می‌شود) تشخیص داده و سپس FTS آن‌ها را با فرمتی فشرده شده، درون ایندکس‌های مخصوص خود ذخیره می‌کند.کار stemmers تولید حالات inflectional (صرفی) یک کلمه بر اساس دستور زبانی مشخص است.
اهمیت آنالیز inflectional، در اینجا است که برای مثال اگر در متنی واژه‌ی jumps وجود داشت و کاربر در حین جستجو، jumped را وارد کرد، FTS بر اساس دستور زبان مورد استفاده، پیشتر، حالات مختلف صرفی jump را ذخیره کرده‌است و امکان انجام یک چنین کوئری پیشرفته‌ای را پیدا می‌کند.


نصب و فعال سازی Semantic Language Database

کار TFS تنها به خرد کردن واژه‌ها و آنالیز صرفی آن‌ها خلاصه نمی‌شود. در مرحله‌ی بعد، انجام Statistical semantic search میسر می‌شود. در اینجا SQL Server بر اساس آمار واژه‌های کلیدی استخراج شده، توانایی یافتن متونی مشابه و یا مرتبط را پیدا می‌کند. Semantic Search جزو تازه‌های SQL Server 2012 است.

برای اینکار نیاز است بانک اطلاعاتی Semantic language statistics نیز نصب شود. برای اطمینان از نصب بودن آن، کوئری ذیل را اجرا کنید:
 select * from sys.fulltext_semantic_language_statistics_database;
اگر حاصل آن خالی بود، نیاز است مستقلا نصب شود. این بانک اطلاعاتی ویژه را در یکی از دو مسیر ذیل
 x64\Setup\SemanticLanguageDatabase.msi
x86\Setup\SemanticLanguageDatabase.msi
در DVD یا فایل ISO نصب SQL Server 2012 می‌توانید پیدا کنید. فایل نصاب msi آن‌را اجرا کنید، دو فایل mdf و ldf را در مسیری که مشخص می‌کنید، کپی می‌کند.
پس از آن نیاز است این بانک اطلاعاتی را Attach و همچنین ثبت کرد:
CREATE DATABASE semanticsdb
    ON ( FILENAME = 'D:\SQL_Data\SemanticLanguageDatabase\semanticsdb.mdf' )
    LOG ON ( FILENAME = 'D:\SQL_Data\SemanticLanguageDatabase\semanticsdb_log.ldf' )
    FOR ATTACH
GO

EXEC sp_fulltext_semantic_register_language_statistics_db @dbname = N'semanticsdb'
GO
زمانیکه این بانک اطلاعاتی کپی می‌شود، دسترسی Write کاربر وارد شده به سیستم را در برگه‌ی Security فایل‌های mdf و ldf آن ندارد. به همین جهت ممکن است در حین Attach، پیام عدم دسترسی را دریافت کنید که با مراجعه به خواص فایل‌ها و تنظیم دسترسی Write کاربر جاری، مشکل برطرف می‌شود.
پس از مراحل فوق، اگر مجددا کوئری یاد شده بر روی sys.fulltext_semantic_language_statistics_database را اجرا کنید، یک سطر خروجی خواهد داشت.
مطالب
بهبود صفحه‌‌ی بارگذاری اولیه در Blazor WASM
در بار اول اجرای برنامه‌های Blazor WASM، کار دریافت و کش شدن اسمبلی‌های NET Runtime. و برنامه انجام می‌شوند:


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


افزودن یک progress-bar به صفحه‌ی آغازین برنامه‌های Blazor WASM

Blazor امکان دسترسی به چرخه‌ی حیات ابتدایی آن‌را نیز میسر کرده‌است. برای اینکار ابتدا باید به آن گفت که دریافت خودکار تمام موارد مورد نیاز را انجام نده و ما اینکار را خودمان انجام خواهیم داد:
<script src="_framework/blazor.webassembly.js" autostart="false"></script>
برای اینکار باید به تگ اسکریپتی که به blazor.webassembly.js اشاره می‌کند، ویژگی autostart را با مقدار false، افزود. از این پس باید خودمان کار آغاز Blazor را انجام دهیم که در طی دو مرحله، انجام خواهد شد:
الف) تغییر متن Loading پیش‌فرض جهت نمایش یک progress-bar
<body>
<div id="app">
    <div class="d-flex flex-column min-vh-100">
        <div class="d-flex vh-100">
<div class="d-flex w-100 justify-content-center align-self-center">
<div class="d-flex flex-column w-25">
<div>Loading <label id="progressbarLabel"></label></div>
<div class="progress mt-2" style="height: 2em;">
  <div id="progressbar" class="progress-bar progress-bar-striped"></div>  
</div>
</div>
</div>
        </div>
    </div>
</div>
به فایل index.html برنامه مراجعه کرده و بجای loading پیش‌فرض آن، یک چنین طرحی را قرار می‌دهیم که به همراه یک label و یک progressbar در وسط صفحه است:


ب) سپس فایل جدید js/blazorLoader.js را با محتوای زیر اضافه می‌کنیم. در ابتدای این فایل به المان‌های progressbar و progressbarLabel طرح فوق اشاره می‌شود:
(function () {
  let resourceIndex = 0;
  const fetchResponsePromises = [];
  const progressbar = document.getElementById("progressbar");
  const progressbarLabel = document.getElementById("progressbarLabel");
  const loadStart = new Date().getTime();

  if (!isAutostartDisabled()) {
    console.warn(
      "`blazor.webassembly.js` script tag doesn`t have the `autostart=false` attribute."
    );
    return;
  }

  Blazor.start({
    loadBootResource: function (type, filename, defaultUri, integrity) {
      if (type === "dotnetjs") {
        progressbarLabel.innerText = filename;
        return defaultUri;
      }

      const responsePromise = fetch(defaultUri, {
        cache: "no-cache",
        integrity: integrity,
      });
      fetchResponsePromises.push(responsePromise);
      responsePromise.then((response) => {
        if (!progressbar) {
          console.warn("Couldn't find the progressbar element on the page.");
          return;
        }

        if (!progressbarLabel) {
          console.warn(
            "Couldn't find the progressbarLabel element on the page."
          );
          return;
        }

        resourceIndex++;
        const totalResourceCount = fetchResponsePromises.length;
        const percentLoaded = Math.round(
          100 * (resourceIndex / totalResourceCount)
        );
        progressbar.style.width = `${percentLoaded}%`;
        progressbar.innerText = `${percentLoaded} % [${resourceIndex}/${totalResourceCount}]`;
        progressbarLabel.innerText = filename;
        if (percentLoaded >= 100) {
          var span = new Date().getTime() - loadStart;
          console.log(`All done in ${span} ms.`);
        }
      });

      return responsePromise;
    },
  });

  function isAutostartDisabled() {
    var wasmScript = document.querySelector(
      'script[src="_framework/blazor.webassembly.js"]'
    );
    if (!wasmScript) {
      return false;
    }

    var autostart = wasmScript.attributes["autostart"];
    return autostart && autostart.value === "false";
  }
})();
این فایل باید پس از تعریف مدخل blazor.webassembly.js به فایل index.html اضافه شود:
<script src="_framework/blazor.webassembly.js" autostart="false"></script>
<script src="js/blazorLoader.js"></script>
</body>
توضیحات:
- محتوای blazorLoader.js، به صورت خود اجرا شونده تهیه شده‌است.
- متد Blazor.start، کار آغاز دستی Blazor WASM و دریافت فایل‌های مورد نیاز آن‌را انجام می‌دهد.
- خاصیت loadBootResource آن، به تابعی اشاره می‌کند که پیشنیازهای اجرایی Blazor WASM را دریافت می‌کند.
- در متد سفارشی loadBootResource که تهیه کرده‌ایم، responsePromise‌ها را شمارش کرده و بر اساس تعداد کلی آن‌ها و مواردی که دریافت آن‌ها به پایان رسیده‌است، یک progress-bar را تشکیل و نمایش می‌دهیم.
- تابع isAutostartDisabled، بررسی می‌کند که آیا ویژگی autostart مساوی false، به تگ اسکریپت blazor.webassembly.js اضافه شده‌است یا خیر؟


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