Jussi Kosunen
Jussi Kosunen

Reputation: 8307

ASP.NET Web API 2 versioned routing with inheritance

I've been trying to implement versioned routing in Web API 2.2 (5.2.3) where a newer version of a controller has priority over previous versions when selecting an action for a specific route.

e.g. Given the following controller class hierarcy:

namespace MyApi.Controllers.V1 {
    public class FooController {
        protected IFooRepository Repo; // Assume Repo is injected via IoC

        [Route("{id:int:min(1)}")]
        public IFoo GetSingle(int id) {
            Repo.Single(new FooSearchOpts { Id = id });
        }

        public IEnumerable<IFoo> GetAll() {
            Repo.Search();
        }
    }
}

namespace MyApi.Controllers.V3 {
    public class FooController : V1.FooController {
        public IEnumerable<IFoo> Search(string type) {
            Repo.Search(new FooSearchOpts { Type = type });
        }
    }
}

namespace MyApi.Controllers.V4 {
    public class FooController : V3.FooController {
        public IEnumerable<IFoo> SearchV4(string type, string name) {
            Repo.Search(new FooSearchOpts {
                Type = type,
                Name = name
            });
        }
    }
}

namespace MyApi.Controllers.V5 {
    public class FooController : V4.FooController {
        /* nothing relevant */
    }
}

I can get some routes working by creating a custom IHttpControllerSelector that reads {version} from the route data (e.g. GET /api/v*/foo/123 always points to GetSingle).

The problem appears when there are multiple possibles routes in different versions of the controller. I need the request GET /api/v*/foo to choose the following methods:

/api/v1/foo -> V1.FooController.GetAll
/api/v2/foo -> V1.FooController.GetAll
/api/v3/foo -> V3.FooController.Search
/api/v4/foo -> V4.FooController.SearchV4
/api/v5/foo -> V5.FooController.SearchV4

Just specifying a custom controller selector understandibly complains about duplicate/ambiguous actions, but I can't find any way to retain the existing action selection logic when I specify a custom IHttpActionSelector. Do I really need to reimplement all the method name parsing and parameter checking shenanigans by hand or is there a way to leverage the existing logic?

Unfortunately I can't post all of what I have already but the main routing config looks something like this:

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        // VersionedRouteProvider specifies a global route prefix of "api/v{version}/{ctrl}"
        config.MapHttpAttributeRoutes(new VersionedRouteProvider());

        // VersionedControllerSelector uses {version} and {ctrl} to map out controllers
        var controllerSelector = new VersionedControllerSelector(config);
        config.Services.Replace(typeof(IHttpControllerSelector), controllerSelector);

        // VersionedActionSelector creates actions from controllers,
        // currently it would require iterating MethodInfos
        // and manually creating all HttpActionDescriptors
        // based on the methods' names and parameters
        config.Services.Replace(typeof(IHttpActionSelector), new VersionedActionSelector(controllerSelector));
    }
}

I've looked into creating a custom IDirectRouteProvider and IDirectRouteFactory but it seems like I'd still need a custom IHttpActionSelector to handle ambiguous routes.

Thanks.

Upvotes: 0

Views: 580

Answers (1)

Stephen Brickner
Stephen Brickner

Reputation: 2602

Versioning should never work this way or you could disable all of your users code, as they will receive different results than they previously did. Versioning should be done with attribute routing (api/v2/whatever) to allow the consumers of your api to switch to the newer version as they choose.

[HttpGet, Route("api/v1/users")
public IHttpActionResult UsersVersion1() {}

[HttpGet, Route("api/v2/users")
public IHttpActionResult UsersVersion2() {}

Upvotes: 1

Related Questions