مطالب
یکدست کردن «ی» و «ک» در ASP.NET Core با پیاده‌سازی یک Model Binder سفارشی
معادل مطلب جاری را برای ASP.NET MVC 5.x در مطلب «یکدست کردن "ی" و "ک" در ASP.NET MVC با پیاده‌سازی یک Model Binder» می‌توانید مطالعه کنید. در اینجا قصد داریم یک چنین قابلیتی را با توجه به تغییرات ASP.NET Core نیز تهیه کنیم.


تهیه یک binder provider پردازش رشته‌ها

کار model binding، تطابق اطلاعات رسیده‌ی از درخواست جاری، با پارامترهای اکشن متد یک کنترلر است. هر مقدار رسیده، به یک binder متناسب ارسال می‌شود تا پردازش آن مدیریت گردد. به صورت پیش فرض در ASP.NET Core، تعدد 14 عدد binder providers که اینترفیس IModelBinderProvider را پیاده سازی می‌کنند، در این بین جهت یافتن یک binder مناسب، بررسی خواهند شد. برای مثال کار یک binder، پردازش نوع‌های پیچیده‌است (complex types) و دیگری نوع‌های ساده (simple types) مانند int و string را پردازش می‌کند.


public class CustomStringModelBinderProvider : IModelBinderProvider
{
    public IModelBinder GetBinder(ModelBinderProviderContext context)
    {
        if (context == null)
        {
            throw new ArgumentNullException(nameof(context));
        }
 
        if (context.Metadata.IsComplexType)
        {
            return null;
        }
 
        var fallbackBinder = new SimpleTypeModelBinder(context.Metadata.ModelType);
        if (context.Metadata.ModelType == typeof(string))
        {
            return new CustomStringModelBinder(fallbackBinder);
        }
        return fallbackBinder;
    }
}
بنابراین اولین قدم تهیه‌ی یک model binder سفارشی، تهیه‌ی یک تامین کننده‌ی سفارشی است که با پیاده سازی اینترفیس IModelBinderProvider ارائه می‌شود. در اینجا چون می‌خواهیم نوع‌های ساده‌ی رشته‌ای را پردازش کنیم، اگر نوع جاری رسیده، یک نوع پیچیده بود (context.Metadata.IsComplexType) نال را بازگشت می‌دهیم تا model binder بعدی ثبت شده‌ی در لیست تامین کننده‌های مرتبط، مورد آزمایش قرار گیرد.
سپس اگر نوع مدل جاری رشته‌ای بود، وهله‌ای از CustomStringModelBinder را بازگشت می‌دهیم (کلاسی است که آن‌را در ادامه تهیه خواهیم کرد). درغیراینصورت همان SimpleTypeModelBinder توکار این فریم‌ورک را بازگشت خواهیم داد.


تهیه‌ی یک model binder سفارشی پردازش رشته‌ها

تا اینجا تامین کننده‌ای را که مشخص می‌کند چه model binder ایی قرار است بازگشت داده شود، تهیه کردیم. مرحله‌ی بعد، پیاده سازی CustomStringModelBinder با پیاده سازی اینترفیس IModelBinder است:
public class CustomStringModelBinder : IModelBinder
{
    private readonly IModelBinder _fallbackBinder;
    public CustomStringModelBinder(IModelBinder fallbackBinder)
    {
        if (fallbackBinder == null)
        {
            throw new ArgumentNullException(nameof(fallbackBinder));
        }
        _fallbackBinder = fallbackBinder;
    }
 
    public Task BindModelAsync(ModelBindingContext bindingContext)
    {
        if (bindingContext == null)
        {
            throw new ArgumentNullException(nameof(bindingContext));
        }
 
        var valueProviderResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
        if (valueProviderResult != ValueProviderResult.None)
        {
            bindingContext.ModelState.SetModelValue(bindingContext.ModelName, valueProviderResult);
 
            var valueAsString = valueProviderResult.FirstValue;
            if (string.IsNullOrWhiteSpace(valueAsString))
            {
                return _fallbackBinder.BindModelAsync(bindingContext);
            }
 
            var model = valueAsString.Replace((char)1610, (char)1740).Replace((char)1603, (char)1705);
            bindingContext.Result = ModelBindingResult.Success(model);
            return Task.CompletedTask;
        }
 
        return _fallbackBinder.BindModelAsync(bindingContext);
    }
}
عملیات اصلی پردازشی یک Model binder در متد BindModelAsync آن صورت می‌گیرد. ابتدا مقداری را که در حال پردازش است دریافت می‌کنیم (توسط ValueProvider.GetValue). سپس ی و ک آن‌را یکدست کرده و به عنوان نتیجه‌ی عملیات تنظیم خواهیم کرد. این کار سبب خواهد شد تا هر مقداری را که کاربر وارد و ارسال کند، پیش از رسیدن به اکشن متد و پارامترهای آن، مورد پردازش و یکدست سازی قرار گیرد.
در اینجا تمام مواردی را که نمی‌خواهیم پردازش کنیم، به همان SimpleTypeModelBinder که از طریق سازنده‌ی کلاس دریافت می‌کنیم، واگذار خواهیم کرد.


معرفی به binder provider سفارشی به سیستم

مرحله‌ی آخر این عملیات، معرفی binder تهیه شده به سیستم است که روش آن را در ذیل مشاهده می‌کنید:
public static class CustomStringModelBinderExtensions
{
    public static MvcOptions UseCustomStringModelBinder(this MvcOptions options)
    {
        if (options == null)
        {
            throw new ArgumentNullException(nameof(options));
        }
 
        var simpleTypeModelBinder = options.ModelBinderProviders.FirstOrDefault(x => x.GetType() == typeof(SimpleTypeModelBinderProvider));
        if (simpleTypeModelBinder == null)
        {
            return options;
        }
 
        var simpleTypeModelBinderIndex = options.ModelBinderProviders.IndexOf(simpleTypeModelBinder);
        options.ModelBinderProviders.Insert(simpleTypeModelBinderIndex, new CustomStringModelBinderProvider());
        return options;
    }
}
در اینجا ابتدا به دنبال SimpleTypeModelBinderProvider توکار گشته و سپس آن‌را با CustomStringModelBinderProvider خود جایگزین می‌کنیم. اگر این model binder سفارشی ما در ایندکس نامناسبی در لیست options.ModelBinderProviders قرارگیرد، هیچگاه فراخوانی نخواهد شد؛ برای مثال اگر پس از SimpleTypeModelBinderProvider قرارگیرد.
در آخر تنها کافی است در کلاس آغازین برنامه، متد الحاقی UseCustomStringModelBinder فوق را به تنظیمات Mvc اضافه کنیم:
public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc(options =>
    {
        options.UseCustomStringModelBinder(); 
    });
مطالب
استفاده از Data Annotations جهت تعریف خواص ستون‌ها در PdfReport
در مطلب «تولید پویای ستون‌ها در PdfReport» عنوان شد که ذکر قسمت MainTableColumns و تمام تعاریف مرتبط با آن در PdfReports اختیاری است. همچنین به کمک متد MainTableAdHocColumnsConventions می‌توان بر اساس نوع‌های داده‌ای، بر روی نحوه نمایش ستون‌ها تاثیر گذاشت. برای مثال هرجایی DateTime مشاهده شد، به صورت خودکار تبدیل به تاریخ شمسی شود.
روش دیگری که این روزها در اکثر فریم‌های دات نتی مرسوم شده است، استفاده از Data Annotations جهت انتساب یک سری متادیتا به خاصیت‌های تعریف شده کلاس‌ها است. برای مثال ASP.NET MVC از این قابلیت زیاد استفاده می‌کند (در تولید پویای کد، یا اعتبار سنجی‌های سمت سرور و کاربر).
به همین جهت برای سازگاری بیشتر PdfReport با مدل‌های اینگونه فریم ورک‌ها، اکثر ویژگی‌ها و Data Annotations متداول را نیز می‌توان در PdfReport بکار برد. همچنین تعدادی ویژگی سفارشی نیز تعریف شده است، که در ادامه به بررسی آن‌ها خواهیم پرداخت.

آشنایی با مدل‌های بکار رفته در مثال جاری:
using System.ComponentModel;

namespace PdfReportSamples.Models
{
    public enum JobTitle
    {
        [Description("Grunt")]
        Grunt,

        [Description("Programmer")]
        Programmer,

        [Description("Analyst Programmer")]
        AnalystProgrammer,

        [Description("Project Manager")]
        ProjectManager,

        [Description("Chief Information Officer")]
        ChiefInformationOfficer,
    }
}
در اینجا یک enum، جهت تعیین سمت شغلی تعریف شده است. برای اینکه بتوان خروجی مطلوبی را در گزارشات شاهد بود، می‌توان از ویژگی Description، جهت تعیین مقدار نمایشی آن‌ها نیز استفاده کرد و این تعاریف در PdfReport خوانده و اعمال می‌شوند.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using PdfReportSamples.Models;
using PdfRpt.Aggregates.Numbers;
using PdfRpt.ColumnsItemsTemplates;
using PdfRpt.Core.Contracts;
using PdfRpt.Core.Helper;
using PdfRpt.DataAnnotations;

namespace PdfReportSamples.DataAnnotations
{
    public class Person
    {
        [IsVisible(false)]
        public int Id { get; set; }

        [DisplayName("User name")]
        //Note: If you don't specify the ColumnItemsTemplate, a new TextBlockField() will be used automatically.
        [ColumnItemsTemplate(typeof(TextBlockField))]
        public string Name { get; set; }

        [DisplayName("Job title")]
        public JobTitle JobTitle { set; get; }

        [DisplayName("Date of birth")]
        [DisplayFormat(DataFormatString = "{0:MM/dd/yyyy}")]
        public DateTime DateOfBirth { get; set; }

        [DisplayName("Date of death")]
        [DisplayFormat(NullDisplayText = "-", DataFormatString = "{0:MM/dd/yyyy}")]
        public DateTime? DateOfDeath { get; set; }

        [DisplayFormat(DataFormatString = "{0:n0}")]
        [CustomAggregateFunction(typeof(Sum))]
        public int Salary { get; set; }

        [IsCalculatedField(true)]
        [DisplayName("Calculated Field")]
        [DisplayFormat(DataFormatString = "{0:n0}")]
        [AggregateFunction(AggregateFunction.Sum)]
        public string CalculatedField { get; set; }

        [CalculatedFieldFormula("CalculatedField")]
        public static Func<IList<CellData>, object> CalculatedFieldFormula =
                                                    list =>
                                                    {
                                                        if (list == null) return string.Empty;
                                                        var salary = (int)list.GetValueOf<Person>(x => x.Salary);
                                                        return salary * 0.8;
                                                    };//Note: It's a static field, not a property.
    }
}
مدل فوق جهت مقدار دهی اطلاعات یک شخص تعریف شده است.
- اگر قصد ندارید خاصیتی در این بین، در گزارشات ظاهر شود، از ویژگی IsVisible با مقدار false استفاده کنید.
- از ویژگی DisplayName جهت تعیین برچسب‌های سرستون‌ها استفاده خواهد شد.
- ذکر ویژگی ColumnItemsTemplate اختیاری است و اگر عنوان نشود به صورت خودکار از TextBlockField استفاده خواهد شد. اما اگر نیاز به استفاده از قالب‌های ستون‌های سفارشی و یا حتی قالب‌های پیش فرض دیگری که متنی نیستند، وجود دارد، می‌توانید از ویژگی ColumnItemsTemplate به همراه نوع کلاس مورد نظر استفاده نمائید. کلاس‌های پیش فرض قالب‌های ستون‌ها در PdfReport در پوشه Lib\ColumnsItemsTemplates سورس آن قرار دارند.
- برای تعیین نحوه فرمت اطلاعات در اینجا می‌توان از ویژگی DisplayFormat استفاده کرد. این ویژگی در اسمبلی System.ComponentModel.DataAnnotations.dll دات نت تعریف شده است؛ که در اینجا نمونه‌ای از استفاده از آن‌را برای تعیین نحوه نمایش تاریخ، ملاحظه می‌کنید. توسط این ویژگی حتی می‌توان مشخص ساخت (توسط پارامتر NullDisplayText) که اگر اطلاعاتی null بود، بجای آن چه عبارتی نمایش داده شود.
- اگر علاقمند به اعمال تابعی تجمعی به ستونی خاص هستید، از ویژگی CustomAggregateFunction استفاده کنید. پارامتر آن نوع کلاس تابع مورد نظر است. یک سری تابع تجمعی پیش فرض در فضای نام PdfRpt.Aggregates.Numbers قرار دارند. البته امکان تهیه انواع سفارشی آن‌ها نیز پیش بینی شده است که در قسمت‌های بعد به آن خواهیم پرداخت.
- امکان تعریف خواص محاسباتی نیز پیش بینی شده است. برای این منظور دو کار را باید انجام داد:
الف) ویژگی IsCalculatedField را با مقدار true بر روی خاصیت مورد نظر اعمال کنید.
ب) هم نام خاصیت محاسباتی افزوده شده به کلاس جاری، ویژگی CalculatedFieldFormula را بر روی یک فیلد استاتیک عمومی در آن کلاس به نحوی که ملاحظه می‌کنید (مطابق امضای فیلد CalculatedFieldFormula فوق)، تعریف نمائید. (علت این است که نمی‌توان توسط ویژگی‌ها از delegates استفاده کرد و این محدودیت ذاتی وجود دارد)


