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 b6b81073..7202c7c8 100644 --- a/XmlSchemaClassGenerator/ModelBuilder.cs +++ b/XmlSchemaClassGenerator/ModelBuilder.cs @@ -917,6 +917,7 @@ private IEnumerable CreatePropertiesForElements(Uri source, TypeM } var effectiveElement = substitute?.Element ?? element; + var isNullableByChoice = IsNullableByChoice(item.XmlParent); var propertyName = _configuration.NamingProvider.ElementNameFromQualifiedName(effectiveElement.QualifiedName, effectiveElement); var originalPropertyName = propertyName; if (propertyName == typeModel.Name) @@ -934,9 +935,9 @@ private IEnumerable CreatePropertiesForElements(Uri source, TypeM OriginalPropertyName = originalPropertyName, Type = substitute?.Type ?? CreateTypeModel(element.ElementSchemaType, elementQualifiedName), IsNillable = element.IsNillable, - IsNullable = item.MinOccurs < 1.0m || (item.XmlParent is XmlSchemaChoice), + IsNullable = item.MinOccurs < 1.0m || isNullableByChoice, IsCollection = item.MaxOccurs > 1.0m || particle.MaxOccurs > 1.0m, // http://msdn.microsoft.com/en-us/library/vstudio/d3hx2s7e(v=vs.100).aspx - DefaultValue = element.DefaultValue ?? ((item.MinOccurs >= 1.0m && item.XmlParent is not XmlSchemaChoice) ? element.FixedValue : null), + DefaultValue = element.DefaultValue ?? ((item.MinOccurs >= 1.0m && !isNullableByChoice) ? element.FixedValue : null), FixedValue = element.FixedValue, XmlNamespace = !string.IsNullOrEmpty(effectiveElement.QualifiedName.Namespace) && effectiveElement.QualifiedName.Namespace != typeModel.XmlSchemaName.Namespace ? effectiveElement.QualifiedName.Namespace : null, @@ -969,7 +970,7 @@ private IEnumerable CreatePropertiesForElements(Uri source, TypeM OwningType = typeModel, Name = "Any", Type = new SimpleModel(_configuration) { ValueType = (_configuration.UseXElementForAny ? typeof(XElement) : typeof(XmlElement)), UseDataTypeAttribute = false }, - IsNullable = item.MinOccurs < 1.0m || (item.XmlParent is XmlSchemaChoice), + IsNullable = item.MinOccurs < 1.0m || IsNullableByChoice(item.XmlParent), IsCollection = item.MaxOccurs > 1.0m || particle.MaxOccurs > 1.0m, // http://msdn.microsoft.com/en-us/library/vstudio/d3hx2s7e(v=vs.100).aspx IsAny = true, XmlParticle = item.XmlParticle, @@ -1020,6 +1021,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 NamespaceModel CreateNamespaceModel(Uri source, XmlQualifiedName qualifiedName) { NamespaceModel namespaceModel = null;