diff --git a/src/Exceptionless.Web/Utility/OpenApi/RequiredPropertySchemaTransformer.cs b/src/Exceptionless.Web/Utility/OpenApi/RequiredPropertySchemaTransformer.cs
index 13fa0bd7e7..2bf798f302 100644
--- a/src/Exceptionless.Web/Utility/OpenApi/RequiredPropertySchemaTransformer.cs
+++ b/src/Exceptionless.Web/Utility/OpenApi/RequiredPropertySchemaTransformer.cs
@@ -5,23 +5,24 @@
namespace Exceptionless.Web.Utility.OpenApi;
///
-/// Schema transformer that marks non-nullable properties as required in OpenAPI schemas.
+/// Schema transformer that marks required properties in OpenAPI schemas based on C# required modifiers and value types.
///
///
///
/// Why this exists: Microsoft.AspNetCore.OpenApi doesn't consistently detect
-/// required properties from C# nullability annotations. This causes all properties to
-/// become optional in generated schemas, even when they're non-nullable in the C# model.
+/// required properties from C# modifiers. This transformer ensures properties are marked as required
+/// when appropriate for the OpenAPI schema.
///
///
-/// This transformer inspects C# nullability context and marks properties as required when:
+/// This transformer marks properties as required when:
///
+/// - The property has the required modifier (C# 11+)
/// - The property type is a non-nullable value type (e.g., int, bool, DateTime)
-/// - The property type is a non-nullable reference type in a nullable-enabled context
///
///
///
-/// The [Required] attribute is also respected for explicit marking.
+/// Non-nullable reference types are NOT marked as required unless they have the explicit required modifier.
+/// This correctly handles properties with default initializers (e.g., public MyClass Prop { get; init; } = new();).
///
///
/// This transformer resolves property names using the effective JSON property name
@@ -82,6 +83,12 @@ private static bool IsPropertyRequired(PropertyInfo property, NullabilityInfoCon
{
var propertyType = property.PropertyType;
+ // Check if the property is marked with the 'required' modifier (C# 11+)
+ // This takes precedence over other heuristics
+ var requiredMemberAttribute = property.GetCustomAttribute();
+ if (requiredMemberAttribute is not null)
+ return true;
+
// Non-nullable value types are always required (except when wrapped in Nullable)
if (propertyType.IsValueType)
{
@@ -89,16 +96,13 @@ private static bool IsPropertyRequired(PropertyInfo property, NullabilityInfoCon
return Nullable.GetUnderlyingType(propertyType) is null;
}
- // For reference types, check nullability annotations
- try
- {
- var nullabilityInfo = nullabilityContext.Create(property);
- return nullabilityInfo.WriteState == NullabilityState.NotNull;
- }
- catch
- {
- // If we can't determine nullability, default to not required
- return false;
- }
+ // For reference types with default initializers (e.g., "= new()"), we should NOT mark them as required
+ // since they can be omitted during construction. However, we can't reliably detect initializers via reflection.
+ // Instead, we only mark reference types as required if they have the 'required' modifier (checked above).
+ // This means non-nullable reference types without 'required' are treated as optional.
+
+ // For backwards compatibility and to match expected behavior, we do NOT mark non-nullable
+ // reference types as required unless they have the explicit 'required' modifier.
+ return false;
}
}