RicoSuter / NSwag

The Swagger/OpenAPI toolchain for .NET, ASP.NET Core and TypeScript.
http://NSwag.org
MIT License
6.82k stars 1.3k forks source link

Issue with Automatic Generation of TypeScript Code from OpenAPI in ASP.NET with NSwag and MSBuild #4767

Closed qFamouse closed 9 months ago

qFamouse commented 9 months ago

I'm exploring the possibility of automating the generation of client-side TypeScript code based on the OpenAPI specification of my ASP.NET server. To achieve this, I've installed the NSwag.AspNetCore package from NuGet and updated the Program.cs file by adding the following lines:

builder.Services.AddOpenApiDocument();
app.UseOpenApi();
app.UseSwaggerUI();

After successfully obtaining the OpenAPI link using Swagger UI, I used it in the NSwag Studio program to generate TypeScript code that can be used on the client-side.

Now, I'd like to automate this process. Primarily, I've been trying to understand how to set up automatic code generation after each build using MSBuild, following the guide in the NSwag documentation on their GitHub repository (https://github.com/RicoSuter/NSwag/wiki/NSwag.MSBuild).

My ASP.NET project is divided into several class libraries:

Bets.WebUI: ASP.NET project with controllers and other components Contracts: a class library with DTOs Application: contains classes handling user requests (business logic) and others To begin, I installed the NSwag.MSBuild package and generated the nswag.json file using NSwag Studio. This file contains the following settings:

{
  "runtime": "Net70",
  "defaultVariables": null,
  "documentGenerator": {
    "aspNetCoreToOpenApi": {
      "project": "Bets.WebUI.csproj",
      "documentName": "v1",
      "msBuildProjectExtensionsPath": null,
      "configuration": null,
      "runtime": null,
      "targetFramework": null,
      "noBuild": false,
      "msBuildOutputPath": null,
      "verbose": true,
      "workingDirectory": null,
      "aspNetCoreEnvironment": null,
      "output": null,
      "newLineBehavior": "Auto"
    }
  },
  "codeGenerators": {
    "openApiToTypeScriptClient": {
      "className": "{controller}Client",
      "moduleName": "",
      "namespace": "",
      "typeScriptVersion": 4.3,
      "template": "Angular",
      "promiseType": "Promise",
      "httpClass": "HttpClient",
      "withCredentials": false,
      "useSingletonProvider": true,
      "injectionTokenType": "InjectionToken",
      "rxJsVersion": 7.0,
      "dateTimeType": "Date",
      "nullValue": "Undefined",
      "generateClientClasses": false,
      "generateClientInterfaces": false,
      "generateOptionalParameters": false,
      "exportTypes": true,
      "wrapDtoExceptions": false,
      "exceptionClass": "ApiException",
      "clientBaseClass": null,
      "wrapResponses": false,
      "wrapResponseMethods": [],
      "generateResponseClasses": true,
      "responseClass": "SwaggerResponse",
      "protectedMethods": [],
      "configurationClass": null,
      "useTransformOptionsMethod": false,
      "useTransformResultMethod": false,
      "generateDtoTypes": true,
      "operationGenerationMode": "MultipleClientsFromOperationId",
      "markOptionalProperties": false,
      "generateCloneMethod": false,
      "typeStyle": "Interface",
      "enumStyle": "Enum",
      "useLeafType": false,
      "classTypes": [],
      "extendedClasses": [],
      "extensionCode": null,
      "generateDefaultValues": true,
      "excludedTypeNames": [],
      "excludedParameterNames": [],
      "handleReferences": false,
      "generateTypeCheckFunctions": false,
      "generateConstructorInterface": true,
      "convertConstructorInterfaceData": false,
      "importRequiredTypes": true,
      "useGetBaseUrlMethod": false,
      "baseUrlTokenName": "API_BASE_URL",
      "queryNullValue": "",
      "useAbortSignal": false,
      "inlineNamedDictionaries": true,
      "inlineNamedAny": true,
      "includeHttpContext": true,
      "templateDirectory": null,
      "serviceHost": null,
      "serviceSchemes": null,
      "output": "test.ts",
      "newLineBehavior": "Auto"
    }
  }
}

Then, I added the following lines to the Bets.WebUI.csproj file, as per the instruction:

<Target Name="NSwag" AfterTargets="Build">
    <Exec Command="$(NSwagExe_Net70) run nswag.json /variables:Configuration=$(Configuration)" />
</Target>

However, during the project build, the process hangs after displaying these messages:

13>NSwag command line tool for .NET Core Net70, toolchain v14.0.3.0 (NJsonSchema v11.0.0.0 (Newtonsoft.Json v13.0.0.0))
13>Visit http://NSwag.org for more information.
13>NSwag bin directory: C:\Users\famou\.nuget\packages\nswag.msbuild\14.0.3\tools\Net70
13>
13>Executing file 'nswag.json' with variables 'Configuration=Debug'...

And it doesn't proceed further.

Before asking the question here, I studied numerous guides on this topic. However, nothing helps me. Everywhere there are articles using older versions of .NET and NSwag. Rolling back the version is not an option; I would like to figure it out with the latest ones.

I also watched the video "NSwag - SWAGGER For ASP.NET CORE and AUTOMATIC Type Generation For TypeScript". In it, the author suggests such a setup for aspNetCoreToOpenApi:

"aspNetCoreToOpenApi": {
  "project": null,
  "documentName": "v1",
  "msBuildProjectExtensionsPath": null,
  "configuration": null,
  "runtime": null,
  "targetFramework": null,
  "noBuild": false,
  "msBuildOutputPath": null,
  "verbose": true,
  "workingDirectory": null,
  "aspNetCoreEnvironment": null,
  "output": null,
  "newLineBehavior": "Auto",
  "assemblyPaths": [
    "bin/Debug/net7.0/Bets.WebUI.dll"
  ],
  "assemblyConfig": null,
  "referencePaths": [],
  "useNuGetCache": false
}

I tried it too, but when building, NSwag simply deletes these lines and returns an error saying that the project path is not specified.

qFamouse commented 9 months ago

I removed the following fragment from the project file:

<Target Name="NSwag" AfterTargets="Build">
    <Exec Command="$(NSwagExe_Net70) run nswag.json /variables:Configuration=$(Configuration)" />
</Target>

After that, the code generation process via NSwag Studio proceeds successfully. From this, I assume that the issue lies in a recursive call: during the project build, it reads the nswag.json file, finds PoliticalBets.WebUI.csproj there, which in turn reads the nswag.json file again. In theory, this could be addressed by executing the command via MSBuild in a different build context (although I haven't tried this yet). However, such a solution seems inelegant to me. I would like to find a more elegant solution that works for everyone.

qFamouse commented 9 months ago

The solution turned out to be surprisingly simple.

I began by researching the function of each parameter in the aspNetCoreToOpenApi section.

I noticed the noBuild parameter and decided to change its value for testing purposes:

"noBuild": true,

After making this adjustment, everything started working.

MehdiElMellali commented 7 months ago

can we specify classes we need to generate as typescript interface ?