Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

C#:- Enums names not generating the same name in client side #1234

Open
satyanarayan18 opened this issue Mar 23, 2018 · 44 comments
Open

C#:- Enums names not generating the same name in client side #1234

satyanarayan18 opened this issue Mar 23, 2018 · 44 comments

Comments

@satyanarayan18
Copy link

C# Code:-
public enum Test
{
Pass,
Fail
}

public class ClassA
{
public Test TestEnum {get;set;}
}

Client Side Type Script:-

export enum ClassATestEnum
{
_0=0,
_1=1
}

@RicoSuter
Copy link
Owner

Are you using Swashbuckle to generate the Swagger spec?

@satyanarayan18
Copy link
Author

Yes.

@RicoSuter
Copy link
Owner

Check your Swagger spec: Is the required information (the actual enum names) even available in the spec? When NSwag generates a Swagger spec, it adds an additional x-enumNames property with all enum names so that the generator knows the names and values... In Swashbuckle I think you only have the option to use string serialization instead of numbers so that the value und names are the same...

@satyanarayan18
Copy link
Author

I am using "swagger": "2.0" and in the spec I have not found anything x-enumNames.

Could you please guide me what I am doing wrong and let me know any additional information required

@RicoSuter
Copy link
Owner

The problem is that Swagger does not support number enums with names, this is why we added a custom property x-enumNames to NSwag so that you can do it... but this only works if the generator (like NSwag does) generates this property (Swashbuckle does not). But you can configure Swashbuckle and ASP.NET to serialize enums not as integer but as string so that the enum value also is the name and the code gen works as expected...

@satyanarayan18
Copy link
Author

Thanks for the quick response.

I have added code to serialize enums as string instead of int
services.AddMvc()
.AddJsonOptions(options =>
options.SerializerSettings.Converters.Add(new StringEnumConverter()));

Now I am getting enum with names but still not getting exact enum name as that of name defined in C#

@RicoSuter
Copy link
Owner

Whats the diff?

@satyanarayan18
Copy link
Author

satyanarayan18 commented Mar 23, 2018

export enum ClassATestEnum
{
Pass="Pass",
Fail="Fail"
}

but the name is not changing. Even I found that if the same Enum is used in multiple classes then that many enums are generated but not the single one

@RicoSuter
Copy link
Owner

strange, can you post the part of this enum in the generated swagger spec?

@satyanarayan18
Copy link
Author

That was just an example but now I am posting the exact code

Enum generated in the spec,

export enum AdministrativeActivityInputDtoCategory {
Todo = "Todo",
Survey = "Survey",
Notification = "Notification",
}
public class AdministrativeActivityInputDto
{
public ActivityCategoryEnum Category { get; set; }
}

public enum ActivityCategoryEnum
{
Todo,
Survey,
Notification
}

Hope it helps

@RicoSuter
Copy link
Owner

I need to see this in the generated Swagger spec?

@satyanarayan18
Copy link
Author

Are you referring to swagger.json file or proxy file which are generated through nswag

@RicoSuter
Copy link
Owner

swagger.json - i need to know what Swashbuckle generated...

@satyanarayan18
Copy link
Author

