I am using .NET Core 3.0 Web API, I have a situation where I have set the prefix in the baseapicontroller and also set the route in the derived controller, theoretically I thought that it would work like "baseapicontroller route/derived controller route" but it is not working like that, .net core picks only the derived class route and ignore the baseapicontroller route.
I have tried to use the attribute "Route" and "RoutePrefix", it is still the same.
[ApiController]
[RoutePrefix("api/v1")]
//[Route("api/v1")]
public class BaseApiController : ControllerBase
{
}
derived controller
[Route("testing-route")]
public class TestRouteController : BaseApiController
{
    [HttpGet]
    public string test()
    {
        return "";
    }
}
I want the URL to be "api/v1/testing-route/test", I don't want to repeat api/v1 in every controller. and also I know people might say that I can use "api/v1/[controller]/[action]" but that is not my requirement. I want to use the custom route for the controller.
Looking forward to get any good answers.
Thanks
The built-in RouteAttribute will not work with controller inheritance by default. However, to achieve your goal, you could create a custom IControllerModelConvention to apply a rule that takes consideration of controller inheritance at startup-time.
To avoid mixing up with the default [RouteAttribute], I create a custom MyRoutePrefixAttribute instead of using the default [RouteAttribute] (Feel free to use the [RouteAttribute] if you think this behavior should be overrided ):
[AttributeUsage(AttributeTargets.Class)]
public class MyRoutePrefixAttribute : Attribute
{
    public MyRoutePrefixAttribute(string prefix) { Prefix = prefix; }
    public string Prefix { get; }
}
And to lookup this [MyRoutePrefixAttribute] from the inheritance chain, I create a RoutePrefixConvention that will combine the prefixes recursively:
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);
        }
    }
}
this convention will NOT influence the performance: it only searches all the [MyRoutePrefixAttribute] attributes through the inheritance chain at startup time. 
Finally, don't forget to add this convention within your startup:
services.AddControllersWithViews(opts =>{
    opts.Conventions.Add(new RoutePrefixConvention());
});
let's create three controllers to test the above 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";
    }
}
Now when accessing /root/controllers/api/v1/testing-route, We'll get a string of  abc:

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With