Reputation: 2973
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
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
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