diff --git a/Directory.Build.props b/Directory.Build.props
index ed12cd2634..589f3ffb95 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -37,7 +37,7 @@
snupkg
true
true
- 9.0.4
+ 9.1.0
false
diff --git a/src/Swashbuckle.AspNetCore.Newtonsoft/SchemaGenerator/NewtonsoftDataContractResolver.cs b/src/Swashbuckle.AspNetCore.Newtonsoft/SchemaGenerator/NewtonsoftDataContractResolver.cs
index 6132126b1c..15cfe03bd8 100644
--- a/src/Swashbuckle.AspNetCore.Newtonsoft/SchemaGenerator/NewtonsoftDataContractResolver.cs
+++ b/src/Swashbuckle.AspNetCore.Newtonsoft/SchemaGenerator/NewtonsoftDataContractResolver.cs
@@ -22,11 +22,13 @@ public DataContract GetDataContractForType(Type type)
jsonConverter: JsonConverterFunc);
}
- var jsonContract = _contractResolver.ResolveContract(effectiveType);
+ var jsonContract = _contractResolver.ResolveContract(type);
- if (jsonContract is JsonPrimitiveContract && !jsonContract.UnderlyingType.IsEnum)
+ var effectiveUnderlyingType = Nullable.GetUnderlyingType(jsonContract.UnderlyingType) ?? jsonContract.UnderlyingType;
+
+ if (jsonContract is JsonPrimitiveContract && !effectiveUnderlyingType.IsEnum)
{
- if (!PrimitiveTypesAndFormats.TryGetValue(jsonContract.UnderlyingType, out var primitiveTypeAndFormat))
+ if (!PrimitiveTypesAndFormats.TryGetValue(effectiveUnderlyingType, out var primitiveTypeAndFormat))
{
primitiveTypeAndFormat = Tuple.Create(DataType.String, (string)null);
}
@@ -38,16 +40,16 @@ public DataContract GetDataContractForType(Type type)
jsonConverter: JsonConverterFunc);
}
- if (jsonContract is JsonPrimitiveContract && jsonContract.UnderlyingType.IsEnum)
+ if (jsonContract is JsonPrimitiveContract && effectiveUnderlyingType.IsEnum)
{
- var enumValues = jsonContract.UnderlyingType.GetEnumValues();
+ var enumValues = effectiveUnderlyingType.GetEnumValues();
// Test to determine if the serializer will treat as string
var serializeAsString = (enumValues.Length > 0) && JsonConverterFunc(enumValues.GetValue(0)).StartsWith('\"');
var primitiveTypeAndFormat = serializeAsString
? PrimitiveTypesAndFormats[typeof(string)]
- : PrimitiveTypesAndFormats[jsonContract.UnderlyingType.GetEnumUnderlyingType()];
+ : PrimitiveTypesAndFormats[effectiveUnderlyingType.GetEnumUnderlyingType()];
return DataContract.ForPrimitive(
underlyingType: jsonContract.UnderlyingType,
diff --git a/src/Swashbuckle.AspNetCore.SwaggerGen/DependencyInjection/ConfigureSchemaGeneratorOptions.cs b/src/Swashbuckle.AspNetCore.SwaggerGen/DependencyInjection/ConfigureSchemaGeneratorOptions.cs
index 6017437bf7..b1c245c574 100644
--- a/src/Swashbuckle.AspNetCore.SwaggerGen/DependencyInjection/ConfigureSchemaGeneratorOptions.cs
+++ b/src/Swashbuckle.AspNetCore.SwaggerGen/DependencyInjection/ConfigureSchemaGeneratorOptions.cs
@@ -32,6 +32,7 @@ private static void DeepCopy(SchemaGeneratorOptions source, SchemaGeneratorOptio
target.DiscriminatorNameSelector = source.DiscriminatorNameSelector;
target.DiscriminatorValueSelector = source.DiscriminatorValueSelector;
target.UseAllOfToExtendReferenceSchemas = source.UseAllOfToExtendReferenceSchemas;
+ target.UseOneOfForNullableEnums = source.UseOneOfForNullableEnums;
target.SupportNonNullableReferenceTypes = source.SupportNonNullableReferenceTypes;
target.NonNullableReferenceTypesAsRequired = source.NonNullableReferenceTypesAsRequired;
target.SchemaFilters = [.. source.SchemaFilters];
diff --git a/src/Swashbuckle.AspNetCore.SwaggerGen/DependencyInjection/SwaggerGenOptionsExtensions.cs b/src/Swashbuckle.AspNetCore.SwaggerGen/DependencyInjection/SwaggerGenOptionsExtensions.cs
index c2df6a5b4a..78908bdb33 100644
--- a/src/Swashbuckle.AspNetCore.SwaggerGen/DependencyInjection/SwaggerGenOptionsExtensions.cs
+++ b/src/Swashbuckle.AspNetCore.SwaggerGen/DependencyInjection/SwaggerGenOptionsExtensions.cs
@@ -284,6 +284,15 @@ public static void UseAllOfToExtendReferenceSchemas(this SwaggerGenOptions swagg
swaggerGenOptions.SchemaGeneratorOptions.UseAllOfToExtendReferenceSchemas = true;
}
+ ///
+ /// Extend enumeration schemas using the oneOf construct to allow when referenced.
+ ///
+ ///
+ public static void UseOneOfForNullableEnums(this SwaggerGenOptions swaggerGenOptions)
+ {
+ swaggerGenOptions.SchemaGeneratorOptions.UseOneOfForNullableEnums = true;
+ }
+
///
/// Enable detection of non nullable reference types to set Nullable flag accordingly on schema properties
///
diff --git a/src/Swashbuckle.AspNetCore.SwaggerGen/PublicAPI/PublicAPI.Unshipped.txt b/src/Swashbuckle.AspNetCore.SwaggerGen/PublicAPI/PublicAPI.Unshipped.txt
index e69de29bb2..6a7560bb8a 100644
--- a/src/Swashbuckle.AspNetCore.SwaggerGen/PublicAPI/PublicAPI.Unshipped.txt
+++ b/src/Swashbuckle.AspNetCore.SwaggerGen/PublicAPI/PublicAPI.Unshipped.txt
@@ -0,0 +1,3 @@
+static Microsoft.Extensions.DependencyInjection.SwaggerGenOptionsExtensions.UseOneOfForNullableEnums(this Swashbuckle.AspNetCore.SwaggerGen.SwaggerGenOptions swaggerGenOptions) -> void
+Swashbuckle.AspNetCore.SwaggerGen.SchemaGeneratorOptions.UseOneOfForNullableEnums.get -> bool
+Swashbuckle.AspNetCore.SwaggerGen.SchemaGeneratorOptions.UseOneOfForNullableEnums.set -> void
diff --git a/src/Swashbuckle.AspNetCore.SwaggerGen/SchemaGenerator/JsonSerializerDataContractResolver.cs b/src/Swashbuckle.AspNetCore.SwaggerGen/SchemaGenerator/JsonSerializerDataContractResolver.cs
index 021a95c0eb..fd9d1ffaae 100644
--- a/src/Swashbuckle.AspNetCore.SwaggerGen/SchemaGenerator/JsonSerializerDataContractResolver.cs
+++ b/src/Swashbuckle.AspNetCore.SwaggerGen/SchemaGenerator/JsonSerializerDataContractResolver.cs
@@ -45,7 +45,7 @@ public DataContract GetDataContractForType(Type type)
primitiveTypeAndFormat = PrimitiveTypesAndFormats[exampleType];
return DataContract.ForPrimitive(
- underlyingType: effectiveType,
+ underlyingType: type,
dataType: primitiveTypeAndFormat.Item1,
dataFormat: primitiveTypeAndFormat.Item2,
jsonConverter: (value) => JsonConverterFunc(value, type));
diff --git a/src/Swashbuckle.AspNetCore.SwaggerGen/SchemaGenerator/SchemaGenerator.cs b/src/Swashbuckle.AspNetCore.SwaggerGen/SchemaGenerator/SchemaGenerator.cs
index f483d9134e..cae08b07a4 100644
--- a/src/Swashbuckle.AspNetCore.SwaggerGen/SchemaGenerator/SchemaGenerator.cs
+++ b/src/Swashbuckle.AspNetCore.SwaggerGen/SchemaGenerator/SchemaGenerator.cs
@@ -44,6 +44,18 @@ private OpenApiSchema GenerateSchemaForMember(
MemberInfo memberInfo,
DataProperty dataProperty = null)
{
+ if (dataProperty != null)
+ {
+ var customAttributes = memberInfo.GetInlineAndMetadataAttributes();
+
+ var requiredAttribute = customAttributes.OfType().FirstOrDefault();
+
+ if (!IsNullable(requiredAttribute, dataProperty, memberInfo))
+ {
+ modelType = Nullable.GetUnderlyingType(modelType) ?? modelType;
+ }
+ }
+
var dataContract = GetDataContractFor(modelType);
var schema = _generatorOptions.UseOneOfForPolymorphism && IsBaseTypeWithKnownTypesDefined(dataContract, out var knownTypesDataContracts)
@@ -129,6 +141,13 @@ private OpenApiSchema GenerateSchemaForParameter(
ParameterInfo parameterInfo,
ApiParameterRouteInfo routeInfo)
{
+ var customAttributes = parameterInfo.GetCustomAttributes();
+
+ if (customAttributes.OfType().Any())
+ {
+ modelType = Nullable.GetUnderlyingType(modelType) ?? modelType;
+ }
+
var dataContract = GetDataContractFor(modelType);
var schema = _generatorOptions.UseOneOfForPolymorphism && IsBaseTypeWithKnownTypesDefined(dataContract, out var knownTypesDataContracts)
@@ -143,8 +162,6 @@ private OpenApiSchema GenerateSchemaForParameter(
if (schema.Reference == null)
{
- var customAttributes = parameterInfo.GetCustomAttributes();
-
var defaultValue = parameterInfo.HasDefaultValue
? parameterInfo.DefaultValue
: customAttributes.OfType().FirstOrDefault()?.Value;
@@ -154,6 +171,11 @@ private OpenApiSchema GenerateSchemaForParameter(
schema.Default = GenerateDefaultValue(dataContract, modelType, defaultValue);
}
+ if (Nullable.GetUnderlyingType(modelType) is not null)
+ {
+ schema.Nullable = true;
+ }
+
schema.ApplyValidationAttributes(customAttributes);
if (routeInfo != null)
{
@@ -255,7 +277,7 @@ private OpenApiSchema GenerateConcreteSchema(DataContract dataContract, SchemaRe
case DataType.Number:
case DataType.String:
{
- schemaFactory = () => CreatePrimitiveSchema(dataContract);
+ schemaFactory = () => CreatePrimitiveSchema(dataContract, schemaRepository);
returnAsReference = dataContract.UnderlyingType.IsEnum && !_generatorOptions.UseInlineDefinitionsForEnums;
break;
}
@@ -301,16 +323,41 @@ private bool TryGetCustomTypeMapping(Type modelType, out Func sch
(modelType.IsConstructedGenericType && _generatorOptions.CustomTypeMappings.TryGetValue(modelType.GetGenericTypeDefinition(), out schemaFactory));
}
- private static OpenApiSchema CreatePrimitiveSchema(DataContract dataContract)
+ private OpenApiSchema CreatePrimitiveSchema(DataContract dataContract, SchemaRepository schemaRepository)
{
+ var underlyingType = Nullable.GetUnderlyingType(dataContract.UnderlyingType) ?? dataContract.UnderlyingType;
+
+ if (underlyingType.IsEnum && dataContract.UnderlyingType != underlyingType)
+ {
+ var enumDataContract = GetDataContractFor(underlyingType);
+
+ var enumSchema = GenerateConcreteSchema(enumDataContract, schemaRepository);
+
+ if (_generatorOptions.UseInlineDefinitionsForEnums)
+ {
+ enumSchema.Enum.Add(null);
+ return enumSchema;
+ }
+
+ if (_generatorOptions.UseOneOfForNullableEnums)
+ {
+ enumSchema.OneOf =
+ [
+ new OpenApiSchema { Reference = enumSchema.Reference },
+ new OpenApiSchema { Enum = [null] }
+ ];
+ enumSchema.Reference = null;
+ }
+
+ return enumSchema;
+ }
+
var schema = new OpenApiSchema
{
Type = FromDataType(dataContract.DataType),
Format = dataContract.DataFormat
};
- var underlyingType = dataContract.UnderlyingType;
-
if (underlyingType.IsEnum)
{
var enumValues = underlyingType.GetEnumValues().Cast