I’m sure this is rarely run into, but every once in a while, you might reuse the name of class between two namespaces. For example:
- YourCompany.CommonComponentLibary.SystemOperation
- YourCompany.ProjectXXXXX.SystemOperation
If both of those classes are then exposed as return types from an API endpoint, there is a chance that Swagger might throw an error that looks something like this:
The Swashbuckle team has run into this / thought about this and there is a function called CustomSchemaIds to handle it. The function’s usage looks a little like this (default implementation):
As best as I can tell the intention behind this function is to generate out the expected name for a given Type, completely agnostic of any external information.
Not using external information creates helps ensure the result should always be the same, because their is no “state of the system” information used to create unique names. This makes the names consistent despite processing order or updates to the underlying code base. So, it’s a good thing.
But … it can make for rather large names. Using the above code snippet as a example, the FullName is a unique name, but it contains a lot of information about how you internally generated the name. I’m not looking to have that information concealed for any security or risk purpose, it can just be hard to read in a json/yaml format or even in a Swagger UI display.
So, it might be easier to create a new Custom Schema Id which would try to stick with the default implementation, but alter it to return a longer name if conflicts occur. For example, what if the rules were:
- Use the default implementation of the Class Name (without Namespace) when possible.
- If the Class Name has already been used, then start prefixing Namespace names (from closest to the Class Name, back down to the root Namespace last).
An example of this might be:
- YourCompany.CommonComponentLibary.SystemOperation –> SystemOperation
- YourCompany.ProjectXXXXX.SystemOperation –> ProjectXXXXX.SystemOperation
But, to do that you would need to know about the current registered types (ie. external information).
Interestingly enough, the code which calls the CustomSchemaId/SchemaIdSelector function has access to a SchemaRepository class which contains exactly that information. But, it doesn’t pass the SchemaRepository into the function. The SchemaRespository was available within the code at the time of the SchemaIdSelector’s introduction (Swashbuckle.AspNetCore.SwaggerGen-5.0.0-rc2), so it could have been passed in. But, sometimes it’s just hard for see weird use cases like the one I’m describing.
There is a way to implement the use case I’m describing, by replicating the SchemaRepository within your own code. It doesn’t take a lot of effort, but it can feel like you’re doing something wrong. Here’s what that can look like:
As a sidenote, I think I’ll touch on is the large change that came with version 5.0.0-rc3. That version introduced/switch over to OpenApi 3.0 and the Microsoft.OpenApi libraries. It was a great change, but it also changed the way that the SchemaIdSelector worked. In the 5.0.0-rc2 version (the first version that introduced the selector), the selector would only be called once per type. In 5.0.0-rc3+, it started to be called on every type lookup. This means when you’re writing a custom selector, the selector needs to detect if a type has been selected before, and return the same value as it did previously.