@Sajid was mostly correct. When you version by URL segment, you need to use the API version route constraint. There's a couple of issues at play. It looks like you are migrating from an unversioned API to a versioned API, which is a supported scenario. The example values you gave are incongruent. It looks like you are going from v1
to v10
. The values are irrelevant, but realize that your existing API does have some logical name, even if you never previously assigned it a value.
Issue 1
You didn't specify your setup, but it should be something like:
var constraintResolver = new DefaultInlineConstraintResolver()
{
ConstraintMap = { ["apiVersion"] = typeof( ApiVersionRouteConstraint ) }
};
configuration.MapHttpAttributeRoutes( constraintResolver );
configuration.AddApiVersioning(
options =>
{
// required because the original API doesn't have a version in the URL
options.AssumeDefaultVersionWhenUnspecified = true;
// this is already the default value, but you might need to change it.
// this is the value that will be 'assumed' by the original, unversioned
// API. it is addressable by /api/products. if you meant to start at 1.0,
// then you can use a lower value such as 0.9
options.DefaultApiVersion = new ApiVersion(1, 0);
});
Issue 2
The token [controller]
is not supported in Web API. This is a concept, along with [action]
, were introduced in ASP.NET Core routing.
You didn't fully elaborate on how you solved the issue, but your changes almost certainly highlight how mixing styles can be confusing. It's unclear if the API was matched by an attribute route or a convention-based route.
Issue 3
Your route template does not include the API Version route constraint (see Issue 1 for registration). This is required so API Versioning knows how to extract the API Version from the URL. API Versioning does not do magic string parsing with regular expressions, etc.
The template should be: [RoutePrefix("api/v{version:apiVersion}/products")]
as mentioned by @Sajid. Keep in might that the literal v
is not part of an API Version, but is common in the URL segment versioning method. version
is the name of the route parameter, but can be any value you want. apiVersion
is the key of the registered route constraint, but you can change it in the template if you also change it in the registration (see Issue 1).
Issue 4
The annotated API version is 1.0
, but the route template seems to imply that you meant 10.0
. You can use any value you want, but they need to be congruent. If you do mean to use 1.0
don't forget to the change the options.DefaultApiVersion
or you'll get a runtime exception because the original API and your new API will be considered duplicates as they have the same API version. There is no concept of no API Version once you opt in.
For example:
// ~/api/v10/products
[ApiVersion("10.0")]
[RoutePrefix("api/v{version:apiVersion}/products")]
public class ProductsV1Controller : ApiController { }
Issue 5
I strongly advise against mixing routing styles. It is supported, but it can be confusing to troubleshoot and for maintainers. I would choose either Direct Routing (aka Attribute Routing) or Convention-Based Routing. There are edge cases where mixing them will not work, but they are rare.
This means you should choose one of the following:
// setup (see Issue 1)
configuration.MapHttpAttributeRoutes( constraintResolver );
// old controller
[RoutePrefix("api/products")]
public class ProductsController : ApiController { }
// new controller; ControllerNameAttribute isn't need because it's not used
[ApiVersion("10.0")]
[RoutePrefix("api/v{version:apiVersion}/products")]
public class ProductsV1Controller : ApiController { }
// setup
httpConfig.Routes.MapHttpRoute(
name: "VersionedApi",
routeTemplate: "api/v{apiVersion}/{controller}/{id}",
defaults: new { id = RouteParameter.Optional },
constraints: new { apiVersion = new ApiVersionRouteConstraint() });
httpConfig.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional },
constraints: null);
// old controller
public class ProductsController : ApiController { }
// new controller
[ApiVersion("10.0")]
[ControllerName("Products")] // required because the convention-based name is "ProductsV1"
public class ProductsV1Controller : ApiController { }
Warning: You can mix these styles, but troubleshooting will become more difficult. You need extensive test coverage to ensure things are correct. One-off manual testing may yield false positives.
I hope that helps
[RoutePrefix("api/v10/[controller]")]
to[Route("api/v{v:apiVersion}/products")]
and call it like :api/v1.0/products/
– Mohammed Sajid