.NET 5 - SWAGGER.json

.NET 5 - SWAGGER.json

我已经为我的 public api 生成了一个 swagger.json 文件。使用以下代码效果非常好:

在启动中 - 配置服务

            //Register Swagger Options as Dependency Injection
        services.AddTransient<IConfigureOptions<SwaggerGenOptions>, ConfigureSwaggerOptions>();
        
        services.AddSwaggerGen(options =>
        {
            // add a custom operation filter which sets default values
            //options.OperationFilter<SwaggerDefaultValues>();

            var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
            var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
            options.IncludeXmlComments(xmlPath);

        });

正在启动 - 配置

app.UseSwagger();
        app.UseSwaggerUI(c =>
        {
            c.InjectStylesheet("../css/customize-swagger-ui.css");
            c.InjectJavascript("../js/customize-swagger-ui.js");
            // build a swagger endpoint for each discovered API version
            foreach (var description in provider.ApiVersionDescriptions)
            {
                c.SwaggerEndpoint($"/swagger/{description.GroupName}/swagger.json", description.GroupName.ToUpperInvariant());

            }

        });

我的问题是,我想在 API-Request 后面添加方法的名称。否则,用于客户端代码生成的 NSWAG 等工具会创建许多不同的方法,这些方法称为 APIRoute1、APIRoute2、APIRoute3、...

这是 swagger 生成文件的 json 片段:

"paths": {
"/api/v1/Order": {
  "post": {
    "tags": [
      "Order"
    ],
    "summary": "Creates a new Order if no order currently exists",
    "requestBody": {
      "description": "",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/CreateOrderModel"
          }
        },
        "text/json": {
          "schema": {
            "$ref": "#/components/schemas/CreateOrderModel"
          }
        },
        "application/*+json": {
          "schema": {
            "$ref": "#/components/schemas/CreateOrderModel"
          }
        }
      }
    },
    "responses": {
      "200": {
        "description": "Success"
      }
    }
  }
},
"/api/v1/Order/{orderNumber}": {
  "get": {
    "tags": [
      "Order"
    ],
    "summary": "Reads a given Order if exists",
    "parameters": [
      {
        "name": "orderNumber",
        "in": "path",
        "description": "Order Number",
        "required": true,
        "schema": {
          "type": "integer",
          "format": "int32"
        }
      }
    ],
    "responses": {
      "200": {
        "description": "Success",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/OrderDTO"
            }
          }
        }
      },
      "400": {
        "description": "Bad Request",
        "content": {
          "application/json": {
            "schema": {
              "type": "string"
            }
          }
        }
      },
      "404": {
        "description": "Not Found",
        "content": {
          "application/json": {
            "schema": {
              "type": "string"
            }
          }
        }
      }
    }
  }
}

APIRoute只是一个占位符作为例子。 APIRouteX 背后的方法是用相同的路由实现的,但 http 变量不同,如 (POST, DELETE, PUT)。所以真的很难阅读客户端代码生成的代码。

是否可以在swagger.json文件的路由后面添加“Method-Name”?

SWAGGER 配置选项

    /// <summary>
/// Configures the Swagger Options like Titel, Contact Informations etc
/// </summary>
public class ConfigureSwaggerOptions : IConfigureOptions<SwaggerGenOptions>
{
    readonly IApiVersionDescriptionProvider provider;

    /// <summary>
    /// Constructor which adds ApiVersionDescription by Dependency Injection
    /// </summary>
    /// <param name="provider">API Version Description Provider</param>
    public ConfigureSwaggerOptions(IApiVersionDescriptionProvider provider) => this.provider = provider;

    /// <summary>
    /// Adds a seperate Swagger Document for each API Version
    /// </summary>
    /// <param name="options">Swagger Generic Options</param>
    public void Configure(SwaggerGenOptions options)
    {
        foreach (var description in provider.ApiVersionDescriptions)
        {
            options.SwaggerDoc(description.GroupName, CreateInfoForApiVersion(description.ApiVersion.ToString(), description.IsDeprecated));
        }
    }

