using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.ApplicationModels; using Microsoft.AspNetCore.Mvc.Infrastructure; namespace MAX.Framework.Conventions; /// 为 API 控制器的属性路由统一添加 api 前缀,已含前缀的路由保持不变。 public sealed class ApiRoutePrefixConvention : IApplicationModelConvention { public void Apply(ApplicationModel application) { foreach (var controller in application.Controllers) { if (!controller.Attributes.OfType().Any() && !controller.ControllerType.Assembly.IsDefined(typeof(ApiControllerAttribute), true)) { continue; } var hasControllerRoute = controller.Selectors.Any(selector => selector.AttributeRouteModel != null); foreach (var selector in controller.Selectors) { if (selector.AttributeRouteModel is { } route) { AddPrefix(route); } } foreach (var action in controller.Actions) foreach (var selector in action.Selectors) { // 绝对动作路由会覆盖控制器路由,因此需要单独添加前缀。 if (selector.AttributeRouteModel is { } route && (!hasControllerRoute || route.IsAbsoluteTemplate)) { AddPrefix(route); } } } } private static void AddPrefix(AttributeRouteModel route) { var template = route.Template ?? ""; var absolute = route.IsAbsoluteTemplate; template = template.StartsWith("~/", StringComparison.Ordinal) ? template[2..] : template.TrimStart('/'); if (!template.Equals("api", StringComparison.OrdinalIgnoreCase) && !template.StartsWith("api/", StringComparison.OrdinalIgnoreCase)) { template = string.IsNullOrEmpty(template) ? "api" : $"api/{template}"; } route.Template = absolute ? $"~/{template}" : template; } }