RicoSuter / NSwag

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

C#:- Enums names not generating the Display Name in client side (TypeScript) #2258

Open santhoshhere opened 5 years ago

santhoshhere commented 5 years ago

I used the nswag to generate the code in typescript from my EF core API. The enum based class doesn't work as expected when using [DisplayName] or [Description] annotation.

C#:

public enum BloodGroups
    {
        [Description("O +ve")] OPositive,
        [Description("O -ve")] ONegative,
        [Description("A +ve")] APositive,
        [Description("A -ve")] ANegative,
        [Description("B +ve")] BPositive,
        [Description("B -ve")] BNegative,
        [Description("AB +ve")] ABPositive,
        [Description("AB -ve")] ABNegative

    }

Generated Code:


export enum BloodGroups {
    OPositive = 0, 
    ONegative = 1, 
    APositive = 2, 
    ANegative = 3, 
    BPositive = 4, 
    BNegative = 5, 
    ABPositive = 6, 
    ABNegative = 7, 
}

As i'm binding the values to a dropdown(select) I need to use the (O +ve, O -ve).

I used the below code,

services.AddMvc().AddJsonOptions(options => options.SerializerSettings.ContractResolver = new Newtonsoft.Json.Serialization.DefaultContractResolver());
services.AddMvc().AddJsonOptions(options => options.SerializerSettings.Converters.Add(new StringEnumConverter()));

Any idea how to make this work? or am i wrong in anything?

RicoSuter commented 5 years ago

Use StringEnumConverter?

santhoshhere commented 5 years ago

Isn't the second line of code does that? Or it should be written somewhere. Is there any code that would help me?

RicoSuter commented 5 years ago

Can you post the generated spec?

santhoshhere commented 5 years ago

the nswag.json? or the typescript enum ?

RicoSuter commented 5 years ago

The openapi/swagger spec...

santhoshhere commented 5 years ago
{
  "runtime": "NetCore22",
  "defaultVariables": null,
  "swaggerGenerator": {
    "webApiToSwagger": {
      "controllerNames": [],
      "isAspNetCore": true,
      "resolveJsonOptions": false,
      "defaultUrlTemplate": "api/{controller}/{id?}",
      "addMissingPathParameters": false,
      "includedVersions": null,
      "defaultPropertyNameHandling": "Default",
      "defaultReferenceTypeNullHandling": "Null",
      "defaultEnumHandling": "Integer",
      "flattenInheritanceHierarchy": false,
      "generateKnownTypes": true,
      "generateEnumMappingDescription": false,
      "generateXmlObjects": false,
      "generateAbstractProperties": false,
      "ignoreObsoleteProperties": false,
      "allowReferencesWithProperties": false,
      "excludedTypeNames": [],
      "serviceHost": null,
      "serviceBasePath": null,
      "serviceSchemes": [],
      "infoTitle": "My Title",
      "infoDescription": null,
      "infoVersion": "1.0.0",
      "documentTemplate": null,
      "documentProcessorTypes": [],
      "operationProcessorTypes": [],
      "typeNameGeneratorType": null,
      "schemaNameGeneratorType": null,
      "contractResolverType": null,
      "serializerSettingsType": null,
      "useDocumentProvider": true,
      "documentName": "v1",
      "aspNetCoreEnvironment": null,
      "createWebHostBuilderMethod": null,
      "startupType": null,
      "allowNullableBodyParameters": true,
      "output": null,
      "outputType": "Swagger2",
      "assemblyPaths": [ "bin/Debug/netcoreapp2.2/WebAPI.dll" ],
      "assemblyConfig": null,
      "referencePaths": [],
      "useNuGetCache": false
    }
  },
  "codeGenerators": {
    "swaggerToTypeScriptClient": {
      "className": "{controller}Client",
      "moduleName": "",
      "namespace": "",
      "typeScriptVersion": 2.7,
      "template": "Angular",
      "promiseType": "Promise",
      "httpClass": "HttpClient",
      "useSingletonProvider": false,
      "injectionTokenType": "InjectionToken",
      "rxJsVersion": 6.3,
      "dateTimeType": "Date",
      "nullValue": "Undefined",
      "generateClientClasses": true,
      "generateClientInterfaces": false,
      "generateOptionalParameters": false,
      "exportTypes": true,
      "wrapDtoExceptions": false,
      "clientBaseClass": null,
      "wrapResponses": false,
      "wrapResponseMethods": [],
      "generateResponseClasses": true,
      "responseClass": "SwaggerResponse",
      "protectedMethods": [],
      "configurationClass": null,
      "useTransformOptionsMethod": false,
      "useTransformResultMethod": false,
      "generateDtoTypes": true,
      "operationGenerationMode": "MultipleClientsFromOperationId",
      "markOptionalProperties": true,
      "generateCloneMethod": false,
      "typeStyle": "Class",
      "classTypes": [],
      "extendedClasses": [],
      "extensionCode": null,
      "generateDefaultValues": true,
      "excludedTypeNames": [],
      "excludedParameterNames": [],
      "handleReferences": false,
      "generateConstructorInterface": true,
      "convertConstructorInterfaceData": false,
      "importRequiredTypes": true,
      "useGetBaseUrlMethod": false,
      "baseUrlTokenName": "API_BASE_URL",
      "queryNullValue": "",
      "inlineNamedDictionaries": false,
      "templateDirectory": null,
      "typeNameGeneratorType": null,
      "propertyNameGeneratorType": null,
      "enumNameGeneratorType": null,
      "serviceHost": "https://localhost:44375",
      "serviceSchemes": null,
      "output": "../../Angular/src/app/services/services.ts"
    } 
  }
}
RicoSuter commented 5 years ago

