Service Fabric Web API Versioning issue

324 Views Asked by At

I'm working on a service fabric project with multiple stateless services. When i try to add versioning as in the code below

[Authorize]
[ApiVersion("2.0")]
[Route("api/v{version:apiVersion}/[controller]")]
public class SessionController : Controller
{
...
}

it's not working when calling the service later using Postman or using some client winforms app i made just to call this service. And when i say it's not working i mean it's not looking for a specific version i placed in the controller.

e.g. I'm calling http://localhost:1234/api/v1.0/session/set-session and as you can see in my controller i only have version 2.0. Now my API gets hit this way or another no matter what version number i put in.

I added code to the Startup.cs

services.AddApiVersioning(options => {
       options.DefaultApiVersion = new ApiVersion(2, 0);
       options.AssumeDefaultVersionWhenUnspecified = true;
       options.ReportApiVersions = true;
       options.ApiVersionReader = new HeaderApiVersionReader("x-api-version");
});

Specific API call looks like this:

 [HttpPost]
 [Route("set-session")]
 public async Task<IActionResult> SetSession([FromBody] SessionModel model)
 { ... }

Can anyone tell me what am i missing or maybe api versioning is not supported in service fabric at all? Thanks.

1

There are 1 best solutions below

1
On

Does your solution work locally? Based on what I see, I would suspect - no. This should have nothing to do with Service Fabric at all.

Issue 1

I see that your base class inherits from Controller, which is allowed, but is usually ControllerBase. No concern there, just FYI. The crux of the problem is likely that your controller has not applied the [ApiController] attribute. API Versioning defines IApiControllerSpecification and IApiControllerFilter, which is used to filter which controllers should be considered an API. This is important for developers building applications that have the UI and API parts mixed. A controller is a controller in ASP.NET Core and it was difficult to distinguish these two in the early days. There is now a built-in IApiControllerSpecification that considers any controller with [ApiController] applied to be an API. This can be changed, replaced, or completely disabled using ApiVersioningOptions.UseApiBehavior = false.

If your library/application is only APIs, you can decorate all controllers at once using:

[assembly: ApiController]

Since your controller is not currently being considered an API, all requests matching the route are being directed there. The value 1.0 is being considered an arbitrary string rather than an API version. This is why it matches at all instead of HTTP 400. I suspect you must only have one API and it is defined as 2.0; otherwise, I would expect an AmbiguousActionException.

Issue 2

Your example shows that you are trying to version by URL segment, but you've configured the options to only consider the header x-api-version. This option should be configured with one of the following:

URL Segment (only)

options.ApiVersionReader = new UrlSegmentApiVersionReader();

URL Segment and Header

// registration order is irrelevant
options.ApiVersionReader = ApiVersionReader.Combine(
    new UrlSegmentApiVersionReader(),
    new HeaderApiVersionReader("x-api-version"));

Default (Query String and URL Segment)

// NOTE: this is the configuration
// options.ApiVersionReader = ApiVersionReader.Combine(
//    new QueryStringApiVersionReader(),
//    new UrlSegmentApiVersionReader());

Side Note

As defined, using the URL segment and header versioning methodologies don't make sense. You have a single route which requires an API version. A client will always have to include the API version in every request so there is no point to also supporting a header.

If you define 2 routes, then it makes sense:

[Route("api/[controller]")]                       // match by header
[Route("api/v{version:apiVersion}/[controller]")] // match by url segment

Versioning by URL segment, while common, is the least RESTful. It violates the Uniform Interface constraint. This issue demonstrates yet another problem with that approach. Query string, header, media type, or any combination thereof will all work with the single route template of: [Route("api/[controller]")]

Observation 1

You have configured options.AssumeDefaultVersionWhenUnspecified = true. This will have no effect when versioning by URL segment. It is impossible to provide a default value of route parameter in the middle of a template. The same would be true for api/value/{id}/subvalues if {id} is not specified.

This option will have an effect if you:

  • Add a second route template that doesn't have the API version parameter
  • You update your versioning strategy to not use a URL segment

It should be noted that is a highly abused feature. It is meant to grandfather in existing services that didn't previously have explicit versioning because adding it will break existing clients. You should be cognizant of that if that isn't your use case.