8

I am trying to get Web API versioning working with an inherited class. I am working with two very simple variations of the stock Values controller.

[ApiVersion("1.0")]
[RoutePrefix("api/v{version:apiVersion}/Values")]
[ControllerName("Values")]
public class ValuesController : ApiController
{
    // GET api/values
    [Route("")]
    public IEnumerable<string> Get()
    {
        return new string[] { "value1", "value2" };
    }

    // GET api/values/5
    [Route("{id:int}")]
    public virtual string Get(int id)
    {
        return "value from 1";
    }
}

[ApiVersion("2.0")]
[RoutePrefix("api/v{version:apiVersion}/Values")]
[ControllerName("Values")]
public class Values2Controller : ValuesController
{
    //Want to use the method in the base class
    //public IEnumerable<string> Get()
    //{
    //    return new string[] { "value2-1", "value2-2" };
    // }

    [Route("{id:int}")]
    // GET api/values/5
    public new string Get(int id)
    {
        return "value from 2";
    }
} 

My start up configuration is also pretty straightforward.

public static void Register(HttpConfiguration config)
{
    var constraintResolver = new DefaultInlineConstraintResolver()
    {
        ConstraintMap = {["apiVersion"] = typeof(ApiVersionRouteConstraint)}
    };
    config.MapHttpAttributeRoutes(constraintResolver);
    config.AddApiVersioning(o => { o.AssumeDefaultVersionWhenUnspecified = true; });
}

The non-overridden routes work exactly as I would expect http://localhost:32623/api/v1.0/Values/12 -> "value from 1" http://localhost:32623/api/v2.0/Values/12 -> "value from 2"

Calling v1 of the default Get Route http://localhost:32623/api/v1.0/Values -> Value1, Value2

However trying the same route on the child controller results in an error.

http://localhost:32623/api/v2.0/Values

<Message>
The HTTP resource that matches the request URI 'http://localhost:32623/api/v2.0/Values' does not support the API version '2.0'.
</Message>
<InnerError>
<Message>
No route providing a controller name with API version '2.0' was found to match request URI 'http://localhost:32623/api/v2.0/Values'.
</Message>
</InnerError>

The error message suggests that the overridden member expects a "1.0" route and I am able to work around this with a method like this in the child class.

[Route("")]
public override IEnumerable<string> Get()
{
    return base.Get();
}

But this seems less than ideal across a larger application. Is there a way to make this work the way I would like, without these "empty" overrides?

1 Answer 1

6
+100

What you need to do is overwrite the DefaultDirectRoutePrivider to allow route inheritance:

public class WebApiCustomDirectRouteProvider : DefaultDirectRouteProvider {
    protected override IReadOnlyList<IDirectRouteFactory>
        GetActionRouteFactories(HttpActionDescriptor actionDescriptor) {
        // inherit route attributes decorated on base class controller's actions
        return actionDescriptor.GetCustomAttributes<IDirectRouteFactory>(inherit: true);
    }
}

And with that done, you will then need to configure it in your web api configuration along with the custom route constraint

public static void Register(HttpConfiguration config) {
    var constraintResolver = new DefaultInlineConstraintResolver() {
        ConstraintMap = {["apiVersion"] = typeof(ApiVersionRouteConstraint)}
    };
    var directRouteProvider = new WebApiCustomDirectRouteProvider();
    // Attribute routing. (with inheritance)
    config.MapHttpAttributeRoutes(constraintResolver, directRouteProvider);
    config.AddApiVersioning(_ => { _.AssumeDefaultVersionWhenUnspecified = true; });
}

So now the inheriting value controller will now have the base routes available in derived controllers

For demonstrative purposes

[ApiVersion("1.0")]
[RoutePrefix("api/v{version:apiVersion}/Values")]
[ControllerName("Values")]
public class ValuesController : ApiController {

    [HttpGet]
    [Route("")] // GET api/v1.0/values
    public virtual IHttpActionResult Get() {
        return Ok(new string[] { "value1", "value2" });
    }

    [HttpGet]
    [Route("{id:int}")] // GET api/v1.0/values/5
    public virtual IHttpActionResult Get(int id) {
        return Ok("value from 1");
    }
}

[ApiVersion("2.0")]
[RoutePrefix("api/v{version:apiVersion}/Values")]
[ControllerName("Values")]
public class Values2Controller : ValuesController {

    //Will have inherited GET "api/v2.0/Values" route

    // GET api/v2.0/values/5 (Route also inherited from base controller)
    public override IHttpActionResult Get(int id) {
        return Ok("value from 2");
    }
} 

You will notice that the route in the child is not used on the overridden action as it too will also be inherited from the base controller.

Sign up to request clarification or add additional context in comments.

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.