در ادامه کدهای منبع داده فرضی مثال جاری ذکر شده است:
using System;
using System.Collections.Generic;
using PdfReportSamples.Models;

namespace PdfReportSamples.DataAnnotations
{
    public static class PersonnelDataSource
    {
        public static IList<Person> CreatePersonnelList()
        {
            return new List<Person>
            {
                new Person
                {
                    Id = 1,
                    Name = "Edward",
                    DateOfBirth = new DateTime(1900, 1, 1),
                    DateOfDeath = new DateTime(1990, 10, 15),
                    JobTitle = JobTitle.ChiefInformationOfficer,
                    Salary = 5000
                },
                new Person
                {
                    Id = 2,
                    Name = "Margaret", 
                    DateOfBirth = new DateTime(1950, 2, 9), 
                    DateOfDeath = null,
                    JobTitle = JobTitle.AnalystProgrammer,
                    Salary = 4000
                },
                new Person
                {
                    Id = 3,
                    Name = "Grant", 
                    DateOfBirth = new DateTime(1975, 6, 13), 
                    DateOfDeath = null,
                    JobTitle = JobTitle.Programmer,
                    Salary = 3500
                }
            };
        }
    }
}
در پایان، نحوه استفاده از منبع داده فوق جهت تامین یک گزارش، به نحو زیر می‌باشد:
using System;
using PdfRpt.Core.Contracts;
using PdfRpt.FluentInterface;

namespace PdfReportSamples.DataAnnotations
{
    public class DataAnnotationsPdfReport
    {
        public IPdfReportData CreatePdfReport()
        {
            return new PdfReport().DocumentPreferences(doc =>
            {
                doc.RunDirection(PdfRunDirection.LeftToRight);
                doc.Orientation(PageOrientation.Portrait);
                doc.PageSize(PdfPageSize.A4);
                doc.DocumentMetadata(new DocumentMetadata { Author = "Vahid", Application = "PdfRpt", Keywords = "Test", Subject = "Test Rpt", Title = "Test" });
            })
             .DefaultFonts(fonts =>
             {
                 fonts.Path(Environment.GetEnvironmentVariable("SystemRoot") + "\\fonts\\tahoma.ttf",
                                  Environment.GetEnvironmentVariable("SystemRoot") + "\\fonts\\verdana.ttf");
             })
             .PagesFooter(footer =>
             {
                 footer.DefaultFooter(printDate: DateTime.Now.ToString("MM/dd/yyyy"));
             })
             .PagesHeader(header =>
             {
                 header.DefaultHeader(defaultHeader =>
                 {
                     defaultHeader.ImagePath(AppPath.ApplicationPath + "\\Images\\01.png");
                     defaultHeader.Message("new rpt.");
                     defaultHeader.RunDirection(PdfRunDirection.LeftToRight);
                 });
             })
             .MainTableTemplate(template =>
             {
                 template.BasicTemplate(BasicTemplate.ClassicTemplate);
             })
             .MainTablePreferences(table =>
             {
                 table.ColumnsWidthsType(TableColumnWidthType.FitToContent);
             })
             .MainTableDataSource(dataSource =>
             {
                 dataSource.StronglyTypedList(PersonnelDataSource.CreatePersonnelList());
             })
             .MainTableEvents(events =>
             {
                 events.DataSourceIsEmpty(message: "There is no data available to display.");
             })
             .MainTableSummarySettings(summary =>
             {
                 summary.OverallSummarySettings("Total");
                 summary.PageSummarySettings("Page Summary");
                 summary.PreviousPageSummarySettings("Pervious Page Summary");
             })
             .MainTableAdHocColumnsConventions(adHocColumns =>
             {
                 adHocColumns.ShowRowNumberColumn(true);
                 adHocColumns.RowNumberColumnCaption("#");
             })
             .Export(export =>
             {
                 export.ToExcel();
                 export.ToXml();
             })
             .Generate(data => data.AsPdfFile(AppPath.ApplicationPath + "\\Pdf\\DataAnnotationsSampleRpt.pdf"));
        }
    }
}
همانطور که مشخص است، از ذکر متد MainTableColumns به علت استفاده از DataAnnotations صرفنظر شده و PdfReport این تعاریف را بر اساس ویژگی‌های خواص کلاس شخص دریافت می‌کند. تنها از متد MainTableAdHocColumnsConventions جهت مشخص سازی اینکه نیاز به نمایش ستون ردیف می‌باشد، استفاده کرده‌ایم.


مطالب
مقدمه‌ای بر صفحات Razor در ASP.NET Core 2.0 - بخش اوّل
Page یا «صفحه» در Razor، یکی از ویژگی‌های جدید در  ASP.NET Core MVC است که تمرکز کدنویسی را بر روی صفحات قرار می‌دهد و این موجب راحتی کدنویسی و بالارفتن راندمان می‌شود. این «صفحات» نیازمند استفاده از نسخۀ ASP.NET Core 2.0.0 و نسخه‌های بعد از آن هستند که در  Visual Studio 2017 Update 3 و نسخه‌های بعدی در دسترس است.
«صفحات» Razor به‌طور پیش‌‎فرض در MVC در دسترس است و کافیست در فایل  Startup.cs، «صفحات» Razor فعال شود:
public class Startup
{
    public void ConfigureServices(IServiceCollections services)
    {
        services.AddMvc(); // موجب فعال شدن «صفحات»  و کنترلرها می‌شود
    }

    public void Configure(IApplicationBuilder app)
    {
        app.UseMvc();
    }
}
تمام ویژگی‌های جدید «صفحات» Razor در اسمبلی Microsoft.AspNetCore.Mvc.RazorPages در دسترس است و کافیست بدان ارجاع دهید. همچنین اگر ارجاعی به پکیج  Microsoft.AspNetCore.Mvc  داشته باشید نیز «صفحات» Razor در دسترس خواهند بود.
«صفحۀ» زیر را در نظر بگیرید:
@page

@{
    var message = "Hello, World!";
}

<html>
<body>
    <p>@message</p>
</body>
</html>
این کد، بسیار شبیه «صفحات» ویوی Razor معمولی است؛ اما آنچه آن را متفاوت می‌سازد، استفاده از دایرکتیو page@  است. با استفاده از  page@، درواقع این فایل نقش اکشن متد MVC را نیز انجام می‌دهد و بدین معناست که می‌تواند به‌طورمستقیم، درخواست‌ها را بدون وساطت هیچ کنترلری مدیریت کند. دایرکتیو  page@، باید در ابتدای صفحۀ Razor قرار بگیرد. این دایرکتیو رفتار اصلی سایر Razorها را تحت تأثیر قرار می‌دهد.
ارتباط میان مسیرهای URL با صفحات، از طریق محل قرارگیری «صفحات» در فایل سیستم، اتفاق می‌افتد. جدول زیر نحوۀ ارتباط میان مسیر «صفحات» Razor و URL متناظر آن را نشان می‌دهد: 
 URL متناظر  نام فایل و مسیر آن 
/  یا  /Index
 /Pages/Index.cshtml
/Contact /Pages/Store/Contact.cshtml
/Store/Contact /Pages/Store/Contact.cshtml
برنامه هنگام اجرا، به‌طور پیش‌فرض، برای یافتن فایل «صفحات» Razor در پوشۀ «صفحات» جستجو می‌کند.
ویژگی‌های جدید «صفحات» Razor  بدین منظور طراحی شده‌اند تا الگوهای عمومی به‌کار رفته در پیمایش‌گر صفحات وب را آسان‌تر کنند. صفحۀ «تماس با ما» را در نظر بگیرید که در آن مدل Contact پیاده‌سازی شده است. فایل  MyApp/Contact.cs  بدین شکل است:
using System.ComponentModel.DataAnnotations;

namespace MyApp 
{
    public class Contact
    {
        [Required]
        public string Name { get; set; }

        [Required]
        public string Email { get; set; }
    }
}
و فایل ویوی آن یعنی MyApp/Pages/Contact.cshtml به شکل زیر است:
@page
@using MyApp
@using Microsoft.AspNetCore.Mvc.RazorPages
@addTagHelper "*, Microsoft.AspNetCore.Mvc.TagHelpers"
@inject ApplicationDbContext Db

@functions {
    [BindProperty]
    public Contact Contact { get; set; }

    public async Task<IActionResult> OnPostAsync()
    {
        if (ModelState.IsValid)
        {
            Db.Contacts.Add(Contact);
            await Db.SaveChangesAsync();
            return RedirectToPage();
        }

        return Page();
    }
}

<html>
<body>
    <p>فرم زیر را پر کنید تا در اسرع وقت، کارشناسان ما با شما بگیرند</p> 
    <div asp-validation-summary="All"></div>
    <form method="POST">
      <div>Name: <input asp-for="Contact.Name" /></div>
      <div>Email: <input asp-for="Contact.Email" /></div>
      <input type="submit" />
    </form>
</body>
</html>
این «صفحه»، هندلری با عنوان OnPostAsync  دارد که هنگام ارسال درخواست‌های POST (هنگامی‌که کاربری فرم را ثبت می‌کند) اجرا می‌شود. البته می‌توان برای هر نوع تقاضای HTTP، هندلری را اضافه کرد که معمولاً از  OnGet  برای هرگونه تقاضای نشان دادن یک صفحۀ HTML استفاده می‌شود و از  OnPost  برای ارسال اطلاعات از طریق فرم استفاده می‌شود و همان‌طور که می‌دانیم پسوند Async اختیاری است. اما اغلب به‌طور قراردادی به‌کار برده می‌شود. کد نوشته شده داخل OnPostAsync بسیار شبیه چیزی است که به‌طور معمول در داخل یک کنترلر نوشته می‌شود. این الگویی است برای صفحاتی که در آن‌ها بیشتر اصول اوّلیۀ MVC از قبیل بایند کردن مدل‌ها، اعتبارسنجی و نتایج اکشن‌ها قابل استفاده است.  
روند اصلی  OnPostAsync  به‌شکل زیر است:
کنترل خطاهای اعتبارسنجی.
  1. اگر خطایی نبود، اطلاعات ذخیره شده و به صفحۀ دیگر ریدایرکت می‌شود.
  2. درغیراین‌صورت، صفحه را دوباره به‌همراه خطاهای اعتبار سنجی نمایش می‌دهد.
  3. اگر اطلاعات موفقت‌آمیز وارد شوند، آنگاه متد هندلر OnPostAsync، هلپر RedirctToPage را برای برگرداندن نمونه‌ای از RedirectToPageResult فراخوانی می‌کند. این یک نوع جدید بازگشتی برای اکشن متد است که شبیه RedirectToAction  یا RedirectToRoute  است؛ با این تفاوت که مخصوص صفحات طراحی شده است.
