نظرات مطالب
ASP.NET MVC #19
می‌شود از VaryByParam استفاده کرد (مثال دوم فوق)
[OutputCache(Duration = 60, VaryByParam = "userId")]
public ActionResult Index(string userId)
البته کش کردن صفحاتی که نیاز به اعتبارسنجی دارند اشتباه است (نکته مهم انتهای بحث).
- از کلاس CacheManager مطرح شده در انتهای بحث استفاده کنید. کلید آن‌را مساوی یک عبارت منحصر به فرد مانند شماره کاربری به علاوه نام صفحه قرار دهید. مقدار آن را حاصل عملیات سنگینی که مد نظر دارید.
مطالب
آشنایی با ساختار IIS قسمت اول
در مقاله قبل در مورد نحوه ذخیره سازی در حافظه نوشتیم و به user mode و kernel mode اشاراتی کردیم که می‌توانید به آن رجوع کنید.
در این سری مقالات قصد داریم به بررسی اجزا و روند کاری موجود در IIS بپردازیم که چگونه IIS کار می‌کند و شامل چه بخش هایی می‌شود. مطمئنا آشنایی با این بخش‌ها در روند شناسایی رفتارهای وب اپلیکیشن‌ها و واکنش‌های سرور، کمک زیادی به ما خواهد کرد. در اینجا نسخه IIS7 را به عنوان مرجع در نظر گرفته‌ایم.
وب سرور IIS در عبارت مخفف Internet information services به معنی سرویس‌های اطلاعاتی اینترنت می‌باشد. IIS شامل کامپوننت‌های زیادی است که هر کدام ازآن‌ها کار خاصی را انجام میدهند؛ برای مثال گوش دادن به درخواست‌های ارسال شده به سرور، مدیریت فرآیندها Process و خواندن فایل‌های پیکربندی Configuration؛ این اجزا شامل protocol listener ،Http.sys و WSA و .. می‌شوند.
Protocol Listeners
این پروتکل‌ها به درخواست‌های رسیده گوش کرده و آن‌ها را مورد پردازش قرار می‌دهند و پاسخی را به درخواست کننده، ارسال می‌کنند. هر listener بر اساس نوع پروتکل متفاوت هست. به عنوان مثال کلاینتی، درخواست صفحه‌ای را می‌کند و http listener که به آن Http.sys می‌گویند به آن پاسخ می‌دهد. به طور پیش فرض http.sys به درخواست‌های http و https گوش فرا می‌دهد، این کامپوننت از IIS6 اضافه شده است ولی در نسخه 7 از SSL نیز پشتیبانی می‌کند.
Http.sys یا Hypertext transfer protocol stack
کار این واحد در سه مرحله دریافت درخواست، ارسال آن به واحد پردازش IIS و ارسال پاسخ به کلاینت است؛ قبل از نسخه 6 از Winsock یا windows socket api  که یک کامپوننت user-mod بود استفاده می‌شد ولی Http.sys یک کامپوننت Kernel-mod هست.

