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
中添加自定义路由令牌解析?
我目前的解决方案:
- 硬编码路由
- 创建将包含带有自定义令牌的路由的自定义属性,以及将解析自定义令牌并生成
Route
属性的源生成器。
区域正是您所需要的。如 .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) {}
}
我正在为我的项目使用 https://github.com/ardalis/ApiEndpoints(每个控制器一个动作),我 运行 认为 [Route("[controller]")]
不太适合我,因为控制器看起来像这样:
我需要更像 [Route("[namespace]")]
的东西,但 ASP.NET Core 不支持它。
有没有办法在 Startup.cs
中添加自定义路由令牌解析?
我目前的解决方案:
- 硬编码路由
- 创建将包含带有自定义令牌的路由的自定义属性,以及将解析自定义令牌并生成
Route
属性的源生成器。
区域正是您所需要的。如 .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) {}
}