ASP.NET 核心 API:添加自定义路由令牌解析器

ASP.NET Core API: Add custom route token resolver

我正在为我的项目使用 https://github.com/ardalis/ApiEndpoints(每个控制器一个动作),我 运行 认为 [Route("[controller]")] 不太适合我,因为控制器看起来像这样:

我需要更像 [Route("[namespace]")] 的东西,但 ASP.NET Core 不支持它。

有没有办法在 Startup.cs 中添加自定义路由令牌解析?

我目前的解决方案:

区域正是您所需要的。如 .net 核心文档中所述:

Areas are an ASP.NET feature used to organize related functionality into a group as a separate:

  • Namespace for routing.
  • Folder structure for views and Razor Pages.

要使用区域路由,您只需将它们添加到启动项中。

像这样:

app.UseEndpoints(endpoints =>
{
    endpoints.MapControllerRoute(
        name: "MyArea",
        pattern: "{area:exists}/{controller=Home}/{action=Index}/{id?}");

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

之后路线将遵循 Areas 文件夹内的文件夹结构。 有关详细信息,请访问 Areas in ASP.NET Core 文档。

您可以通过实施 IApplicationModelConvention 来实现。更多信息在这里:Custom routing convention

public class NamespaceRoutingConvention : IApplicationModelConvention
{
    public void Apply(ApplicationModel application)
    {
        foreach (var controller in application.Controllers)
        {
            controller.Selectors[0].AttributeRouteModel = new AttributeRouteModel()
            {
                Template = controller.ControllerType.Namespace.Replace('.', '/') + "/[controller]}"
            };
            
        }
    }
}

然后添加到startup.cs

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

非常感谢@Kahbazi 为我指明了正确的方向!

这是我想出的:

private class CustomRouteToken : IApplicationModelConvention
{
    private readonly string _tokenRegex;
    private readonly Func<ControllerModel, string?> _valueGenerator;

    public CustomRouteToken(string tokenName, Func<ControllerModel, string?> valueGenerator)
    {
        _tokenRegex = $@"(\[{tokenName}])(?<!\[(?=]))";
        _valueGenerator = valueGenerator;
    }

    public void Apply(ApplicationModel application)
    {
        foreach (var controller in application.Controllers)
        {
            string? tokenValue = _valueGenerator(controller);
            UpdateSelectors(controller.Selectors, tokenValue);
            UpdateSelectors(controller.Actions.SelectMany(a => a.Selectors), tokenValue);
        }
    }

    private void UpdateSelectors(IEnumerable<SelectorModel> selectors, string? tokenValue)
    {
        foreach (var selector in selectors.Where(s => s.AttributeRouteModel != null))
        {
            selector.AttributeRouteModel.Template = InsertTokenValue(selector.AttributeRouteModel.Template, tokenValue);
            selector.AttributeRouteModel.Name = InsertTokenValue(selector.AttributeRouteModel.Name, tokenValue);
        }
    }

    private string? InsertTokenValue(string? template, string? tokenValue)
    {
        if (template is null)
        {
            return template;
        }

        return Regex.Replace(template, _tokenRegex, tokenValue);
    }
}

Startup.cs中配置令牌(这可以用扩展方法包装):

services.AddControllers(options => options.Conventions.Add(
    new CustomRouteToken(
        "namespace",
        c => c.ControllerType.Namespace?.Split('.').Last()
    ));

之后自定义令牌可用于路由:

[ApiController]
[Route("api/[namespace]")]
public class Create : ControllerBase {}
[ApiController]
public class Get : ControllerBase 
{
    [HttpGet("api/[namespace]/{id}", Name = "[namespace]_[controller]")]
    public ActionResult Handle(int id) {}
}