Встроенный RouteAttribute
не будет работать с наследованием контроллера по умолчанию. Однако для достижения своей цели вы можете создать пользовательский IControllerModelConvention
, чтобы применить правило, учитывающее наследование контроллера во время запуска.
How-to
Чтобы избежать путаницы со значением по умолчанию [RouteAttribute]
, я создаю пользовательский MyRoutePrefixAttribute
вместо использования значения по умолчанию [RouteAttribute]
(не стесняйтесь использовать [RouteAttribute]
, если считаете, что это поведение должно быть переопределено):
[AttributeUsage(AttributeTargets.Class)]
public class MyRoutePrefixAttribute : Attribute
{
public MyRoutePrefixAttribute(string prefix) { Prefix = prefix; }
public string Prefix { get; }
}
И для поиска этого [MyRoutePrefixAttribute]
из цепочки наследования я создаю RoutePrefixConvention
, который будет рекурсивно объединять префиксы:
public class RoutePrefixConvention : IControllerModelConvention
{
public void Apply(ControllerModel controller)
{
foreach (var selector in controller.Selectors)
{
var prefixes = GetPrefixes(controller.ControllerType); // [prefix, parentPrefix, grandpaPrefix,...]
if(prefixes.Count == 0) continue;
// combine these prefixes one by one
var prefixRouteModels = prefixes.Select(p => new AttributeRouteModel(new RouteAttribute(p.Prefix)))
.Aggregate( (acc , prefix)=> AttributeRouteModel.CombineAttributeRouteModel(prefix, acc));
selector.AttributeRouteModel = selector.AttributeRouteModel != null ?
AttributeRouteModel.CombineAttributeRouteModel(prefixRouteModels, selector.AttributeRouteModel):
selector.AttributeRouteModel = prefixRouteModels;
}
}
private IList<MyRoutePrefixAttribute> GetPrefixes(Type controlerType)
{
var list = new List<MyRoutePrefixAttribute>();
FindPrefixesRec(controlerType, ref list);
list = list.Where(r => r!=null).ToList();
return list;
// find [MyRoutePrefixAttribute('...')] recursively
void FindPrefixesRec(Type type, ref List<MyRoutePrefixAttribute> results)
{
var prefix = type.GetCustomAttributes(false).OfType<MyRoutePrefixAttribute>().FirstOrDefault();
results.Add(prefix); // null is valid because it will seek prefix from parent recursively
var parentType = type.BaseType;
if(parentType == null) return;
FindPrefixesRec(parentType, ref results);
}
}
}
это соглашение будет НЕ влиятьпроизводительность: он ищет только все атрибуты [MyRoutePrefixAttribute]
по цепочке наследования во время запуска .
Наконец, не забудьте добавить это соглашение при запуске:
services.AddControllersWithViews(opts =>{
opts.Conventions.Add(new RoutePrefixConvention());
});
Демонстрация
Давайте создадим три контроллера для тестирования вышеуказанной RoutePrefixConvention:
RootApiController
-> BaseApiController
-> TestRouteController
[ApiController]
[MyRoutePrefix("root/controllers")]
public class RootApiController : ControllerBase { }
[MyRoutePrefix("api/v1")]
public class BaseApiController : RootApiController { }
[Route("testing-route")]
public class TestRouteController : BaseApiController
{
[HttpGet]
public string test()
{
return "abc";
}
}
Теперь при доступе к /root/controllers/api/v1/testing-route
мы получим строку abc
: