简体   繁体   中英

RESTful api versioning and grouping in doc from Swagger with multiple endpoints

I am trying to implement the version options on a MVC dotnet Core app that has API endpoint on it.

The set up i am after is like this

 --AiM api
   |_v1
   |_v2
 --RMS api
   |_v1

I have it mostly working but the items on v1 are not showing up on v2. The output is like so

在此处输入图片说明 在此处输入图片说明

But when we get to the version 2 on the AiM v2 endpoint I only the one item在此处输入图片说明

Which is not what i was expecting

I have made a test to get each one showing on its different pages in swagger like this

In controller

[ApiVersion("2.0")]
[ApiVersion("1.0")]
[ApiExplorerSettings(GroupName = "aim_v1")]
[Route("aim/v{version:apiVersion}/write/")]
public class aimWriter_v1Controller : Controller
{

    [SwaggerOperation(Tags = new[] { "AiM Departments" })]
    [HttpPost("departments/delete/{id}")]
    public IActionResult departments(string foo)
    {
        return Json(new
        {
            results = "edited"
        });
    }

    [SwaggerOperation(Tags = new[] { "AiM Contacts" })]
    [HttpPost("contacts/delete/{id}")]
    public IActionResult contact_delete(string foo)
    {
        return Json(new
        {
            results = "edited"
        });
    }

    [SwaggerOperation(Tags = new[] { "AiM Contacts" })]
    [HttpPost("contacts/activate/{id}")]
    public IActionResult contact_activate(string foo)
    {
        return Json(new
        {
            results = "edited"
        });
    }
}


[ApiVersion("2.0")]
[ApiExplorerSettings(GroupName = "aim_v2")]
[Route("aim/v{version:apiVersion}/write/")]
public class aimWriter_v2Controller : Controller
{

    [SwaggerOperation(Tags = new[] { "AiM Contacts" })]
    [HttpPost("contacts/delete/{id}")]
    public IActionResult contact_delete(string foo)
    {
        return Json(new
        {
            results = "edited"
        });
    }


}


[ApiVersion("2.0")]
[ApiVersion("1.0")]
[ApiExplorerSettings(GroupName = "aim_v1")]
[Route("aim/v{version:apiVersion}/")]
public class aim_v1Controller : Controller
{

    [SwaggerOperation(Tags = new[] { "AiM Rooms" })]
    [HttpPost("rooms")]
    public IActionResult rooms(string foo)
    {
        return Json(new
        {
            results = "foo"
        });
    }

    [SwaggerOperation(Tags = new[] { "AiM Buildings" })]
    [HttpPost("buildings/rooms/{id}")]
    public IActionResult building_rooms(string foo)
    {
        return Json(new
        {
            results = "foo"
        });
    }

    [SwaggerOperation(Tags = new[] { "AiM Rooms" })]
    [HttpPost("rooms/{id}")]
    public IActionResult room(string foo)
    {
        return Json(new
        {
            results = "foo"
        });
    }
}

// set up as just a new endpoint (NOTE: in different controller)
[ApiVersion("1.0")]
[ApiExplorerSettings(GroupName = "rms_v1")]
[Route("rms/v{version:apiVersion}/")]
public class rms_v1Controller : Controller
{
    [SwaggerOperation(Tags = new[] { "RMS Orders" })]
    [HttpPost("set_order/{id}")]
    public IActionResult set_order(string foo)
    {
        return Json(new
        {
            results = "foo"
        });
    }

}

And in the Startup.cs

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddRouting(options => options.LowercaseUrls = true);
        services.AddMvc();

        services.AddApiVersioning(options => {
            options.AssumeDefaultVersionWhenUnspecified = true ;
            options.DefaultApiVersion = new ApiVersion(new DateTime(2016, 7, 1));
        });
        services.AddSwaggerGen(c =>
        {


            c.SwaggerDoc("aim_v1", new Info
            {
                Version = "aim/v1",
                Title = "WSU HTTP API"
            });
            c.SwaggerDoc("aim_v2", new Info
            {
                Version = "aim/v2",
                Title = "WSU HTTP API v2"
            });
            c.SwaggerDoc("rms_v1", new Info
            {
                Version = "rms/v1",
                Title = "WSU HTTP API"
            });




            //Set the comments path for the swagger json and ui.
            var basePath = PlatformServices.Default.Application.ApplicationBasePath;
            var xmlPath = Path.Combine(basePath, "project.in.bin.def.xml");
            c.IncludeXmlComments(xmlPath);
        });

    }
    public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
    {

        // Enable middleware to serve generated Swagger as a JSON endpoint.
        app.UseSwagger(o =>
        {
            o.PreSerializeFilters.Add((swaggerDoc, httpReq) => swaggerDoc.Host = httpReq.Host.Value);
            o.RouteTemplate = "doc/{documentName}/scheme.json";
        });

        // Enable middleware to serve swagger-ui (HTML, JS, CSS etc.), specifying the Swagger JSON endpoint.
        app.UseSwaggerUI(c =>
        {
            c.RoutePrefix = "docs";
            c.SwaggerEndpoint("/doc/aim_v1/scheme.json", "AiM v1.0.0");
            c.SwaggerEndpoint("/doc/rms_v1/scheme.json", "Rms v1.0.0");
            c.SwaggerEndpoint("/doc/aim_v2/scheme.json", "AiM v2.0.0");
        });
    }

And in the index.html for the swagger ui doc template file has

<script type="text/javascript">
    window.JSConfig = JSON.parse('{"SwaggerEndpoints":[{"Url":"/doc/aim_v1/scheme.json","Description":"AiM v1.0.0"},{"Url":"/doc/aim_v2/scheme.json","Description":"AiM v2.0.0"},{"Url":"/doc/rms_v1/scheme.json","Description":"RMS v1.0.0"}],"BooleanValues":["false","true"],"DocExpansion":"list","SupportedSubmitMethods":["get","post","put","delete","patch"],"OnCompleteScripts":[],"OnFailureScripts":[],"ShowRequestHeaders":false,"JsonEditor":false,"OAuth2ClientId":"your-client-id","OAuth2ClientSecret":"your-client-secret-if-required","OAuth2Realm":"your-realms","OAuth2AppName":"your-app-name","OAuth2ScopeSeparator":" ","OAuth2AdditionalQueryStringParams":{}}');

$(function () {

  hljs.configure({
    highlightSizeThreshold: 5000
  });

  // Pre load translate...
  if(window.SwaggerTranslator) {
    window.SwaggerTranslator.translate();
  }
  window.swaggerUi = new SwaggerUi({
      url: "/doc/aim_v1/scheme.json",
    dom_id: "swagger-ui-container",
    supportedSubmitMethods: ['get', 'post'],
    onComplete: function(swaggerApi, swaggerUi){
      if(typeof initOAuth == "function") {
        initOAuth({
            clientId: "ffff==",
            clientSecret: "bbbb",
            realm: "wsu-api",
            appName: "wsu-api-broker",
            scopeSeparator: " ",
            additionalQueryStringParams: {}
        });
      }

      if(window.SwaggerTranslator) {
        window.SwaggerTranslator.translate();
      }
      _.each(JSConfig.OnCompleteScripts, function (script) {
          $.getScript(script);
      });

    },
    onFailure: function(data) {
      log("Unable to Load SwaggerUI");
    },
    docExpansion: false,
    jsonEditor: false,
    defaultModelRendering: 'schema',
    showRequestHeaders: false
  });

  window.swaggerUi.load();

  function log() {
    if ('console' in window) {
      console.log.apply(console, arguments);
    }
  }

});

In order to get the items on the different endpoints I used the [ApiExplorerSettings(GroupName = "aim_v1")] on the classes and matched them up in the Startup.cs and index.html files. At this point I am unsure where to make my edit to get all of the [ApiVersion("1.0")] items show on the [ApiVersion("2.0")] as I think the ApiExplorerSettings GroupName is what it locking this up.

To integrate everything smoothly, you also need to add the official API Explorer package for API Versioning. This will collate all of the API version information for you in a way that Swagger will understand. The official Swagger/Swashbuckle integration wiki topic has additional details and examples.

The setup will look like:

public void ConfigureServices( IServiceCollection services )
{
    // note: this option is only necessary when versioning by url segment.
    // the SubstitutionFormat property can be used to control the format of the API version
    services.AddMvcCore().AddVersionedApiExplorer(
        options =>
        {
            options.GroupNameFormat = "'v'VVV";
            options.SubstituteApiVersionInUrl = true;
        } );

    services.AddMvc();
    services.AddApiVersioning();
    services.AddSwaggerGen(
        options =>
        {
            var provider = services.BuildServiceProvider().GetRequiredService<IApiVersionDescriptionProvider>();

            foreach ( var description in provider.ApiVersionDescriptions )
            {
                options.SwaggerDoc( description.GroupName, CreateInfoForApiVersion( description ) );
            }

            options.IncludeXmlComments( XmlCommentsFilePath );
        } );
}

public void Configure( IApplicationBuilder app, IHostingEnvironment env, IApiVersionDescriptionProvider provider )
{
    app.UseMvc();
    app.UseSwagger();
    app.UseSwaggerUI(
        options =>
        {
            foreach ( var description in provider.ApiVersionDescriptions )
            {
                options.SwaggerEndpoint( $"/swagger/{description.GroupName}/swagger.json", description.GroupName.ToUpperInvariant() );
            }
        } );
}

static string XmlCommentsFilePath
{
    get
    {
        var basePath = PlatformServices.Default.Application.ApplicationBasePath;
        var fileName = typeof( Startup ).GetTypeInfo().Assembly.GetName().Name + ".xml";
        return Path.Combine( basePath, fileName );
    }
}

static Info CreateInfoForApiVersion( ApiVersionDescription description )
{
    var info = new Info()
    {
        Title = $"Sample API {description.ApiVersion}",
        Version = description.ApiVersion.ToString(),
        Description = "A sample application with Swagger, Swashbuckle, and API versioning.",
        Contact = new Contact() { Name = "Bill Mei", Email = "bill.mei@somewhere.com" },
        TermsOfService = "Shareware",
        License = new License() { Name = "MIT", Url = "https://opensource.org/licenses/MIT" }
    };

    if ( description.IsDeprecated )
    {
        info.Description += " This API version has been deprecated.";
    }

    return info;
}

A full working answer is in that question: Grouping and Versioning not working well together in swagger in asp.net core 3.1 web api

As the author said, the DocInclusionPredicate in AddSwaggerGen in the ConfigureServices is doing the trick to map the proper controller to the wanted swagger file.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM