Mahdi Ataollahi
Mahdi Ataollahi

Reputation: 5202

swagger error: Conflicting schemaIds: Duplicate schemaIds detected for types A and B

Using Web API and using swashbuckle to generate swagger documentation, I defined two different classes with the same name in two different namespaces. when I open swagger page in my browser it says

Conflicting schemaIds: Duplicate schemaIds detected for types A and B. See the config setting - "UseFullTypeNameInSchemaIds" for a potential workaround

full message:

500 : {"Message":"An error has occurred.","ExceptionMessage":"Conflicting schemaIds: Duplicate schemaIds detected for types A and B. See the config setting - \"UseFullTypeNameInSchemaIds\" for a potential workaround","ExceptionType":"System.InvalidOperationException","StackTrace":" at Swashbuckle.Swagger.SchemaRegistry.CreateRefSchema(Type type)\r\n at Swashbuckle.Swagger.SchemaRegistry.CreateInlineSchema(Type type)\r\n at Swashbuckle.Swagger.SchemaRegistry.b__1f(JsonProperty prop)\r\n at System.Linq.Enumerable.ToDictionary[TSource,TKey,TElement](IEnumerable1 source, Func2 keySelector, Func2 elementSelector, IEqualityComparer1 comparer)\r\n at Swashbuckle.Swagger.SchemaRegistry.CreateObjectSchema(JsonObjectContract jsonContract)\r\n at Swashbuckle.Swagger.SchemaRegistry.CreateDefinitionSchema(Type type)\r\n at Swashbuckle.Swagger.SchemaRegistry.GetOrRegister(Type type)\r\n at Swashbuckle.Swagger.SwaggerGenerator.CreateOperation(ApiDescription apiDesc, SchemaRegistry schemaRegistry)\r\n at Swashbuckle.Swagger.SwaggerGenerator.CreatePathItem(IEnumerable1 apiDescriptions, SchemaRegistry schemaRegistry)\r\n at Swashbuckle.Swagger.SwaggerGenerator.<>c__DisplayClass7.<GetSwagger>b__4(IGrouping2 group)\r\n at System.Linq.Enumerable.ToDictionary[TSource,TKey,TElement](IEnumerable1 source, Func2 keySelector, Func2 elementSelector, IEqualityComparer1 comparer)\r\n at Swashbuckle.Swagger.SwaggerGenerator.GetSwagger(String rootUrl, String apiVersion)\r\n at Swashbuckle.Application.SwaggerDocsHandler.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)\r\n at System.Net.Http.HttpMessageInvoker.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)\r\n at System.Web.Http.Dispatcher.HttpRoutingDispatcher.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)\r\n at System.Net.Http.DelegatingHandler.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)\r\n at System.Web.Http.HttpServer.d__0.MoveNext()"} http://localhost:24215/swagger/docs/v1

I don't want to change my classes' names. How can I fix it?

Upvotes: 124

Views: 76064

Answers (12)

Hassan Faghihi
Hassan Faghihi

Reputation: 2021

Situation

  • You are using dual project with similar model name and you do not wish to use a shared library for any reason
  • You don't want to change your main project schema, and want as close as original schema
  • You hate those long namespace-ed schema to be there, specially when your project names are multiple part, same as clean architecture projects with high level name, and multi-level breakdown...

My Problem

I had a micro-service project, using clean architecture, and azure pipeline to act as API client generator,... I want any type of project to be a different microservice, and now I came across the General data, and unfortunately I'm a bit late to deliver the code, so I decided to create the structured project under the same Microsoft `solution, and just use a same API, so no one mix subsystem calls, or create unwanted coupling until the next phase, where I can find time to do that.

Now I have two of almost everything, and swagger generate error. and if I shorten the name manually, even the simplest generic type that used multiple time will fail.

Solution:

  • 1st set: I tried to use default type name for default output of my main project
  • 2nd set: I added General before any type name: General.<type name>
  • I tried to parse class name as close as the one swagger generate, specially for generic ones
builder.Services.AddInfrastructureServices(builder.Configuration);
builder.Services.AddInternalServices(builder.Configuration);

// General
Tahvieh.General.Application.DependencyInjection.AddApplicationServices(builder.Services);
Tahvieh.General.Infrastructure.Persistence.DependencyInjection.AddInfrastructureServices(builder.Services, builder.Configuration);

Notice the TODO code

builder.Services.AddSwaggerGen(options =>
{
    // TODO: remove after moving general
    options.CustomSchemaIds(DualProjectSwaggerSchemaHelper.GenerateSchemaId);

    // ...
}


// Helper class for custom schema ID generation
public class DualProjectSwaggerSchemaHelper
{
    public static string GenerateSchemaId(Type type)
    {
        // Check if the type is a generic type
        if (type.IsGenericType)
        {
            // Get the base name (without "`" for generics)
            var baseName = type.GetGenericTypeDefinition().Name;
            baseName = baseName.Contains("`") ? baseName.Substring(0, baseName.IndexOf('`')) : baseName;

            // Get generic argument names and concatenate them with the base name
            var genericArgs = type.GetGenericArguments()
                .Select(arg => arg.Name)
                .Aggregate((current, next) => current + next);

            // Combine namespace prefix with generic argument names
            return GetNamespacePrefix(type) + $"{genericArgs}{baseName}";
        }

        // For non-generic types, apply only the namespace prefix if applicable
        return GetNamespacePrefix(type) + type.Name;
    }

    private static string GetNamespacePrefix(Type type)
    {
        // TODO: Replace your project name space with `Tahvieh.General` or write your customized selector, and secondary placed value
        return type.Namespace?.StartsWith("Tahvieh.General") == true ? "General." : string.Empty;
    }
}

Upvotes: 0

Kerrmiter
Kerrmiter

Reputation: 676

To handle both generic and inner classes in the model, this works for me:

builder.Services.AddSwaggerGen(options => options.CustomSchemaIds(type =>
    type.ToString()
        .Replace("+", "_")     // plus is not allowed
        .Replace("`1", "")));  // just for aesthetic reasons

Upvotes: 0

calingasan
calingasan

Reputation: 1005

Here's what I did so it looks exactly how you wrote it in c#

services.AddSwaggerGen(options => {
    options.CustomSchemaIds(x => {
       var namespaceSection = x.Namespace;
       var name = x.Name.Replace("`1", string.Empty);
       var genericParameters = string.Join(',', (IEnumerable<Type>)x.GetGenericArguments());

       return $"{namespaceSection}.{name}{(!string.IsNullOrEmpty(genericParameters) 
        ? "<" + genericParameters + ">"
        : string.Empty)}";
    });
});

