将路由从baseapi控制器继承到派生控制器

Sye*_*eer 4 c# asp.net-core-webapi asp.net-core-3.0

我正在使用.NET Core 3.0 Web API,我有一种情况,我在baseapicontroller中设置了前缀,并在派生控制器中设置了路由,理论上我认为它会像“ baseapicontroller路由/派生控制器路由”一样工作,但是它不是这样工作的,.net core 仅选择派生类路由并忽略 baseapicontroller 路由。

我尝试使用属性“ Route ”和“ RoutePrefix ”,它仍然是相同的。

[ApiController]
[RoutePrefix("api/v1")]
//[Route("api/v1")]
public class BaseApiController : ControllerBase
{
}
Run Code Online (Sandbox Code Playgroud)

派生控制器

[Route("testing-route")]
public class TestRouteController : BaseApiController
{
    [HttpGet]
    public string test()
    {

        return "";
    }
}
Run Code Online (Sandbox Code Playgroud)

我希望 URL 为“ api/v1/testing-route/test ”,我不想在每个控制器中重复api/v1 。而且我知道人们可能会说我可以使用“ api/v1/[controller]/[action] ”,但这不是我的要求。我想使用控制器的自定义路由。

期待得到任何好的答案。

谢谢

itm*_*nus 7

默认情况下,内置函数RouteAttribute不适用于控制器继承。但是,为了实现您的目标,您可以创建一个自定义IControllerModelConvention来应用在启动时考虑控制器继承的规则。

如何

为了避免与默认值混淆[RouteAttribute],我创建了一个自定义值MyRoutePrefixAttribute而不是使用默认值(如果您认为应该覆盖此行为,请[RouteAttribute]随意使用):[RouteAttribute]

[AttributeUsage(AttributeTargets.Class)]
public class MyRoutePrefixAttribute : Attribute
{
    public MyRoutePrefixAttribute(string prefix) { Prefix = prefix; }
    public string Prefix { get; }
}
Run Code Online (Sandbox Code Playgroud)

为了[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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

此约定不会影响性能:它仅在启动时[MyRoutePrefixAttribute]通过继承链搜索所有属性。

最后,不要忘记在您的启动中添加此约定:

services.AddControllersWithViews(opts =>{
    opts.Conventions.Add(new RoutePrefixConvention());
});
Run Code Online (Sandbox Code Playgroud)

演示

让我们创建三个控制器来测试上面的 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";
    }
}
Run Code Online (Sandbox Code Playgroud)

现在访问时/root/controllers/api/v1/testing-route,我们将得到一串 abc

通过继承自定义路由前缀