"AdministrativeActivityInputDto": {
"type": "object",
"properties": {
"category": {
"type": "string",
"enum": [
"Todo",
"Survey",
"Notification"
]
}
}

@RicoSuter
Copy link
Owner

And what enum is generated?

@satyanarayan18
Copy link
Author

export enum AdministrativeActivityInputDtoCategory {
Todo = "Todo",
Survey = "Survey",
Notification = "Notification",
}

@RicoSuter
Copy link
Owner

Ok looks correct. What’s wrong about that?

@satyanarayan18
Copy link
Author

satyanarayan18 commented Mar 23, 2018

Name of enum generated on client side, it is not the mention on C#.

Enum name on C# :- ActivityCategoryEnum
Enum name generated :- AdministrativeActivityInputDtoCategory (It is taking the class name with the property name)

So if the same enum is used in multiple classes then multiple enums are generated on client side instead of generating only one enum.

@RicoSuter
Copy link
Owner

Yes, there is no way to "restore" the original name with the Swashbuckle generated spec because the enum is not referenced and thus does not have a name... How would NSwag restore the name with the given spec?

@satyanarayan18
Copy link
Author

satyanarayan18 commented Mar 23, 2018

But don't you think so this way I will be having n number of enums generated if reference in n places. This way client side file size also increases and there by impacting the performance.

Is there any possibility to handle the duplication I mean Can I apply some attribute on the enum to get the exact name and avoid duplication.

@RicoSuter
Copy link
Owner

If the spec is generated with NSwag it automatically uses references so that it is only generated once and with the correct name - I dont know if this is possible with Swashbuckle.

@satyanarayan18
Copy link
Author

Ok... Thanks for the update.. Lastly can I get the enum name along with the integer value associated to it like,

Todo = 0,
Survey = 1,
Notification = 2,

@RicoSuter
Copy link
Owner

This is what Swashbuckle generates and Swagger originally supports:

String enum:

"category": {
  "type": "string",
  "enum": [
    "Todo",
    "Survey",
    "Notification"
  ]
}

Integer enum:

"category": {
  "type": "string",
  "enum": [
    1,
    2,
    3
  ]
}

As you can see there is no way to store both information (name and value). This is why NSwag will generate and process the following to store both:

"category": {
  "type": "string",
  "x-enumNames": [
    "Todo",
    "Survey",
    "Notification"
  ],
  "enum": [
    1,
    2,
    3
  ]
}

Also NSwag references the enum so that the original name can be restored. I don't know if this is also possible with Swashbuckle.

@satyanarayan18
Copy link
Author

satyanarayan18 commented Mar 23, 2018

Where can I see this piece of code

"category": {
  "type": "string",
  "x-enumNames": [
    "Todo",
    "Survey",
    "Notification"
  ],
  "enum": [
    1,
    2,
    3
  ]
}

x-enumNames I am not able to find in the generated proxy of nsswag

@RicoSuter
Copy link
Owner

You currently generate the swagger.json with Swashbuckle, so this x-enumNames is missing...

If you generate the swagger.json with NSwag it will automatically add x-enumNames which will be picked up by the NSwag code generators.

Maybe there is a way to manually add this x-enumNames also with Swashbuckle (a document filter?)

@satyanarayan18
Copy link
Author

Can I get the example to generate swagger.json using NSwag

@RicoSuter
Copy link
Owner

e.g. with middlewares like in Swashbuckle
https://github.com/RSuter/NSwag/wiki/Middlewares

(there are a lot of other options)

@satyanarayan18
Copy link
Author

Thanks for the quick response.

Also, I have one more query.

Can I generate multiple swagger.json file from a single api end point?

Is there any way like to place some attribute to distinguish and generate multiple swagger.json file

@RicoSuter
Copy link
Owner

You can add multiple UseSwagger calls with different routes and a custom document processors (see wiki) to exclude operations

@satyanarayan18
Copy link
Author

My API's are generated dynamically I am using ABP(ASP.Net Boilerplate) so can you just possible ways to generate the multiple swagger.json file.

@RicoSuter
Copy link
Owner

In Startup.cs you probably have the UseSwagger registration, you can register multiple with different routes and custom exclusions.

@satyanarayan18
Copy link
Author

Ok.. will explore more into that option..

If you have any link with examples to show generating multiple swagger.json with single endpoint that would really helpful in context to ABP

@Eneuman
Copy link

Eneuman commented Apr 27, 2018

If you can't use NSwag to generate the swagger definition, you can extend swashbuckle to include x-enumNames like this:

Do not use "DescribeAllEnumsAsStrings" option in Swaschbuckle.
Create a custom SchemaFilter that looks like this:

  public class XEnumNamesSchemaFilter : ISchemaFilter
  {
    public void Apply(Schema model, SchemaFilterContext context)
    {
      var typeInfo = context.SystemType.GetTypeInfo();
      if (typeInfo.IsEnum)
      {
        var names = Enum.GetNames(context.SystemType);
        model.Extensions.Add("x-enumNames", names);
      }
    }
  }

Apply it like this:
options.SchemaFilter<Infrastructure.SchemaFilters.XEnumNamesSchemaFilter>();

Swashbuckle willl now generate a swagger definition that NSwag code generators will handle fine.

@yoli799480165
Copy link

ah, I figured out why this problem occurred. Both of your two questions #1499 & #1234 are the same reason. It's not the bug of NSwag, it's abp's fault.
abp use Swashbuckle.AspNetCore as it's default doc generator in server side, and client use nswag to generate ts code.
you should change Swashbuckle.AspNetCore to NSwag.AspNetCore, The problem will be solved.

@RicoSuter
Copy link
Owner

RicoSuter commented Mar 7, 2019

You need to know the following:

  • JSON Schema/Swagger/OpenAPI only describes the enum values not the names (limitation)
  • That is why NJS/NSwag generates the x-enumNames property to preserve this, e.g. when the values are numbers (default of Newtonsoft.Json)
  • Another option would be to change the Newtonsoft.Json serialization behavior so that enums are serialized as string and the values are the same as the names - then the x-enumNames property is not needed
  • I don't know how Swashbuckle is handling the integer enum case - it seems that it stores the names in the description (which is not used by NJS/NSwag for code generation) - or does it have another way to preserve the enum names?

@FinHorsley
Copy link

Hi there, we've followed the NSwag Enum docs, but we're having some problems with System.Text.Json and enums showing up as their int values in the Swagger docs (the enums are being serialised as strings in the json going over the wire). Here is what our Startup looks like

.AddControllers()
.AddJsonOptions(options =>
{
    // Serialize enums as strings globally.
    options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter());
})

The only solution to our problem at the moment is to annotate all our enum classes with the JsonStringEnumConverter, but this is a bit of a pain to do for each model

using System.Text.Json.Serialization;

[JsonConverter(typeof(JsonStringEnumConverter))] // Applying this means Swagger displays [Starting, Running], not [1,2]
public enum ImportState
{
    Starting = 1,
    Running = 2
}

