{controller} placeholder doesn't work when generating TypeScript client with NSwag

227 Views Asked by At

I want to generate a TypeScript client from an OpenApi specification. I'm using NSwagStudio to create the config file. I leave the default {controller}Client value, but the placeholder has no effect, as the class name is generated as just Client.

What could be the problem?

"codeGenerators": {
   "openApiToTypeScriptClient": {
     "className": "{controller}Client",
      "operationGenerationMode": "MultipleClientsFromOperationId",
...
[ApiController]
public class WorkoutsController : ControllerBase
{
    private readonly IWorkoutService _workoutService;

    public WorkoutsController(IWorkoutService workoutService)
    {
        _workoutService = workoutService;
    }

    [ProducesResponseType(StatusCodes.Status404NotFound)]
    [ProducesResponseType(typeof(WorkoutResponse), StatusCodes.Status200OK)]
    [HttpGet(ApiEndpoints.Workouts.Get)]
    public async Task<IActionResult> Get([FromRoute] Guid id, CancellationToken token)
    {
        var workout = await _workoutService.GetByIdAsync(id, token);
        if (workout is null)
            return NotFound();

        var workoutResponse = workout.ToWorkoutResponse();
        return Ok(workoutResponse);
    }
...
{
    "openapi": "3.0.1",
    "info": {
        "title": "fitflow.RestApi",
        "version": "1.0"
    },
    "paths": {
        "/api/workouts/{id}": {
            "get": {
                "tags": [
                    "Workouts"
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "style": "simple",
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success",
                        "content": {
                            "text/plain": {
                                "schema": {
                                    "$ref": "#/components/schemas/WorkoutResponse"
                                }
                            },
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/WorkoutResponse"
                                }
                            },
                            "text/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/WorkoutResponse"
                                }
                            }
                        }
                    },
                    "404": {
                        "description": "Not Found",
                        "content": {
                            "text/plain": {
                                "schema": {
                                    "$ref": "#/components/schemas/ProblemDetails"
                                }
                            },
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ProblemDetails"
                                }
                            },
                            "text/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ProblemDetails"
                                }
                            }
                        }
                    }
                }
            }
        },
        "/api/workouts": {
...
2

There are 2 best solutions below

0
Tawab Wakil On

Make sure you have the NSwag.AspNetCore package installed from NuGet. Otherwise if you have the default Swashbuckle.AspNetCore installed, it will use that, which would be undesirable in this case and would cause the issue you are experiencing.

In addition, ensure you have the following in your Program.cs. This is required for OpenAPI 3.0 generation through NSwag:

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddOpenApiDocument();

And these are the NSwag extensions for serving Swagger docs:

var app = builder.Build();
app.UseOpenApi();     // use in place of app.UseSwagger()
app.UseSwaggerUi3();  // use in place of app.UseSwaggerUI()
0
Presto On

I found something related with that issue.

This state is related with shape of operationId attribute in json file generated by Swagger.

For sure at 100% your operationId in json doesn't have the controller name in value (as prefix).

To generate separate class names for each of API controllers the operationId should does look like that: controller_action. With option operationGenerationMode set to MultipleClientsFromOperationId in .nswag config file everything should be generated as expected.

In my Angular project I have three API controllers for now and only for one of them the operationId is generating with expected mask controller_action.

For some reasons for rest of them, the operationId contains only the action name.

  1. If you have got the situation that for all of api controllers the operationId attributes are generating in wrong way (wrong for that scenario, only with action name) then the best idea will be set operationGenerationMode option in .nswag config file to value MultipleClientsFromFirstTagAndOperationId.

    It will use the tag attribute to combine proper class name in api.service.ts generated file.

  2. In my scenario, where for some controllers operationId's are generated well but for some not, probably I have to implement own class to handle way of generating this operationId, so:

    using System;
    using NSwag.Generation.Processors;
    using NSwag.Generation.Processors.Contexts;
    
    public class OperationIdProcessor : IOperationProcessor
    {
         public bool Process(OperationProcessorContext context)
         {
             var controllerName = context.ControllerType.Name.Replace("Controller", "", StringComparison.OrdinalIgnoreCase);
             var actionName = context.MethodInfo.Name;
    
             var operationId = $"{controllerName}_{actionName}";
             context.OperationDescription.Operation.OperationId = operationId;
    
             return true;
         }
    }
    

    and in my Program.cs file where I have got a services configuration, I need to add:

    services.AddOpenApiDocument(configure =>
    {
        configure.OperationProcessors.Add(new OperationIdProcessor());
    });
    

Of course the perfect solution will be just find the reason why this operationId is generating in different ways and unify this process but unfortunately I spend on it a few days and didn't find anything special.

So I started thing about workarounds (like with additional class to handle process of creating operationId) and decided to share my investigation results.

Maybe my small pieces of solution will be helpful to find this one, perfect, solution or will be just the solution for someone.