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?

4
  • 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? Commented Dec 31, 2013 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. Commented Dec 31, 2013 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. Commented Dec 31, 2013 at 21:59
  • Please vote for the issue I opened if you want them to add inheritance to AttributeRouting - aspnetwebstack.codeplex.com/workitem/1688 Commented Feb 16, 2014 at 5:04

4 Answers 4

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);
    }
}
Sign up to request clarification or add additional context in comments.

14 Comments

Here is what I was going to play around with. I have a generic repository and was going to create a RepositoryBaseApiController<T> that loaded up a IRepository<T> 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<SomeEntity> 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
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...
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 github.com/mccalltd/AttributeRouting/wiki/…
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.
Thanks, Kiran. I opened an issue. Anyone who wants AttributeRouting to have inheritance should vote it up - aspnetwebstack.codeplex.com/workitem/1688
|
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

2 Comments

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 My answer dates from 2014. Probably, a lots has shifted since. To read up the latest doc on this top for .NET 7, see: learn.microsoft.com/en-us/aspnet/core/mvc/controllers/…
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)

1 Comment

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! :)
0

There is another simple solution to avoid ambiguity using action selector,

    [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
    public class IgnoreChildControllerAttribute : ActionMethodSelectorAttribute
    {
        public override bool IsValidForRequest(RouteContext routeContext, ActionDescriptor action)
        {
            // Ensure the method is not included in child class route
            return action is ControllerActionDescriptor controllerActionDescriptor 
                && controllerActionDescriptor.ControllerTypeInfo.AsType() == controllerActionDescriptor.MethodInfo.DeclaringType;
        }
    }

Now you just need to decorate your method with this attribute,

   [IgnoreChildController]
   public string Get(int id)
    {
        return "Success";
    }

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.