There was a similar issue raised on the Swashbuckle repo, domaindrivendev/Swashbuckle.AspNetCore#1269, is this something that's been seen before with Nswag?

@yrikturash
Copy link

If you are using Newtonsoft.Json, can be fixed like this:
services.AddNewtonsoftJson(options => options.SerializerSettings.Converters.Add(new StringEnumConverter()));

@RicoSuter
Copy link
Owner

RicoSuter commented Nov 25, 2020

@FinHorsley In the case of newtonsoft we use the same behavior, i.e. we inspect the serializer settings for this global filter or on the property or enum directly... (not an NSwag config).

In the case of System.Text.Json, we map the JsonStringEnumConverter to the Newtonsoft one, so the behavior should be the same:
https://github.com/RicoSuter/NJsonSchema/blob/master/src/NJsonSchema/Generation/SystemTextJsonUtilities.cs#L36
(for System.Text.Json we map the rules to a Newtonsoft contract resolver so that we only need one implementation internally)

@worthy7
Copy link

worthy7 commented Jan 31, 2021

If you can't use NSwag to generate the swagger definition, you can extend swashbuckle to include x-enumNames like this:

Do not use "DescribeAllEnumsAsStrings" option in Swaschbuckle.
Create a custom SchemaFilter that looks like this:

  public class XEnumNamesSchemaFilter : ISchemaFilter
  {
    public void Apply(Schema model, SchemaFilterContext context)
    {
      var typeInfo = context.SystemType.GetTypeInfo();
      if (typeInfo.IsEnum)
      {
        var names = Enum.GetNames(context.SystemType);
        model.Extensions.Add("x-enumNames", names);
      }
    }
  }

Apply it like this:
options.SchemaFilter<Infrastructure.SchemaFilters.XEnumNamesSchemaFilter>();

Swashbuckle willl now generate a swagger definition that NSwag code generators will handle fine.

I wonder if anyone could show how to do this with the latest version....

@PeterKottas
Copy link

PeterKottas commented Apr 17, 2021

A version of the XEnumNamesSchemaFilter for the latest version Swashbuckle:

public class XEnumNamesSchemaFilter : ISchemaFilter
    {
        private const string NAME = "x-enumNames";

        public void Apply(OpenApiSchema model, SchemaFilterContext context)
        {
            var typeInfo = context.Type;
            // Chances are something in the pipeline might generate this automatically at some point in the future
            // therefore it's best to check if it exists.
            if (typeInfo.IsEnum && !model.Extensions.ContainsKey(NAME))
            {
                var names = Enum.GetNames(context.Type);
                var arr = new OpenApiArray();
                arr.AddRange(names.Select(name => new OpenApiString(name)));
                model.Extensions.Add(NAME, arr);
            }
        }
    }

@worthy7 @eluchsinger

@skironDotNet
Copy link

@satyanarayan18 it's easier to use own contracts so in
AdditionalNamespaceUsages (I'm referring to *.nswag config file for NSwag Studio, in the Studio the name may be slightly diffrent )
add a namespace to your contracts/DTOs project
"your contracts/DTOs project" should be deployed to your internal nuget server and your generated client should reference it.
the flag GenerateDtoTypes should be set to false.
This way you'll use your own enums and own DTOs in auto generated code

@rm-code
Copy link

rm-code commented May 2, 2022

A version of the XEnumNamesSchemaFilter for the latest version Swashbuckle:

public class XEnumNamesSchemaFilter : ISchemaFilter
    {
        private const string NAME = "x-enumNames";

        public void Apply(OpenApiSchema model, SchemaFilterContext context)
        {
            var typeInfo = context.Type;
            // Chances are something in the pipeline might generate this automatically at some point in the future
            // therefore it's best to check if it exists.
            if (typeInfo.IsEnum && !model.Extensions.ContainsKey(NAME))
            {
                var names = Enum.GetNames(context.Type);
                var arr = new OpenApiArray();
                arr.AddRange(names.Select(name => new OpenApiString(name)));
                model.Extensions.Add(NAME, arr);
            }
        }
    }

@worthy7 @eluchsinger

Works like a charm. This should be added to the wiki.

@ramax495
Copy link

With NSwag and System.Text.Json for me works:

services.AddControllersWithViews()
       .AddJsonOptions(o => o.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter())) 

and

services.AddOpenApiDocument(configure =>
{
       ...
       configure.GenerateEnumMappingDescription = true;    //for enums as string names
});

It accepts int-s and strings, generates enums in open-api and .ts client with names and show enums with names in SwaggerUI

export enum PaymentDirection {
    Input = "Input",
    Output = "Output",
}

@mbarmettler
Copy link

mbarmettler commented Feb 12, 2024

@ ramax495 this seems not to work either on 14.0.3 of nswag build.
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, }

is there a solution to generate the correct way again?
any hints or tips would be highly appreciated - thanks

update:
i finally figured it out - and it works for now with this workaround: https://stackoverflow.com/a/73872112

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests