bahadir
bahadir

Reputation: 719

AttributeRouting with IHttpControllerSelector - Api Versioning

I am trying to achieve api versioning using a CustomHttpControlSelector and AttributeRouting on asp.net webapi.

What i am trying to do is distinguish controller's versions by it's namespaces.

if a request is made to /api/v2/foo/bar i want it to match

namespace Web.Controllers.Api.v2
{
    [RoutePrefix("foo")]
    public class LongerThanFooController : ApiController
    {
        [HttpGet]
        [Route("bar")]
        public string BarFunction()
        {
            return "foobar";
        }
    }
}

but as i see when i don't use full url on RoutePrefix (/api/v2/foo) attribute routing doesn't kick in and i get null when i call

 request.GetRouteData().GetSubRoutes();

on my CustomHttpControlSelector. i don't want to Repeat /api/v2 on every controller.

if i decide to remove attributeRouting and use manual routes like

 config.Routes.MapHttpRoute(
         name: "DefaultVersionedApi",
         routeTemplate: "api/v{version}/{controller}/{id}",
         defaults: new { id = RouteParameter.Optional }
 );

 config.Routes.MapHttpRoute(
          name: "DefaultApi",
          routeTemplate: "api/{controller}/{id}",
          defaults: new { id = RouteParameter.Optional, version = Config.LatestVersion }
  );

i lose all flexibility of naming my controllers and functions.

is there a way to get out of this limbo?

note: for the CustomHttpControlSelector i modified code on http://aspnet.codeplex.com/SourceControl/changeset/view/dd207952fa86#Samples/WebApi/NamespaceControllerSelector/NamespaceHttpControllerSelector.cs

Upvotes: 4

Views: 717

Answers (1)

Chris Martinez
Chris Martinez

Reputation: 4368

I realize this is bit of an old question now, but it can answered using the ASP.NET API Versioning package for ASP.NET Web API. In the latest 3.0 version, you can achieve your scenario by updating your configuration with:

var constraintResolver = new DefaultInlineConstraintResolver()
{
  ConstraintMap =
  {
    ["apiVersion"] = typeof( ApiVersionRouteConstraint )
  }
};

configuration.AddApiVersioning(
  options =>
  {
    options.Conventions.Add( new VersionByNamespaceConvention() );
    options.AssumeDefaultVersionWhenUnspecified = true;
    options.ApiVersionSelector = new CurrentImplementationApiVersionSelector( options );
  } );

configuration.MapHttpAttributeRoutes( constraintResolver );

You should also remove your convention-based routes. Those are unnecessary if you are using attribute routing.

The setup for your controller simply changes to:

namespace Web.Controllers.Api.v2
{
  [RoutePrefix("api")]
  public class LongerThanFooController : ApiController
  {
    [HttpGet]
    [Route("foo/bar")]
    [Route("v{version:apiVersion}/foo/bar")]
    public string BarFunction()
    {
      return "foobar";
    }
  }
}

The reason you need two route definitions is that you cannot have default values in the middle of the route template. Default values can only be used at the end. This also means that you need to allow no API version to be specified and indicate the way to determine which API version should be selected is to use the current implementation (e.g. latest). I'm personally not a fan of this approach because I think things should be predictable to clients, but this will achieve your desired result.

Upvotes: 1

Related Questions