امکان تغییر شکل سراسری URLهای تولیدی توسط برنامه‌های ASP.NET Core 2.2
اندازه‌ی قلم متن
تخمین مدت زمان مطالعه‌ی مطلب: دو دقیقه

فرض کنید اکشن متدی را به صورت زیر تعریف کرده‌اید:
namespace MvcHealthCheckTest.Controllers
{
    public class HomeController : Controller
    {
        public IActionResult ViewDetails()
        {
            return View();
        }
زمانیکه لینکی را برای آن تعریف می‌کنید:
<a asp-controller="Home" asp-action="ViewDetails">View Details</a>
در حین رندر نهایی آن، چنین شکلی را پیدا می‌کند (قسمت ViewDetails آن دقیقا با نام اکشن متد متناظر و بزرگی و کوچکی حروف آن تطابق دارد):
 https://localhost:5001/Home/ViewDetails
اکنون قصد داریم، یک چنین URLهایی را در کل برنامه به صورت زیر رندر کنیم:
الف) تمام مسیرها lowercase باشند (مناسب برای SEO؛ از این جهت که تعداد مسیرهای تکراری یکسان با حروف بزرگ و کوچک، به حداقل می‌رسند)
ب) ViewDetailsها تبدیل به view-details شوند. یعنی بین حروف جدا شده‌ی با کلمات بزرگ و کوچک، یک - قرار گیرد.

انجام یک چنین تغییرات سراسری در ASP.NET Core 2.2 با معرفی IOutboundParameterTransformer میسر شده‌است:
using System.Text.RegularExpressions;
using Microsoft.AspNetCore.Routing;

public class CustomUrlTransformer : IOutboundParameterTransformer
{
    public string TransformOutbound(object value)
    {
        return value == null ? null : Regex.Replace(value.ToString(), "([a-z])([A-Z])", "$1-$2");
    }
}
در اینجا یک تغییرشکل دهنده‌ی سراسری URLها را با پیاده سازی اینترفیس IOutboundParameterTransformer تدارک دیده‌ایم که توسط آن بین حروف بزرگ و کوچک، یک - قرار می‌دهد.

روش معرفی آن نیز به سیستم به صورت زیر است:
ابتدا نیاز است این Transformer به صورت یک ConstraintMap جدید به سیستم مسیریابی اضافه شود. در اینجا امکان تنظیم تولید LowercaseUrls نیز وجود دارد. به همین جهت نیازی نیست تا در متد TransformOutbound فوق، در انتهای کار، متد ToLower را نیز فراخوانی کنیم:
namespace MvcTest
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
    // ...
            services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
            services.AddRouting(option =>
                        {
                            option.ConstraintMap.Add(key: "transformer1", value: typeof(CustomUrlTransformer));
                            option.LowercaseUrls = true;
                        });
        }
سپس باید قالب مسیریابی را نیز به صورت زیر تغییر دهیم و کلید ConstraintMap اضافه شده‌ی فوق را به اجزای آن اضافه کنیم:
namespace MvcTest
{
    public class Startup
    {
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
    // ...
            app.UseMvc(routes =>
            {
                routes.MapRoute(
                    name: "default",
                    template: "{controller:transformer1}/{action:transformer1}/{id?}",
                    defaults: new { controller = "Home", action = "Index" }
                    );
            });
        }
که template و defaults آن را به صورت خلاصه‌ی زیر نیز می‌توان نوشت:
app.UseMvc(routes =>
{
    routes.MapRoute(
        name: "default",
        template: "{controller:transformer1=Home}/{action:transformer1=Index}/{id?}"
    );
});
اکنون اگر برنامه را اجرا کنیم، اینبار URL تولیدی، بجای https://localhost:5001/Home/ViewDetails به صورت زیر رندر می‌شود:
https://localhost:5001/home/view-details

  • #
    ‫۵ سال و ۶ ماه قبل، سه‌شنبه ۷ اسفند ۱۳۹۷، ساعت ۲۰:۴۶

    وقتی من در asp-route-title مقدار فارسی و با فاصله مینویسم، url موجود در تصویر تولید میشود، که در address bar مثلا می‌شود «علی%22مجیدی» در صورتی که من میخواهم به جای اون درصد مقدار - باشد.


    • #
      ‫۵ سال و ۶ ماه قبل، سه‌شنبه ۷ اسفند ۱۳۹۷، ساعت ۲۲:۵۸
      - به همین صورت باید باشد؛ در غیر اینصورت مقدار کوئری استرینگ دریافتی، encoding صحیح خودش را از دست خواهد داد و در برنامه قابلیت پردازش نخواهد داشت. مگر اینکه در اکشن متد مدنظر هم این encoding غیراستاندارد را دستی پردازش کنید.
      - موردی که مدنظر شما است احتمالا تولید slug است که در مطلب «بهینه سازی برنامه‌های وب ASP.NET برای موتورهای جستجو (SEO)» متدی برای آن ارائه شده و در برنامه پردازش نمی‌شود؛ فقط جنبه‌ی نمایشی و یا ارائه‌ی به موتورهای جستجو را دارد. نحوه‌ی استفاده‌ی از آن هم در یک View به این صورت است:
      @using System.Text;
      @using System.Text.RegularExpressions;
      
      @functions
      {
         private static string RemoveAccent(string text) { /* defined in https://www.dntips.ir/post/1529*/  }
         public static string GenerateSlug(string title, int maxLenghtSlug = 50) { /* defined in https://www.dntips.ir/post/1529*/   }
      }
      
      @{
        var value = GenerateSlug("this is a test");
      }
      <a asp-controller="Home" asp-action="ViewDetails" asp-route-id="@value">View Details</a>
      • #
        ‫۵ سال و ۶ ماه قبل، چهارشنبه ۸ اسفند ۱۳۹۷، ساعت ۰۰:۳۳
        به نظر شما استفاده از یک TagHelper  سفارشی راه درستی هست برای حل این مورد
        public class AnchorTagHelper : TagHelper {
                /// <summary>
                /// The name of the action method.
                /// </summary>
                [HtmlAttributeName ("asp-action")]
                public string Action { get; set; }
        
                /// <summary>
                /// The name of the controller.
                /// </summary>
                [HtmlAttributeName ("asp-controller")]
                public string Controller { get; set; }
        
                /// <summary>
                /// The name of the area.
                /// </summary>
                [HtmlAttributeName ("asp-area")]
                public string Area { get; set; }
        
                [HtmlAttributeName ("asp-route")]
                public string Route { get; set; }
        
                // Can be async Task
                public override void Process (TagHelperContext context, TagHelperOutput output) {
                    output.TagName = "a";
        
                    string result = string.Empty;
                    if (!string.IsNullOrWhiteSpace (Area)) {
                        result += "/" + Area;
                    }
        
                    if (!string.IsNullOrWhiteSpace (Controller)) {
                        result += "/" + Controller;
                    }
        
                    if (!string.IsNullOrWhiteSpace (Action)) {
                        result += "/" + Action;
                    }
                    if (!string.IsNullOrWhiteSpace (Route)) {
                        Route = ToFriendlyHref (Route);
                        result += "/" + Route;
                    }
        
                    output.Attributes.SetAttribute ("href", result.ToLowerInvariant ());
                    //output.Content.SetContent (currentAttribute.ToString ());
                }
        
                private string ToFriendlyHref (object value) {
                    string text = value.ToString ();
                    List<char> illegalChars = new List<char> () { ' ', '.', '#', '%', '&', '*', '{', '}', '\\', ':', '<', '>', '?', ';', '@', '=', '+', '$', ',' };
                    illegalChars.ForEach (c => {
                        text = text.Replace (c.ToString (), "-");
                    });
                    return text;
                }
            }

        • #
          ‫۵ سال و ۶ ماه قبل، چهارشنبه ۸ اسفند ۱۳۹۷، ساعت ۰۱:۱۱
          - مساله‌ای که با یک متد الحاقی قابل فراخوانی در فایل‌های Razor قابل حل است، نیازی به اینکار ندارد.
          - روشی که برای تولید Url استفاده کردید باید اصلاح شود: «نحوه صحیح تولید Url در ASP.NET MVC» و در اینجا UrlHelper به صورت زیر قابل دریافت است:
          var urlHelper = ViewContext.HttpContext.Items.Values.OfType<IUrlHelper>().FirstOrDefault();
          • #
            ‫۵ سال و ۶ ماه قبل، چهارشنبه ۸ اسفند ۱۳۹۷، ساعت ۰۵:۲۶
            از آنجایی که ما میتونیم n عدد asp-route-param داشته باشیم، نحوه پیاده سازی این در IURLHelper به چه شکل بایستی باشد؟منظورم اینه که قسمت آخر route یعنی کلمه param متغییر هستش، هرچیزی میتونه باشه.
            • #
              ‫۵ سال و ۶ ماه قبل، چهارشنبه ۸ اسفند ۱۳۹۷، ساعت ۱۱:۱۳
              anonymous object در آخر تبدیل به یک ()var routeValues = new RouteValueDictionary می‌شود.
  • #
    ‫۵ سال و ۶ ماه قبل، سه‌شنبه ۷ اسفند ۱۳۹۷، ساعت ۲۳:۰۱
    یک نکته‌ی تکمیلی: بهبود کارآیی IOutboundParameterTransformer تهیه شده
    با توجه به مطلب « چگونه Regex سریعتری داشته باشیم؟ » بهتر است regex نوشته شده را کامپایل کرد تا به حداکثر کارآیی رسید:
    using System.Text.RegularExpressions;
    using Microsoft.AspNetCore.Routing;
    
