Jeff Treuting
Jeff Treuting

Reputation: 13510

Attribute routing and inheritance

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:

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

Upvotes: 66

Views: 32443

Answers (4)

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";
    }

Upvotes: 0

Kiran
Kiran

Reputation: 57989

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

Upvotes: 82

Dejan
Dejan

Reputation: 10363

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

Upvotes: 28

parliament
parliament

Reputation: 22954

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)

Upvotes: 4

Related Questions