This is the config, i need the generated spec..

santhoshhere commented 5 years ago

When i execute the API with this config, i get the typescript code generated. Is that what you needed?

RicoSuter commented 5 years ago

The first step generates the swagger.json - in the ui the first tab on the right

RicoSuter commented 5 years ago

Ah sorry, descriptions and displayvalue are not preserved in c# clients because there is no field for that in swagger

santhoshhere commented 5 years ago

What should i do now? How can i get the desired enum values in the Typescript from NSwag codegen? is there anyother way?

RicoSuter commented 5 years ago

Let me quickly update the NJS wiki here:

https://github.com/RicoSuter/NJsonSchema/wiki/Enums https://github.com/RicoSuter/NJsonSchema/wiki/JsonSchemaGenerator#integer-vs-string-enumerations

santhoshhere commented 5 years ago

The first step generates the swagger.json - in the ui the first tab on the right

Just FYI, i don't use any UI to generate the code. I execute the API with the help of config and it generates in the output folder mentioned in the config(nswag.json)

RicoSuter commented 5 years ago

you can set the ouput here to also get the swagger:

{
  "runtime": "NetCore22",
  "defaultVariables": null,
  "swaggerGenerator": {
    "webApiToSwagger": {
      ...
      "output": "swagger.json",
      ...
santhoshhere commented 5 years ago

The generated spec is,

"BloodGroups": {
      "type": "integer",
      "description": "",
      "x-enumNames": [
        "OPositive",
        "ONegative",
        "APositive",
        "ANegative",
        "BPositive",
        "BNegative",
        "ABPositive",
        "ABNegative"
      ],
      "enum": [
        0,
        1,
        2,
        3,
        4,
        5,
        6,
        7
      ]
    },
santhoshhere commented 5 years ago

Let me quickly update the NJS wiki here:

https://github.com/RicoSuter/NJsonSchema/wiki/Enums https://github.com/RicoSuter/NJsonSchema/wiki/JsonSchemaGenerator#integer-vs-string-enumerations

I'm not completely understanding what is here, can you please put me in the right direction?

ryan4664 commented 4 years ago

@santhoshhere Did you find a solution to this?

HanaaGhasoub commented 2 years ago

Follow this link, it has the solution, just 2 steps:

1- Add the converter you prefer: image Or image

2- Use the converter as enum attribute as below. image

Or image

mbarmettler commented 9 months ago

Hi, i'm also strugle with this - after update to 14.x and NET 8. before that, my enums were generated properly - with the correct integer indexes next to it. after update i get something like this - without StringEnumConverter: export enum ChronoUserRightsEnum { _0 = 0, _1 = 1, _2 = 2, _3 = 3, _4 = 4, _5 = 5, }

and with EnumConverter something like this: export enum ChronoUserRightsEnum { Firma = "Firma", Department = "Department", Person = "Person", Filiale = "Filiale", Group = "Group", Hierarchical = "Hierarchical", }

but what i really need would be like (with version 13.x):

export enum ChronoUserRightsEnum { Firma = 0, Department = 1, Person = 2, Filiale = 3, Group = 4, Hierarchical = 5, }

any hints or tips would be highly appreciated - thanks

kavatari commented 4 months ago

@mbarmettler dir you find a solution? I have the same challenge here.

alexAlchemy commented 4 months ago

@mbarmettler Same issue 😢

mbarmettler commented 4 months ago

Hi @alexAlchemy and @kavatari

i'v struggled a lot and gathered several posts and information to have this achieved.

i ended up using these additional config steps in my Startup.cs:

//adding addional Swashbuckle / OpneApi configurations c.SchemaFilter(); c.UseAllOfForInheritance(); c.OperationFilter();`

NSwagEnumExtensionSchemaFilter: ` ///

/// Adds extra schema details for an enum in the swagger.json i.e. x-enumNames (used by NSwag to generate Enums for C# client) /// https://github.com/RicoSuter/NSwag/issues/1234 /// public class NSwagEnumExtensionSchemaFilter : ISchemaFilter { public void Apply(OpenApiSchema schema, SchemaFilterContext context) { if (schema is null) throw new ArgumentNullException(nameof(schema));

     if (context is null)
         throw new ArgumentNullException(nameof(context));

     if (context.Type.IsEnum)
         schema.Extensions.Add("x-enumNames", new NSwagEnumOpenApiExtension(context));
 }

}

public class NSwagEnumOpenApiExtension : IOpenApiExtension { private readonly SchemaFilterContext _context; public NSwagEnumOpenApiExtension(SchemaFilterContext context) { _context = context; }

 public void Write(IOpenApiWriter writer, OpenApiSpecVersion specVersion)
 {
     string[] enums = Enum.GetNames(_context.Type);
     JsonSerializerOptions options = new() { WriteIndented = true };
     string value = JsonSerializer.Serialize(enums, options);
     writer.WriteRaw(value);
 }

} `

checkout this reference: https://github.com/RicoSuter/NSwag/issues/1234

FromQueryModelFilter:

`public class FromQueryModelFilter : IOperationFilter { public void Apply(OpenApiOperation operation, OperationFilterContext context) { var description = context.ApiDescription; if (description.HttpMethod.ToLower() != HttpMethod.Get.ToString().ToLower()) { // We only want to do this for GET requests, if this is not a // GET request, leave this operation as is, do not modify return; }

        var actionParameters = description.ActionDescriptor.Parameters;
        var apiParameters = description.ParameterDescriptions
                .Where(p => p.Source.IsFromRequest)
                .ToList();

        if (actionParameters.Count == apiParameters.Count)
        {
            // If no complex query parameters detected, leave this operation as is, do not modify
            return;
        }

        operation.Parameters = CreateParameters(actionParameters, operation.Parameters, context);
    }

    private IList<OpenApiParameter> CreateParameters(
        IList<ParameterDescriptor> actionParameters,
        IList<OpenApiParameter> operationParameters,
        OperationFilterContext context)
    {
        var newParameters = actionParameters
            .Select(p => CreateParameter(p, operationParameters, context))
            .Where(p => p != null)
            .ToList();

        return newParameters.Any() ? newParameters : null;
    }

    private OpenApiParameter CreateParameter(
        ParameterDescriptor actionParameter,
        IList<OpenApiParameter> operationParameters,
        OperationFilterContext context)
    {
        var operationParamNames = operationParameters.Select(p => p.Name);
        if (operationParamNames.Contains(actionParameter.Name))
        {
            // If param is defined as the action method argument, just pass it through
            return operationParameters.First(p => p.Name == actionParameter.Name);
        }

        if (actionParameter.BindingInfo == null)
        {
            return null;
        }

        var generatedSchema = context.SchemaGenerator.GenerateSchema(actionParameter.ParameterType, context.SchemaRepository);

        var newParameter = new OpenApiParameter
        {
            Name = actionParameter.Name,
            In = ParameterLocation.Query,
            Schema = generatedSchema
        };

        return newParameter;
    }
}`

hope this helps you to get the stuff generated as needed. cheers & happy coding

alexAlchemy commented 4 months ago

@mbarmettler Hey, many thanks for the response above.

I've been having quite a few issues beside the enum one so I'm going to try some alternatives and see if I have any better luck but if may come back to this solution 🙏