Swagger UI - Web API versioning when using Microsoft.AspNet.WebApi.Versioning - c#

We are using Web API 2 on our project with Swagger. My problem is that when Microsoft.AspNet.WebApi.Versioning is applied as following:
the Swagger UI is ignoring the fact that now I have version in my API which needs to be provided.
I looked at several examples but none seem to address this issue in a satisfying manner.
How do I force Swagger to let me add the API version or just add the version number automatically to the URL?
Swagger configuration so far:
GlobalConfiguration.Configuration
.EnableSwagger(c =>
{
c.SingleApiVersion("v1", "MoovShack.ServerApi");
// If your API has multiple versions, use "MultipleApiVersions" instead of "SingleApiVersion".
// In this case, you must provide a lambda that tells Swashbuckle which actions should be
// included in the docs for a given API version. Like "SingleApiVersion", each call to "Version"
// returns an "Info" builder so you can provide additional metadata per API version.
//
//c.MultipleApiVersions(
// (apiDesc, targetApiVersion) => ResolveVersionSupportByRouteConstraint(apiDesc, targetApiVersion),
// (vc) =>
// {
// vc.Version("v2", "Swashbuckle Dummy API V2");
// vc.Version("v1", "Swashbuckle Dummy API V1");
// });
c.OperationFilter<MoovShackTokenHeaderParameter>();
})
.EnableSwaggerUi(c =>
{
// If your API has multiple versions and you've applied the MultipleApiVersions setting
// as described above, you can also enable a select box in the swagger-ui, that displays
// a discovery URL for each version. This provides a convenient way for users to browse documentation
// for different API versions.
//
//c.EnableDiscoveryUrlSelector();
});
You can see that so far MultipleApiVersions are disabled - from one good reason as it doesn't produce any results. Especially since I am not sure what "ResolveVersionSupportByRouteConstraint" should do.
I also read that "EnableDiscoveryUrlSelector" has some kind of impact but I am also not sure if that applies to my case. When I enabled it, nothing happened.