This should out put something like

SampleProject.LibraryName.GenericClass<GenericTypeName>

Upvotes: 0

Conman_123
Conman_123

Reputation: 651

My issue was just with nested classes, and I didn't want to show the full namespace of my classes, so I just remove the namespace from the start of the fullname, which still fixes nested classes as they are output like Order_OrderItem.

options.CustomSchemaIds(type => type.FullName.TrimStart(type.Namespace + "."));

Upvotes: 4

Javier Contreras
Javier Contreras

Reputation: 1123

If you are using generic types in your payloads and you are using a Nswag proxy client, you should consider doing this.

services.AddSwaggerGen(options =>
{
    options.CustomSchemaIds(type => type.ToString().Replace("`1", ""));
});

The reason is to avoid this type of output when you are using Nswag.

enter image description here

Replace("`1", "") will format generic types and result in a cleaner client:

enter image description here

Upvotes: 0

Mahdi Abyaznezhad
Mahdi Abyaznezhad

Reputation: 522

In my case, I had 2 of the same classes in my 2 microservice projects (A, and B).

When I opened the dependencies folder in project(A), I found that I added the other project(B) as a reference to this project by mistake.

After removing the dependency between my two projects the conflict was solved.

Upvotes: -1

Carsten F&#252;hrmann
Carsten F&#252;hrmann

Reputation: 3490

For Swashbuckle.AspNetCore 5.2.1 (on .NET Core 3.1), the Swashbuckle configuration API seems to lack the options described in the older solutions. Instead, the following change in the Startup.cs worked for me:

  services.AddSwaggerGen(c =>
  {
     // Existing configuration

     // Tweak to the Schema generator
     c.SchemaGeneratorOptions = new SchemaGeneratorOptions {SchemaIdSelector = type => type.FullName};
  }

Upvotes: 14

Ghebrehiywet
Ghebrehiywet

Reputation: 934

Every class in the swagger JSON must have a unique schemaId.

Swashbuckler tries to just use the class name as a simple schemaId, however if you have two classes in different namespaces with the same name (as you do) this will not work.

As the error suggests, you can use the config setting "UseFullTypeNameInSchemaIds*" for a potential workaround (Update: not available in recent versions)

In newer versions you can achieve the same behavior via options.CustomSchemaIds(x => x.FullName).

Here is an example:

   services.ConfigureSwaggerGen(options =>
   {
       //your custom configuration goes here

...

  // UseFullTypeNameInSchemaIds replacement for .NET Core
       options.CustomSchemaIds(x => x.FullName);
   });

for more information http://wegotcode.com/microsoft/swagger-fix-for-dotnetcore/

Upvotes: 185

mguoth
mguoth

Reputation: 91

If your model contains generic types, consider using Type.ToString() instead of Type.FullName to get rid of assembly information generated for generic parameter type and make your schema ids more consistent.

services.AddSwaggerGen(options =>
{
    options.CustomSchemaIds(type => type.ToString());
});

Example showing the difference on List<string>:

Console.WriteLine(typeof(List<string>).FullName);
Console.WriteLine(typeof(List<string>).ToString());

// Output:
//    System.Collections.Generic.List`1[[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]
//    System.Collections.Generic.List`1[System.String]

Upvotes: 9

Hidan
Hidan

Reputation: 411

I am using Asp.net Core 2.1. This error resulted when I tried to show Swagger UI.

I solved the problem this way:

In Starup.cs, in ConfigureServices() add c.CustomSchemaIds(i => i.FullName);

see example below:

services.AddSwaggerGen(c =>
        {
            c.SwaggerDoc("v1", new Info
            {
                Title = "ASP.NET Core 2.1+ ConsumerApp API",
                Version = "v1"
            });
            // Set the comments path for the Swagger JSON and UI.
            var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
            var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
            c.IncludeXmlComments(xmlPath);
            c.CustomSchemaIds(i => i.FullName);
        });

Upvotes: 34

Karl Merecido
Karl Merecido

Reputation: 384

If you comment out or add:

c.UseFullTypeNameInSchemaIds();

In that section, it seems to do same thing.

Upvotes: 17

Mahdi Ataollahi
Mahdi Ataollahi

Reputation: 5202

I finally found a way in swagger configs. Go to App_Start\SwaggerConfig.cs file and under EnableSwagger lambda expression add this line:

c.SchemaId(x => x.FullName);

Full code is like this:

GlobalConfiguration.Configuration 
    .EnableSwagger(c =>
    {
        // your configs...

        c.SchemaId(x => x.FullName);

        // other configs...
    })
    .EnableSwaggerUi(c =>
        // ....
    });

Upvotes: 71

Related Questions