    /// <summary>
    /// Creates Header Informations for API
    /// </summary>
    /// <param name="description">ApiVersionDescription</param>
    /// <returns>Api Info</returns>
    static OpenApiInfo CreateInfoForApiVersion(string version, bool isDeprecated)
    {
        var info = new OpenApiInfo()
        {
            Title = "Rüstkontrolle API",
            Version = version,
            Description = "Service Dokumentation für das AddOn Rüstkontrolle im SICK Werkerportal",
            Contact = new OpenApiContact() { Name = "Lukas Adler", Email = "lukas.adler@sick.de" },
            License = new OpenApiLicense() { Name = "",}
        };

        if (isDeprecated)
        {
            info.Description += "API version is deprecated.";
        }
        return info;
    }
}

Swagger 默认值

    /// <summary>
/// Swagger Operation Filter
/// </summary>
public class SwaggerDefaultValues : IOperationFilter
{
    /// <summary>
    /// Applies Operations and OperationsFilterContext on API Descriptions
    /// </summary>
    /// <param name="operation">Operation</param>
    /// <param name="context">OperationFilterContext</param>
    public void Apply(OpenApiOperation operation, OperationFilterContext context)
    {

        var apiDescription = context.ApiDescription;

        if (operation.Parameters == null)
        {
            return;
        }

        foreach (var parameter in operation.Parameters)
        {
            var description = apiDescription.ParameterDescriptions.First(p => p.Name == parameter.Name);

            if (parameter.Description == null)
            {
                parameter.Description = description.ModelMetadata?.Description;
            }

            parameter.Required |= description.IsRequired;
        }
    }
}

NSWAG 配置

    {
  "runtime": "NetCore21",
  "defaultVariables": null,
  "documentGenerator": {
    "fromDocument": "HERE IS NORMALLY MY API DESCRIPTION",
      "url": "https://localhost:44314/swagger/v1/swagger.json",
      "output": null,
      "newLineBehavior": "Auto"
    }
  },
  "codeGenerators": {
    "openApiToCSharpClient": {
      "clientBaseClass": null,
      "configurationClass": null,
      "generateClientClasses": true,
      "generateClientInterfaces": false,
      "clientBaseInterface": null,
      "injectHttpClient": true,
      "disposeHttpClient": true,
      "protectedMethods": [],
      "generateExceptionClasses": true,
      "exceptionClass": "ApiException",
      "wrapDtoExceptions": true,
      "useHttpClientCreationMethod": false,
      "httpClientType": "System.Net.Http.HttpClient",
      "useHttpRequestMessageCreationMethod": false,
      "useBaseUrl": true,
      "generateBaseUrlProperty": true,
      "generateSyncMethods": false,
      "generatePrepareRequestAndProcessResponseAsAsyncMethods": false,
      "exposeJsonSerializerSettings": false,
      "clientClassAccessModifier": "public",
      "typeAccessModifier": "public",
      "generateContractsOutput": false,
      "contractsNamespace": null,
      "contractsOutputFilePath": null,
      "parameterDateTimeFormat": "s",
      "parameterDateFormat": "yyyy-MM-dd",
      "generateUpdateJsonSerializerSettingsMethod": true,
      "useRequestAndResponseSerializationSettings": false,
      "serializeTypeInformation": false,
      "queryNullValue": "",
      "className": "{controller}Client",
      "operationGenerationMode": "MultipleClientsFromOperationId",
      "additionalNamespaceUsages": [],
      "additionalContractNamespaceUsages": [],
      "generateOptionalParameters": false,
      "generateJsonMethods": false,
      "enforceFlagEnums": false,
      "parameterArrayType": "System.Collections.Generic.IEnumerable",
      "parameterDictionaryType": "System.Collections.Generic.IDictionary",
      "responseArrayType": "System.Collections.Generic.ICollection",
      "responseDictionaryType": "System.Collections.Generic.IDictionary",
      "wrapResponses": false,
      "wrapResponseMethods": [],
      "generateResponseClasses": true,
      "responseClass": "SwaggerResponse",
      "namespace": "MyNamespace",
      "requiredPropertiesMustBeDefined": true,
      "dateType": "System.DateTimeOffset",
      "jsonConverters": null,
      "anyType": "object",
      "dateTimeType": "System.DateTimeOffset",
      "timeType": "System.TimeSpan",
      "timeSpanType": "System.TimeSpan",
      "arrayType": "System.Collections.Generic.ICollection",
      "arrayInstanceType": "System.Collections.ObjectModel.Collection",
      "dictionaryType": "System.Collections.Generic.IDictionary",
      "dictionaryInstanceType": "System.Collections.Generic.Dictionary",
      "arrayBaseType": "System.Collections.ObjectModel.Collection",
      "dictionaryBaseType": "System.Collections.Generic.Dictionary",
      "classStyle": "Poco",
      "jsonLibrary": "NewtonsoftJson",
      "generateDefaultValues": true,
      "generateDataAnnotations": true,
      "excludedTypeNames": [],
      "excludedParameterNames": [],
      "handleReferences": false,
      "generateImmutableArrayProperties": false,
      "generateImmutableDictionaryProperties": false,
      "jsonSerializerSettingsTransformationMethod": null,
      "inlineNamedArrays": false,
      "inlineNamedDictionaries": false,
      "inlineNamedTuples": true,
      "inlineNamedAny": false,
      "generateDtoTypes": true,
      "generateOptionalPropertiesAsNullable": false,
      "generateNullableReferenceTypes": false,
      "templateDirectory": null,
      "typeNameGeneratorType": null,
      "propertyNameGeneratorType": null,
      "enumNameGeneratorType": null,
      "serviceHost": null,
      "serviceSchemes": null,
      "output": null,
      "newLineBehavior": "Auto"
    }
  }
}

