89

I receive the following error.

InvalidOperationException: Can't use schemaId "$Registration" for type "$PortalService.Models.Registration". The same schemaId is already used for type "$PortalService.Models.Registration"

I have tried the suggestions in the following link without any succcess.

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

I only have one Registration class in models. I have tried renaming the class without success.

I am using an OData .Net Core 3.1 project.

Configure Swagger is below

 services.AddTransient<IConfigureOptions<SwaggerGenOptions>, ConfigureSwaggerOptions>();
            services.AddSwaggerGen(c =>
            {
                c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
                {
                    Description = @"JWT Authorization header using the Bearer scheme. \r\n\r\n 
                      Enter 'Bearer' [space] and then your token in the text input below.
                      \r\n\r\nExample: 'Bearer 12345abcdef'",
                    Name = "Authorization",
                    In = ParameterLocation.Header,
                    Type = SecuritySchemeType.ApiKey,
                    Scheme = "Bearer"
                });

                c.AddSecurityRequirement(new OpenApiSecurityRequirement()
                  {
                    {
                      new OpenApiSecurityScheme
                      {
                        Reference = new OpenApiReference
                          {
                            Type = ReferenceType.SecurityScheme,
                            Id = "Bearer"
                          },
                          Scheme = "oauth2",
                          Name = "Bearer",
                          In = ParameterLocation.Header,

                        },
                        new List<string>()
                      }
                    });
            });

Use Swagger is below

  app.UseSwagger(c =>
            {
                //c.PreSerializeFilters.Add((swaggerDoc, httpReq) => swaggerDoc.BasePath = basepath);

                 
                c.PreSerializeFilters.Add((swaggerDoc, httpReq) => {
                    Microsoft.OpenApi.Models.OpenApiPaths paths = new Microsoft.OpenApi.Models.OpenApiPaths();
                    foreach (var path in swaggerDoc.Paths)
                    {
                        paths.Add(path.Key.Replace(path.Key, basepath + path.Key), path.Value);
                    }
                    swaggerDoc.Paths = paths;
                });
            });
            app.UseSwaggerUI(
                options =>
                {
                    options.RoutePrefix = string.Empty;

                    // build a swagger endpoint for each discovered API version

                    foreach (var description in provider.ApiVersionDescriptions)
                    {
                        options.SwaggerEndpoint($"{basepath}/swagger/{description.GroupName}/swagger.json", description.GroupName.ToUpperInvariant());
                    }

                });

This appears to be related to

Swagger crashes with circular model references

I have found that if I comment out the partner back reference from registration, the error goes away but I need this reference. I am not clear how to fix the situation.

[ForeignKey("Partner")]
[DataMember(Name = "PartnerOID")]
[Column(TypeName = "VARCHAR(100)")]
public string PartnerOID { get; set; }
//public virtual Partner Partner { get; set; }
1

7 Answers 7

158

Try this John: https://github.com/domaindrivendev/Swashbuckle.AspNetCore/issues/1607#issuecomment-607170559 helped me.

I can understand what is happening; i have several enums with 'Status' or 'Type'. Using options.CustomSchemaIds( type => type.ToString() ); solved this.

