diff --git a/XmlSchemaClassGenerator.Tests/XmlTests.cs b/XmlSchemaClassGenerator.Tests/XmlTests.cs
index 33549254..21dd017f 100644
--- a/XmlSchemaClassGenerator.Tests/XmlTests.cs
+++ b/XmlSchemaClassGenerator.Tests/XmlTests.cs
@@ -1100,6 +1100,89 @@ public void ChoiceMembersAreNullable()
Assert.Contains("Opt4Specified", content);
}
+ [Fact]
+ public void NestedElementInChoiceIsNullable()
+ {
+ // Because nullability isn't directly exposed in the generated C#, we use "XXXSpecified" on a value type
+ // as a proxy.
+ const string xsd = @"
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+";
+
+ var generator = new Generator
+ {
+ NamespaceProvider = new NamespaceProvider
+ {
+ GenerateNamespace = key => "Test"
+ }
+ };
+ var contents = ConvertXml(nameof(NestedElementInChoiceIsNullable), xsd, generator);
+ var content = Assert.Single(contents);
+
+ Assert.Contains("ElementASpecified", content);
+ Assert.Contains("ElementBSpecified", content);
+ }
+
+ [Fact]
+ public void OnlyFirstElementOfNestedElementsIsForcedToNullableInChoice()
+ {
+ // Because nullability isn't directly exposed in the generated C#, we use the "RequiredAttribute"
+ // as a proxy.
+ const string xsd = @"
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+";
+
+ var generator = new Generator
+ {
+ NamespaceProvider = new NamespaceProvider
+ {
+ GenerateNamespace = key => "Test"
+ }
+ };
+ var contents = ConvertXml(nameof(OnlyFirstElementOfNestedElementsIsForcedToNullableInChoice), xsd, generator).ToArray();
+ var assembly = Compiler.Compile(nameof(OnlyFirstElementOfNestedElementsIsForcedToNullableInChoice), contents);
+
+ var elementWithChildProperty = assembly.GetType("Test.Root")?.GetProperty("ElementWithChild");
+ var nestedChildProperty = assembly.GetType("Test.RootElementWithChild")?.GetProperty("NestedChild");
+ Assert.NotNull(elementWithChildProperty);
+ Assert.NotNull(nestedChildProperty);
+
+ Type requiredType = typeof(System.ComponentModel.DataAnnotations.RequiredAttribute);
+ bool elementWithChildIsRequired = Attribute.GetCustomAttribute(elementWithChildProperty, requiredType) != null;
+ bool nestedChildIsRequired = Attribute.GetCustomAttribute(nestedChildProperty, requiredType) != null;
+ Assert.False(elementWithChildIsRequired);
+ Assert.True(nestedChildIsRequired);
+ }
+
[Fact]
public void AssemblyVisibleIsInternalClass()
{
diff --git a/XmlSchemaClassGenerator/ModelBuilder.cs b/XmlSchemaClassGenerator/ModelBuilder.cs
index cd87db16..d4d7c2aa 100644
--- a/XmlSchemaClassGenerator/ModelBuilder.cs
+++ b/XmlSchemaClassGenerator/ModelBuilder.cs
@@ -866,7 +866,7 @@ private PropertyModel PropertyFromAttribute(TypeModel owningTypeModel, XmlSchema
IsRequired = attribute.Use == XmlSchemaUse.Required
};
- property.SetFromNode(originalName, () => attribute.Use != XmlSchemaUse.Optional, attribute);
+ property.SetFromNode(originalName, attribute.Use != XmlSchemaUse.Optional, attribute);
property.SetSchemaNameAndNamespace(owningTypeModel, attribute);
property.Documentation.AddRange(GetDocumentation(attribute));
@@ -895,7 +895,7 @@ private IEnumerable CreatePropertiesForElements(Uri source, TypeM
UseDataTypeAttribute = false
};
property = new PropertyModel(_configuration, "Any", typeModel, owningTypeModel) { IsAny = true };
- property.SetFromParticles(particle, item);
+ property.SetFromParticles(particle, item, item.MinOccurs >= 1.0m && !IsNullableByChoice(item.XmlParent));
break;
case XmlSchemaGroupRef groupRef:
var group = Groups[groupRef.RefName];
@@ -932,6 +932,27 @@ private IEnumerable CreatePropertiesForElements(Uri source, TypeM
return properties;
}
+ private static bool IsNullableByChoice(XmlSchemaObject parent)
+ {
+ while (parent != null)
+ {
+ switch (parent)
+ {
+ case XmlSchemaChoice:
+ return true;
+ // Any ancestor element between the current item and the
+ // choice would already have been forced to nullable.
+ case XmlSchemaElement:
+ case XmlSchemaParticle p when p.MinOccurs < 1.0m:
+ return false;
+ default:
+ break;
+ }
+ parent = parent.Parent;
+ }
+ return false;
+ }
+
private PropertyModel PropertyFromElement(TypeModel owningTypeModel, XmlSchemaElementEx element, Particle particle, Particle item, Substitute substitute)
{
PropertyModel property;
@@ -946,8 +967,9 @@ private PropertyModel PropertyFromElement(TypeModel owningTypeModel, XmlSchemaEl
var typeModel = substitute?.Type ?? CreateTypeModel(GetQualifiedName(owningTypeModel, particle.XmlParticle, element), element.ElementSchemaType);
property = new PropertyModel(_configuration, name, typeModel, owningTypeModel) { IsNillable = element.IsNillable };
- property.SetFromParticles(particle, item);
- property.SetFromNode(originalName, () => item.MinOccurs >= 1.0m && item.XmlParent is not XmlSchemaChoice, element);
+ var isRequired = item.MinOccurs >= 1.0m && !IsNullableByChoice(item.XmlParent);
+ property.SetFromParticles(particle, item, isRequired);
+ property.SetFromNode(originalName, isRequired, element);
property.SetSchemaNameAndNamespace(owningTypeModel, effectiveElement);
if (property.IsArray && !_configuration.GenerateComplexTypesForCollections)
diff --git a/XmlSchemaClassGenerator/TypeModel.cs b/XmlSchemaClassGenerator/TypeModel.cs
index 13290093..583e1fd6 100644
--- a/XmlSchemaClassGenerator/TypeModel.cs
+++ b/XmlSchemaClassGenerator/TypeModel.cs
@@ -527,11 +527,11 @@ public PropertyModel(GeneratorConfiguration configuration, string name, TypeMode
OwningType = owningType;
}
- public void SetFromNode(string originalName, Func useFixedIfNoDefault, IXmlSchemaNode xs)
+ public void SetFromNode(string originalName, bool useFixedIfNoDefault, IXmlSchemaNode xs)
{
OriginalPropertyName = originalName;
- DefaultValue = xs.DefaultValue ?? (useFixedIfNoDefault() ? xs.FixedValue : null);
+ DefaultValue = xs.DefaultValue ?? (useFixedIfNoDefault ? xs.FixedValue : null);
FixedValue = xs.FixedValue;
Form = xs.Form switch
{
@@ -540,13 +540,13 @@ public void SetFromNode(string originalName, Func useFixedIfNoDefault, IXm
};
}
- public void SetFromParticles(Particle particle, Particle item)
+ public void SetFromParticles(Particle particle, Particle item, bool isRequired)
{
Particle = item;
XmlParticle = item.XmlParticle;
XmlParent = item.XmlParent;
- IsRequired = item.MinOccurs >= 1.0m && (item.XmlParent is not XmlSchemaChoice);
+ IsRequired = isRequired;
IsCollection = item.MaxOccurs > 1.0m || particle.MaxOccurs > 1.0m; // http://msdn.microsoft.com/en-us/library/vstudio/d3hx2s7e(v=vs.100).aspx
}
@@ -1230,9 +1230,21 @@ public override CodeTypeReference GetReferenceFor(NamespaceModel referencingName
{
var collectionType = forInit ? (Configuration.CollectionImplementationType ?? Configuration.CollectionType) : Configuration.CollectionType;
- type = collectionType.IsGenericType ? collectionType.MakeGenericType(type)
- : collectionType == typeof(Array) ? type.MakeArrayType()
- : collectionType;
+ if (collectionType.IsGenericType)
+ {
+ type = collectionType.MakeGenericType(type);
+ }
+ else
+ {
+ if (collectionType == typeof(Array))
+ {
+ type = type.MakeArrayType();
+ }
+ else
+ {
+ type = collectionType;
+ }
+ }
}
return CodeUtilities.CreateTypeReference(type, Configuration);