在 NSWag 设置中,operationGenerationMode 设置为 MultipleClientsFromOperationId :

From the first operation tag and operation ID (operation name = operation ID, client name = first operation tag).

但是 OpenAPI 合同(swagger.json 您的文件)没有指定操作的 ID。

我看到两个解决方案,选择其他值 operationGenerationMode 或指定操作的 id。

其他 operationGenerationModeare :

public enum OperationGenerationMode
{
    /// <summary>Multiple clients from the Swagger operation ID in the form '{controller}_{action}'.</summary>
    MultipleClientsFromOperationId,

    /// <summary>From path segments (operation name = last segment, client name = second to last segment).</summary>
    MultipleClientsFromPathSegments,

    /// <summary>From the first operation tag and path segments (operation name = last segment, client name = first operation tag).</summary>
    MultipleClientsFromFirstTagAndPathSegments,

    /// <summary>From the first operation tag and operation ID (operation name = operation ID, client name = first operation tag).</summary>
    MultipleClientsFromFirstTagAndOperationId,

    /// <summary>From the Swagger operation ID.</summary>
    SingleClientFromOperationId,

    /// <summary>From path segments suffixed by HTTP operation name</summary>
    SingleClientFromPathSegments,
}

如果你喜欢指定操作的id,你可以在Http[Get|Put|...]Attribute中设置名称:

[HttpGet(Name = "SpecifiedOperationId")]
public IActionResult Get()

您可以使用 SwaggerGenOptions.CustomOperationIds 指定您自己的自定义操作 ID。例如。我使用以下设置:

services.AddSwaggerGen(c =>
{
    c.CustomOperationIds(x => ${x.ActionDescriptor.RouteValues["controller"]}_{x.ActionDescriptor.RouteValues["action"]}");
});

这会创建类似于 Client_GetDetail 的操作 ID,您可以配置 NSWAG 以创建类似于 clientApi.getDetail().

的客户端 API

PS 您正在使用 Swashbuckle in your project, if you use NSwag 那么它应该默认设置它。