0

I have problem with display endpoints in SwaggerUI. For example

[ApiVersion("5.0")]
[RoutePrefix("mobile/v{version:apiVersion}/persons")]
public class LessonController : ApiController
{

 -------------------------------------

    [HttpGet]
    [Route("{personId}/groups/{groupId}/lessons/{lessonId}/lessonDetails")]
    public LessonDetailsModel LessonDetails(long personId, long groupId, long lessonId)
    {
       ----------------------------------

It's endpoint don't show but if i change RoutePrefix to

 [RoutePrefix("mobile/v{version:apiVersion}/persons1")]

endpoint show

UPD: SwaggerConfig

 GlobalConfiguration.Configuration
            .EnableSwagger("mobile/swagger/docs/{apiVersion}", c => {
                  c.MultipleApiVersions(
                    ResolveVersionSupportByRouteConstraint,
                    (vc) =>
                    {
                        vc.Version("v5.0", "Api version 5");
                        vc.Version("v4.0", "Api version 4");
           ------------------------------------------------------
                    });

                c.UseFullTypeNameInSchemaIds();
                c.ResolveConflictingActions(apiDescriptions => apiDescriptions.First());
                c.OperationFilter<RemoveVersionParameters>();
                c.OperationFilter<InjectCustomApiParameters>();
                c.DocumentFilter<SetVersionInPaths>();
                c.IncludeXmlComments(Path.ChangeExtension($"{Assembly.GetExecutingAssembly().GetName().CodeBase}", ".xml"));
            })
            .EnableSwaggerUi("mobile/swagger/ui/{*assetPath}", c => {
                c.DocumentTitle("Mobile Api Swagger");
                c.DisableValidator();
                c.EnableDiscoveryUrlSelector();
            });

  • Please show us how your Startup's `ConfigureServices` and `Configure` methods look like. Please include only the related part. – Peter Csala Jun 04 '20 at 11:48
  • BTW [here is a walkthrough](https://dev.to/htissink/versioning-asp-net-core-apis-with-swashbuckle-making-space-potatoes-v-x-x-x-3po7) how to achieve versioning with Swashbuckle – Peter Csala Jun 04 '20 at 11:50
  • Hmm, that's interesting. `MultipleApiVersions` and `EnableDiscoveryUrlSelector` calas are there... This *v5.0* is strange for me, to be honest I haven't seen minor version inside an API version. Have you tried it without minor version, so only just with major? (**v4** and **v5**) – Peter Csala Jun 04 '20 at 14:19
  • Unfortunately, this does not affect the display, only thing i found is change RoutePrefix, maybe IHttpControllerSelector performs poorly – Алексей Алексеев Jun 04 '20 at 14:31
  • Just to make it clear: when you are talking about it is not shown, what do you mean? 1) None of versions are shown 2) Only one of the versions is shown (first or last registered) 3) A non-existing endpoint is shown, where the placeholder is not replaced with proper version information 4) Other? – Peter Csala Jun 05 '20 at 06:34

1 Answers1

1

I have found solution for my problem. I implemented IApiExplorer.

public class VersionedApiExplorer : IApiExplorer
{
    private readonly IApiExplorer _innerApiExplorer;
    private readonly HttpConfiguration _configuration;
    private readonly Lazy<Collection<ApiDescription>> _apiDescriptions;
    private MethodInfo _apiDescriptionPopulator;

    public VersionedApiExplorer(IApiExplorer apiExplorer, HttpConfiguration configuration)
    {
        _innerApiExplorer = apiExplorer;
        _configuration = configuration;
        _apiDescriptions = new Lazy<Collection<ApiDescription>>(Init);
    }

    public Collection<ApiDescription> ApiDescriptions => _apiDescriptions.Value;

    private Collection<ApiDescription> Init()
    {
        var descriptions = _innerApiExplorer.ApiDescriptions;

        var controllerSelector = _configuration.Services.GetHttpControllerSelector();
        var controllerMappings = controllerSelector.GetControllerMapping();

        var flatRoutes = FlattenRoutes(_configuration.Routes).ToList();
        var result = new Collection<ApiDescription>();

        foreach (var description in descriptions)
        {
            result.Add(description);

            if (controllerMappings == null || !description.Route.Constraints.Any()) continue;
            var matchingRoutes = flatRoutes.Where(r =>
                r.RouteTemplate == description.Route.RouteTemplate && r != description.Route);

            foreach (var route in matchingRoutes)
                GetRouteDescriptions(route, result);
        }

        return result;
    }

    private void GetRouteDescriptions(IHttpRoute route, IReadOnlyCollection<ApiDescription> apiDescriptions)
    {
        if (route.DataTokens["actions"] is IEnumerable<HttpActionDescriptor> actionDescriptor &&
            actionDescriptor.Any())
            GetPopulateMethod().Invoke(_innerApiExplorer,
                new object[] {actionDescriptor.First(), route, route.RouteTemplate, apiDescriptions});
    }

    private MethodInfo GetPopulateMethod()
    {
        if (_apiDescriptionPopulator == null)
            _apiDescriptionPopulator = _innerApiExplorer.GetType()
                .GetMethods(BindingFlags.NonPublic | BindingFlags.Instance).FirstOrDefault(
                    m => m.Name == "PopulateActionDescriptions" && m.GetParameters().Length == 4);

        return _apiDescriptionPopulator;
    }

    public static IEnumerable<IHttpRoute> FlattenRoutes(IEnumerable<IHttpRoute> routes)
    {
        foreach (var route in routes)
        {
            switch (route)
            {
                case HttpRoute _:
                    yield return route;
                    break;
                case IReadOnlyCollection<IHttpRoute> subRoutes:
                {
                    foreach (var subRoute in FlattenRoutes(subRoutes))
                        yield return subRoute;
                    break;
                }
            }
        }
    }
}