هنگامیکه فرم ثبت شده، خطای اعتبارسنجی داشته باشد، آنگاه متد هندلر OnPostAsync هلپر متد Page را فراخوانی می‌کند. این هلپر یک نمونه از PageResult را برمی‌گرداند. این شبیه کاری است که اکشن‌ها در کنترلر‌ها، یک ویو را برگردانند. PageResult خروجی پیش‌فرض یک هندلر متد است و هندلر متدی که نوع  void  را برگرداند «صفحۀ» جاری را رندر می‌کند.
خصیصۀ  Contact از attribute جدید [BindProperty] برای مقید کردن مدل استفاده می‌کند. «صفحات» به‌طور پیش‌فرض، ویژگی‌هایی را که GET نیستند، مقید می‌کنند. مقیدکردن به خواص، موجب کاهش کدی می‌شود که شما باید در فرم مورد نظر خود بیاورید؛ مثلاً به‌راحتی می‌توان نوشت ( <input asp-for="Contacts.Name" /> ) که با این کار مقیدسازی فیلد و خصیصۀ مورد نظر به‌طور خودکار انجام می‌شود.
به‌جای استفاده از دایرکتیو  model@ در اینجا، از ویژگی جدید «صفحات» استفاده می‌کنیم. به‌طور پیش‌فرض دایرکتیو کلاس Page همان مدل است و ویژگی‌هایی شبیه مقیدکردن مدل‌ها، تگ هلپرها و هلپرهای HTML، همگی فقط با ویژگی‌هایی که درون functions@ نوشته شده‌اند، کار می‌کنند. هنگام استفاده از «صفحات» به‌طور خودکار به ویومدل دسترسی وجود دارد.
دایرکتیو  inject@  قبل از شروع هندلر متد، قرار گرفته و برای تزریق وابستگی در «صفحات» استفاده می‌شود که همانند Razor ویوهای قبل کار می‌کند. متغیر Db در اینجا از نوع  ApplicationDbContext است که به «صفحه» تزریق می‌شود. 
در اینجا نیازی به نوشتن هیچ دستوری برای اعتبارسنجی anti-forgery نیست. تولید و اعتبارسنجی anti-forgery به‌طور خودکار در «صفحات» انجام می‌شود و برای دستیابی به این ویژگی امنیتی نیاز به تنظیمات اضافه‌تری نیست.
می‌توان ویوی MyApp/Pages/Contact.chsml  را از مدل آن به شکل زیر جدا کرد: 
@page
@using MyApp
@using MyApp.Pages
@using Microsoft.AspNetCore.Mvc.RazorPages
@addTagHelper "*, Microsoft.AspNetCore.Mvc.TagHelpers"
@model ContactModel

<html>
<body>
    <p>فرم زیر را پر کنید تا در اسرع وقت، کارشناسان ما با شما بگیرند </p>
    <div asp-validation-summary="All"></div>
    <form method="POST">
      <div>Name: <input asp-for="Contact.Name" /></div>
      <div>Email: <input asp-for="Contact.Email" /></div>
      <input type="submit" />
    </form>
</body>
</html>
و بخش مدل «صفحه» را به شکل زیر:
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;

namespace MyApp.Pages
{
    public class ContactModel : PageModel
    {
        public ContactModel(ApplicationDbContext db)
        {
            Db = db;
        }

        [BindProperty]
        public Contact Contact { get; set; }

        private ApplicationDbContext Db { get; }

        public async Task<IActionResult> OnPostAsync()
        {
            if (ModelState.IsValid)
            {
                Db.Contacts.Add(Contact);
                await Db.SaveChangesAsync();
                return RedirectToPage();
            }

            return Page();
        }
    }
}
براساس قرارداد، کلاس باید به‌شکل PageModel باشد و در همان فضای نامی باشد که صفحۀ آن قرار دارد و نیازی به استفاده از functions @ نیست و تنها تغییر آن، تزریق وابستگی از طریق کلاس سازنده است. 
با استفاده از PageModel می‌توان از الگوی آزمون واحد بهره برد؛ اما مستلزم نوشتن صریح کلاس و سازندۀ آن است. مزیت «صفحات» بدون فایل PageModel این است که از کامپایل در زمان اجرا پشتیبانی می‌کنند که این قابلیت در هنگام کدنویسی مفید است.
ادامه دارد...
بازخوردهای دوره
استفاده از StructureMap به عنوان یک IoC Container
سلام؛ توی این ورژن جدید کدها رو به صورت زیر تغییر دادم. آیا درسته؟ ممنون میشم بررسی بفرمایید
    public static class IoC
    {  
        public static IContainer Initialize()
        {
            var container = new Container(x =>
            {

               x.For<IUnitOfWork>().HybridHttpOrThreadLocalScoped().Use(() => new baranDbContext());
                x.For<IUserService>().Use<UserService>();
                x.For<IUserMetaDataService>().Use<UserMetaDataService>();
            });

            return container;
        }
        public class StructureMapControllerFactory : DefaultControllerFactory
        {
            private readonly IContainer _container;

            public StructureMapControllerFactory(IContainer container)
            {
                _container = container;
            }
            protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
            {
                if (controllerType == null)
                    return null;

                return (IController)_container.GetInstance(controllerType);
            }
        }
} 
و یه سول دیگه اینکه در یک فایل جدا معادل دستور زیر چی میشه؟ چون از این خط خطا میگیره
var userService = ObjectFactory.GetInstance<IUserService>();
نظرات مطالب
حذف پردازش درخواست‌های فایل‌های استاتیک در متد Application_AuthenticateRequest
یک نکته‌ی تکمیلی
پیاده سازی مطلب جاری برای ASP.NET Identity 2.x یک چنین تغییراتی را پیدا می‌کند:
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
   // ...
    Provider = new CookieAuthenticationProvider
    {
        OnValidateIdentity = context =>
        {
            if(shouldIgnoreRequest(context)) // How to ignore Authentication Validations for static files in ASP.NET Identity
            {
                return Task.FromResult(0);
            }
            return container.GetInstance<IApplicationUserManager>().OnValidateIdentity().Invoke(context);
        }
    },
    // ...
});
با این متد بررسی درخواست‌ها:
private static bool shouldIgnoreRequest(CookieValidateIdentityContext context)
{
    string[] reservedPath =
    {
        "/__browserLink",
        "/img",
        "/fonts",
        "/Scripts",
        "/Content",
        "/Uploads",
        "/Images"
    };
    return reservedPath.Any(path => context.OwinContext.Request.Path.Value.StartsWith(path, StringComparison.OrdinalIgnoreCase)) ||
                       BundleTable.Bundles.Select(bundle => bundle.Path.TrimStart('~')).Any(bundlePath => context.OwinContext.Request.Path.Value.StartsWith(bundlePath,StringComparison.OrdinalIgnoreCase));
}
نظرات مطالب
بازنویسی سطح دوم کش برای Entity framework 6
ممنون از پاسخ کاملتون 
با فهمیدن این نکته کلاس repository رو به این روش تغییر دادم و کلاس کش رو هم اصلاح کردم 
//Function in Repository Class 
public IQueryable<T> FindAll(bool doCache, params Expression<Func<T, object>>[] includes)
        {
            return includes.Aggregate<Expression<Func<T, object>>, IQueryable<T>>(
                 _dbset, (current, experssion) => current.Include(experssion));
        }
