S. ten Brinke
S. ten Brinke

Reputation: 2973

ASP.NET Web API versioning with URL Query gives "duplicate route" error

First, a little disclaimer: I have already created a GitHub issue for this at the aspnet-api-versioning repo. The content of this question is basically the same as the content in that github issue.

I am using ASP.NET Web API on .NET 4.5.2.

My example API looks like this:

namespace App.Backend.Controllers.Version1
{
    [ApiVersion("1.0")]
    public class SomeController: ApiController
    {
        [HttpGet]
        [Route("api/entity/{id:int:min(1)}")]
        public async Task<IHttpActionResult> ApiAction(int id) //Already running in production
        {
            //Accessible by using ?api-version=1.0 OR by omitting that since this is the default version
            return Ok();
        }
    }
}

namespace App.Backend.Controllers.Version2
{
    [ApiVersion("2.0")]
    public class SomeController : ApiController
    {
        [HttpGet]
        [Route("api/entity/{id:int:min(1)}")]
        public async Task<IHttpActionResult> ApiAction(int id)
        {
            //Accessible by using ?api-version=2.0 OR by omitting that since this is the default version
            return Ok();
        }
    }
}

The config is as follows:

// Add versioning
config.AddApiVersioning(o => 
{
    o.AssumeDefaultVersionWhenUnspecified = true;
    o.DefaultApiVersion = new ApiVersion(1, 0);
});

When I send a request, though, the following happens:

System.InvalidOperationException: A route named 'RegisterHours' is already in the route collection. Route names must be unique.

Duplicates:

api/some/ApiAction
api/some/ApiAction

This is weird to me because in the wiki there is an example exactly like my situation

I'd like to use the ?api-version={version} option but it looks I have no choice to use the URL path version now (api/v1.0/some/apiAction and api/v2.0/some/apiAction. If that's true, I guess I have to add another Route to every existing action which will be like api/v{version:apiVersion}/controller/action to allow them to use v1.0 so it will be uniform in the entire application?

What do you guys advise? I could just use /v2.0/ in the URL of version 2 of the API I guess, but I'd prefer the query string version.

Upvotes: 1

Views: 791

Answers (2)

Chris Martinez
Chris Martinez

Reputation: 4368

It doesn't show it in the example, but the error message:

System.InvalidOperationException: A route named 'RegisterHours' is already in the route collection. Route names must be unique.

means that there are multiple entries with the same name in the route table. If I had to guess, the attribute routes are actually being defined as:

[Route("api/entity/{id:int:min(1)}", Name = "RegisterHours")]

...or something like that.

Unfortunately, the route table is not API version-aware. The names in the route table must be unique. When you specify the name in the RouteAttribute, it causes this issue. The only real way around it is to use unique route names; for example, Name = "RegisterHoursV1" and Name = "RegisterHoursV2".

Aside: you don't need:

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

unless you are versioning by URL segment.

Upvotes: 1

John Lucas
John Lucas

Reputation: 1617

After you have fixed the duplicate "RegisterHours" (as per the git hub issues page responses) you should also ensure you have the constraintResolver setup in your startup.cs

    public void Configuration( IAppBuilder builder )
    {
        // we only need to change the default constraint resolver for services that want urls with versioning like: ~/v{version}/{controller}
        var constraintResolver = new DefaultInlineConstraintResolver() { ConstraintMap = { ["apiVersion"] = typeof( ApiVersionRouteConstraint ) } };
        var configuration = new HttpConfiguration();
        var httpServer = new HttpServer( configuration );

        // reporting api versions will return the headers "api-supported-versions" and "api-deprecated-versions"
        configuration.AddApiVersioning( o => o.ReportApiVersions = true );
        configuration.MapHttpAttributeRoutes( constraintResolver );
        builder.UseWebApi( httpServer );
    }

Otherwise your attributes to change the route (api/entity) won't work because the route doesn't match the controller name "Some" and so won't match the default routing ie. ~\api\controllername\

public class ***Some***Controller : ApiController
{
    [HttpGet]
    [Route( "api/***entity***/{id:int:min(1)}" )]    <--- won't work without the constraint resolver
    public async Task<IHttpActionResult> ApiAction( int id )
    {
        //Accessible by using ?api-version=2.0 OR by omitting that since this is the default version
        return Ok();
    }

Upvotes: 0

Related Questions