Search code examples
c#swaggeropenapiopenapi-generator

OpenApi/Swagger prefix function with controller?


Currently I am consuming an api which uses the OpenApi spec which is generated using Swagger. In the api there are multiple endpoints, for example /Customer/GetById and /License/GetById. This causes the code generation to generate a client which justs lists the methods using numbers.

Picture showing how the generated api client looks like

It would be better to have a method named LicenseGetByIdAsync(...) and CustomerGetByIdAsync(...). (Or anything to distinguish them)

(how) can I achieve this using my nswag config? I can also change the OpenApi json generation

My nswag file looks like this:

{
   "runtime": "Default",
   "defaultVariables": null,
   "documentGenerator": {
      "fromDocument": {
      "json": "$(InputSwagger)",
      "url": "http://redocly.github.io/redoc/openapi.yaml",
      "output": null
      }
   },
   "codeGenerators": {
      "openApiToCSharpClient": {
      "clientBaseClass": null,
      "configurationClass": null,
      "generateClientClasses": true,
      "generateClientInterfaces": true,
      "injectHttpClient": true,
      "disposeHttpClient": false,
      "protectedMethods": [],
      "generateExceptionClasses": true,
      "exceptionClass": "$(ClientName)Exception",
      "wrapDtoExceptions": true,
      "useHttpClientCreationMethod": false,
      "httpClientType": "System.Net.Http.HttpClient",
      "useHttpRequestMessageCreationMethod": false,
      "useBaseUrl": false,
      "generateBaseUrlProperty": true,
      "generateSyncMethods": false,
      "exposeJsonSerializerSettings": true,
      "clientClassAccessModifier": "public",
      "typeAccessModifier": "public",
      "generateContractsOutput": false,
      "contractsNamespace": null,
      "contractsOutputFilePath": null,
      "parameterDateTimeFormat": "s",
      "generateUpdateJsonSerializerSettingsMethod": true,
      "serializeTypeInformation": false,
      "queryNullValue": "",
      "className": "$(ClientName)Client",
      "operationGenerationMode": "MultipleClientsFromOperationId",
      "additionalNamespaceUsages": [],
      "additionalContractNamespaceUsages": [],
      "generateOptionalParameters": false,
      "generateJsonMethods": true,
      "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": "$(ClientName)Response",
      "namespace": "$(ClientNamespace)",
      "requiredPropertiesMustBeDefined": false,
      "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",
      "generateDefaultValues": true,
      "generateDataAnnotations": true,
      "excludedTypeNames": [],
      "excludedParameterNames": [],
      "handleReferences": false,
      "generateImmutableArrayProperties": false,
      "generateImmutableDictionaryProperties": false,
      "jsonSerializerSettingsTransformationMethod": null,
      "inlineNamedArrays": false,
      "inlineNamedDictionaries": false,
      "inlineNamedTuples": true,
      "inlineNamedAny": false,
      "generateDtoTypes": true,
      "templateDirectory": null,
      "typeNameGeneratorType": null,
      "propertyNameGeneratorType": null,
      "enumNameGeneratorType": null,
      "serviceHost": null,
      "serviceSchemes": null,
      "output": "$(GeneratedSwaggerClientFile)"
      }
   }
}

and my swagger file looks like this

{
   "openapi": "3.0.1",
   "info": {
      "title": "ConfigurationManagement.Api",
      "version": "v1"
   },
   "paths": {
      "/api/customer/getbyid/{id}": {
         "get": {
            "tags": [
               "Customer"
            ],
            "summary": "Gets an Customer by the id",
            "parameters": [
               {
                  "name": "id",
                  "in": "path",
                  "description": "The id of the customer.",
                  "required": true,
                  "schema": {
                     "type": "string",
                     "format": "uuid"
                  }
               }
            ],
            "responses": {
               "200": {
                  "description": "Success",
                  "content": {
                     "application/json": {
                        "schema": {
                           "$ref": "#/components/schemas/CustomerDtoSingleItemResultDto"
                        }
                     }
                  }
               }
            }
         }
      },
...
      "/api/license/getbyid/{id}": {
         "get": {
            "tags": [
               "License"
            ],
            "summary": "Gets an License by the id",
            "parameters": [
               {
                  "name": "id",
                  "in": "path",
                  "description": "The id of the license.",
                  "required": true,
                  "schema": {
                     "type": "string",
                     "format": "uuid"
                  }
               }
            ],
            "responses": {
               "200": {
                  "description": "Success",
                  "content": {
                     "application/json": {
                        "schema": {
                           "$ref": "#/components/schemas/LicenseDtoSingleItemResultDto"
                        }
                     }
                  }
               }
            }
         }
      },
...
   }
}

Solution

  • I have fixed the issue in two steps:

    1. I added a IOperationFilter in the api which generates the swagger file
       public class OperationControllerPrefixFilter : IOperationFilter
       {
          public void Apply(OpenApiOperation operation, OperationFilterContext context)
          {
             // for example: api/customer/list will become [] {'customer', 'list'}
             var arr = context.ApiDescription.RelativePath
                .Split('/')
                .Skip(1)
                .Take(2)
                .ToArray();
    
             // becomes customer_list
             operation.OperationId = $"{arr[0]}_{arr[1]}";
             Debug.WriteLine(operation.OperationId);
          }
       }
    
    1. I changed operationGenerationMode from MultipleClientsFromOperationId to SingleClientFromOperationId in the nswag of the consuming application.

    example in consumer