We use it like this in our project and swagger recognizes it and it looks fine
[ApiVersion( "1.0" )]
[Route("api/v{version:apiVersion}/[controller]")]
public class SomeControlelr: Controller{
[HttpGet("", Name = "Someaction"), MapToApiVersion("1.0")]
public async Task<IActionResult> SomeAction(string someParameter)

Related

Call a versioned webapi controller path ending with a number

Why does adding versioning to a webApi project removes number from the controller path name?
Replication steps :
Create a fresh .net6 project. And rename WeatherForecastController to WeatherForecast2Controller.
Run app and call https://localhost:x/WeatherForecast2 (where x is your port)
Observe valid/expected results
Add the below code in program.cs/startup
services.AddApiVersioning(config =>
{
config.AssumeDefaultVersionWhenUnspecified = true;
config.DefaultApiVersion = new ApiVersion(1, 0);
config.ReportApiVersions = true;
config.ApiVersionReader = ApiVersionReader.Combine(
new QueryStringApiVersionReader("version"),
new HeaderApiVersionReader("x-version"));
config.UseApiBehavior = false;
});
Run app and call https://localhost:x/WeatherForecast2 (where x is your port).
Step 5 will return a 404 not found error.
However if you call https://localhost:x/WeatherForecast. It will work.
So why does adding versioning, change the url path?
It's not entirely clear if you are using <= 5.0 or 6.0+.
History
The reason this behavior happens is because the only logical way to group controllers together is by their names. A controller name, therefore, becomes very important. This is problematic in code because two or more controllers in the same namespace cannot have the same type name. The assumption and long-defined convention has been to allow ASP.NET to remove the Controller suffix and then remove any remaining numbers. This allows ValuesController, Values2Controller and Values3Controller to all map to the logical controller name Values by default. In most cases, that's probably want someone wants. If API Versioning doesn't do this, then there is no way to collate all API versions together for an API.
Contrary to popular belief, route templates are not considered for grouping controllers (e.g. APIs). There is too much ambiguity as to how a template can map to code. Take the simplest example of two different versions of the same API with different route templates: V1 = values/{id}, V2 = values/{id:int}. These are semantically equivalent, but not the same. API Versioning does not try to understand what the route template means nor compare their equivalence. It can easily get a lot more complicated; especially, for overlapping route templates. For example, should order/{oid}/customer/{cid} be part of the Orders API or the Customer API? Only the service author knows for sure.
Regression
In the 5.0 release, a regression was accidentally introduced due to an over-optimization. The controller name is used in two places: the actual name of the controller and the name used to group controllers. It seems reasonable they'd be the same and why normalize (e.g. trim suffixes) more than necessary? It seemed like a good idea, but it caused unexpected behavior - such as this one. There are also legitimate reasons to have a number in the name of a controller; for example, S3Controller.
Fix
In library versions <= 5.0, developers had no control over the behavior of how names were normalized. In 5.1 and 6.0+, this is now exposed via the IControllerNameConvention service, which has two methods: one for normalizing the controller name and one for normalizing the group name. The following implementations are provided out of the box as properties on ControllerNameConvention:
Default: The default, out-of-the-box conventions
Original: The original names without any normalization (could result in the wrong behavior)
Grouped: The group name is normalized, but the controller name is unmodified
If none of those work for you, then you can create your own custom convention. In 5.1 this is wired up via ApiVersioningOptions.ControllerNameConvention, while in 6.0+ IControllerNameConvention is a transient service in the DI container.
Workaround
There are two ways you can workaround the problem using the current version you are leveraging:
Explicit Route Template
If you omit using the [controller] token, the routing problem will be resolved; for example, api/weatherforecast. You appear to have already discovered this.
Explicit Controller Name
The controller name is derived from a convention, even without API Versioning. It was understood this behavior could be a problem so API Versioning provides a way to explicit set it with the ControllerNameAttribute.
[ControllerName("WeatherForecast")]
[Route("api/[controller]")] // ← expands to 'api/WeatherForecast'
public class WeatherForecast2Controller : ControllerBase { }
Edge Case
This will solve the routing issues, but it will not fix the controller name issue. That should only matter if you are planning on documenting your API with OpenAPI (formerly Swagger). For example, S3Controller will simply show up as S, even though the route might be api/s3.
I think that your controller may have issues, as I used your AddApiVersioning code and it works for me. To avoid the conflicting action names, you can two different controllers.
ApiController
namespace WebApiVersioningApp.Controllers;
[ApiVersion("1.0")]
[ApiVersion("2.0")]
[ApiController]
[Route("api/Version2")]
public class Version2Controller : ControllerBase
{
[MapToApiVersion("1.0")]
[HttpGet(Name = "GetWeatherForecastV1")]
public string GetV1()
{
return "Version 1";
}
[MapToApiVersion("2.0")]
[HttpGet(Name = "GetWeatherForecastV2")]
public string GetV2()
{
return "Version 2";
}
}
Program:
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(c =>
c.ResolveConflictingActions(apiDescriptions => apiDescriptions.First())
);
// Versioning setup
builder.Services.AddApiVersioning(o =>
{
o.ReportApiVersions = true;
o.AssumeDefaultVersionWhenUnspecified = true;
o.DefaultApiVersion = new ApiVersion(1, 0);
o.ApiVersionReader = ApiVersionReader.Combine(
new QueryStringApiVersionReader("version"),
new HeaderApiVersionReader("x-version"));
o.UseApiBehavior = false;
});
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();
Hope this works for you.

Classes Added Using GenerateSchema Appear in the Swagger UI But Not in swagger.json

I have an Asp.Net Core C# back end project containing controllers and web api methods. Swagger is used to test the web api methods, and to generate swagger.json - we are generating class definitions for the front end to use using nswag studio, based on swagger.json.
I need to add explicitly add some additional class definition to swagger.json. These are for classes which do not appear in any web api endpoints, but which we send to our front end using SignalR.
I have added the additional class definitions using GenerateSchema(). These additional class definitions appear in the Swagger UI (https://localhost:7103/swagger/index.html), but do not appear in swagger.json, for reasons which are unclear. I get the same results when using RegisterType().
Any help on being able to include these definitions in swagger.json would be greatly appreciated.
The code is as follows:
services.AddSwaggerGen(options =>
{
..
options.DocumentFilter< AdditionalSchemasDocumentFilter >();
..
});
public class AdditionalSchemasDocumentFilter : IDocumentFilter
{
..
context.SchemaGenerator.GenerateSchema(typeof(Notification), context.SchemaRepository);
}
app.UseSwagger();
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint($"/swagger/v1/swagger.json", "BackOffice API");
..
});
I have resolved this issue, as per my post at https://learn.microsoft.com/en-us/answers/questions/925357/classes-added-using-generateschema-appear-in-the-s.html

