ارتقاء به ASP.NET Core 1.0 - قسمت 11 - بررسی بهبودهای Razor
اندازه‌ی قلم متن
تخمین مدت زمان مطالعه‌ی مطلب: چهار دقیقه

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

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

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


معرفی functions@

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


معرفی inject@

توسط دایرکتیو جدید inject@، یک خاصیت عمومی به ASP.NET Core اعلام می‌شود و سپس مقدار دهی آن بر اساس تنظیمات IoC Container برنامه به صورت خودکار صورت خواهد گرفت. برای مثال زمانیکه می‌خواهیم به سرویس توکار HostingEnvironment در یک  View دسترسی پیدا کنیم، می‌توان در ابتدای آن نوشت:
 @inject Microsoft.AspNetCore.Hosting.IHostingEnvironment Host;
در این حالت کد فوق از دیدگاه ASP.NET Core به صورت ذیل تفسیر می‌شود:
 [Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public Microsoft.AspNetCore.Hosting.IHostingEnvironment Host { get; private set; }
این خاصیت عمومی نیز با توجه به از پیش ثبت شده بودن سرویس IHostingEnvironment  و مشخص شدن توسط RazorInjectAttribute، توسط IoC Container آن شناسایی شده و تامین می‌شود.
اکنون برای استفاده‌ی از آن خواهیم داشت:
 <div>
 Running in @Host.EnvironmentName
</div>
البته استفاده‌ی از inject@ شاید به نوعی سؤ استفاده‌ی از الگوی MVC به شما رود؛ از این جهت که اطلاعات مورد نیاز یک View، باید از طریق کنترلر آن تامین شود و خود View نباید به صورت مستقیم درخواست تامین آ‌ن‌ها را بدهد. اما باید دقت داشت که در نهایت View نیاز دارد تا کدها را اجرا کرده و خروجی را تولید کند و برای این منظور، در پشت صحنه سرویس‌های زیادی مانند IUrlHelper ، IViewComponentHelper ، IHtmlHelper و غیره به همین ترتیب در اختیار آن قرار می‌گیرند. به علاوه استفاده‌ی از تزریق وابستگی‌ها بهتر است از روش ارث بری صفحات پایه، از این جهت که انتخاب composition همواره مقدم است بر inheritance و سبب انعطاف پذیری بیشتری نسبت به قبل می‌گردد. داشتن یک صفحه‌ی پایه که بتواند تمام نیازهای انواع و اقسام Viewها را تامین کند، دور از انتظار و گاهی از اوقات، سبب سنگینی بیش از حد پردازش تمام Viewها خواهد شد. اما تزریق سرویس‌هایی اینچنینی جهت تامین نیازهای اولیه و تکراری یک یا چند View خاص، کل برنامه را سنگین نکرده و همچنین انعطاف پذیری بیشتری را در جهت تامین آن‌ها فراهم می‌کند.
به علاوه باید دقت داشت اگر تعریف inject@ فوق را در فایل view import قرار دهیم، این سرویس در اختیار تمام Viewهای برنامه قرار خواهد گرفت و دیگر نیازی به قرار دادن آن در یک کلاس پایه‌ی سفارشی نیست.
یکی از مفیدترین استفاده‌های از قابلیت تزریق سرویس‌ها در Viewها می‌تواند دسترسی به سرویس تامین تنظیمات برنامه باشد (که در مورد نحوه‌ی تامین آن در مطلب «ارتقاء به ASP.NET Core 1.0 - قسمت 7 - کار با فایل‌های config» بیشتر بحث شد):
 @inject IOptions<SmtpConfig> Settings;
  • #
    ‫۶ سال و ۷ ماه قبل، شنبه ۲۱ بهمن ۱۳۹۶، ساعت ۱۳:۰۷
    یک نکته‌ی تکمیلی
     
    پردازش Razor در ASP.NET Core کاملا Async است. بنابراین در اینجا فقط از متدهای Html.xyxAsyc برای رندر محتوا استفاده کنید و یا از تگ‌هلپر جدید partial که به ASP.NET Core 2.1 اضافه شده‌است. اجرای synchronous و یا همزمان views/partials/view components در ASP.NET Core امن نیست و حتما باید به صورت async فراخوانی شوند. تمام متدهای synchronous مربوط به Razor در ASP.NET Core 3.0 حذف خواهند شد.
    Html.Partial causes deadlocks and should be marked obsolete
    • #
      ‫۶ سال و ۷ ماه قبل، دوشنبه ۲۳ بهمن ۱۳۹۶، ساعت ۱۶:۴۱
      یک نکته: برای تشخیص کدهای blocking در ASP.NET Core می‌توان از افزونه‌ی « Ben.BlockingDetector » استفاده کرد.
  • #
    ‫۶ سال و ۵ ماه قبل، شنبه ۱ اردیبهشت ۱۳۹۷، ساعت ۱۸:۰۰
    یک نکته‌ی تکمیلی: خطایابی خودکار فایل‌های Razor در نگارش 2.1

    از نگارش ASP.NET Core 2.1 preview 2 به بعد، کامپایلر ASP.NET Core، فایل‌های Razor را هم در حین کامپایل برنامه به صورت خودکار بررسی می‌کند (Razor compilation on build). برای نمونه اگر model@ تعریف شده‌ی در یک فایل razor حاوی خاصیت مورد استفاده نباشد، در حین کامپایل برنامه، خطای مرتبط با آن فایل cshtml را می‌توان مشاهده کرد:
    Views\UploadFileExtensions\Index.cshtml(25,135): 
    error CS1061: 'UserFileViewModel' does not contain a definition for 'UserFile' 
    and no extension method 'UserFile' accepting a first argument of type 'UserFileViewModel' could be found 
    (are you missing a using directive or an assembly reference?)
  • #
    ‫۶ سال و ۴ ماه قبل، چهارشنبه ۱۹ اردیبهشت ۱۳۹۷، ساعت ۱۵:۰۱
    یک نکته‌ی تکمیلی: دریافت خطای «Error RZ3007: Targeted tag name cannot be null or whitespace» پس از نصب SDK جدید

    اگر SDK جدیدی را نصب کنید و بلافاصله سعی در اجرای برنامه‌ای که با SDK قبلی کامپایل شده‌است داشته باشید، ممکن است خطای فوق را دریافت کنید. برای رفع آن، ابتدای پوشه‌های bin و obj را حذف کنید. سپس دستور dotnet restore را صادر کنید. اکنون برنامه بدون مشکل اجرا می‌شود.
  • #
    ‫۶ سال و ۱ ماه قبل، پنجشنبه ۲۵ مرداد ۱۳۹۷، ساعت ۲۱:۴۲
    جهت جلوگیری از Encoding  مدل ارسالی به ویو در View page source 

    کانفیگ زیر را به کلاس استارتاپ اضافه کنید
    فضا‌های نامی : 
    using System.Text.Encodings.Web;
    using System.Text.Unicode;
    using Microsoft.Extensions.WebEncoders;

    services.Configure<WebEncoderOptions> (options => {
        options.TextEncoderSettings = new TextEncoderSettings (UnicodeRanges.All);
    });

  • #
    ‫۲ سال و ۱۰ ماه قبل، سه‌شنبه ۱۱ آبان ۱۴۰۰، ساعت ۰۲:۰۶
    سلام معادل 
    @using Menu.Helper
    @model IEnumerable<.Models.Entities.Category>
    @ShowTree(Model)
    
    @helper ShowTree(IEnumerable<Menu.Models.Entities.Category> categories)
    {
        foreach (var item in categories)
        {
        <lidropdown-submenu" : "")">
    
            @Html.ActionLink(item.Name, actionName: "Category", controllerName: "Product", routeValues: new { Id = item.Id, productName = item.Name.ToSeoUrl() }, htmlAttributes: null)
            @if (item.Children.Any())
            {
                <ul>
                    @ShowTree(item.Children)
                </ul>
                    }
        </li>
    
            }
    }
    با استفاده از @function امکان پذیر هست؟  
    @model IEnumerable<TR_Mahmodabad.Context.Entities.Navbar>
    
    @functions
    {
        public void ShowTree(IEnumerable<Navbar> navbars)
        {
            foreach (var item in navbars)
            {
                <lidropdown-submenu" : "")">
                    @Html.ActionLink(item.Title, actionName: "", controllerName: "Product", routeValues: new {Id = item.Id, title = item.Title},htmlAttributes:null)
                    @if (item.HasChiled)
                    {
                        <ul>
                            @ShowTree(item.Children) // خطا
                        </ul>
                    }
                </li>
            }
        }
    }
    • #
      ‫۲ سال و ۱۰ ماه قبل، سه‌شنبه ۱۱ آبان ۱۴۰۰، ساعت ۱۴:۳۴
      چون ShowTree خروجی void دارد، باید نحوه‌ی فراخوانی آن داخل حلقه به صورت زیر باشد:
      @{
          ShowTree(item.Children);
      }
      و یا هنگام فراخوانی نهایی:
      @{
        ShowTree(Model);
      }