NSwag namespace in model names
Asked Answered
D

6

10

It's possible to generate client code so that model's class names have full namespaces as prefix?

That should avoid same class name conflicts.

Example

com.foo.MyClass 

and

it.foo.MyClass

Up to now what I got is MyClass and MyClass2 that's not so much meaningful.

Should be better to have, in case of name collision, ComFooMyClass and ItFooMyClass.

Donne answered 21/7, 2017 at 15:15 Comment(2)
Actually I'm running into issues where it clashes when running swaggerui because my application already has the same class names.Villein
If using Swashbuckle see #40644552Meal
D
5

I've found a solution using a custom SchemaNameGenerator instead of a custom TypeNameGenerator (where I don't have package information).

internal class MySchemaNameGenerator : DefaultSchemaNameGenerator, ISchemaNameGenerator
{
    public override string Generate(Type type)
    {
        string retValue = base.Generate(type);
        // Quite ugly but do fix the concept
        if (retValue.Equals("BaseClass"))
        {
            retValue = type.FullName.Replace(".","_");
        }
        return retValue;
    }
}

Always set through settings:

 app.UseSwaggerUi(typeof(WebApiApplication).Assembly, new SwaggerUiSettings
                {
                    SchemaNameGenerator = new MySchemaNameGenerator(),
                    ...

This way I get something more meaningful

"/api/test/models/base": {
      "get": {
        "tags": [
          "Test"
        ],
        "operationId": "Test_Get2",
        "parameters": [],
        "responses": {
          "200": {
            "description": "",
            "schema": {
              "$ref": "#/definitions/WebApi_Models_BaseClass"
            },
            "x-nullable": true
          }
        }
      }
    },
    "/api/test/models/extended": {
      "get": {
        "tags": [
          "Test"
        ],
        "operationId": "Test_Get3",
        "parameters": [],
        "responses": {
          "200": {
            "description": "",
            "schema": {
              "$ref": "#/definitions/ExtendedClass"
            },
            "x-nullable": true
          }
        }
      }
    },
    "/api/test/modelli/base": {
      "get": {
        "tags": [
          "Test"
        ],
        "operationId": "Test_Get4",
        "parameters": [],
        "responses": {
          "200": {
            "description": "",
            "schema": {
              "$ref": "#/definitions/WebApi_Modelli_BaseClass"
            },
            "x-nullable": true
          }
        }
      }
    },

Even if the discriminator property for polymorphism wants the base name "BaseClass".

Donne answered 26/7, 2017 at 8:23 Comment(3)
Here I've got two BaseClass (in my question was MyClass) in two different namespaces: WebApi.Models and WebApi.ModelliDonne
Severity Code Description Project File Line Suppression State Error CS1061 'SwaggerUIOptions' does not contain a definition for 'SchemaNameGenerator' and no accessible extension method 'SchemaNameGenerator' accepting a first argument of type 'SwaggerUIOptions' could be found (are you missing a using directive or an assembly reference?) aaaa.bbbb.WebApi.dddd C:\aaaa\bbbb\Startup.cs 247 ActiveVillein
Does this require installing nuget packages to access these classes? Seems like a lot of overhead in my project to generate some classes.Villein
W
9

Let me update shadowsheep's answer for a more recent version of NSwag:

services.AddSwaggerDocument(cfg => { cfg.SchemaNameGenerator = new CustomSchemaNameGenerator(); });

With:

internal class CustomSchemaNameGenerator : ISchemaNameGenerator
{
    public string Generate(Type type)
    {
        return type.FullName.Replace(".", "_");
    }
}
Wellfavored answered 7/12, 2019 at 11:15 Comment(0)
D
5

I've found a solution using a custom SchemaNameGenerator instead of a custom TypeNameGenerator (where I don't have package information).

internal class MySchemaNameGenerator : DefaultSchemaNameGenerator, ISchemaNameGenerator
{
    public override string Generate(Type type)
    {
        string retValue = base.Generate(type);
        // Quite ugly but do fix the concept
        if (retValue.Equals("BaseClass"))
        {
            retValue = type.FullName.Replace(".","_");
        }
        return retValue;
    }
}

Always set through settings:

 app.UseSwaggerUi(typeof(WebApiApplication).Assembly, new SwaggerUiSettings
                {
                    SchemaNameGenerator = new MySchemaNameGenerator(),
                    ...

This way I get something more meaningful

"/api/test/models/base": {
      "get": {
        "tags": [
          "Test"
        ],
        "operationId": "Test_Get2",
        "parameters": [],
        "responses": {
          "200": {
            "description": "",
            "schema": {
              "$ref": "#/definitions/WebApi_Models_BaseClass"
            },
            "x-nullable": true
          }
        }
      }
    },
    "/api/test/models/extended": {
      "get": {
        "tags": [
          "Test"
        ],
        "operationId": "Test_Get3",
        "parameters": [],
        "responses": {
          "200": {
            "description": "",
            "schema": {
              "$ref": "#/definitions/ExtendedClass"
            },
            "x-nullable": true
          }
        }
      }
    },
    "/api/test/modelli/base": {
      "get": {
        "tags": [
          "Test"
        ],
        "operationId": "Test_Get4",
        "parameters": [],
        "responses": {
          "200": {
            "description": "",
            "schema": {
              "$ref": "#/definitions/WebApi_Modelli_BaseClass"
            },
            "x-nullable": true
          }
        }
      }
    },

Even if the discriminator property for polymorphism wants the base name "BaseClass".

Donne answered 26/7, 2017 at 8:23 Comment(3)
Here I've got two BaseClass (in my question was MyClass) in two different namespaces: WebApi.Models and WebApi.ModelliDonne
Severity Code Description Project File Line Suppression State Error CS1061 'SwaggerUIOptions' does not contain a definition for 'SchemaNameGenerator' and no accessible extension method 'SchemaNameGenerator' accepting a first argument of type 'SwaggerUIOptions' could be found (are you missing a using directive or an assembly reference?) aaaa.bbbb.WebApi.dddd C:\aaaa\bbbb\Startup.cs 247 ActiveVillein
Does this require installing nuget packages to access these classes? Seems like a lot of overhead in my project to generate some classes.Villein
B
4

When using NSwag via C#, you can provide an own TypeNameGenerator (via the settings object) to customize the way how the class names are generated.

Brow answered 25/7, 2017 at 19:7 Comment(3)
I tried setting my TypeNameGenerator with TypeNameGenerator = new MyTypeNameGenerator() but in generate I've only these parameters: JsonSchema4 schema, string typeNameHint, IEnumerable<string> reservedTypeNames so that I cannot infer package information tha's right the information I need.Donne
When using c# OpenAPI connected service ver 1.2.2 Is it possible to (always) use full type names like “com.foo.MyClass“ or “it.foo.MyClass” in generated client code. Im facing the same issue as OP and would hate to rename some classes just to avoid this renaming to MyClass2. Not to mention all that confusion that can result from renamed classes..Morey
What if we cannot or don't want to use Nswag via C#, is it possible to handle when generating from CLI or the GUI app?Villein
V
1

Updated for NSwag.AspNetCore as of v13.15.10 based on @shadowsheep answer.

The extension method UseSwaggerUi has been deprecated and we should now use UseSwaggerUi3. This method does not provides you with a way to set your custom instance of ISchemaNameGenerator, instead you set this at service registration time as follows:

services.AddOpenApiDocument(configure => {
    configure.SchemaNameGenerator = new CustomSchemaNameGenerator();
});

For completeness I'm leaving CustomSchemaNameGenerator below:

class CustomSchemaNameGenerator : NJsonSchema.Generation.ISchemaNameGenerator
{
    public string Generate(Type type) => type.FullName.Replace(".", "_");
}
Ventre answered 8/7, 2022 at 15:16 Comment(0)
S
0

I extended @Dejan's answer by adding an attribute to my classes.

internal class CustomSchemaNameGenerator : ISchemaNameGenerator
{
    private static List<string> exludes = new List<string> { "Presentation", "Domain", "Endpoints", "Models", "Request", "Requests", "Response", "Responses" };
    public string Generate(Type type)
    {
        if (type.GetCustomAttributes<ClientGenAttribute>(false).FirstOrDefault() is ClientGenAttribute clientGenAttribute && clientGenAttribute.Name is not null)
        {
            return clientGenAttribute.Name;
        }
        return string.Join("_", type.FullName!.Split(".").Except(exludes));
    }
}

I'm also cutting out some Clean Architecture common namespace parts

And the attribute:

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)]
public class ClientGenAttribute : Attribute
{
    public readonly string Name;

    public ClientGenAttribute(string Name)
    {
        this.Name = Name;
    }
}

You can then do something like this:

[ClientGen("CreateLoginRequest")]
public sealed record PresentationRequest()
{
    public required Guid ClientId { get; init; }
    public required string Email { get; init; }
    public ICollection<ClaimRequest>? Claims { get; init; }
}
Scepter answered 3/4, 2023 at 12:42 Comment(0)
E
-1

To generate full namespace. You need to setup API to return swagger with full namespace

services.AddSwaggerGen(c =>
{
    ...  //any lines you aready have 
    c.CustomSchemaIds((type) => type.FullName); //show full namespace
}

You can't generate a client via NSwagStudio, you need to do via code with this solution https://mcmap.net/q/1062828/-nswag-namespace-in-model-names

And here is ready code to generate client via own code

using NJsonSchema;
using NSwag;
using NSwag.CodeGeneration.CSharp;
using NSwag.CodeGeneration.OperationNameGenerators;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Threading.Tasks;

...

public async Task<string> GetClientCode(string swaggerUrl, CSharpClientGeneratorSettings settings)
{
    settings.OperationNameGenerator = new SingleClientFromOperationIdOperationNameGenerator();
    settings.CSharpGeneratorSettings.TypeNameGenerator = new MyTypeNameGenerator();
    var swagger = await GetAsync(swaggerUrl);
    var document = await OpenApiDocument.FromJsonAsync(swagger);
    var codeGen = new CSharpClientGenerator(document, settings);

    var code = codeGen.GenerateFile();
    return code;
}

public class MyTypeNameGenerator : ITypeNameGenerator
{
    public string Generate(JsonSchema schema, string typeNameHint, IEnumerable<string> reservedTypeNames)
    {
        if (typeNameHint == null && schema.IsEnumeration && schema.Title != null)
                return schema.Title; //for method argument when expected type is IEnumerable<Enum> (swagger definition must contain title - see last link) 

        return typeNameHint;   //this contains full namespace (assuming returned in swagger definition)
    }
}

private async Task<string> GetAsync(string uri)
{
    HttpWebRequest request = (HttpWebRequest)WebRequest.Create(uri);
    request.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate;

    using (HttpWebResponse response = (HttpWebResponse)await request.GetResponseAsync())
    using (Stream stream = response.GetResponseStream())
    using (StreamReader reader = new StreamReader(stream))
    {
        return await reader.ReadToEndAsync();
    }
}

here about enum title

https://github.com/RicoSuter/NSwag/issues/2103#issuecomment-853965927

Exteroceptor answered 3/6, 2021 at 15:21 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.