【问题标题】:Automatically generate lowercase dashed routes in ASP.NET Core在 ASP.NET Core 中自动生成小写虚线路由
【发布时间】:2017-03-13 02:00:34
【问题描述】:

ASP.NET Core 默认使用像 http://localhost:5000/DashboardSettings/Index 这样的 CamelCase-Routes。但是我想使用由破折号分隔的小写路由:http://localhost:5000/dashboard-settings/index 它们更常见和一致,因为我的应用程序扩展了一个运行 Wordpress 的网站,该网站也有带有破折号的小写 URL。

我了解到我可以使用路由选项将网址更改为小写:

services.ConfigureRouting(setupAction => {
    setupAction.LowercaseUrls = true;
});

这行得通,但给了我没有任何分隔符的网址,例如 http://localhost:5000/dashboardsettings/index,它们的可读性很差。我可以使用像

这样的路由属性来定义自定义路由
[Route("dashboard-settings")]
class DashboardSettings:Controller {
    public IActionResult Index() {
        // ...
    }
}

但这会导致额外的工作并且容易出错。我更喜欢搜索大写字符的自动解决方案,在它们之前插入一个破折号并使大写字符小写。对于旧的 ASP.NET,这不是一个大问题,但在 ASP.NET Core 上,我看不到如何处理这个问题的方向。

这里有什么方法?我需要某种接口,我可以在其中生成 url(例如标签助手)并用破折号分隔符替换 CamelCase。然后我需要另一种路由接口,以便将破折号分隔符 url 转换回 CamelCase,以便与我的控制器/动作名称正确匹配。

【问题讨论】:

  • 服务的命名空间是什么?以及在哪里插入 ConfigureRouting?
  • services.ConfigureRouting 似乎不再存在 - 我认为您现在使用 services.AddRouting 代替

标签: c# asp.net-core url routes asp.net-core-mvc


【解决方案1】:

在 ASP.NET Core 版本中更新 >= 2.2

为此,首先创建SlugifyParameterTransformer类应该如下:

public class SlugifyParameterTransformer : IOutboundParameterTransformer
{
    public string TransformOutbound(object value)
    {
        // Slugify value
        return value == null ? null : Regex.Replace(value.ToString(), "([a-z])([A-Z])", "$1-$2").ToLower();
    }
}

对于 ASP.NET Core 2.2 MVC:

Startup 类的ConfigureServices 方法中:

services.AddRouting(option =>
{
    option.ConstraintMap["slugify"] = typeof(SlugifyParameterTransformer);
});

并且路由配置应该如下:

app.UseMvc(routes =>
{
    routes.MapRoute(
       name: "default",
       template: "{controller:slugify}/{action:slugify}/{id?}",
       defaults: new { controller = "Home", action = "Index" });
 });

对于 ASP.NET Core 2.2 Web API:

Startup 类的ConfigureServices 方法中:

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc(options => 
    {
        options.Conventions.Add(new RouteTokenTransformerConvention(new SlugifyParameterTransformer()));
    }).SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
}

对于 ASP.NET Core >=3.0 MVC:

Startup 类的ConfigureServices 方法中:

services.AddRouting(option =>
{
    option.ConstraintMap["slugify"] = typeof(SlugifyParameterTransformer);
});

并且路由配置应该如下:

app.UseEndpoints(endpoints =>
{
    endpoints.MapAreaControllerRoute(
        name: "AdminAreaRoute",
        areaName: "Admin",
        pattern: "admin/{controller:slugify=Dashboard}/{action:slugify=Index}/{id:slugify?}");

    endpoints.MapControllerRoute(
        name: "default",
        pattern: "{controller:slugify}/{action:slugify}/{id:slugify?}",
        defaults: new { controller = "Home", action = "Index" });
});

对于 ASP.NET Core >=3.0 Web API:

Startup类的ConfigureServices方法中:

services.AddControllers(options => 
{
    options.Conventions.Add(new RouteTokenTransformerConvention(new SlugifyParameterTransformer()));
});

对于 ASP.NET Core >=3.0 Razor 页面

Startup 类的ConfigureServices 方法中:

services.AddRazorPages(options => 
{
    options.Conventions.Add(new PageRouteTransformerConvention(new SlugifyParameterTransformer()));
});

这将使/Employee/EmployeeDetails/1 路由到/employee/employee-details/1

【讨论】:

  • Bazillion 喜欢你!
  • 不适用于[Route("MyController/MyAction")]MyController 变为 my-controller,但 MyAction 保持不变。
  • @МаксимКошевой 你已经在你的路由中添加了纯字符串,所以你必须自己写。你可以写:[Route("my-controller/my-action")][Route("[controller]/[action]")]
  • 哦,所以这个解决方案只影响令牌,明白了。整个 URL 受 LowercaseUrls 影响。如果有 slugify 的东西就好了
【解决方案2】:

在这里聚会有点晚了,但是.. 可以通过实现 IControllerModelConvention 来做到这一点。

 public class DashedRoutingConvention : IControllerModelConvention
 {
        public void Apply(ControllerModel controller)
        {
            var hasRouteAttributes = controller.Selectors.Any(selector =>
                                               selector.AttributeRouteModel != null);
            if (hasRouteAttributes)
            {
                // This controller manually defined some routes, so treat this 
                // as an override and not apply the convention here.
                return;
            }

            foreach (var controllerAction in controller.Actions)
            {
                foreach (var selector in controllerAction.Selectors.Where(x => x.AttributeRouteModel == null))
                {
                    var template = new StringBuilder();

                    if (controllerAction.Controller.ControllerName != "Home")
                    {
                        template.Append(PascalToKebabCase(controller.ControllerName));
                    }

                    if (controllerAction.ActionName != "Index")
                    {
                        template.Append("/" + PascalToKebabCase(controllerAction.ActionName));
                    }

                    selector.AttributeRouteModel = new AttributeRouteModel()
                    {
                        Template = template.ToString()
                    };
                }
            }
        }

        public static string PascalToKebabCase(string value)
        {
            if (string.IsNullOrEmpty(value))
                return value;

            return Regex.Replace(
                value,
                "(?<!^)([A-Z][a-z]|(?<=[a-z])[A-Z])",
                "-$1",
                RegexOptions.Compiled)
                .Trim()
                .ToLower();
        }
}

然后在 Startup.cs 中注册

public void ConfigureServices(IServiceCollection services)
{
    // Add framework services.
    services.AddMvc(options => options.Conventions.Add(new DashedRoutingConvention()));
}

可以在这里找到更多信息和示例https://docs.microsoft.com/en-us/aspnet/core/mvc/controllers/routing

【讨论】:

  • 它有效,但如果我有 /Home/UrlWithDash 操作方法和 UrlWithDashController,则会引发异常(不明确的引用)。因此,在命名控制器和操作时要小心。
【解决方案3】:

我正在使用 Asp.NetCore 2.0.0 和 Razor Pages(不需要显式控制器),所以只需要:

  1. 启用小写 URL:

    services.AddRouting(options =&gt; options.LowercaseUrls = true);

  2. 创建一个名为Dashboard-Settings.cshtml的文件,生成的路由变成/dashboard-settings

【讨论】:

    【解决方案4】:

    复制自 ASP.NET Core 3.0(与 2.2 相同)documentation

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddMvc(options =>
        {
            options.Conventions.Add(new RouteTokenTransformerConvention(
                                         new SlugifyParameterTransformer()));
        });
    }
    
    public class SlugifyParameterTransformer : IOutboundParameterTransformer
    {
        public string TransformOutbound(object value)
        {
            if (value == null) { return null; }
    
            // Slugify value
            return Regex.Replace(value.ToString(), "([a-z])([A-Z])", "$1-$2").ToLower();
        }
    }
    

    【讨论】:

    • 由于某种原因这对我不起作用,而使用ConstraintMap的答案可以
    • 这是最终对我有用的解决方案。
    • 为什么TransformOutbound方法接收的是object参数而不是string
    【解决方案5】:

    感谢您提供的信息,但最好过滤选择器,以便跳过具有自定义路由模板的选择器:例如 [HttpGet("/[controller]/{id}")]

    foreach (var selector in controllerAction.Selectors
                                             .Where(x => x.AttributeRouteModel == null))
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-07-21
      • 1970-01-01
      • 2019-10-30
      • 1970-01-01
      • 1970-01-01
      • 2016-11-12
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多