//function in cache class
public IQueryable<T> FindAll(bool doCache, params Expression<Func<T, object>>[] includes)
        {
            if (doCache)
            {
                var result = _repository.FindAll(doCache, includes).Cacheable();
                return result;
            }
            else
                return _repository.FindAll(doCache, includes);
        }
//Call  in service class
_cache.FindAll(true,s=>s.Tag)

نظرات مطالب
آشنایی با مفاهیم نوع داده Enum و توسعه آن - قسمت دوم
وقتی کلاس Description در   فضای نام  System.ComponentModel وجود داره دلیلی نداره  کلاس مشابه ای تعریف کنیم.
بخاطر اینکه مصرف کننده محض نباشیم یک متد الحاقی به نام ()GetEnumList اضافه کردم که لیست اعضای یک Enum  رو برای استفاده در کمبو باکس و ... بر می‌گرودنه :
ابتدا کلاس زیر به کلاس ExtensionMethodCls اضافه می‌کنیم :
 public class EnumObject
        {
            public Enum ValueMember { get; set; }
            public int intValueMember
            {
                get { return int.Parse(ValueMember.ToString("D")); }
            }
            public string stringValueMember
            {
                get { return ValueMember.ToString(""); }
            }
            public string DisplayMember
            {
                get { return ValueMember.GetDescription(); }
            }
        }
و متد الحاقی زیر رو برای گرفتن لیست تعریف می‌کنیم:
      public static List<EnumObject> GetEnumList(this Enum enu)
        {
            List<EnumObject> li = new List<EnumObject>();
            foreach (var item in enu.GetType().GetEnumValues())
            {
                li.Add(new EnumObject { ValueMember = (Enum)item });
            }
            return li;
        }
نحوه استفاده  :
          comboBox1.DataSource = Grade.VeryGood.GetEnumList();
          comboBox1.DisplayMember = "DisplayMember";
          comboBox1.ValueMember = "ValueMember";
همون طوری که در کد بالا می‌بینید برای گرفتن لیست مجبور شدیم یکی از اعضای enum  رو انتخاب کنیم (Grade.VeryGood.GetEnumList()) شاید انتخاب یکی از اعضا و بعد درخواست لیست اعضا رو کردن کار قشنگی نباشه به همین دلیل متد زیر رو تعریف کردیم :
        public static List<EnumObject> EnumToList<T>()
        {
            Type enumType = typeof(T);
            if (enumType.BaseType != typeof(Enum))
                throw new ArgumentException("T must be of type System.Enum");

            List<EnumObject> li = new List<EnumObject>();
            foreach (var item in enumType.GetEnumValues())
            {
                li.Add(new EnumObject { ValueMember = (Enum)item });
            }
            return li;
        }
نحوه استفاده :
    comboBox1.DataSource =ExtensionMethodCls.EnumToList<Grade>();
          comboBox1.DisplayMember = "DisplayMember";
          comboBox1.ValueMember = "ValueMember";
کد کامل :
 public static class ExtensionMethodCls
    {
        public class EnumObject
        {
            public Enum ValueMember { get; set; }
            public int intValueMember
            {
                get { return int.Parse(ValueMember.ToString("D")); }
            }
            public string stringValueMember
            {
                get { return ValueMember.ToString(""); }
            }
            public string DisplayMember
            {
                get { return ValueMember.GetDescription(); }
            }
        }
 
    

        public static List<EnumObject> EnumToList<T>()
        {
            Type enumType = typeof(T);
            if (enumType.BaseType != typeof(Enum))
                throw new ArgumentException("T must be of type System.Enum");

            List<EnumObject> li = new List<EnumObject>();
            foreach (var item in enumType.GetEnumValues())
            {
                li.Add(new EnumObject { ValueMember = (Enum)item });
            }
            return li;
        }

        public static List<EnumObject> GetEnumList(this Enum enu)
        {
            List<EnumObject> li = new List<EnumObject>();
            foreach (var item in enu.GetType().GetEnumValues())
            {
                li.Add(new EnumObject { ValueMember = (Enum)item });
            }
            return li;
        }

        public static string GetDescription(this Enum enu)
        {

            Type type = enu.GetType();

            MemberInfo[] memInfo = type.GetMember(enu.ToString());

            if (memInfo != null && memInfo.Length > 0)
            {

                object[] attrs = memInfo[0].GetCustomAttributes(typeof(DescriptionAttribute), false);

                if (attrs != null && attrs.Length > 0)
                    return ((DescriptionAttribute)attrs[0]).Description;
            }

            return enu.ToString();

        }
    }
مطالب
CheckBoxList در ASP.NET MVC

ASP.NET MVC به همراه HtmlHelper توکاری جهت نمایش یک ChekBoxList نیست؛ اما سیستم Model binder آن، این نوع کنترل‌ها را به خوبی پشتیبانی می‌کند. برای مثال، یک پروژه جدید خالی ASP.NET MVC را آغاز کنید. سپس یک کنترلر Home جدید را نیز به آن اضافه کنید. در ادامه، برای متد Index آن، یک View خالی را ایجاد نمائید. سپس محتوای این View را به نحو زیر تغییر دهید:
@{
ViewBag.Title = "Index";
}
<h2>
Index</h2>
@using (Html.BeginForm())
{
<input type='checkbox' name='Result' value='value1' />
<input type='checkbox' name='Result' value='value2' />
<input type='checkbox' name='Result' value='value3' />
<input type="submit" value="submit" />
}

و کنترلر Home را نیز مطابق کدهای زیر ویرایش کنید:
using System.Web.Mvc;

namespace MvcApplication21.Controllers
{
public class HomeController : Controller
{
[HttpGet]
public ActionResult Index()
{
return View();
}

[HttpPost]
public ActionResult Index(string[] result)
{
return View();
}
}
}

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


بله. همانطور که ملاحظه می‌کنید، تمام عناصر ارسالی انتخاب شده که دارای نامی مشابه بوده‌اند، به یک آرایه قابل بایند هستند و سیستم model binder می‌داند که چگونه باید این اطلاعات را دریافت و پردازش کند.
از این مقدمه می‌توان به عنوان پایه و اساس نوشتن یک HtmlHelper سفارشی CheckBoxList استفاده کرد.
برای این منظور یک پوشه جدید را به نام app_code، به ریشه پروژه اضافه نمائید. سپس یک فایل خالی را به نام Helpers.cshtml نیز به آن اضافه کنید. محتوای این فایل را به نحو زیر تغییر دهید:

@helper CheckBoxList(string name, List<System.Web.Mvc.SelectListItem> items)
{
<div class="checkboxList">
@foreach (var item in items)
{
@item.Text
<input type="checkbox" name="@name"
value="@item.Value"
@if (item.Selected) { <text>checked="checked"</text> }
/>
< br />
}
</div>
}

و برای استفاده از آن، کنترلر Home را مطابق کدهای زیر ویرایش کنید:

using System.Collections.Generic;
using System.Web.Mvc;

namespace MvcApplication21.Controllers
{
public class HomeController : Controller
{
[HttpGet]
public ActionResult Index()
{
ViewBag.Tags = new List<SelectListItem>
{
new SelectListItem { Text = "Item1", Value = "Val1", Selected = false },
new SelectListItem { Text = "Item2", Value = "Val2", Selected = false },
new SelectListItem { Text = "Item3", Value = "Val3", Selected = true }
};
return View();
}

[HttpPost]
public ActionResult GetTags(string[] tags)
{
return View();
}

[HttpPost]
public ActionResult Index(string[] result)
{
return View();
}
}
}

و در این حالت View برنامه به شکل زیر درخواهد آمد:
@{
ViewBag.Title = "Index";
}
<h2>
Index</h2>
@using (Html.BeginForm())
{

<input type='checkbox' name='Result' value='value1' />
<input type='checkbox' name='Result' value='value2' />
<input type='checkbox' name='Result' value='value3' />
<input type="submit" value="submit" />
}

@using (Html.BeginForm(actionName: "GetTags", controllerName: "Home"))
{
@Helpers.CheckBoxList("Tags", (List<SelectListItem>)ViewBag.Tags)
<input type="submit" value="submit" />
}

با توجه به اینکه کدهای Razor قرار گرفته در پوشه خاص app_code در ریشه سایت، به صورت خودکار در حین اجرای برنامه کامپایل می‌شوند، متد Helpers.CheckBoxList در تمام Viewهای برنامه در دسترس خواهد بود. در این متد، یک نام و لیستی از SelectListItemها دریافت می‌گردد. سپس به صورت خودکار یک CheckboxList را تولید خواهد کرد. برای دریافت مقادیر ارسالی آن به سرور هم باید مطابق متد GetTags تعریف شده در کنترلر Home عمل کرد. در اینجا Value عناصر انتخابی به صورت آرایه‌ای از رشته‌ها در دسترس خواهد بود.

روشی جامع‌تر
در آدرس زیر می‌توانید یک HtmlHelper بسیار جامع را جهت تولید CheckBoxList در ASP.NET MVC بیابید. در همان صفحه روش استفاده از آن، به همراه چندین مثال ارائه شده است:
https://github.com/devnoob/MVC3-Html.CheckBoxList-custom-extension

مطالب
KnockoutJs #5
Custom Binding در KO
در پست‌های قبلی(^و^و^) با انواع مقید سازی در KO آشنا شدید. اما در پیاده سازی، محدود به این نوع‌هایی click، value، text و ... نیستیم؛ بلکه می‌توانیم نوع مورد نظر برای عملیات مقید سازی را بنابر نیاز خود بسازیم که به آن‌ها Custom Binding گفته می‌شود. Custom Binding یکی از امکانات قدرتمند موجود در KO است و مورد اصلی استفاده آن در طراحی کامپوننت‌ها و ویجت‌ها می‌باشد.

مکانیزم پیاده سازی Custom Binding
 برای شروع باید binding مورد نظر، به خاصیت ko.bindingHandlers رجیستر شود. سپس با تعیین کردن و شخصی سازی دو تابع init و update می‌توان نوع مقید سازی مورد نظر را تعریف کرد.
»init : این تابع فقط یک بار آن هم به ازای هر عنصری که عملیات مقید سازی را شامل می‌شود، فراخوانی خواهد شد.
»update : این تابع برای تعیین نوع عمل مورد انتظار در هنگام تغییر کردن مقدار عنصر DOM استفاده می‌شود.
برای مثال:
ko.bindingHandlers.myCustomBinding = {
    init: function(element, valueAccessor, allBindingsAccessor, viewModel , bindingContext) {
      
    },
    update: function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
  
    }
};
:پارامتر‌های توابع
هر دو تابع بالا دقیقا دارای پنج پارامتر یکسان هستند که در زیر به تفصیل شرح داده شده‌اند:
»element : برای دسترسی مستقیم به عنصر DOMی که شامل مقید سازی است، می‌توان از این پارامتر استفاده کرد.
»valueAccessor : این پارامتر تابعی است که امکان دسترسی به هر آنچه را که به binding مورد نظر پاس داده باشیم، در اختیار ما قرار می‌دهد. برای مثال اگر observable را پاس داده باشیم، خروجی این تابع دقیقا همان observable خواهد بود. اگر از یک عبارت یا expression استفاده کرده باشیم خروجی این تابع برابر با حاصل آن عبارت خواهد بود.
»allBindingsAccessor : برای پیدا کردن لیست تمام عناصری است که به یک data-bind attribute مشترک اشاره می‌کنند.
»viewModel : برای دسترسی به viewModel عنصر مقید شده استفاده می‌شد. در knockout نسخه 3 به بعد این گزینه منسوخ شده است. به جای آن باید از پارامتر bindingContext.$data یا bindingContext.$rowData استفاده کرد.
»bindngContext : این پارامتر  شی binding Context  را که عنصر مورد نظر به آن مقید شده است، شامل می‌شود. این آبجکت شامل خواص parent$ و parents$ و root$ است.

یک مثال ساده:
ko.bindingHandlers.jqButton= {
    init: function(element, valueAccessor) {
        var options = valueAccessor() || {};
        $(element).button(options);
    }
};
 و روش استفاده از آن در عناصر DOM:
<button data-bind="click: greet, jqButton: { icons: { primary: 'ui-icon-gear' } }">Test</button>
دموی این مثال