    public class CustomUrlTransformer : IOutboundParameterTransformer
    {
        private static readonly Regex _camelCasingRegEx = new Regex("([a-z])([A-Z])", RegexOptions.Compiled);
    
        public string TransformOutbound(object value)
        {
            return value == null ? null : _camelCasingRegEx.Replace(value.ToString(), "$1-$2");
        }
    }
  • #
    ‫۵ سال و ۵ ماه قبل، شنبه ۱۰ فروردین ۱۳۹۸، ساعت ۱۸:۰۷
    سلام؛ در خصوص redirect کردن آدرس‌های دارای {id} به {id}/{Name} (مثل سایت جاری) در برنامه‌های net core ممنون میشم راهنمایی بفرمایید.
  • #
    ‫۵ سال و ۵ ماه قبل، یکشنبه ۱۱ فروردین ۱۳۹۸، ساعت ۱۹:۳۰
    سلام؛ اگر آدرس به صورت p Ost  یا POST وارد شود در دسترس هست. چطور میشه این مشکل را برطرف کرد که فقط با post (حروف کوچک) در دسترس باشد؟  
    • #
      ‫۵ سال و ۵ ماه قبل، یکشنبه ۱۱ فروردین ۱۳۹۸، ساعت ۱۹:۴۷
      نیاز به redirect permanent دارد یا نوعی URL rewrite است. روش اینکار، نوشتن یک IRule مانند RedirectWwwRule است:
      app.UseRewriter(new RewriteOptions().Add(new RedirectLowerCaseRule()));
      public class RedirectLowerCaseRule : IRule
      {
          public int StatusCode { get; } = (int)HttpStatusCode.MovedPermanently;
      
          public void ApplyRule(RewriteContext context)
          {
              HttpRequest request = context.HttpContext.Request;
              PathString path = context.HttpContext.Request.Path;
              HostString host = context.HttpContext.Request.Host;
      
              if (path.HasValue && path.Value.Any(char.IsUpper) || host.HasValue && host.Value.Any(char.IsUpper))
              {
                  HttpResponse response = context.HttpContext.Response;
                  response.StatusCode = StatusCode;
                  response.Headers[HeaderNames.Location] = (request.Scheme + "://" + host.Value + request.PathBase + request.Path).ToLower() + request.QueryString;
                  context.Result = RuleResult.EndResponse; 
              }
              else
              {
                  context.Result = RuleResult.ContinueRules;
              } 
          }
      }
  • #
    ‫۲ سال و ۶ ماه قبل، سه‌شنبه ۳ اسفند ۱۴۰۰، ساعت ۲۱:۰۱
    یک نکته‌ی تکمیلی: از LowercaseQueryStrings استفاده نکنید!

    به همراه نکات این مطلب، خاصیت دیگری به نام LowercaseQueryStrings نیز وجود دارد و به همان نحوی که LowercaseUrls تنظیم می‌شود، قابل تنظیم است، اما نحوه‌ی پردازش آن به صورت زیر است:
     if (_options.LowercaseUrls && _options.LowercaseQueryStrings) 
     { 
         queryString = queryString.ToLowerInvariant(); 
     }
    یعنی کل کوئری استرینگ و حتی محتوای آن‌را هم تغییر می‌دهد. در این حالت برای مثال اگر اطلاعات کوئری استرینگ شما به همراه داده‌ای رمزنگاری شده باشد، به علت از دست رفتن و تخریب اصل اطلاعات، دیگر قابلیت رمزگشایی را نخواهد داشت.