6
  • 1
    This works. @John Bowyer put breakpoint into options.CustomSchemaIds( type => { type.ToString(); // breakpoint } ); to see what types come into this lambda. Or try this code options.CustomSchemaIds( type => type.ToString() + type.GetHashCode() );
    – bobah75
    Commented Aug 31, 2020 at 16:25
  • 22
    using type.FullName is also a good way to do this... Commented Apr 20, 2021 at 12:32
  • 1
    This helped me. In a medium-large project, there were same named enum definitions in various classes. Enum type's string representation solved the schema creation of swagger.
    – Hamit Enes
    Commented Feb 3, 2022 at 7:57
  • 4
    github.com/swagger-api/swagger-ui/issues/7911 there are still issues with the + sign. c.CustomSchemaIds(s => s.FullName.Replace("+", ".")); avoids that issue
    – M. Koch
    Commented May 17, 2023 at 15:14
  • Does fullname cause owasp.org/www-project-top-ten/2017/…?
    – kanils_
    Commented Sep 7, 2023 at 7:59
104

The only change needed is in your Startup.cs inside the method ConfigureServices.

You should add the following:

services.AddSwaggerGen(options =>
{
    options.CustomSchemaIds(type => type.ToString());
});
2
  • 5
    Thanks so much, the AddSwaggerGen mention helped a lot. My problem is resolved.
    – BoBoDev
    Commented Oct 11, 2021 at 22:51
  • This worked for me. Still trying to understand why it is choking with my code, though. Thanks Commented Jul 25, 2023 at 1:19
8

I've been using options.CustomSchemaIds(type => type.ToString()); and options.CustomSchemaIds(type => $"{type.Name}_{System.Guid.NewGuid().ToString().Replace("-", "")}") to create uniqueness on schema names. Both results in longer schema names which I hate.

Here's a different approach which track the name repetition, which I prefer.

Helper class:

internal static class SwashbuckleSchemaHelper
{
   private static readonly Dictionary<string, int> _schemaNameRepetition = new Dictionary<string, int>();

   public static string GetSchemaId(Type type)
   {
      string id = type.Name;

      if (!_schemaNameRepetition.ContainsKey(id))
          _schemaNameRepetition.Add(id, 0);

      int count = (_schemaNameRepetition[id] + 1);
      _schemaNameRepetition[id] = count;

      return type.Name + (count > 1 ? count.ToString() : "");
   }
}

Usage:

options.CustomSchemaIds(type => SwashbuckleSchemaHelper.GetSchemaId(type));

This would result as below, if the class name was duplicated.

  • Invoice
  • LineItem
  • Status
  • Status2
3

Here is a slightly more flexible solution

options.CustomSchemaIds(type => SwashbuckleSchemaHelper.GetSchemaId(type));

Helper

public static class SwashbuckleSchemaHelper
{
    private static readonly string _rootNamespace;
    private static readonly string _dtoFolder = "Dtos";

    static SwashbuckleSchemaHelper()
    {
        _rootNamespace = Path.GetFileNameWithoutExtension(Assembly.GetExecutingAssembly().ManifestModule.Name);
    }

    private static string GetRelativeNamespace(string typeNamespace)
    {
        if (!typeNamespace.StartsWith(_rootNamespace))
        {
            return typeNamespace;
        }

        var relativenamespace = typeNamespace.Substring(_rootNamespace.Length + _dtoFolder.Length + 1).TrimStart('.');
        if (string.IsNullOrEmpty(relativenamespace))
        {
            return string.Empty;
        }

        return $"{relativenamespace}.";
    }

    public static string GetSchemaId(Type type)
    {
        var schemaBase = $"{GetRelativeNamespace(type.Namespace)}{type.Name}";

        if (type.IsGenericType)
        {
            string? schemaGeneric;
            if (type.GenericTypeArguments.Length > 0)
            {
                var firstItem = type.GenericTypeArguments.First();
                schemaGeneric = $"<{GetRelativeNamespace(firstItem.Namespace)}{firstItem.Name}>";
            }
            else
            {
                schemaGeneric = $"<{Guid.NewGuid()}>";
            }

            return $"{schemaBase}{schemaGeneric}";
        }

        return $"{schemaBase}";
    }
}
3

To get a slightly nicer type name I came up with this:

  public class SwashbuckleSchemaHelper
  {
      private readonly Dictionary<string, int> _schemaNameRepetition = new();

      // borrowed from https://github.com/domaindrivendev/Swashbuckle.AspNetCore/blob/95cb4d370e08e54eb04cf14e7e6388ca974a686e/src/Swashbuckle.AspNetCore.SwaggerGen/SchemaGenerator/SchemaGeneratorOptions.cs#L44
      private string DefaultSchemaIdSelector(Type modelType)
      {
          if (!modelType.IsConstructedGenericType) return modelType.Name.Replace("[]", "Array");

          var prefix = modelType.GetGenericArguments()
              .Select(genericArg => DefaultSchemaIdSelector(genericArg))
              .Aggregate((previous, current) => previous + current);

          return prefix + modelType.Name.Split('`').First();
      }

      public string GetSchemaId(Type modelType)
      {
          string id = DefaultSchemaIdSelector(modelType);

          if (!_schemaNameRepetition.ContainsKey(id))
              _schemaNameRepetition.Add(id, 0);

          int count = _schemaNameRepetition[id] + 1;
          _schemaNameRepetition[id] = count;

          return $"{id}{(count > 1 ? count.ToString() : "")}";
      }
  }

Usage looks like this:

services.AddSwaggerGen(options =>
{
    var schemaHelper = new SwashbuckleSchemaHelper();
    options.CustomSchemaIds(type => schemaHelper.GetSchemaId(type));
});

More details here:

https://blog.johnnyreilly.com/2022/08/31/swashbuckle-schemaid-already-used

1

The best way as far as I see to handle this is to provide CustomSchemaIds method, however this means that the default schema selector will no longer run which means you lose support for generics.

The best way I found to deal with this was to simply copy the implementation of DefaultSchemaIdSelector, then run that before/after my custom logic (which adds a prefix to schemaId based on namespace).

DefaultSchemaIdSelector:

private static string DefaultSchemaIdSelector(Type modelType)
{
    if (!modelType.IsConstructedGenericType)
    {
        return modelType.Name.Replace("[]", "Array");
    }

    var prefix = modelType.GetGenericArguments()
        .Select(DefaultSchemaIdSelector)
        .Aggregate((previous, current) => previous + current);

    return $"{prefix}{modelType.Name.Split('`').First()}";
}

Then in SwaggerGen:

c.CustomSchemaIds(type =>
{
    var schemaId = DefaultSchemaIdSelector(type);

    // Some namespace checking
    var prefix = ".....";

    return $"{prefix}.{schemaId}";
});

Older answer: (might still be relevant)

After doing some research it seems like the way to go is to use the FriendlyId extension method to generate custom schema id's... but I couldn't find that method in any of the recent versions of Swashbuckle/Swagger so I just ended up copying the implementation from the old Swashbuckle.Core package to my project and refactoring it a bit:

public static class TypeExtensions
{
    public static string FriendlyId(this Type type, bool fullyQualified = false)
    {
        var typeName = fullyQualified
            ? type.FullNameSansTypeParameters().Replace("+", ".")
            : type.Name;

        if (!type.IsGenericType)
        {
            return typeName;
        }

        var genericArgumentIds = type.GetGenericArguments()
            .Select(t => FriendlyId(t, fullyQualified))
            .ToArray();

        return new StringBuilder(typeName)
            .Replace($"`{genericArgumentIds.Length}", string.Empty)
            .Append($"[{string.Join(",", genericArgumentIds).TrimEnd(',')}]")
            .ToString();
    }

    public static string FullNameSansTypeParameters(this Type type)
    {
        var fullName = type.FullName;

        if (string.IsNullOrEmpty(fullName))
        {
            fullName = type.Name;
        }

        var chopIndex = fullName.IndexOf("[[", StringComparison.Ordinal);
        return (chopIndex == -1) ? fullName : fullName[..chopIndex];
    }
}

Usage in AddSwaggerGen method:

c.CustomSchemaIds(t => t.FriendlyId(true));
-1

Here is my version of SwashbuckleSchemaHelper

public static class SwashbuckleSchemaHelper
{
    public static string GetSchemaId(Type type)
    {
        var sb = new StringBuilder();

        sb.Append(type.Namespace);
        sb.Append('.');
        sb.Append(type.Name);

        if (type.IsGenericType)
        {
            sb.Append('<');

            var arguments = type.GenericTypeArguments
                .Select(GetSchemaId)
                .ToArray();

            sb.Append(string.Join(',', arguments));

            sb.Append('>');
        }

        return sb.ToString();
    }
}
1
  • As it’s currently written, your answer is unclear. Please edit to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers in the help center.
    – Community Bot
    Commented Jan 26, 2023 at 1:29

Not the answer you're looking for? Browse other questions tagged or ask your own question.