Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Failed to generate documentation for multiple API versions using ApiVersioning and ApiExplorer #1393

Closed
paulreicherdt opened this issue Jun 18, 2018 · 16 comments

Comments

@paulreicherdt
Copy link

Hello,

I'm trying to generate documentation for multiple API version in my ASP.NET Core 2.1 web api project. I configured versioned ApiExplorer and use ApiVersionning. The idea is to use single swagger ui route with multiple swagger routes per version.

Here is my Startup.cs functions:

public void ConfigureServices(IServiceCollection services)
{
	services.AddMvcCore()
		.AddVersionedApiExplorer(options =>
		{
			options.GroupNameFormat = "'v'VVV";
			options.SubstituteApiVersionInUrl = true;
			options.AssumeDefaultVersionWhenUnspecified = true;
			options.DefaultApiVersion = new ApiVersion(1, 0);
		})
		.AddDataAnnotations()
		.AddJsonFormatters()
		.AddJsonOptions(options => options.SerializerSettings.Converters.Add(new StringEnumConverter()));

	services.AddApiVersioning(options => { options.ReportApiVersions = true; });

	services.AddSwagger();
}

public void Configure(IApplicationBuilder app, IHostingEnvironment env, IApplicationLifetime applicationLifetime, ILoggerFactory loggerFactory, IApiVersionDescriptionProvider provider)
{
	if (env.IsDevelopment())
	{
		app.UseDeveloperExceptionPage();
	}
	else
	{
		app.UseExceptionHandler("/Error");
		app.UseHsts();
	}

	app.UseHttpsRedirection();
	app.UseStaticFiles();

	app.UseMvc();

	app.UseSwaggerUi3WithApiExplorer(settings =>
	{
		settings.SwaggerUiRoute = "/swagger";

		foreach (var description in provider.ApiVersionDescriptions)
		{
			settings.SwaggerRoutes.Add(new SwaggerUi3Route(description.GroupName.ToUpperInvariant(), $"/swagger/{description.GroupName}/swagger.json"));
		}
	});
}

And my two controllers:

[ApiController]
[ApiVersion("1.0")]
[Route("api/v{version:apiVersion}/[controller]")]
[SwaggerTag("Customers", Description = "Operations about customers")]
public class CustomersController : Controller
{
	[HttpGet]
	[SwaggerOperation("Customers_ReadAll")]
	public ActionResult<LookupValuesApiModel> ReadAll()
	{
		// code
	}
}

[ApiController]
[ApiVersion("2.0")]
[Route("api/v{version:apiVersion}/[controller]")]
[SwaggerTag("Products", Description = "Operations about products")]
public class ProductsController : Controller
{
	[HttpGet]
	[SwaggerOperation("Products_ReadAll")]
	public ActionResult<LookupValuesApiModel> ReadAll()
	{
		// code
	}
}

The swagger ui shows a dropdown with two API versions - V1 and V2 - as expected.
But, both versions shows an error:

Failed to load API definition.
Not Found /swagger/v1/swagger.json

If I define a single swagger route, e.g. "/swagger/swagger.json" I get a valid single documentation with proper routes. But the point is, I want to separate different api versions.

Do I miss something in configuration?

Kind regards
Paul

@RicoSuter
Copy link
Owner

API versioning is currently not fully implemented/supported, I think mainly because it is not part of the ASP.NET Core base framework, see:

#1355
#1364

@paulreicherdt
Copy link
Author

Ok, I see. Can you give some indication of when it will be implemented/supported?
API versioning is not currently a main issue in my project, but just for the planning.

@RicoSuter
Copy link
Owner

I think the base functionality is more or less implemented, just needs some bug fixing i think: https://github.com/RSuter/NSwag/blob/master/src/NSwag.SwaggerGeneration/Processors/ApiVersionProcessor.cs

@RicoSuter
Copy link
Owner

Maybe you just have to specify the version for each Swagger registration: https://github.com/RSuter/NSwag/blob/master/src/NSwag.SwaggerGeneration/Processors/ApiVersionProcessor.cs#L23

@paulreicherdt
Copy link
Author

With the latest version 11.17.18 I'm able to spedify IncludedVersions, but still not figured out, how to generate different json files for different versions.

This code works and creates a single json file:

app.UseSwaggerUi3WithApiExplorer(settings =>
{
     settings.GeneratorSettings.OperationProcessors.TryGet<ApiVersionProcessor>().IncludedVersions = new[] { "1.0", "2.0" };
});

But if I try to use IApiVersionDescriptionProvider and specify different swagger routes like:

app.UseSwaggerUi3WithApiExplorer(settings =>
{
    settings.GeneratorSettings.OperationProcessors.TryGet<ApiVersionProcessor>().IncludedVersions = new[] { "1.0", "2.0" };

    foreach (var description in provider.ApiVersionDescriptions)
    {
        settings.SwaggerRoutes.Add(new SwaggerUi3Route(description.GroupName.ToUpperInvariant(), $"/swagger/{description.GroupName}/swagger.json"));
    }
});

I got the same error as described in first post: Failed to load API definition.

Can you please provide a sample, how to properly configure the swagger settings?
Many thanks.

@RicoSuter
Copy link
Owner

RicoSuter commented Jul 2, 2018

This is how to register two routes

app.UseSwaggerWithApiExplorer(settings => 
{
    settings.GeneratorSettings.OperationProcessors.TryGet<ApiVersionProcessor>().IncludedVersions = new[] { "1.0" };
    settings.SwaggerRoute = "a.json"
});

