After updating from v11.20.1 to v12.0.0 I'm now on a deprecated "limbo".

Here it is my current complete v11.20.1 code for configuration:
```c#
public static void UseMySwagger(this IApplicationBuilder app)
{
string title = "My Company DCS Control API";
string description = "My Company API for Core functionalities.";
ControlApiConfiguration apiConfiguration = (ControlApiConfiguration)app.ApplicationServices.GetService(typeof(ControlApiConfiguration));
Uri baseUri = new Uri(apiConfiguration.AuthEndpoint);
app.UseSwagger(typeof(Startup).Assembly, config =>
{
config.GeneratorSettings.OperationProcessors.TryGet<ApiVersionProcessor>().IncludedVersions = new[] { "1.0" };
config.GeneratorSettings.OperationProcessors.Add(new OperationSecurityScopeProcessor("oauth2"));
config.GeneratorSettings.DocumentProcessors.Add(
new SecurityDefinitionAppender("oauth2", new SwaggerSecurityScheme
{
Type = SwaggerSecuritySchemeType.OAuth2,
Flow = SwaggerOAuth2Flow.Implicit,
AuthorizationUrl = new Uri(baseUri, "connect/authorize").ToString(),
Scopes = new Dictionary<string, string> { { "mycompany.core.v1", $"{title} - Full access" } }
}));
config.DocumentPath = "/v1.0.json";
config.GeneratorSettings.Title = title;
config.GeneratorSettings.Description = description;
config.GeneratorSettings.Version = "1.0.0";
config.PostProcess = document =>
{
document.Info.Contact = new SwaggerContact
{
Name = "My Company",
Email = "[email protected]",
Url = "https://www.mycompany.com"
};
};
});
app.UseSwagger(typeof(Startup).Assembly, config =>
{
config.GeneratorSettings.OperationProcessors.TryGet<ApiVersionProcessor>().IncludedVersions = new[] { "2.0" };
config.GeneratorSettings.OperationProcessors.Add(new OperationSecurityScopeProcessor("oauth2"));
config.GeneratorSettings.DocumentProcessors.Add(
new SecurityDefinitionAppender("oauth2", new SwaggerSecurityScheme
{
Type = SwaggerSecuritySchemeType.OAuth2,
Flow = SwaggerOAuth2Flow.Implicit,
AuthorizationUrl = new Uri(baseUri, "connect/authorize").ToString(),
Scopes = new Dictionary<string, string> { { "mycompany.core.v2", $"{title} - Full access" } }
}));
config.DocumentPath = "/v2.0.json";
config.GeneratorSettings.Title = title;
config.GeneratorSettings.Description = description;
config.GeneratorSettings.Version = "2.0.0";
config.PostProcess = document =>
{
document.Info.Contact = new SwaggerContact
{
Name = "My Company",
Email = "[email protected]",
Url = "https://www.mycompany.com"
};
};
});
app.UseSwaggerUi3(config =>
{
config.OAuth2Client = new OAuth2ClientSettings { ClientId = "core-api-swagger", AppName = $"{title} - Swagger" };
config.SwaggerRoutes.Add(new SwaggerUi3Route("v1.0", "/v1.0.json"));
config.SwaggerRoutes.Add(new SwaggerUi3Route("v2.0", "/v2.0.json"));
});
}
```
I want to align my code with your updated specs and avoid stuck on deprecated stuff. :wink:
I need to get an equivalent updated configuration to the above one that is currently implemented in my project.
I read the NSwag v12.0.0 (Build 1032) link, but I wasn't able to find out where the GeneralSettings (or an equivalent new object/method) is located.
Thank you!
app.UseSwagger(typeof(Startup).Assembly, configure) (with assembly or list of controller types) uses WebApiToSwaggerGenerator which is deprecatedapp.UseSwagger(configure) uses AspNetCoreToSwaggerGenerator which is not deprecatedUseSwagger and config.GeneratorSettings must now be done in AddSwaggerDocument(configure) in ConfigureServices()After a few attempts... I'm back!
As pointed out in #1759, this is what I need to configure on NSwag:
[ApiVersion("x.0")] attribute on my controllers.https://api.mydomain.com/control/swagger --> https://myservername:5001/swaggerNow the problems with the following code are:
http://localhost:5001/swagger I get 404 error on v1.0-beta/swagger.json.
UseSwaggerWithConfiguration extension method:I get the UI but with the `No operations defined in spec!` message.

**The CODE**
**Startup**
Here there're the `Startup` class methods (`ConfigureServices` and `Configure`)
```c#
public void ConfigureServices(IServiceCollection services)
{
// Adding AspNetCore Versioning
services.AddApiVersioning(options =>
{
options.AssumeDefaultVersionWhenUnspecified = true;
options.DefaultApiVersion = new ApiVersion(1, 0, "beta"); // Correct?
options.ReportApiVersions = true;
options.ApiVersionReader = new UrlSegmentApiVersionReader();
});
services.AddMvcCore(config =>
{
config.ReturnHttpNotAcceptable = true;
config.Filters.Add<OperationCancelledExceptionFilterAttribute>();
})
.SetCompatibilityVersion(CompatibilityVersion.Version_2_1)
// Adding AspNetCore Versioning
.AddVersionedApiExplorer(options =>
{
options.GroupNameFormat = "VVV";
options.AssumeDefaultVersionWhenUnspecified = true;
options.DefaultApiVersion = new ApiVersion(1, 0, "beta"); // Correct?
options.SubstituteApiVersionInUrl = true;
})
services.AddAuthentication(IdentityServerAuthenticationDefaults.AuthenticationScheme)
.AddIdentityServerAuthentication(options =>
{
options.ApiName = "mycompany.core.v1";
options.Authority = _apiConfiguration.AuthEndpoint;
options.RequireHttpsMetadata = !Environment.IsDevelopment();
});
services.AddCors(options =>
{
options.AddPolicy("default", policy =>
policy.WithOrigins(_apiConfiguration.CorsOrigins)
.AllowAnyHeader()
.AllowAnyMethod());
});
services.Configure<ForwardedHeadersOptions>(options =>
{
options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;
});
// Call to my custom extension method (see code below)
services.AddSwaggerDocumentWithConfiguration();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
// BasePath configuration loaded from JSON settings file
app.UsePathBase(new PathString(_apiConfiguration.BasePath));
app.UseCors("default");
// Taken from your NSwag code samples
app.UseForwardedHeaders(new ForwardedHeadersOptions
{
ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto
});
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
}
else
{
app.UseStatusCodePages();
app.UseHsts();
app.UseGlobalExceptionHandler();
}
// Call to my custom extension method (see code below)
app.UseSwaggerWithConfiguration();
app.UseMvc();
}
Current Configuration
These are my custom extension methods, used to group all the necessary configuration in one separate place.
```c#
public static void AddSwaggerDocumentWithConfiguration(this IServiceCollection services)
{
ServiceProvider serviceProvider = services.BuildServiceProvider();
ControlApiConfiguration apiConfiguration = serviceProvider.GetService
Uri baseUri = new Uri(apiConfiguration.AuthEndpoint);
/* Correct?
* Derived from previous v11.20.1 configuration
* I set API versioned documents in this way.
*/
services.AddSwaggerDocument(config =>
{
config.DocumentName = "1.0";
config.ApiGroupNames = new[] { "1.0-beta" };
config.RequireParametersWithoutDefault = false;
config.DocumentProcessors.Add(
new SecurityDefinitionAppender("oauth2", new SwaggerSecurityScheme
{
Description = "OAuth 2",
Type = SwaggerSecuritySchemeType.OAuth2,
Flow = SwaggerOAuth2Flow.Implicit,
AuthorizationUrl = new Uri(baseUri, "connect/authorize").ToString(),
TokenUrl = new Uri(baseUri, "connect/token").ToString(),
Scopes = new Dictionary<string, string> { { "mycompany.core.v1", $"{title} - Full access" } }
}));
config.OperationProcessors.Add(new OperationSecurityScopeProcessor("oauth2"));
config.Title = title;
config.Description = description;
config.Version = "1.0.0";
config.PostProcess = document =>
{
document.Info.Contact = new SwaggerContact
{
Name = "My Company",
Email = "[email protected]",
Url = "https://www.mycompany.com"
};
};
});
services.AddSwaggerDocument(config =>
{
config.DocumentName = "2.0";
config.ApiGroupNames = new[] { "2.0-beta" };
config.RequireParametersWithoutDefault = false;
config.DocumentProcessors.Add(
new SecurityDefinitionAppender("oauth2", new SwaggerSecurityScheme
{
Description = "OAuth 2",
Type = SwaggerSecuritySchemeType.OAuth2,
Flow = SwaggerOAuth2Flow.Implicit,
AuthorizationUrl = new Uri(baseUri, "connect/authorize").ToString(),
TokenUrl = new Uri(baseUri, "connect/token").ToString(),
Scopes = new Dictionary<string, string> { { "mycompany.core.v2", $"{title} - Full access" } }
}));
config.OperationProcessors.Add(new OperationSecurityScopeProcessor("oauth2"));
config.Title = title;
config.Description = description;
config.Version = "2.0.0";
config.PostProcess = document =>
{
document.Info.Contact = new SwaggerContact
{
Name = "My Company",
Email = "[email protected]",
Url = "https://www.mycompany.com"
};
};
});
}
public static void UseSwaggerWithConfiguration(this IApplicationBuilder app)
{
app.UseSwagger(config =>
{
config.PostProcess = (document, request) =>
{
if (request.Headers.ContainsKey("X-External-Host"))
{
document.Host = request.Headers["X-External-Host"].First();
document.BasePath = request.Headers["X-External-Path"].First();
}
};
});
app.UseSwaggerUi3(config =>
{
config.OAuth2Client = new OAuth2ClientSettings
{
ClientId = "core-api-swagger",
ClientSecret = "secret".ToSha256(),
AppName = $"{title} - Swagger"
};
config.TransformToExternalPath = (internalUiRoute, request) =>
{
// The header X-External-Path is set in the nginx.conf file
var externalPath = request.Headers.ContainsKey("X-External-Path") ? request.Headers["X-External-Path"].First() : "";
return externalPath + internalUiRoute;
};
// Correct?
config.SwaggerRoutes.Add(new SwaggerUi3Route("v1.0-beta", "v1.0-beta/swagger.json"));
config.SwaggerRoutes.Add(new SwaggerUi3Route("v2.0-beta", "v2.0-beta/swagger.json"));
});
}
**Sample Controller**
In the `Get` method I replaced the `ProducesResponseType` attribute with `SwaggerResponse` attribute, as you suggested. While in the `List()` I leaved the `ProducesResponseType` just to see the difference in the swagger UI.
```c#
[ApiController]
[Authorize]
[Route("api/v{version:apiVersion}/[controller]/[action]")]
[ApiVersion("1.0-beta")]
[Produces("application/json")]
[SwaggerTag("Anomalies", Description = "Anomalies management")]
public class AnomaliesController : ControllerBase
{
/// <summary>
/// Find anomaly by ID
/// </summary>
/// <param name="id">ID of the anomaly to return</param>
/// <remarks>Returns a single anomaly object</remarks>
/// <response code="200">Successful operation</response>
/// <response code="400">Invalid ID supplied</response>
/// <response code="404">Anomaly not found</response>
[HttpGet("{id:long}")]
[SwaggerResponse(HttpStatusCode.OK, typeof(api.Anomaly), Description = "Successfull operation")]
[SwaggerResponse(HttpStatusCode.BadRequest, typeof(api.Anomaly), Description = "Invalid ID supplied")]
[SwaggerResponse(HttpStatusCode.NotFound, typeof(api.Anomaly), Description = "Anomaly not found")]
public async Task<ActionResult<api.Anomaly>> Get(long id)
{
return Mapper.Map<api.Anomaly>(await DcsMediator.Send(new GetAnomalyByIdQuery() { Id = id }));
}
/// <summary>
/// Find anomalies by specified parameters
/// </summary>
/// <param name="machineId">ID of the machine that need to be considered for filter</param>
/// <param name="type">AnomalyType value that need to be considered for filter</param>
/// <param name="status">AnomalyStatus value that need to be considered for filter</param>
/// <remarks>Values of filter are considered in AND</remarks>
/// <response code="200">Successful operation</response>
/// <response code="400">Invalid filter values</response>
[HttpGet]
[ProducesResponseType(typeof(List<api.Anomaly>), (int)HttpStatusCode.OK)]
[ProducesResponseType((int)HttpStatusCode.BadRequest)]
public async Task<ActionResult<List<api.Anomaly>>> List(long? machineId, AnomalyType? type, AnomalyStatus? status, CancellationToken cancellationToken)
{
return Mapper.Map<List<api.Anomaly>>(await DcsMediator.Send(
new GetAnomalyListByFilterQuery() { MachineId = machineId, Status = status, Type = type, IncludeMachine = true }, cancellationToken));
}
}
Inside the code, I added some "pin" comments like //Correct? where I'm not sure about.
I hope that maybe, at last, this configuration could become a complex code sample for NSwag, useful for someone else too! :satisfied:
Thank you!
config.ApiGroupNames = new[] { "1.0-beta" }; is wrong, i.e. there is no api group named "1.0-beta" - somehow this has to match the config in AddApiVersioning but for that I don't know enough about api versioning - maybe "1.0" only or "1.0.beta"?You need to use these two routes in UseSwaggerUi() otherwise it cannot find it (404) (or just let it automatically register the document routes)
So, my options are:
What should be the values for `new SwaggerUi3Route()`? What values do they have to match?
About API versioning, I followed [GitHub aspnet-api-versioning](https://github.com/Microsoft/aspnet-api-versioning/wiki/Version-Format#custom-api-version-format-strings) specs to set versioning in `services.AddApiVersioning()` and `services.AddMvcCore().AddVersionedApiExplorer()` like this:
```c#
// VVV: Major, optional minor version, and status --> (ie: /api/v2.0-Alpha/foo) <--
options.GroupNameFormat = "VVV";
--> options.DefaultApiVersion = new ApiVersion(1, 0, "beta"); <--
Using the VVV format and 1.0-beta value in [ApiVersion] tag, it's actually working and by using Postman to call:
http://localhost:5001/api/v1.0-beta/Anomalies/Get/4
I obtain a successfull request.
Anyway, since it is not really necessary to me, I also tried to remove the beta part, but I get same previously described errors: 1 (404) or 2 (No operations defined in spec!).
Because UseSwagger implicitly adds the two document routes (as listed in the previous post), you need to use
config.SwaggerRoutes.Add(new SwaggerUi3Route("v1.0-beta", "swagger/1.0/swagger.json"));
config.SwaggerRoutes.Add(new SwaggerUi3Route("v2.0-beta", "swagger/2.0/swagger.json"));
AddVersionedApiExplorer registers api groups with the name of the registered version but it seems that the name is not "1.0-beta". @commonsensesoftware do you know the registered api group name for this case?
I made some adjustments (see changes below) but I'm still not able to get documentation working.
With this code in place I always get
No operations defined in spec!
What am I missing or doing wrong? :confused:
My custom extension methods for configuration
```c#
services.AddSwaggerDocument(config =>
{
config.DocumentName = "v1.0";
config.ApiGroupNames = new[] { "1.0" };
// ...
config.Version = "1.0.0";
// ...
}
services.AddSwaggerDocument(config =>
{
config.DocumentName = "v2.0";
config.ApiGroupNames = new[] { "2.0" };
// ...
config.Version = "2.0.0";
// ...
}
app.UseSwaggerUi3(config =>
{
// ...
// Without the starting '/' I get 404
config.SwaggerRoutes.Add(new SwaggerUi3Route("v1.0", "/swagger/v1.0/swagger.json"));
config.SwaggerRoutes.Add(new SwaggerUi3Route("v2.0", "/swagger/v2.0/swagger.json"));
}
**Startup**
```c#
services.AddApiVersioning(options =>
{
options.AssumeDefaultVersionWhenUnspecified = true;
options.DefaultApiVersion = new ApiVersion(1, 0);
options.ReportApiVersions = true;
options.ApiVersionReader = new UrlSegmentApiVersionReader();
});
.AddVersionedApiExplorer(options =>
{
// VVV: Major, optional minor version, and status (ie: /api/v2.0-Alpha/foo);
options.GroupNameFormat = "VVV";
options.AssumeDefaultVersionWhenUnspecified = true;
options.DefaultApiVersion = new ApiVersion(1, 0);
options.SubstituteApiVersionInUrl = true;
})
Controller
c#
[ApiController]
[Authorize]
[Route("api/v{version:apiVersion}/[controller]/[action]")]
[ApiVersion("1.0")]
[Produces("application/json")]
public class AnomaliesController : ControllerBase
{
// ...
}
It seems that your ApiGroupNames are wrong/do not exist. Try to inject the IApiDescriptionGroupCollectionProvider interface into one of your controller, set a breakpoint and inspect the groups so that you know the existing groups...
@RSuter Yes, it really seems that no ApiGroup is present.