استفاده از تابع update :
فرض کنید قصد داریم که با تغییر در مقدار یک متغیر، تغییرات مورد نظرمان در عنصر مقید شده نیز مشاهده شود. در این حالت باید از تابع update استفاده نمود. به مثال زیر دقت کنید:
ko.bindingHandlers.flash= {
    update: function(element, valueAccessor) {
        ko.utils.unwrapObservable(valueAccessor()); 
        $(element).hide().fadeIn(500);
    }
};
نکته : دستور ko.utils.unwrapObservable خاصیت مورد نظر را از حالت observe بودن خارج می‌کند.
دموی  این مثال

ادامه دارد...
مطالب
بهبود SEO در ASP.NET MVC
گوگل خلاصه نتایج Indexing یک سایت را توسط ابزاری به نام Google webmaster tools در اختیار علاقمندان قرار می‌دهد. Bing نیز چنین ابزاری را تدارک دیده است.
به آمارهای خطای حاصل از سایت جاری که دقت می‌کردم یک نکته آن جالب بود: «محتوای تکراری»


mydomain.com/Home/Index
mydomain.com/home/index
mydomain.com/Home/index
mydomain.com/home/Index
همانطور که ملاحظه می‌کنید، گوگل به کوچکی و بزرگی حروف بکار رفته در لینک‌ها حساس است. هرچند 4 لینک فوق به یک صفحه اشاره می‌کنند، اما گوگل 4 بار آن‌ها را ایندکس خواهد کرد و نهایتا به صورت یک خطای «محتوای تکراری» در گزارشات SEO آن ظاهر خواهد شد (به همراه کاهش رتبه SEO سایت).

راه حل

برای حل این مساله دو نکته باید درنظر گرفته شود:
الف) هدایت دائمی (Redirect permanent) صفحات قدیمی به صفحاتی جدید، با آدرس lowercase

using System.Globalization;
using System.Web;
using System.Web.Mvc;

namespace WebToolkit
{
    public class ForceWww : ActionFilterAttribute
    {
        public override void OnActionExecuting(ActionExecutingContext filterContext)
        {
            modifyUrlAndRedirectPermanent(filterContext);
            base.OnActionExecuting(filterContext);
        }

        private static void modifyUrlAndRedirectPermanent(ActionExecutingContext filterContext)
        {
            if (canIgnoreRequest(filterContext))
                return;

            var absoluteUrl = HttpUtility.UrlDecode(filterContext.RequestContext.HttpContext.Request.Url.AbsoluteUri.ToString(CultureInfo.InvariantCulture));
            var absoluteUrlToLower = absoluteUrl.ToLowerInvariant();

            absoluteUrlToLower = forceWwwAndLowercase(filterContext, absoluteUrlToLower);
            absoluteUrlToLower = avoidTrailingSlashes(filterContext, absoluteUrlToLower);

            if (!absoluteUrl.Equals(absoluteUrlToLower))
            {
                filterContext.Result = new RedirectResult(absoluteUrlToLower, permanent: true);
            }
        }

        private static string avoidTrailingSlashes(ActionExecutingContext filterContext, string absoluteUrlToLower)
        {
            if (!isRootRequest(filterContext) && absoluteUrlToLower.EndsWith("/"))
                return absoluteUrlToLower.TrimEnd(new[] { '/' });

            return absoluteUrlToLower;
        }

        private static bool isRootRequest(ActionExecutingContext filterContext)
        {
            return filterContext.RequestContext.HttpContext.Request.Url.AbsolutePath == "/";
        }

        private static bool canIgnoreRequest(ActionExecutingContext filterContext)
        {
            return filterContext.IsChildAction || 
                   filterContext.HttpContext.Request.IsAjaxRequest() ||
                   filterContext.RequestContext.HttpContext.Request.Url.AbsoluteUri.Contains("?");
        }

        private static string forceWwwAndLowercase(ActionExecutingContext filterContext, string absoluteUrlToLower)
        {
            if (isLocalRequet(filterContext))
                return absoluteUrlToLower;

            if (absoluteUrlToLower.Contains("www"))
                return absoluteUrlToLower;

            return absoluteUrlToLower.Replace("http://", "http://www.")
                                     .Replace("https://", "https://www.");
        }

        private static bool isLocalRequet(ActionExecutingContext filterContext)
        {
            return filterContext.RequestContext.HttpContext.Request.IsLocal;
        }
    }
}
کلاس فوق، نگارش تکمیل شده ForceWww که پیشتر در این سایت دیده‌اید. توسط آن سه بررسی مختلف بر روی لینک جاری در حال پردازش صورت خواهد گرفت:
- تمام آدرس‌های سایت باید www داشته باشند؛ تا آدرس‌های آن یکنواخت شده و خصوصا مشکلات لاگین و نوشته شدن کوکی‌ها به ازای آدرس‌های مختلف و سر درگمی کاربران کاهش یابد.
- اگر آدرس جاری lowercase نباشد، تبدیل به نمونه lowercase شده و درخواست کننده، به آدرس جدید هدایت می‌شود. این مورد خصوصا جهت موتورهای جستجو برای تصحیح نتایج آن‌ها بسیار مفید است.
- اسلش انتهای لینک‌ها در صورت وجود حذف خواهد شد. این مورد نیز در کاهش تعداد خطاهای «محتوای تکراری» مؤثر است.
- اگر آدرسی، کوئری استرینگ داشته باشد از آن صرفنظر خواهد شد؛ زیرا ممکن است اطلاعات موجود در آن به کوچکی و بزرگی حروف حساس باشند.


ب) کاهش بار سایت توسط تولید خودکار Urlهایی که در بدو امر lowercase هستند

برای پیاده سازی این مطلب می‌توان از پروژه سورس باز «LowercaseRoutesMVC» استفاده کرد. سه فایل cs دارد که می‌توانید به پروژه خود اضافه کنید. پس از آن، هرجایی در پروژه خود routes.MapRoute دارید تبدیل کنید به routes.MapRouteLowercase .
به این ترتیب به صورت خودکار تمام Urlهای تولید شده توسط HTML helpers توکار ASP.NET MVC (و نه Urlهایی که دستی نوشته شده‌اند)، در حین درج در صفحه به صورت lowercase ظاهر خواهند شد (صرفنظر از اینکه نام‌های کنترلرها و یا اکشن متدهای تعریف شده camel case هستند یا خیر). مزیت این مساله کاهش یک مرحله Redirect است که در قسمت الف ذکر شد. در این کتابخانه کمکی نیز از آدرس‌هایی که دارای کوئری استرینگ باشند، صرفنظر می‌شود.