继承从baseapi controller base到deriver controller的路由
Inherit routing from baseapi controller base to deriver controller
我正在使用 .NET Core 3.0 Web API,我有这样一种情况,我在 baseapicontroller 中设置了前缀,还在派生控制器中设置了路由,理论上我认为它会像“baseapicontroller route/derived controller route”但它不是那样工作的,.net core 只选择派生的 class 路由并忽略 baseapicontroller 路由。
我试过使用属性“Route”和“RoutePrefix”,还是一样
[ApiController]
[RoutePrefix("api/v1")]
//[Route("api/v1")]
public class BaseApiController : ControllerBase
{
}
派生控制器
[Route("testing-route")]
public class TestRouteController : BaseApiController
{
[HttpGet]
public string test()
{
return "";
}
}
我希望 URL 是“api/v1/testing-route/test”,我不想重复 api/v1 在每个控制器中。而且我知道人们可能会说我可以使用“api/v1/[controller]/[action]”,但这不是我的要求。我想为控制器使用自定义路由。
期待得到任何好的答案。
谢谢
默认情况下内置的RouteAttribute
不支持控制器继承。但是,为了实现您的目标,您可以创建自定义 IControllerModelConvention
以应用在启动时考虑控制器继承的规则。
操作方法
为了避免与默认的 [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
:
我正在使用 .NET Core 3.0 Web API,我有这样一种情况,我在 baseapicontroller 中设置了前缀,还在派生控制器中设置了路由,理论上我认为它会像“baseapicontroller route/derived controller route”但它不是那样工作的,.net core 只选择派生的 class 路由并忽略 baseapicontroller 路由。
我试过使用属性“Route”和“RoutePrefix”,还是一样
[ApiController]
[RoutePrefix("api/v1")]
//[Route("api/v1")]
public class BaseApiController : ControllerBase
{
}
派生控制器
[Route("testing-route")]
public class TestRouteController : BaseApiController
{
[HttpGet]
public string test()
{
return "";
}
}
我希望 URL 是“api/v1/testing-route/test”,我不想重复 api/v1 在每个控制器中。而且我知道人们可能会说我可以使用“api/v1/[controller]/[action]”,但这不是我的要求。我想为控制器使用自定义路由。
期待得到任何好的答案。
谢谢
默认情况下内置的RouteAttribute
不支持控制器继承。但是,为了实现您的目标,您可以创建自定义 IControllerModelConvention
以应用在启动时考虑控制器继承的规则。
操作方法
为了避免与默认的 [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
: