Search code examples
asp.netasp.net-web-apiasp.net-coreversioning

Asp.net .core web api versioning does not work for Url path segment


I searched for my problem beforehand in various sources but the answers did not provide me with a solution.

I implemetend Url based web api versioning in a .net core 2.2 project with the way presented here. The version that I used for versioning is the latest Microsoft.AspNetCore.Mvc.Versioning 3.1.2. I also tried to understand how it works from the following sources: source1, source2, source3, source4.

I am having a ValueController with a GET method in a folder called Initial and a Value2Controller in a folder called New. Both folders are subfolders of the 'Controllers' folder.

The structure is as follows:

The routing in ValueController is [Route("api/v{version:apiVersion}/[controller]")]

and in Value2Controller is: [Route("api/v{version:apiVersion}/value")]

I have also set options.EnableEndpointRouting = false; in the Startup.cs and I tried calling api/v1/value or api/v2/value. Both times I get the error: Multiple actions matched. It cannot differentiate between the two controllers actions.

I tried using services.AddApiVersioning(); with no options at all and remove AddVersionedApiExplorer. It does not work. The only thing that works is putting [Route("api/v{version:apiVersion}/[controller]")] in both controllers and make the following api calls:

api/v1/value and api/v2/value2.

The configuration in my startup.cs is as follows:

services.AddApiVersioning(options =>
    {
        options.ReportApiVersions = true;
        options.AssumeDefaultVersionWhenUnspecified = true;
        options.DefaultApiVersion = new ApiVersion(1, 0);
        options.ApiVersionReader = new UrlSegmentApiVersionReader();
        options.UseApiBehavior = true;
    });            

services.AddVersionedApiExplorer(
    options =>
    {
        options.GroupNameFormat = "'v'VVV";
        options.SubstituteApiVersionInUrl = true;
        options.AssumeDefaultVersionWhenUnspecified = true;
        options.DefaultApiVersion = new ApiVersion(1, 0);
    });

What am I missing to call either api/v1/value or api/v2/value and go to the correct request?


Solution

  • After some more debugging, I finally figured out why it wasn't working, so I am posting the solution to anyone who will face a similar problem. The problem was within the Controller Inheritance.

    I had created a CustomBaseController (which I had completely disregarded as problematic for some reason) with some methods for global exception handling, the inheritance goes as follows:

    [ApiVersionNeutral]
    [Route("api/[controller]")]
    [ApiController]
    CustomBaseController : Controller
    

    and

    [ApiVersion("1.0")]
    [Route("api/v{version:apiVersion}/[controller]")]
    [ApiController]
    ValuesController : CustomBaseController { // http method implementations}
    
    [ApiVersion("2.0")]
    [Route("api/v{version:apiVersion}/values")]
    [ApiController]
    ValuesController : CustomBaseController { // updated http method implementations}
    

    The versioning mechanism did not agree with [ApiVersionNeutral] attribute even though it made sense to me that the the base controller would not need to change at all. Moreover I only had the basic routing in the base controller.

    Thus I got the error with "Multiple actions matched".

    I also found out that the version 1 controller, can inherit the routing from the base controller and had no reason to have a routing there. For all subsequent controllers, the routing must be: [Route("api/v{version:apiVersion}/values")].

    The working solution along with the initial configuration posted above, is the following:

    [Route("api/v{version:ApiVersion}/[controller]")]
    [ApiController]
    CustomBaseController: Controller {}
    
    [ApiVersion("1.0")]
    [ApiController]
    ValuesController: CustomBaseController { //code }
    
    
    [ApiVersion("2.0")]
    [ApiController]
    [Route("api/v{version:ApiVersion}/values")]
    Values2Controller: CustomBaseController { //code }
    
    [ApiVersion("3.0")]
    [ApiController]
    [Route("api/v{version:ApiVersion}/values")]
    Values3Controller: CustomBaseController { //code }
    

    Getting values from the following urls:

    api/v1/values
    api/v2/values
    api/v3/values
    

    Even though my issue was resolved, I still don't understand why [ApiVersionNeutral] would cause the routing to not be able to detect the versions of the other controllers correctly. Any explanation would be highly appreciated. Thank you @Matt Stannett for your comments, they led me to the right direction.