I am trying to generate an OpenAPI 3 (OAS3) document for an ASP.NET Core 6 minimal web API using the Swashbuckle.AspNetCore
NuGet. Everything is in place with document and schema generation; however, I have one base type for representing entities in the system and many inheritors that define additional entity-specific properties for business logic. For reasons out of scope of discussion, this base entity class implements IDictionary<,>
and removing this interface is not possible.
The problem is thus: as I understand the source code for the data contract resolver during OAS3 schema generation inside Swashbuckle, a type implementing C# Dictionary types will use a Dictionary contract resolver which ignores instance properties and thus generates an empty object schema which is thrown away. I want to override this behavior so entity properties are correctly documented in the schema.
The only solution I have so far found is to remove the IDictionary<,>
interface from the base entity class after which the schema generation succeeds and all instance properties are documented in the document schema bucket, but this has an unfortunate side effect of breaking nearly all areas of the system outside of the OAS3 document generation which is unworkable.
I have been looking for a way to inform the data contract resolver about which resolver to use for the entity types, but since the contract resolver is inside Swashbuckle, I would have to replace their version with a completely custom resolver that I replace in the DI container.
The simplest solution ended up being implementing my own version of the ISerializerDataContractResolver
based on the version provided by the Swashbuckle NuGet and replacing that in the DI container. Follow along to replicate my solution:
Implement ISerializerDataContractResolver
. You can find the version Swashbuckle ships with on GitHub here. If you're using Newtonsoft instead of System.Text.Json, you'll need to build from the Newtonsoft version which is in the same Github repo. The main portion you need to customize is the method DataContract GetDataContractForType(Type)
. You will need to wrap the calls to IsSupportedDictionary(...)
and IsSupportedCollection(...)
with an if
check that matches your dictionary-inheriting types you wish to treat as a regular object
instead. That's all that was needed for my use case. Yours may be more complicated and require more modification.
After calling builder.Services.AddSwaggerGen(...)
, swap your implementation in:
// using Microsoft.Extensions.DependencyInjection.Extensions;
builder.Services.Replace(ServiceDescriptor.Transient(
YourSerializerDataContractResolver.Factory));
I created a static factory method on my implementation, again based on the implementation Swashbuckle provides:
// using Microsoft.AspNetCore.Mvc;
// using Microsoft.Extensions.Options;
// using System.Text.Json;
public static ISerializerDataContractResolver Factory(IServiceProvider services)
{
#if !NETSTANDARD2_0
JsonSerializerOptions serializerOptions = services.GetService<IOptions<JsonOptions>>()?.Value?.JsonSerializerOptions
?? new JsonSerializerOptions();
#else
JsonSerializerOptions serializerOptions = new JsonSerializerOptions();
#endif
return new YourJsonSerializerDataContractResolver(serializerOptions);
}
This factory method is not required, but makes the application startup more concise since it moves the implementation factory code to a separate file.