So, could it be a problem related only to the configuration set with AddApiVersioning() and/or AddVersionedApiExplorer() extension methods in the Startup class?
Yep, its probably a problem with a config in one of these two... check ou my sample and work from this to your solution
Looking at your example, I found some differences with my Startup code.
Yours
```c#
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
services.AddApiVersioning(options =>
{
options.AssumeDefaultVersionWhenUnspecified = true;
options.ApiVersionReader = new UrlSegmentApiVersionReader();
})
.AddMvcCore()
.AddVersionedApiExplorer(options =>
{
options.GroupNameFormat = "VVV";
options.SubstituteApiVersionInUrl = true;
});
// ...
}
**Mine**
```c#
public void ConfigureServices(IServiceCollection services)
{
services.AddApiVersioning(options =>
{
options.AssumeDefaultVersionWhenUnspecified = true;
//options.DefaultApiVersion = new ApiVersion(1, 0);
//options.ReportApiVersions = true;
options.ApiVersionReader = new UrlSegmentApiVersionReader();
});
services.AddMvcCore()
.SetCompatibilityVersion(CompatibilityVersion.Version_2_1)
.AddVersionedApiExplorer(options =>
{
// See: https://github.com/Microsoft/aspnet-api-versioning/wiki/Version-Format#custom-api-version-format-strings
options.GroupNameFormat = "VVV"; // VVV: Major, optional minor version, and status (ie: /api/v2.0-Alpha/foo);
//options.AssumeDefaultVersionWhenUnspecified = true;
//options.DefaultApiVersion = new ApiVersion(1, 0);
options.SubstituteApiVersionInUrl = true;
});
}
Following this article, I setup my Web API project like you see in my code, so without using the AddMvc() method, but instead using just AddMvcCore().
From the article:
So when using AddMvcCore() we have to add everything by ourselves. This means, that we only have in our application what we really want and for example do not include the razor functionality which we do not need anyway.
In your code instead, you're using both methods. Is there any specific reason why you use them both?
If I understood correctly what the article says, the 2 methods are something like "mutual exlusive" at least at logical level (_no compilation errors actually appears if you're using both like you did_). So I think they're intended to be used as an alternative to each other. Am I wrong?
I also already tried to put the AddApiVersioning() after the AddMvcCore(), but nothing changes and no new errors appears too.
In the sample it works:

I'm really not an expert with API versioning and dont know all the required settings to make this work. Maybe @commonsensesoftware can help you here?
In your code instead, you're using both methods. Is there any specific reason why you use them both?
That's probably a mistake...
@OculiViridi I don't believe you've indicated which version of API versioning you're using. If it's 2.x, there's a _chance_ that the order of service registration may be causing you grief. This has been remedied in 3.0+. I would recommend that you register AddMvc() or AddMvcCore() first. In addition, you have to also call AddApiExplorer() when you use AddMvcCore(). AddMvc() automatically does that. This has also been corrected in 3.0. That's almost certianly why you aren't seeing an results. The _API Explorer_ in ASP.NET Core does most of the work. The API Versioning extensions merely collates the results such that they are grouped by API version.
In terms of matching up the groups to Swagger documents, the expectation is that they will match by API version. The default behavior will be to use the result of ApiVersion.ToString(). You have full control over how you want things to be formatted using the GroupNameFormat, which indicates the format that should be used in ApiVersion.ToString(IFormatProvider,string). I notice that you're using the built in format code VVV. This is not wrong; however, be aware that this does not include the v character. The v is not part of the API version. If you want or need to include the v in the group name, you can use the format:
c#
options.GroupNameFormat = "'v'VVV";
It's also worth noting that the configuration for your Swagger documents seem to be nearly identical save the version information. You can use the IApiVersionDescriptionProvider provided by API versioning to enumerate all of the defined API versions in your application to build this information with a simple loop. You can use the various, supported format codes for the API version to achieve the different forms you use in your documentation. You can see an example of this at work here. This service will also do the work of determining whether an API version is deprecated for you. If all the APIs for a given version are deprecated, then the entire API version is considered deprecated too.
I happy to answer any other questions to help you get unblocked.
@commonsensesoftware Thank you for joining the conversation!
I don't believe you've indicated which version of API versioning you're using.
I'm on .NET Core 2.1 (2.1.6).
In addition, you have to also call
AddApiExplorer()when you useAddMvcCore()
Done! I removed it when introduced the AddVersionedApiExplorer(). Now it's back in place.
As suggested by @RSuter, I can see the API Groups (1, 2) in the IApiDescriptionGroupCollectionProvider object using the debugger.

I notice that you're using the built in format code VVV. This is not wrong; however, be aware that this does not include the v character. The v is not part of the API version.
I'm using the VVV format and the v string is added by the attribute:
```c#
[Route("api/v{version:apiVersion}/[controller]/[action]")]
In fact the following piece of my code has always been the same and with NSwag v11.20.1 everything was working and I was able to succesfully call:
/api/v1.0/Anomalies/Get/1
My new idea is to use the version according to the `/api/v1.0-beta/Anomalies/Get/1` format.
But now, even after the below changes, I'm in trouble with swagger documents also using my original previous simplest `/api/v1.0/MyController/Action` format.
> You can use the IApiVersionDescriptionProvider provided by API versioning to enumerate all of the defined API versions in your application to build this information with a simple loop.
Thanks! I will seriously take it into consideration, as it is certainly a cleaner approach, once I have managed to make swagger work again.
Anyway, with Swagger I'm still on
> No operations defined in spec!
What's still wrong in my configuration?
Now API Groups are in place. Is there still something to fix in my `Startup` configuration or where else?
Please take a look at my adjusted code.
**Startup**
```c#
services.AddMvcCore()
.SetCompatibilityVersion(CompatibilityVersion.Version_2_1)
.AddApiExplorer()
.AddVersionedApiExplorer(options =>
{
options.GroupNameFormat = "VVV"; // VVV: Major, optional minor version, and status (ie: /api/v2.0-Alpha/foo);
options.AssumeDefaultVersionWhenUnspecified = true;
options.DefaultApiVersion = new ApiVersion(1, 0);
options.SubstituteApiVersionInUrl = true;
})
.AddAuthorization()
.AddJsonFormatters()
.AddMvcLocalization(LanguageViewLocationExpanderFormat.Suffix)
.AddDataAnnotationsLocalization()
.AddFluentValidation(fv => fv.RegisterValidatorsFromAssemblyContaining<MachineCreateModelValidator>());
services.AddApiVersioning(options =>
{
options.AssumeDefaultVersionWhenUnspecified = true;
options.DefaultApiVersion = new ApiVersion(1, 0);
options.ReportApiVersions = true;
options.ApiVersionReader = new UrlSegmentApiVersionReader();
});
Custom extension methods used for configuration of NSwag
```c#
services.AddSwaggerDocument(config =>
{
config.DocumentName = "v1.0";
config.ApiGroupNames = new[] { "1.0" };
// ...
config.Version = "1.0.0";
// ...
}
services.AddSwaggerDocument(config =>
{
config.DocumentName = "v2.0";
config.ApiGroupNames = new[] { "2.0" };
// ...
config.Version = "2.0.0";
// ...
}
app.UseSwaggerUi3(config =>
{
// ...
// Without the starting '/' I get 404
config.SwaggerRoutes.Add(new SwaggerUi3Route("v1.0", "/swagger/v1.0/swagger.json"));
config.SwaggerRoutes.Add(new SwaggerUi3Route("v2.0", "/swagger/v2.0/swagger.json"));
}
**Controller**
```c#
[ApiController]
[Authorize]
[Route("api/v{version:apiVersion}/[controller]/[action]")]
[ApiVersion("1.0")]
[Produces("application/json")]
public class AnomaliesController : ControllerBase
{
// ...
}
Also, another thing that's not working is the DefaultApiVersion.
With the current code, using Postman, I can succesfully call http://localhost:5001/api/v1.0/Anomalies/Get/1 and get the expected result.
But, if I'm not wrong, I should also be able to call http://localhost:5001/api/Anomalies/Get/1 (without the version specified) and being routed automatically to /v1.0 that's the default version.
Actually I get empty response. With the debugger activated I don't reach the controller.
See below image of Postman result.

What's the problem here?
The first issue appears to be a mismatch on the group names. Remember that the VVV format uses an optional minor version. This means that the version 1.0 will be formatted as 1. This behavior can combined with the literal v character to produce v1, which is a common desire. Remember that v is not part of the API version. Yes, you have it in your route template, but it's outside the route constraint, meaning it's just a literal character. Now, your NSwag configuration says that the group name is "1.0" and "2.0". This does not match the formatted values "1" or "2". This is likely why you don't see any operations generated. Consider using the format codes VV or VVVV to match these strings. You can also change your group names to be "1" and "2" respectively.
Since you've elected to version by URL segment, the DefaultApiVersion is going to work a little differently. It is not possible to have optional or default values in the middle of a route template. This is how routing in ASP.NET Core (and probably any other framework) works. The recommended way to make this work is to include _double routes_ for the _default_ route. For example, add an additional [Route("api/[controller]/[action]")]. This template does not include a route constraint, which is where the API version would be derived from using this method. As you've allowed no API version to be specified and the value cannot be derived from the route parameters, the API version will be assumed to be the value of DefaultApiVersion. I hope that clears things up.
Finally, I'm now able to get my Swagger docs!
@commonsensesoftware As you said,
Now, your NSwag configuration says that the group name is "1.0" and "2.0". This does not match the formatted values "1" or "2".
and, by just changing the GroupNameFormat to VVVV format, I'm now able to list all the controllers in the docs.
By the way, there's still a couple of issues...
My actual configuration is (except for the GroupNameFormat now set to VVVV) exactly the same reported in my previous post here above. So please take it as reference code.
The automatically generated Swagger paths for methods of controller, now include the /v1/ path part. However, I expected it to be /v1.0/... why is it so?
Since I set the VVVV format, I also tried by setting version as v1.0-beta. It gives me /v1-beta/ instead of /v1.0-beta/.

I tried with your suggestion
The recommended way to make this work is to include double routes for the default route. For example, add an additional [Route("api/[controller]/[action]")].
and added
c#
[Route("api/v{version:apiVersion}/[controller]/[action]")]
[Route("api/[controller]/[action]")]
But, doubling the routes, doubles (duplicates) methods on Swagger docs... see image below.
@RSuter Is there another way to do that or a specific setting to avoid duplicates?

I think it could be useful to clarify what are the correspondences of values that must be taken into consideration to make things work properly.
Till now I understand that:
config.ApiGroupNames = new[] { "1.0" } value has to match with the GroupNameFormat format set on the ConfigureServices method of Startup class.config.DocumentName = "v1.0" value has to match with config.SwaggerRoutes.Add(new SwaggerUi3Route("v1.0", "/swagger/v1.0/swagger.json")) for the v1.0 value. [ApiVersion("1.0")] tag in controllers doesn't need to exactly match with the config.ApiGroupNames = new[] { "1.0" } value. I made a test using 1 in the tag and 1.0 in the ApiGroupNames array and it works.Is there any other match to consider that I'm missing?
Here's some additional clarification. Keep in mind that the ApiVersion is a formal type and not a _magic string_. That said, in context of documentation there are several pieces of configuration. For API versioning, groups are collated by API version. I would think that it should fairly obvious that config.ApiGroupNames and the result of formatting using the GroupNameFormat must match. I think there may also be some confusion between the format used for _grouping_ and the format used for _substitution_, which may not be the same. You may want to review the meaning of each of the options.
GroupNameFormat has no default value, which results in the ApiDescription.GroupName being the result of ApiVersion.ToString(). The choice to change it is really about how you want it to match up to NSwag and how NSwag might use this value. For example, it's completely reasonable for NSwag to build a list of documents using the distinct list of ApiDescription.GroupName values. In such a scenario, you may want to control how that value is rendered as it may surface in route paths or other parts of the Swagger UI.
The second part is how you want the API version to be formatted when it is _substituted_ into the corresponding route template. This does not need to be the same as the format used for group names. This is controlled using the SubstitutionFormat whose default value is VVV as that tends to be the most common format used in the URL segment method. The SubstitutionFormat is what affects how the value looks in the final URL template. This means you want your configuration something like:
c#
.AddVersionedApiExplorer(options =>
{
options.GroupNameFormat = "VVVV";
options.SubstitutionFormat = "VVVV";
options.SubstituteApiVersionInUrl = true;
})
I'm not familiar with how NSwag extensibility works, but I assume there is a way to customize Swagger document generation. There should be a way to filter out these operations so that entries are not generated for them. Technically, everything is working the way it's expected to. You really do have two different routes for the same API version.
Another approach is to use a custom IApiDescriptionProvider and register that with ASP.NET Core. You'll want your provider to run last by configuring the Order property. When your provider runs, you can look for ApiDescription results that have the path you don't want and remove them. Using this approach, NSwag will simply never see those ApiDescription instances.
Is there another way to do that or a specific setting to avoid duplicates?
I think you can implement a custom operation processor and filter them out.
Nginx/reverse proxy users, please review: https://github.com/RicoSuter/NSwag/pull/2196
Most helpful comment
app.UseSwagger(typeof(Startup).Assembly, configure)(with assembly or list of controller types) uses WebApiToSwaggerGenerator which is deprecatedapp.UseSwagger(configure)uses AspNetCoreToSwaggerGenerator which is not deprecatedUseSwaggerand config.GeneratorSettings must now be done inAddSwaggerDocument(configure)in ConfigureServices()