Microsoft.AspNetCore.Mvc.Versioning how to default to the "latest" version

Other than changing startup.cs whenever a new version is built,
services.AddApiVersioning(config =>
{
config.DefaultApiVersion = new ApiVersion(1, 0);
});
is there a way of specifying that the "default version" is the latest version, without specifying what that version is? e.g. some way of saying
config.DefaultApiVersion = new ApiVersionLatest();
. . . or does this fly in the face of all that is considered holy by the RESTApi gods?
Thanks
The correct answer depends a little bit about what you expect to achieve. The DefaultApiVersion only comes into play when no other API version is available. API versioning does not have a concept of "no API version". API version-neutral means that an API accepts any and all API versions, including none at all. The default API version can also be thought of as the initial API version.
Here's a few scenarios where the DefaultAPiVersion comes into play:
A controller has no attribution or conventions applied
Selection of possible API versions yields no results
It sounds like you are interested in configuring the most current API version in a single place. You can use the DefaultApiVersion to do this, but only if the controller has no other attribution or conventions. If an API doesn't carry forward, you will have to explicitly decorate the controller with an attribute or convention that indicates the legacy API version to exclude it from the latest version. While this is possible, it's hard to follow IMO.
A better approach would probably to use an extension that that describes the behavior you want. For example:
[AttributeUsage( AttributeTargets.Class, AllowMultiple = false )]
public sealed class LatestApiVersionAttribute : ApiVersionAttribute, IApiVersionProvider
{
public LatestApiVersionAttribute() : base( new ApiVersion( 2, 0 ) ) { }
}
Now you can apply this to all your controllers a la:
[LatestApiVersion]
public class MyController : ControllerBase
{
// ommitted
}
This gives you the chance to manage the latest API version is single place. You might also consider using conventions so you don't even need the custom attribute. Attributes and conventions can be combined.
#spender does mention using a custom IApiVersionSelector; however, selection currently only comes into play when no API version has been specified. To enable this type of configuration, set things up as:
services.AddApiVersioning( options =>
{
options.ApiVersionSelector = new CurrentImplementationApiVersionSelector( options );
options.AssumeDefaultVersionWhenUnspecified = true;
}
This will enable clients that do not specify an API version to always forward to the most recent version of the requested API. A client can still explicitly ask for a specific version, including the latest.
I hope that helps.

NSwag .NET Core API Versioning configuration

I'd like to prepare my .NET Core Web API project so that multiple versions of the API can be managed and documented, according to the REST services standards.
I'm using .NET Core 2.1 with NSwag (v11.18.2). I also installed the Microsoft.AspNetCore.Mvc.Versioning NuGet package.
I already searched with Google for some configuration examples, but the only useful link I found is this.
I'm now able to get Swagger pages for both API versions but with some problems:
Please note that none of the last config settings (Title, Description, etc.) takes effect on any of the 2 routes. It only works if I add them on each of the individual configuration. So I'd also like to know if it possible to avoid that, since the general configuration of the API can be version indipendent (title, description and so on...).
Since the issue with NSwag and Microsoft API Versioning package discussed in the above link, was opened 2-3 months (and NSwag versions too) ago, I'd like to know if it is now truly fixed and in this case, which is the right configuration to set.
Although the version is explicit in the configuration of the controllers, it is still required as a mandatory input parameter of the controller methods and of course I don't want that! See image:
So, my actual configuration, by following that example, is looking like this:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddApiVersioning(options =>
{
options.AssumeDefaultVersionWhenUnspecified = true;
options.DefaultApiVersion = new ApiVersion(1, 0);
options.ReportApiVersions = true;
});
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseSwaggerWithApiExplorer(config =>
{
config.GeneratorSettings.OperationProcessors.TryGet<ApiVersionProcessor>().IncludedVersions = new[] { "1.0" };
config.SwaggerRoute = "v1.0.json";
});
app.UseSwaggerWithApiExplorer(config =>
{
config.GeneratorSettings.OperationProcessors.TryGet<ApiVersionProcessor>().IncludedVersions = new[] { "2.0" };
config.SwaggerRoute = "v2.0.json";
});
app.UseSwaggerUi3(typeof(Startup).GetTypeInfo().Assembly, config =>
{
config.SwaggerRoutes.Add(new SwaggerUi3Route("v1.0", "/v1.0.json"));
config.SwaggerRoutes.Add(new SwaggerUi3Route("v2.0", "/v2.0.json"));
config.GeneratorSettings.Title = "My API";
config.GeneratorSettings.Description = "API functionalities.";
config.GeneratorSettings.DefaultUrlTemplate = "{v:apiVersion}/{controller}/{action}/{id?}";
config.GeneratorSettings.DefaultPropertyNameHandling = PropertyNameHandling.CamelCase
});
}
And these are my actual controllers:
[ApiController]
[ApiVersion("1.0")]
[Route("api/v{version:apiVersion}/[controller]/[action]")]
[SwaggerTag("Test1", Description = "Core operations on machines (v1.0).")]
public class MachinesController : Controller
{
[HttpGet("{id}")]
[ProducesResponseType((int)HttpStatusCode.OK)]
public async Task<ActionResult<Machine>> Get(int id)
{
return await ...
}
}
[ApiController]
[ApiVersion("2.0")]
[Route("api/v{version:apiVersion}/[controller]/[action]")]
[SwaggerTag("Test2", Description = "Core operations on machines (v2.0).")]
public class MachinesController : Controller
{
[HttpGet("{id}")]
[ProducesResponseType((int)HttpStatusCode.OK)]
public async Task<ActionResult<Machine>> Get(int id)
{
return await ...
}
}
They are ignored in the middleware because they are inferred from the settings or do not apply for api explorer (template). However title and description should work...
Please create an issue with the specific issue and a repro, also check out the existing tests in the repo
Fixed with v11.18.3
I believe starting in NSwag 12.0.0, there is significantly improved support for the API Explorer. It's important that the complementary API Explorer package for API versioning is also referenced so that the proper information is provided to NSwag.
The Swagger sample application provided by API Versioning uses Swashbuckle, but the setup will be very similar to NSwag. You can use the IApiVersionDescriptionProvider service to enumerate all of the API versions defined in your application. That should significantly simplify your NSwag configuration.
You're versioning by URL segment; therefore, to address Problem 3 you simply need to configure the API Explorer a la:
services.AddVersionedApiExplorer( options => options.SubstituteApiVersionInUrl = true );
This will replace the {version} route parameter in the route template with the corresponding API version value and remove the API version parameter from the API description.

web api with swagger versioning, ResolveVersionSupportByRouteConstraint not found

I have the api portion working but in my swaggerconfig it cannot find ResolveVersionSupportByRouteConstraint in
c.MultipleApiVersions(
(apiDesc, targetApiVersion) => ResolveVersionSupportByRouteConstraint(apiDesc, targetApiVersion),
(vc) =>
{
vc.Version("v2", "Swashbuckle Dummy API V2");
vc.Version("v1", "Swashbuckle Dummy API V1");
});
This causes my api import into azure api management to fail because the swagger docs returns an error :(
You have to create the method yourself. You may have to tweak the logic for your versioning scheme. Also, this method doesn't seem to get called for me when I choose a different version in the swashbuckle ui. This does get called on load, or if you request the doc via /swashbuckle/docs/.
public static bool ResolveVersionSupportByRouteConstraint(ApiDescription apiDesc, string targetApiVersion)
{
return apiDesc.ID.Contains($"/{targetApiVersion}/");
}

Categories