app.UseSwaggerWithApiExplorer(settings => 
{
    settings.GeneratorSettings.OperationProcessors.TryGet<ApiVersionProcessor>().IncludedVersions = new[] { "2.0" };
    settings.SwaggerRoute = "b.json"
});

app.UseSwaggerUi3(settings =>
{
    settings.SwaggerRoutes.Add("a.json");
    settings.SwaggerRoutes.Add("b.json");
});

app.UseSwaggerUi3WithApiExplorer registers the swagger gen + the UI...

@paulreicherdt
Copy link
Author

Great! Thanks a lot.

There seems to be a problem with SwaggerTag attribute handling. Controllers which contain this attribute are listed in all versions. But they are empty if no action match the currently selected version.

@RicoSuter
Copy link
Owner

There seems to be a problem with SwaggerTag attribute handling. Controllers which contain this attribute are listed in all versions. But they are empty if no action match the currently selected version.

That is very strange...

@RicoSuter
Copy link
Owner

Can you provide a sample, best would be a PR with a test here :-)

image

@paulreicherdt
Copy link
Author

The problem is not the operation count. Your tests are fine :)

It's all about swagger tags. They appear in the document (and then also in swagger UI) even if there are no operations. I cannot create a PR because of firewall blocking by my company. But here is a patch that hopefully should help to understand the issue.

versioning_tests_patch.zip

@RicoSuter
Copy link
Owner

Thanks for the patch! Should be fixed now...
Ill check what CI says and will release a new version if everything looks fine.

@paulreicherdt
Copy link
Author

Just noticed a side-effect of changes to UseSwaggerWithApiExplorer mentioned above.
The xml documentation is broken. For some properties its completely gone, for others - swagger ui shows xml summary from the class and not from property... really strange :(

@paulreicherdt
Copy link
Author

And also optional properties on request models are now shown as required.

@RicoSuter
Copy link
Owner

Really? Have you checked whether the generated spec is wrong? I dont think this is a regression from the commit above..

@paulreicherdt
Copy link
Author

paulreicherdt commented Jul 5, 2018

Yes, the spec is wrong.

Here is the model that is used:

public class MediaForPublisherApiRequest
{
	/// <summary>
	///     The publisher identifier.
	/// </summary>
	[Required]
	[Range(1, int.MaxValue)]
	public int PublisherId { get; set; }

	/// <summary>
	///     The channel type.
	/// </summary>
	[Required]
	public OnlineChannelApiType ChannelType { get; set; }

	/// <summary>
	///     The media name.
	/// </summary>
	public string Name { get; set; }
}

Here is the sample from new generated spec using UseSwaggerWithApiExplorer and UseSwaggerUi3WithApiExplorer

"parameters": [
  {
	"type": "integer",
	"name": "PublisherId",
	"in": "query",
	"required": true,
	"description": "Describes the media for publisher api request parameter for the query.",
	"format": "int32",
	"x-nullable": false
  },
  {
	"type": "string",
	"name": "ChannelType",
	"in": "query",
	"required": true,
	"description": "Describes the media for publisher api request parameter for the query.",
	"x-schema": {
	  "$ref": "#/definitions/OnlineChannelApiType"
	},
	"x-nullable": false,
	"enum": [
	  "Online",
	  "Searchenginemarketing",
	  "Mobile",
	  "Adserving",
	  "Apps",
	  "Social"
	]
  },
  {
	"type": "string",
	"name": "Name",
	"in": "query",
	"required": true,
	"description": "Describes the media for publisher api request parameter for the query.",
	"x-nullable": true
  }
]

And here is the same model spec but generated using UseSwaggerUi3.

"parameters": [
  {
	"type": "integer",
	"name": "publisherId",
	"in": "query",
	"required": true,
	"description": "The publisher identifier.\n            ",
	"format": "int32",
	"maximum": 2147483647.0,
	"minimum": 1.0,
	"x-nullable": false
  },
  {
	"type": "string",
	"name": "channelType",
	"in": "query",
	"required": true,
	"description": "The channel type.\n            ",
	"x-schema": {
	  "$ref": "#/definitions/OnlineChannelApiType"
	},
	"x-nullable": false,
	"enum": [
	  "Online",
	  "Searchenginemarketing",
	  "Mobile",
	  "Adserving",
	  "Apps",
	  "Social"
	]
  },
  {
	"type": "string",
	"name": "name",
	"in": "query",
	"description": "The media name.\n            ",
	"x-nullable": true
  }
]

As you can see, the descriptions are wrong (all the same) and name property is marked as required.

By the way. this text "Describes the media for publisher api request parameter for the query" comes from controller action:

/// <summary>
///     Reads all media for a given media name, channel type and publisherid.
/// </summary>
/// <param name="apiRequest">Describes the media for publisher api request parameter for the query.</param>
[HttpGet]
[Route("publisher")]
[SwaggerOperation("Media_ReadByNameAndChannelAndPublisher")]
public ActionResult<MediaValuesApiModel> ReadByNameAndChannelAndPublisher([FromQuery] MediaForPublisherApiRequest apiRequest)
{
	//...
}

Really confusing :(

@davidbuckleyni
Copy link

I am having same issue in 2021 configured has above to controllers different names and end points but yet still have issues where it fails to load the version 2 swagger is this still an issue im using .net 5 webapi

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants