Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Inherit routing from baseapi controller base to deriver controller

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

like image 331
Syed Abdul Qadeer Avatar asked Oct 30 '25 12:10

Syed Abdul Qadeer


1 Answers

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.

How-to

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());
});

Demo

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:

custom route prefix through inheritance

like image 152
itminus Avatar answered Nov 01 '25 02:11

itminus



Donate For Us

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