66

I am playing around with the idea of having a base controller that uses a generic repository to provide the basic CRUD methods for my API controllers so that I don't have to duplicate the same basic code in each new controller. But am running into problems with the routing attribute being recognized when it's in the base controller. To show exactly what the problem I'm having I've created a really simple WebAPI controller.

When I have a Get method in the main Controller and it inherits from the ApiController directly I don't have any problems and this works as expected.

[RoutePrefix("admin/test")]
public class TestController : ApiController
{
    [Route("{id:int:min(1)}")]
    public string Get(int id)
    {
        return "Success";
    }
}

When I move the Get method into a base controller it is returning the contents of the 404 page.

[RoutePrefix("admin/test")]
public class TestController : TestBaseController
{

}

public class TestBaseController : ApiController
{
    [Route("{id:int:min(1)}")]
    public string Get(int id)
    {
        return "Success";
    }
}

Some more interesting notes:

  • I can access the action at GET /Test/1. So it is finding it based on the default route still.

  • When I try to access POST /admin/test, it returns the following JSON

    { "Message":"No HTTP resource was found that matches the request URI 'http://test.com/admin/test'.", "MessageDetail":"No type was found that matches the controller named 'admin'." }

Does anyone know of a way to get the routing to work with attributes from a base controller?

Alexander Abakumov
  • 13,617
  • 16
  • 88
  • 129
Jeff Treuting
  • 13,910
  • 8
  • 36
  • 47
  • 3
    I'm trying to accomplish the exact same thing - use a base api controller to handle CRUD - I'm surprised it's not more common. This is the first time I've seen mention of someone else attempting this, and I'm now stuck at the routing inheritance problem. Could you share what you ended up doing? – Brett Dec 31 '13 at 12:40
  • 2
    I have the base controller have protected methods that implement the CRUD operations and then when I create a new controller I have to create the public method and have it call the protected method. This way I can put the routes on the method in the controller and not in the base controller, and not have to duplicate the code everywhere. I don't love it, but the alternative was to not use Route attributes and that wasn't going to work in my situation for various reasons. – Jeff Treuting Dec 31 '13 at 17:15
  • Thanks for the reply. I went with something similar - just calling base.[Get|Post|Put|Delete]() and passing through the parameters. It's not ideal, but I suppose the upside is that it is more obvious for people unfamiliar with the code where to put custom CRUD actions. Have you found any other resources online that discuss using a base controller to handle CRUD operations? My searching yielded me no results. – Brett Dec 31 '13 at 21:59
  • Please vote for the issue I opened if you want them to add inheritance to AttributeRouting - https://aspnetwebstack.codeplex.com/workitem/1688 – Tim Hardy Feb 16 '14 at 05:04

3 Answers3

82

Attribute routes cannot be inherited. This was a deliberate design decision. We didn't feel right and didn't see valid scenarios where it would make sense to inherit them.

Could you give a more realistic scenario as to where you would want to use this?

[Update(3/24/2014)]
In the upcoming 5.2 release of MVC Web API, there is going to be an extensibility point called System.Web.Http.Routing.IDirectRouteProvider through which you can enable the inheritance scenario that you are looking for here. You could try this yourself using the latest night builds(documentation on how to use night builds is here)

[Update(7/31/2014)]
Example of how this can be done in Web API 2.2 release:

config.MapHttpAttributeRoutes(new CustomDirectRouteProvider());

//---------