Http.sys مزایای زیر را به همراه دارد:

  • صف درخواست مد کرنل: به خاطر اینکه کرنل مستقیما درخواست‌ها را به پروسه‌های مربوطه میفرستد و اگر پروسه موجود نباشد، درخواست را در صف گذاشته تا بعدا پروسه مورد نظر آن را از صف بیرون بکشد.
  • برای درخواست‌ها یک پیش پردازش و همچنین اعمال فیلترهای امنیتی اعمال می‌گردد. 
  • عملیات کش کردن تماما در محیط کرنل مد صورت می‌گیرد؛ بدون اینکه به حالت یوزرمد سوییچ کند. مد کرنل دسترسی بسیار راحت و مستقیمی را برای استفاده از منابع دارد و لازم نیست مانند مد کاربر به لایه‌های زیرین، درخواست کاری را بدهد؛ چرا که خود مستقیما وارد عمل می‌شود و برداشته شدن واسط در سر راه، موجب افزایش عمل caching می‌شود. همچنین دسترسی به کش باعث می‌شود که مستقیما پاسخ از کش به کاربر برسد و توابع پردازشی در حافظه بارگذاری نشوند. البته این کش کردن محدودیت هایی را هم به همراه دارد:
    1. کش کرنل به صورت پیش فرض بر روی صفحات ایستا فعال شده است؛ نه برای صفحاتی با محتوای پویا که البته این مورد قابل تغییر است که نحوه این تغییر را پایینتر توضیح خواهیم داد.
    2. اگر آدرس درخواستی شامل کوئری باشد صفحه کش نخواهد شد:    http://www.site.info/postarchive.htm?id=25 
    3. برای پاسخ ازمکانیزم‌های فشرده سازی پویا استفاده شده باشد مثل gzip کش نخواهد شد
    4. صفحه درخواست شده صفحه اصلی سایت باشد کش نخواهد شد :   http://www.dotnettip.info ولی اگر درخواست بدین صورت باشه http://www.domain.com/default.htm  کش خواهد کرد.
    5. درخواست به صورت ناشناس anonymous نباشد  و نیاز به authentication داشته باشد کش نخواهد شد (یعنی در هدر شامل گزینه authorization می‌باشد).
    6. درخواست باید از نوع نسخه http1 به بعد باشد.
    7. اگر درخواست شامل Entity-body باشد کش نخواهد کرد.
    8. درخواست شامل If-Range/Range header باشد کش نمی‌شود.
    9. کل حجم response بییشتر از اندازه تعیین شده باشد کش نخواهد گردید، این اندازه در کلید ریجستری UriMaxUriBytes قرار دارد. اطلاعات بیشتر
    10. اندازه هدر بیشتر از اندازه تعیین شده باشد که عموما اندازه تعیین شده یک کیلو بایت است.
    11. کش پر باشد، کش انجام نخواهد گرفت.
    برای فعال سازی کش کرنل راهنمای زیر را دنبال کنید:
    گزینه output cache را در IIS، فعال کنید و سپس گزینه Add را بزنید. کادر add cache rule که باز شود، از شما میخواهد یکی از دو نوع کش مد کاربر و مد کرنل را انتخاب کنید و  مشخص کنید چه نوع فایل‌هایی (مثلا aspx) از این قوانین پیروری کنند و مکانیزم کش کردن به سه روش جلوگیری از کش کردن، کش زمان دار و کش بر اساس آخرین تغییر فایل انجام گردد.


    برای تعیین مقدار سایز کش response که در بالا اشاره کردیم می‌توانید در همان پنجره، گزینه edit feature settings را انتخاب کنید.


    این قسمت از مطلب که به نقل از مقاله  آقای Karol Jarkovsky در این آدرس است یک سری تست هایی با نرم افزار(Web Capacity Analysis Tool (WCAT  گرفته است که به نتایج زیر دست پیدا کرده است:
    Kernel Cache Disabled    4 clients/160 threads/30 sec      257 req/sec
    Kernel Cache Enabled     4 clients/160 threads/30 sec      553 req/sec 
    همانطور که می‌بینید نتیجه فعال سازی کش کرنل پاسخ به بیش از دو برابر درخواست در حالت غیرفعال آن است که یک عدد فوق العاده به حساب میاد.
    برای اینکه خودتان هم تست کرده باشید در این آدرس  برنامه را دانلود کنید و به دنبال فایل request.cfg بگردید و از صحت پارامترهای server و url اطمینان پیدا کنید. در گام بعدی 5 پنجره خط فرمان باز کرده و در یکی از آن‌ها دستور netsh http show cachestate را بنویسید تا تمامی وروردی‌های entry که در کش کرنل ذخیره شده اند لیست شوند. البته در اولین تست کش را غیرفعال کنید و به این ترتیب نباید چیزی نمایش داده شود. در همان پنجره فرمان wcctl –a localhost –c config.cfg –s request.cfg  را زده تا کنترلر برنامه در وضعیت listening قرار بگیرد. در 4 پنجره دیگر فرمان wcclient localhost از شاخه کلاینت را نوشته تا تست آغاز شود. بعد از انجام تست به شاخه نصب کنترلر WCAT رفته و فایل log را بخوانید و اگر دوباره دستور نمایش کش کرنل را بزنید باید خالی باشد. حالا کش را فعال کنید و دوباره عملیات تست را از سر بگیرید و اگر دستور netsh را ارسال کنید باید کش کرنل دارای ورودی باشد.
    برای تغییرات در سطح http.sys می‌توانید از ریجستری کمک بگیرید. در اینجا تعداد زیادی از تنظیمات ذخیره شده در ریجستری برای http.sys لیست شده است.
    بازخوردهای پروژه‌ها
    مشکل در رندر فوتر گروه ها
    سلام، خسته نباشید.
    بنده گزارشی دارم که گروه بندی بر اساس حساب‌های انتخاب شده انجام می‌دهم،
    مشکل کار جمع کل نهایی است هنگامی که گزارش رندر شده است، خطی بر روی آخرین سطر بوجود می‌آید که نمی‌دانم از چیست.
    این کد گزارش : 
    new PdfReport().DocumentPreferences(doc =>
                {
                    doc.RunDirection(PdfRunDirection.RightToLeft);
                    doc.Orientation(PageOrientation.Portrait);
                    doc.PageSize(PdfPageSize.A4);
                    doc.DocumentMetadata(new DocumentMetadata { Author = _company, Application = "نرم افزار ", Keywords = "حساب تفصیلی " + _accountName, Subject = "حساب تفصیلی " + _accountName, Title = "حساب تفصیلی " + _accountName });
                })
                .DefaultFonts(fonts =>
                {
                    fonts.Path(Path.Combine(Environment.CurrentDirectory, @"fonts\irsans.ttf"),
                                Path.Combine(Environment.CurrentDirectory, @"fonts\verdana.ttf"));
                    fonts.Size(8);
                })
                .PagesFooter(footer =>
                {
                    footer.DefaultFooter(string.Concat("کاربر : ", _userService.CurrentUser != null ? _userService.CurrentUser.UserName : string.Empty,
                                                   " | ", "تاریخ تهیه گزارش : ", DateTimeHelper.ToPersianShortDateString(DateTime.Now, true, true)));
                })
                .PagesHeader(header =>
                {
                    header.CustomHeader(new MasterDetailBookReportsHeader
                    {
                        PdfRptFont = header.PdfFont,
                        Company = _company,
                        FinancialYear = _financialPeriod.GetCurrentFinancialPeriodTitle(),
                        ReportType = ReportType.SpecialDetailBook,
                        ReportTitle = "دفتر تفصیلی"
                    });
    
                })
                .MainTableTemplate(template =>
                {
                    template.CustomTemplate(new GrayTemplate());
                })
                .MainTablePreferences(table =>
                {
                    table.ColumnsWidthsType(TableColumnWidthType.Relative);
                    table.GroupsPreferences(new GroupsPreferences
                    {
                        GroupType = GroupType.HideGroupingColumns,
                        RepeatHeaderRowPerGroup = true,
                        ShowOneGroupPerPage = true,
                        SpacingBeforeAllGroupsSummary = 5f,
                        NewGroupAvailableSpacingThreshold = 5f
                    });
                })
                .MainTableDataSource(dataSource =>
                {
                    dataSource.AnonymousTypeList(_rows);
                })
                .MainTableColumns(columns =>
                {
                    columns.AddColumn(column =>
                    {
                        column.PropertyName<VoucherRowPrintViewModel>(x => x.Title);
                        column.CellsHorizontalAlignment(PdfRpt.Core.Contracts.HorizontalAlignment.Center);
                        column.IsVisible(true);
                        column.IsRowNumber(true);
                        column.Order(0);
                        column.Width(0.7f);
                        column.Group(true,
                           (val1, val2) =>
                           {
                               return val1.ToString() == val2.ToString();
                           });
                    });
                    columns.AddColumn(column =>
                    {
                        column.PropertyName("rowNumber");
                        column.CellsHorizontalAlignment(PdfRpt.Core.Contracts.HorizontalAlignment.Center);
                        column.IsVisible(true);
                        column.IsRowNumber(true);
                        column.Order(0);
                        column.Width(0.7f);
                        column.HeaderCell("ردیف");
                    });
                    columns.AddColumn(column =>
                    {
                        column.PropertyName<VoucherRowPrintViewModel>(x => x.VoucherNumber);
                        column.CellsHorizontalAlignment(PdfRpt.Core.Contracts.HorizontalAlignment.Center);
                        column.IsVisible(true);
                        column.Order(0);
                        column.Width(1);
                        column.HeaderCell("سند");
                    });
                    columns.AddColumn(column =>
                    {
                        column.PropertyName<VoucherRowPrintViewModel>(x => x.VoucherDate);
                        column.CellsHorizontalAlignment(PdfRpt.Core.Contracts.HorizontalAlignment.Center);
                        column.IsVisible(true);
                        column.Order(1);
                        column.Width(1.5f);
                        column.ColumnItemsTemplate(template =>
                        {
                            template.TextBlock();
                            template.DisplayFormatFormula(obj => DateTimeHelper.ToPersianShortDateString((DateTime)obj));
                        });
                        column.HeaderCell("تاریخ");
                    });
                    columns.AddColumn(column =>
                    {
                        column.PropertyName<VoucherRowPrintViewModel>(x => x.Description);
                        column.CellsHorizontalAlignment(PdfRpt.Core.Contracts.HorizontalAlignment.Left);
                        column.IsVisible(true);
                        column.Order(0);
                        column.Width(4);
                        column.HeaderCell("شرح");
                    });
                    columns.AddColumn(column =>
                    {
                        column.PropertyName<VoucherRowPrintViewModel>(x => x.Debtor);
                        column.CellsHorizontalAlignment(PdfRpt.Core.Contracts.HorizontalAlignment.Right);
                        column.IsVisible(true);
                        column.Order(2);
                        column.Width(1.5f);
                        column.ColumnItemsTemplate(template =>
                        {
                            template.TextBlock();
                            template.DisplayFormatFormula(obj => obj == null ? string.Empty : string.Format("{0:n0}", obj));
                        });
                        column.AggregateFunction(aggregateFunction =>
                        {
                            aggregateFunction.NumericAggregateFunction(AggregateFunction.Sum);
                            aggregateFunction.DisplayFormatFormula(obj => obj == null ? string.Empty : string.Format("{0:n0}", obj));
                        });
                        column.HeaderCell("بدهکار");
                    });
                    columns.AddColumn(column =>
                    {
                        column.PropertyName<VoucherRowPrintViewModel>(x => x.Creditor);
                        column.CellsHorizontalAlignment(PdfRpt.Core.Contracts.HorizontalAlignment.Right);
                        column.IsVisible(true);
                        column.Order(3);
                        column.Width(1.5f);
                        column.ColumnItemsTemplate(template =>
                        {
                            template.TextBlock();
                            template.DisplayFormatFormula(obj => obj == null ? string.Empty : string.Format("{0:n0}", obj));
                        });
                        column.AggregateFunction(aggregateFunction =>
                        {
                            aggregateFunction.NumericAggregateFunction(AggregateFunction.Sum);
                            aggregateFunction.DisplayFormatFormula(obj => obj == null ? string.Empty : string.Format("{0:n0}", obj));
                        });
                        column.HeaderCell("بستانکار");
                    });
                    columns.AddColumn(column =>
                    {
                        column.PropertyName<VoucherRowPrintViewModel>(x => x.CaclulatedDetection);
                        column.CellsHorizontalAlignment(PdfRpt.Core.Contracts.HorizontalAlignment.Left);
                        column.IsVisible(true);
                        column.Order(4);
                        column.Width(1);
                        column.AggregateFunction(aggregateFunction =>
                        {
                            aggregateFunction.CustomAggregateFunction(new CumulativeAggregateFunction(true));
                            aggregateFunction.DisplayFormatFormula(obj => obj == null ? string.Empty : obj.ToString());
                        });
                        column.HeaderCell("تشخیص");
                    });
                    columns.AddColumn(column =>
                    {
                        column.PropertyName<VoucherRowPrintViewModel>(x => x.CaclulatedRemains);
                        column.CellsHorizontalAlignment(PdfRpt.Core.Contracts.HorizontalAlignment.Right);
                        column.IsVisible(true);
                        column.Order(5);
                        column.Width(1.5f);
                        column.ColumnItemsTemplate(template =>
                        {
                            template.TextBlock();
                            template.DisplayFormatFormula(obj => obj == null ? string.Empty : string.Format("{0:n0}", obj));
                        });
                        column.AggregateFunction(aggregateFunction =>
                        {
                            aggregateFunction.CustomAggregateFunction(new CumulativeAggregateFunction());
                            aggregateFunction.DisplayFormatFormula(obj => obj == null ? string.Empty : string.Format("{0:n0}", obj));
                        });
                        column.HeaderCell("مانده");
                    });
    
                })
                .MainTableSummarySettings(summarySettings =>
                {
                    summarySettings.OverallSummarySettings("جمع کل");
                    summarySettings.PreviousPageSummarySettings("نقل از صفحه قبل");
                })
    
                .MainTableEvents(events =>
                {
                    events.DataSourceIsEmpty(message: "داده ای جهت نمایش وجود ندارد.");
                    events.CellCreated(args =>
                    {
                        args.Cell.BasicProperties.CellPadding = 4f;
                    });
                    events.MainTableAdded(args =>
                    {
                        var taxTable = new PdfGrid(3);  // Create a clone of the MainTable's structure  
                        taxTable.RunDirection = 3;
                        taxTable.SetWidths(new float[] { 3, 3, 3 });
                        taxTable.WidthPercentage = 100f;
                        taxTable.SpacingBefore = 10f;
    
                        taxTable.AddSimpleRow(
                            (data, cellProperties) =>
                            {
                                data.Value = "امضاء تنظیم کننده";
                                cellProperties.ShowBorder = true;
                                cellProperties.PdfFont = args.PdfFont;
                            },
                            (data, cellProperties) =>
                            {
                                data.Value = "امضاء حسابدار";
                                cellProperties.ShowBorder = true;
                                cellProperties.PdfFont = args.PdfFont;
                            },
                            (data, cellProperties) =>
                            {
                                data.Value = "امضاء مدیرعامل";
                                cellProperties.ShowBorder = true;
                                cellProperties.PdfFont = args.PdfFont;
                            });
                        args.PdfDoc.Add(taxTable);
                    });
                })
                .Export(export =>
                {
                    export.ToExcel("خروجی اکسل");
                    export.ToCsv("خروجی CSV");
                    export.ToXml("خروجی XML");
                })
                .Generate(data => data.AsPdfFile(_documentSource));
    دفتر-تفصیلی.pdf 
    این هم نمونه فایل pdf  خروجی.
    نظرات اشتراک‌ها
    معرفی کتابخانه‌ی DNTPersianUtils.Core
    در خصوص کلاس اعتبار سنجی شماره شبا:
    حلقه‌ای که برای بررسی نوشته شده  (به نظر من) خیلی هوشمندانه است. ابتدا از رقم چهارم سمت چپ شروع می‌کند و سپس بعد از اتمام، ۴ رقم اول را هم لحاظ می‌کند.
    در تعیین بخش‌پذیری عدد ۲۶ رقمی شماره شبا بر ۹۷، متوجه الگوریتم به کار رفته نشدم. ابتدا باقیمانده‌ی ۱۰ برابر هر عدد با مقدار عددی کاراکتر محاسبه می‌شود. سپس برای عدد بعدی همین روال طی می‌شود و ...
    اگر معادل IR ابتدا را حساب کنیم مثلا ۲۸۱۷ و ارقام سوم و چهارم را پشت آن گذاشته، از ابتدای شماره شبا حذف کرده و به انتهای آن اضافه کنیم، می‌شود باقیمانده را بر ۹۷ محاسبه کرد.
    پاسخ با الگوریتم شما یکسان است (شاید روش‌های دیگری هم وجود داشته باشد!) چنانچه ممکن است، در خصوص آن الگوریتم بخش‌پذیری بر ۹۷ که استفاده کرده‌اید، قدری توضیح می‌دهید؟ متشکرم
    نظرات مطالب
    BulkInsert در EF CodeFirst
    اگر در حین کار با کتابخانه‌های مختلف، صفحه دیالوگ گشودن فایل به همراه پیام cs file not found مشاهده شد، این صفحه را لغو کنید تا استثنای اصلی نمایش داده شود. همچنین در EF باید Inner exception را هم بررسی کنید.
    علت اصلی هم به اینجا بر می‌گردد که فایل pdb، به همراه کتابخانه‌ی مورد نظر توزیع شده و این فایل حاوی محل قرارگیری سورس کتابخانه و همچنین شماره سطر مرتبط با استثناء است. چون این سورس بر روی سیستم شما موجود نیست و فایل pdb نیز پیوست شده، صفحه‌ی باز کردن فایل یافت نشده، نمایش داده می‌شود.
    نظرات مطالب
    Value Types ارجاعی در C# 7.2
    یک نکته‌ی تکمیلی: اضافه شدن پارامترهای از نوع ref readonly به C# 12

    در انتهای نکته‌ی خروجی ref readonly عنوان شد که «در ابتدا قصد داشتند ref readonly را برای تعریف پارامترهای value type نیز بکار برند، اما این تصمیم با معرفی پارامترهای از نوع in جایگزین شد» اما ... مجددا به C# 12 اضافه شده‌است:
    مثال زیر را درنظر بگیرید:
    namespace CS8Tests;
    
    public class RefReadonlySample
    {
       public void Test()
       {
          var number = 5;
          Print(ref number);
          Console.WriteLine($"After Print -> Your number is {number}");
          
          // Output:
          // Print -> Your number is 5
          // After Print -> Your number is 6
       }
       
       private void Print(ref int number)
       {
          Console.WriteLine($"Print -> Your number is {number}");
          number++;
       }
    }
    در این مثال، ارجاعی از متغیر عددی number (که یک value type است) به کمک واژه‌ی کلیدی ref به متد Print ارسال شده و درون این متد، مقدار این متغیر تغییر کرده‌است که این تغییر به خارج از متد Print نیز منعکس می‌شود.
    اگر بخواهیم از تغییرات پارامتر number در متد Print جلوگیری کنیم، می‌توان از واژه‌ی کلیدی in که در C# 7.2 ارائه شد، استفاده کرد:
     private void Print(in int number)
    در این حالت در سطر ++number، به خطای زیر می‌رسیم:
    error CS8331: Cannot assign to variable 'number' or use it as the right hand side of a ref assignment because it is a readonly variable

    اکنون در C# 12 همین عمل را توسط واژه‌های کلیدی ref readonly نیز می‌توان پیاده سازی کرد:
    private void Print(ref readonly int number)
    خطایی را هم که گزارش می‌دهد، دقیقا همانند خطای ذکر شده‌ی واژه‌ی کلیدی in است.

    سؤال: چرا این تغییر در C# 12 رخ داده‌است، زمانیکه واژه‌ی کلیدی in، دقیقا همین کار را انجام می‌داد؟
    هدف، وضوح بیشتر API تولیدی و تاکید بر readonly بودن ارجاع دریافتی در این حالت و یکدستی قسمت‌های مختلف زبان است.
    همچنین واقعیت این است که یک چنین قابلیت‌هایی، استفاده‌ی روزمره‌ای را در زبان #‍C ندارند و بیشتر هدف از وجود آن‌ها، استفاده از API کتابخانه‌های C++/C در زبان #C است. برای مثال بجای اینکه تمام ارجاعات فقط خواندنی آن‌ها را به پارامترهایی از نوع in تبدیل کنند (در کدهای قدیمی) که سبب بروز مشکلات عدم سازگاری می‌شود، اکنون می‌توانند به سادگی refهای قدیمی تعریف شده را ref readonly کنند؛ بدون اینکه استفاده کنندگان با مشکلی مواجه شوند.
    نظرات مطالب
    آموزش LINQ بخش سوم
    بله. توضیح دادم چرا. چون طراحی جدول شما اشتباه هست. اگر عدد وارد می‌کنید، نوع فیلد را int تعیین کنید. مابقی آن به صورت خودکار درست می‌شود (و نیازی به هیچ نکته‌ی خاصی هم ندارد). اگر الزامی به ورود رشته هست، باید بجای 10 بنویسید 010 تا مقایسه‌ها درست شوند (این 0‌های پیش از اعداد باید تعداد کاراکترها را به تعداد کاراکترهای بزرگترین عدد رشته‌ای که دارید برساند؛ اگر بزرگترین عدد شد 1000 باید تمام این‌ها را به 0010 اصلاح کنید). چون این‌ها رشته هستند نه عدد. تمام داده‌ها وارد شده هم باید به همین صورت اصلاح شوند. روش دیگر هم این است که مستقیما SQL بنویسید و (cast(code as int کنید. راه دیگری ندارد. حتی کوئری SQL ایی هم که در بالا نوشتید جواب نمی‌دهد چون cast as int ندارد. بنابراین ساده‌ترین و منطقی‌ترین روش، انتخاب نوع فیلد مناسب هست.
    SELECT * FROM document WHERE CAST(code AS INT) > 1 and CAST(code AS INT) < 10
    نظرات مطالب
    خلاصه اشتراک‌های روز دو شنبه 21 آذر 1390
    اون صفحه مرتبط است به Lifecycle Policy محصولات موجود و نوشته شده که ساپورت نگارش 5 آن تا تاریخ 10/12/2021 هست. مورد دیگری ذکر نشده.
    برای سایر محصولات می‌تونید به این صفحه مراجعه کنید: http://support.microsoft.com/gp/lifeselect
    مثلا SQL Server: http://support.microsoft.com/lifecycle/?c2=1044
    لیست محصولات موجود را دارد به همراه تاریخ نهایی منقضی شدن ساپورت آن‌ها.
    مطالب
    بررسی تغییرات Blazor 8x - قسمت سوم - روش ارتقاء برنامه‌های Blazor Server قدیمی به دات نت 8
    در قسمت قبل، با نحوه‌ی رندر سمت سرور و روش فعالسازی قابلیت‌های تعاملی در این حالت، آشنا شدیم. از این نکات می‌توان جهت ارتقاء ساختار پروژه‌های قدیمی Blazor Server به Blazor Server 8x استفاده کرد. البته همانطور که پیشتر نیز عنوان شد، در دات نت 8 دیگر خبری از قالب‌های قدیمی پروژه‌های blazor server و blazor wasm نیست و اگر دقیقا همین موارد مدنظر هستند، آن‌ها را می‌توان با تنظیم سطح رندر و میزان تعاملی که مدنظر است، شبیه سازی کرد و یا حتی هر دو را هم با هم در یک پروژه داشت.


    1) به‌روز رسانی شماره نگارش دات‌نت

    اولین قدم در جهت ارتقاء پروژه‌های قدیمی، تغییر شماره نگارش TargetFramework موجود در فایل csproj. به net8.0 است. پس از اینکار نیاز است تمام بسته‌های نیوگت موجود را نیز به نگارش‌های جدیدتر آن‌ها ارتقاء دهید.


    2) فعالسازی حالت SSR تعاملی سمت سرور

    پایه‌ی تمام تغییرات انجام شده‌ی در Blazor 8x، قابلیت SSR است و تمام امکانات دیگر برفراز آن اجرا می‌شوند. به همین جهت پس از ارتقاء شماره نگارش دات‌نت، نیاز است SSR را فعال کنیم و برای اینکار باید به هاست ASP.NET Core بگوئیم که درخواست‌های رسیده را به کامپوننت‌های Razor هدایت کند. بنابراین، به فایل Program.cs مراجعه کرده و دو تغییر زیر را به آن اعمال کنید:
    // ...
    builder.Services.AddRazorComponents().AddInteractiveServerComponents();
    // ...
    app.MapRazorComponents<App>().AddInteractiveServerRenderMode();
    یک نمونه‌ی کامل از فایل Program.cs را در قسمت قبل مشاهده کردید و یا حتی می‌توانید دستور dotnet new blazor --interactivity Server را جهت ساخت یک پروژه‌ی آزمایشی جدید بر اساس SDK دات نت 8 و ایده گیری از آن، اجرا کنید.

    در اینجا ترکیب کامپوننت‌های تعاملی سمت سرور (AddInteractiveServerComponents) و رندر تعاملی سمت سرور (AddInteractiveServerRenderMode)، دقیقا همان Blazor Server قدیمی است که ما با آن آشنا هستیم.

    یک نکته: اگر از قالب جدید dotnet new blazor --interactivity None استفاده کنیم، یعنی حالت تعاملی بودن آن‌را به None تنظیم کنیم، کلیات ساختار پروژه‌ای را که مشاهده خواهیم کرد، با حالت تعاملی Server آن یکی است؛ فقط در تنظیمات Program.cs آن، گزینه‌های فوق را نداریم و به صورت زیر ساده شده‌است:
    // ...
    
    builder.Services.AddRazorComponents();
    
    // ...
    
    app.MapRazorComponents<App>();
    در این نوع برنامه‌ها نمی‌توان جزایر/قسمت‌های تعاملی Blazor Server را در صفحات و کامپوننت‌های SSR، تعریف کرد. در مورد جزایر تعاملی، در مطالب بعدی بیشتر بحث خواهیم کرد.


    3) ایجاد فایل جدید App.razor

    در دات نت 8، دیگر خبری از فایل آغازین Host.cshtml_ پروژه‌های Blazor Server قدیمی نیست و کدهای آن با تغییراتی، به فایل جدید App.razor منتقل شده‌اند. در این قسمت، کار هدایت درخواست‌های رسیده به کامپوننت‌های برنامه رخ می‌دهد و از این پس، صفحه‌ی ریشه‌ی برنامه خواهد بود.


    در این تصویر، مقایسه‌ای را بین جریان پردازش یک درخواست رسیده در دات نت 8، با نگارش قبلی Blazor Server مشاهده می‌کنید. در دات نت 8، فایل Host.cshtml_ (یک Razor Page آغازین برنامه) با یک کامپوننت Razor به نام App.razor جایگزین شده‌است و فایل قدیمی App.razor این پروژه‌ها به Routes.razor، تغییر نام یافته‌است.

    نمونه‌ای از فایل App.razor جدید را که در قسمت قبل نیز معرفی کردیم، در اینجا با جزئیات بیشتری بررسی می‌کنیم:
    <!DOCTYPE html>
    <html lang="en">
    
    <head>
        <meta charset="utf-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <base href="/" />
        <link rel="stylesheet" href="bootstrap/bootstrap.min.css" />
        <link rel="stylesheet" href="app.css" />
        <link rel="stylesheet" href="MyApp.styles.css" />
        <link rel="icon" type="image/png" href="favicon.png" />
        <HeadOutlet />
    </head>
    
    <body>
        <Routes />
        <script src="_framework/blazor.web.js"></script>
    </body>
    
    </html>
    در این فایل جدید تغییرات زیر رخ داده‌اند:
    - تمام دایرکتیوهای تعریف شده مانند page ،@addTagHelper@ و غیره حذف شده‌اند.
    - base href تعریف شده اینبار فقط با یک / شروع می‌شود و نه با /~. این مورد خیلی مهم است! اگر به آن دقت نکنید، هیچکدام از فایل‌های استاتیک برنامه مانند فایل‌های css. و js.، بارگذاری نخواهند شد!
    - پیشتر برای رندر HeadOutlet، از یک تگ‌هلپر استفاده می‌شد. این مورد در نگارش جدید با یک کامپوننت ساده جایگزین شده‌است.
    - تمام component tag helper‌های پیشین حذف شده‌اند و نیازی به آن‌ها نیست.
    - ارجاع پیشین فایل blazor.server.js با فایل جدید blazor.web.js جایگزین شده‌است.

    یک نکته: همانطور که مشاهده می‌کنید، فایل App.razor یک کامپوننت است و اینبار به همراه تگ <script> نیز شده‌است. یعنی در این نگارش از Blazor می‌توان اسکریپت‌ها را در کامپوننت‌ها نیز ذکر کرد؛ فقط با یک شرط! این کامپوننت حتما باید SSR باشد. اگر این تگ اسکریپتی را در یک کامپوننت تعاملی ذکر کنید، همانند قابل (و نگارش‌های پیشین Blazor) با خطا مواجه خواهید شد.


    4) ایجاد فایل جدید Routes.razor و مدیریت سراسری خطاها و صفحات یافت نشده

    همانطور که عنوان شد، فایل قدیمی App.razor این پروژه‌ها به Routes.razor تغییر نام یافته‌است که درج آن‌را در قسمت body مشاهده می‌کنید. محتوای این فایل نیز به صورت زیر است:
    <Router AppAssembly="@typeof(Program).Assembly">
        <Found Context="routeData">
            <RouteView RouteData="@routeData" DefaultLayout="@typeof(Layout.MainLayout)" />
            <FocusOnNavigate RouteData="@routeData" Selector="h1" />
        </Found>
    </Router>
    این محتوای جدید، فاقد ذکر کامپوننت NotFound قبلی است؛ از این جهت که سیستم مسیریابی جدید Blazor 8x با ASP.NET Core 8x یکپارچه است و نیازی به این کامپوننت نیست. یعنی اگر قصد مدیریت آدرس‌های یافت نشده‌ی برنامه را دارید، باید همانند ASP.NET Core به صورت زیر در فایل Program.cs عمل کرده:
    app.UseStatusCodePagesWithRedirects("/StatusCode/{0}");
    و سپس کامپوننت جدید StatusCode.razor را برای مدیریت آن به نحو زیر به برنامه اضافه کنید و بر اساس responseCode دریافتی، واکنش‌های متفاوتی را ارائه دهید:
    @page "/StatusCode/{responseCode}"
    
    <h3>StatusCode @ResponseCode</h3>
    
    @code {
        [Parameter] public string? ResponseCode { get; set; }
    }

    یک نکته: اگر پروژه‌ای را بر اساس قالب dotnet new blazor --interactivity Server ایجاد کنیم، در فایل Program.cs آن، چنین تنظیمی اضافه شده‌است:
    if (!app.Environment.IsDevelopment())
    {
       app.UseExceptionHandler("/Error", createScopeForErrors: true);
    }
    که دقیقا معادل رفتاری است که در برنامه‌های ASP.NET Core قابل مشاهده‌است. این مسیر Error/، به کامپوننت جدید Components\Pages\Error.razor نگاشت می‌شود. بنابراین اگر در برنامه‌های جدید Blazor Server، استثنائی رخ دهد، با استفاده از میان‌افزار ExceptionHandler فوق، کامپوننت Error.razor نمایش داده خواهد شد.  باید دقت داشت که این کامپوننت ویژه، تحت هر شرایطی در حالت یک static server component رندر می‌شود.

    سؤال: در اینجا (برنامه‌های Blazor Server) چه تفاوتی بین UseExceptionHandler و UseStatusCodePagesWithRedirects وجود دارد؟
    میان‌افزار UseExceptionHandler برای مدیریت استثناءهای آغازین برنامه، پیش از تشکیل اتصال دائم SignalR وارد عمل می‌شود. پس از آن و تشکیل اتصال وب‌سوکت مورد نیاز، فقط از میان‌افزار UseStatusCodePagesWithRedirects استفاده می‌کند.
    اگر علاقمند نیستید تا تمام خطاهای رسیده را همانند مثال فوق در یک صفحه مدیریت کنید، می‌توانید حداقل سه فایل زیر را به برنامه اضافه کنید تا خطاهای متداول یافت نشدن آدرسی، بروز خطایی و یا عدم دسترسی را مدیریت کنند:

    404.razor
    @page "/StatusCode/404"
    
    <PageTitle>Not found</PageTitle>
    
    <h1>Not found</h1>
    <p role="alert">Sorry, there's nothing at this address.</p>

    500.razor
    @page "/StatusCode/500"
    
    <PageTitle>Unexpected error</PageTitle>
    
    <h1>Unexpected error</h1>
    <p role="alert">There was an unexpected error.</p>

    401.razor
    @page "/StatusCode/401"
    
    <PageTitle>Not Authorized</PageTitle>
    
    <h1>Not Authorized</h1>
    <p role="alert">Sorry, you are not authorized to access this page.</p>


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

    پس از این تغییرات اگر برنامه را اجرا کنید، بر اساس روش جدید static server-side rendering کار می‌کند و تعاملی نیست. یعنی تمام کامپوننت‌های آن به صورت پیش‌فرض، یکبار بر روی سرور رندر شده و خروجی آن‌ها به مرورگر کاربر ارسال می‌شوند و هیچ اتصال دائم SignalR ای برقرار نخواهد شد. برای فعالسازی سراسری قابلیت‌های تعاملی برنامه و بازگشت به حالت Blazor Server قبلی، به فایل App.razor مراجعه کرده و دو تغییر زیر را اعمال کنید تا به صورت خودکار به تمام زیرکامپوننت‌ها، یعنی کل برنامه، اعمال شود:
    <HeadOutlet @rendermode="@InteractiveServer" />
    ...
    <Routes @rendermode="@InteractiveServer" />

    نکته 1: اجرای دستور زیر در دات‌نت 8، قالب پروژه‌ای را ایجاد می‌کند که رفتار آن همانند پروژه‌های Blazor Server نگارش‌های قبلی دات‌نت است (این مورد را در قسمت قبل بررسی کردیم)؛ یعنی همه‌جای آن به صورت پیش‌فرض، تعاملی است:
    dotnet new blazor --interactivity Server --all-interactive

    نکته 2: البته ...  InteractiveServer، دقیقا همان حالت پیش‌فرض برنامه‌های Blazor Server قبلی نیست! این حالت رندر، به صورت پیش‌فرض به همراه پیش‌رندر (pre-rendering) هم هست. یعنی در این حالت، روال رویدادگردان OnInitializedAsync یک کامپوننت، دوبار فراخوانی می‌شود (که باید به آن دقت داشت و عدم توجه به آن می‌تواند سبب انجام دوباره‌ی کارهای سنگین آغازین یک کامپوننت شود)؛ یکبار برای پیش‌رندر صفحه به صورت یک HTML استاتیک (بدون فعال سازی هیچ قابلیت تعاملی) که برای موتورهای جستجو و بهبود SEO مفید است و بار دیگر برای فعالسازی قسمت‌های تعاملی آن، درست پس از زمانیکه اتصال SignalR صفحه، برقرار شد (البته امکان فعالسازی حالت پیش‌رندر در Blazor Server قبلی هم وجود داشت؛ ولی مانند Blazor 8x، به صورت پیش‌فرض فعال نبود). در صورت نیاز، برای سفارشی سازی و لغو آن می‌توان به صورت زیر عمل کرد:
    @rendermode InteractiveServerRenderModeWithoutPrerendering
    
    @code{
      static readonly IComponentRenderMode InteractiveServerRenderModeWithoutPrerendering = 
            new InteractiveServerRenderMode(false);
    }

    در مورد پیش‌رندر و روش مدیریت دوبار فراخوانی شدن روال رویدادگردان OnInitializedAsync یک کامپوننت در این حالت، در قسمت‌های بعدی این سری بیشتر بحث خواهد شد.