public class CustomDirectRouteProvider : DefaultDirectRouteProvider
{
    protected override IReadOnlyList<IDirectRouteFactory> 
    GetActionRouteFactories(HttpActionDescriptor actionDescriptor)
    {
        // inherit route attributes decorated on base class controller's actions
        return actionDescriptor.GetCustomAttributes<IDirectRouteFactory>
        (inherit: true);
    }
}
Kiran
  • 56,921
  • 15
  • 176
  • 161
  • 4
    Here is what I was going to play around with. I have a generic repository and was going to create a RepositoryBaseApiController that loaded up a IRepository to use for the basic CRUD actions. That way if I needed to provide a real simple WebAPI for an entity, I could just create a SomeEntityController and inherit from RepositoryBaseApiController and it would take care of all the basics for me. I wouldn't have to create the actions, validation checks and routes each time. I would use RoutePrefix() on the main controller but the base controller would have the relative Route – Jeff Treuting Nov 14 '13 at 22:38
  • Thanks for the scenario. We considered this scenario but the problem in some scenarios users could be having very specific route templates to their actions in the base controller in which case inheriting this to the sub class is incorrect... – Kiran Nov 14 '13 at 23:07
  • I have a bunch of controllers that handle file converters. Each one has common endpoints having to do with uploading the file and the only option I see without inheritance is to repeat code or delegate to helpers immediately inside the action, very ugly. AttributeRouting supported this why wouldn't the native implementation https://github.com/mccalltd/AttributeRouting/wiki/Inherit-Routes-from-Actions-in-Base-Controllers – parliament Dec 03 '13 at 22:05
  • A common use-case where inheritance of attribute routed actions is desirable is versioning REST apis. For example, I'd like to have different classes with different [RoutePrefix] attributes for each version of an API. The vCurrent API would be a controller that inherits from the latest API controller, and specifies [RoutePrefix("api/current")]. I'm assuming it's possible to replace the default controller discovery logic for attribute routing... – crimbo Dec 19 '13 at 00:36
  • @KiranChalla so is there no workaround here? I am trying to accomplish the exact same thing as Jeff. – Brett Dec 31 '13 at 12:44
  • @JeffTreuting, I have the exact same scenario. – Jeff Feb 14 '14 at 17:08
  • 12
    I'm trying to do the same thing - have a base CRUD controller that handles all the basic crud tasks with a generic service interface. It's a perfectly realistic scenario. Your response concerning users having "specific route templates to their actions in the base controller" is very easily solved - don't inherit from that base controller. You've gained nothing from this design decision and prevented some very good functionality. For every comment here, I'm sure there are hundreds more who are trying to do the exact same thing. You guys made a mistake here. Please fix it. – Tim Hardy Feb 15 '14 at 05:18
  • The AttributeRouting.WebApi nuget package implements this exact functionality - https://github.com/mccalltd/AttributeRouting/wiki/Inherit-Routes-from-Actions-in-Base-Controllers. It's obviously something the community desires. – Tim Hardy Feb 15 '14 at 05:52
  • @TimHardy: You can open an issue over here with your scenario details over here: http://aspnetwebstack.codeplex.com/workitem/list/basic – Kiran Feb 15 '14 at 07:22
  • 2
    Thanks, Kiran. I opened an issue. Anyone who wants AttributeRouting to have inheritance should vote it up - https://aspnetwebstack.codeplex.com/workitem/1688 – Tim Hardy Feb 16 '14 at 04:14
  • Same issue with me. I could REALLY use route inheritance. – Matt May 02 '14 at 15:10
  • This - the custom route provider - works beautifully for me! Thanks, Kiran – Kieren Johnstone Feb 23 '16 at 08:37
  • I tried this to enable inheritance of RoutePrefix, but it didn't work so I wrote up a solution here: http://stackoverflow.com/a/39625735/1876622 – HeyZiko Sep 21 '16 at 20:33
  • My particular use case is having a general `RoutePrefix("api/v1")` and then inherit that prefix for all the controllers I build. This way I can change that without fear of screwing up, and also I don't have to manually set it for each new controller I make (which may introduce errors). But this is for doing stuff the other way around (set `[Route("")]` on the parent controller and set `[RoutePrefix("")]` on the child), which does absolutely zero sense to me! – tfrascaroli May 30 '17 at 09:34
28

Using Web API 2.2, you can:

public class BaseController : ApiController
{
    [Route("{id:int}")]
    public string Get(int id)
    {
        return "Success:" + id;
    }
}
[RoutePrefix("api/values")]
public class ValuesController : BaseController
{
}

config.MapHttpAttributeRoutes(new CustomDirectRouteProvider());
public class CustomDirectRouteProvider : DefaultDirectRouteProvider
{
    protected override IReadOnlyList<IDirectRouteFactory> 
    GetActionRouteFactories(HttpActionDescriptor actionDescriptor)
    {
        return actionDescriptor.GetCustomAttributes<IDirectRouteFactory>
        (inherit: true);
    }
}

as outlined here: http://www.asp.net/web-api/overview/releases/whats-new-in-aspnet-web-api-22

Dejan
  • 9,150
  • 8
  • 69
  • 117
  • This seems helpful, but that MSDN documentation sucks. What / where is `config`? Why is there a class `CustomDirectRouteProvider` right under it? Its just written oddly. I can figure it out, but its just irritating. – CarComp May 03 '23 at 14:00
  • @CarComp My answer dates from 2014. Probably, a lots has shifted since. To read up the latest doc on this top for .NET 7, see: https://learn.microsoft.com/en-us/aspnet/core/mvc/controllers/routing?view=aspnetcore-7.0#attribute-routing-for-rest-apis – Dejan May 05 '23 at 05:27
4

Got it.

[Route("api/baseuploader/{action}")]
public abstract class BaseUploaderController : ApiController
{
    [HttpGet] 
    public string UploadFile() 
    {
        return "UploadFile";
    }
}


[Route("api/values/{action}")]
public class ValuesController : BaseUploaderController
{
    [HttpGet]
    public string Get(int id)
    {
        return "value";
    }
}

One caveat here is that the route action paramter must be the same as the action name. I could not find a way to get around that. (You cannot rename the route with a RouteAttribute)

parliament
  • 21,544
  • 38
  • 148
  • 238
  • Yes api/values/upload file will give "upload file". And yes it only works because of {action} mapping directly to the method name. I could not get it to work with a RouteAttribute but if you do, post back! :) – parliament Feb 19 '14 at 22:32