diff --git a/src/SoapCore.Tests/Wsdl/Services/IShouldSerializeTypeMemberService.cs b/src/SoapCore.Tests/Wsdl/Services/IShouldSerializeTypeMemberService.cs new file mode 100644 index 00000000..97045c71 --- /dev/null +++ b/src/SoapCore.Tests/Wsdl/Services/IShouldSerializeTypeMemberService.cs @@ -0,0 +1,20 @@ +using System; +using System.ServiceModel; + +namespace SoapCore.Tests.Wsdl.Services +{ + [ServiceContract] + public interface IShouldSerializeTypeMemberService + { + [OperationContract] + TypeWithShouldSerializeMember Method(); + } + + public class ShouldSerializeTypeMemberService : IShouldSerializeTypeMemberService + { + public TypeWithShouldSerializeMember Method() + { + throw new NotImplementedException(); + } + } +} diff --git a/src/SoapCore.Tests/Wsdl/Services/TypeWithShouldSerializeMember.cs b/src/SoapCore.Tests/Wsdl/Services/TypeWithShouldSerializeMember.cs new file mode 100644 index 00000000..fc509165 --- /dev/null +++ b/src/SoapCore.Tests/Wsdl/Services/TypeWithShouldSerializeMember.cs @@ -0,0 +1,14 @@ +namespace SoapCore.Tests.Wsdl.Services +{ + public class TypeWithShouldSerializeMember + { + public int IntProperty { get; set; } + + public int? OptionalIntProperty { get; set; } + + public bool ShouldSerializeOptionalIntProperty() + { + return OptionalIntProperty.HasValue; + } + } +} diff --git a/src/SoapCore.Tests/Wsdl/WsdlTests.cs b/src/SoapCore.Tests/Wsdl/WsdlTests.cs index dcf7ea3b..67cccb0b 100644 --- a/src/SoapCore.Tests/Wsdl/WsdlTests.cs +++ b/src/SoapCore.Tests/Wsdl/WsdlTests.cs @@ -897,6 +897,29 @@ public async Task CheckXmlAttributeSerialization(SoapSerializer soapSerializer) Assert.IsNull(optionalIntAttribute.Attribute("use")); } + [DataTestMethod] + [DataRow(SoapSerializer.XmlSerializer)] + public async Task CheckShouldSerializeMemberSerialization(SoapSerializer soapSerializer) + { + var wsdl = await GetWsdlFromMetaBodyWriter(soapSerializer); + Assert.IsNotNull(wsdl); + + var root = XElement.Parse(wsdl); + + var shouldSerializeType = GetElements(root, _xmlSchema + "complexType").SingleOrDefault(a => a.Attribute("name")?.Value == "TypeWithShouldSerializeMember"); + Assert.IsNotNull(shouldSerializeType); + + // verify that value types (such as int) have use="required" attribute + var intElement = GetElements(shouldSerializeType, _xmlSchema + "element").SingleOrDefault(a => a.Attribute("name")?.Value == "IntProperty"); + Assert.IsTrue(intElement.Attribute("minOccurs").Value == "1"); + Assert.IsTrue(intElement.Attribute("maxOccurs").Value == "1"); + + // verify that if a value type has a ShouldSerialize*() method, it is not marked as required + var optionalIntAttribute = GetElements(shouldSerializeType, _xmlSchema + "element").SingleOrDefault(a => a.Attribute("name")?.Value == "OptionalIntProperty"); + Assert.IsTrue(optionalIntAttribute.Attribute("minOccurs").Value == "0"); + Assert.IsTrue(optionalIntAttribute.Attribute("maxOccurs").Value == "1"); + } + [DataTestMethod] [DataRow(SoapSerializer.XmlSerializer)] public async Task CheckSoapHeaderTypes(SoapSerializer soapSerializer) diff --git a/src/SoapCore/Meta/MetaBodyWriter.cs b/src/SoapCore/Meta/MetaBodyWriter.cs index 845ba20c..572e99ae 100644 --- a/src/SoapCore/Meta/MetaBodyWriter.cs +++ b/src/SoapCore/Meta/MetaBodyWriter.cs @@ -1014,7 +1014,7 @@ private void AddSchemaTypePropertyOrField(XmlDictionaryWriter writer, MemberInfo bool isOptional = member.HasShouldSerializeMethod(parentTypeToBuild); - AddSchemaType(writer, toBuild, name, isAttribute: true, isUnqualified: isUnqualified, isOptionalAttribute: isOptional); + AddSchemaType(writer, toBuild, name, isAttribute: true, isUnqualified: isUnqualified, isOptional: isOptional); } else if (messageBodyMemberAttribute != null) { @@ -1045,7 +1045,9 @@ private void AddSchemaTypePropertyOrField(XmlDictionaryWriter writer, MemberInfo var hasSpecifiedBoolean = member.DeclaringType.GetProperties() .Any(p => p.Name == $"{member.Name}Specified" && p.PropertyType == typeof(bool) && p.GetCustomAttribute() != null); - AddSchemaType(writer, toBuild, elementNameFromAttribute ?? member.Name ?? parentTypeToBuild.ChildElementName, isArray: createListWithoutProxyType, isListWithoutWrapper: createListWithoutProxyType, isUnqualified: isUnqualified, defaultValue: defaultValue, hasSpecifiedBoolean: hasSpecifiedBoolean); + bool isOptional = member.HasShouldSerializeMethod(parentTypeToBuild); + + AddSchemaType(writer, toBuild, elementNameFromAttribute ?? member.Name ?? parentTypeToBuild.ChildElementName, isArray: createListWithoutProxyType, isListWithoutWrapper: createListWithoutProxyType, isUnqualified: isUnqualified, defaultValue: defaultValue, hasSpecifiedBoolean: hasSpecifiedBoolean, isOptional: isOptional); } } @@ -1065,7 +1067,7 @@ private void AddSchemaType(XmlDictionaryWriter writer, Type type, string name, b AddSchemaType(writer, new TypeToBuild(type), name, isArray, @namespace, isAttribute, isUnqualified: isUnqualified); } - private void AddSchemaType(XmlDictionaryWriter writer, TypeToBuild toBuild, string name, bool isArray = false, string @namespace = null, bool isAttribute = false, bool isListWithoutWrapper = false, bool isUnqualified = false, string defaultValue = null, bool isOptionalAttribute = false, bool hasSpecifiedBoolean = false) + private void AddSchemaType(XmlDictionaryWriter writer, TypeToBuild toBuild, string name, bool isArray = false, string @namespace = null, bool isAttribute = false, bool isListWithoutWrapper = false, bool isUnqualified = false, string defaultValue = null, bool isOptional = false, bool hasSpecifiedBoolean = false) { var type = toBuild.Type; @@ -1165,7 +1167,7 @@ private void AddSchemaType(XmlDictionaryWriter writer, TypeToBuild toBuild, stri } else { - writer.WriteAttributeString("minOccurs", type.IsValueType && defaultValue == null && !hasSpecifiedBoolean ? "1" : "0"); + writer.WriteAttributeString("minOccurs", type.IsValueType && defaultValue == null && !hasSpecifiedBoolean && !isOptional ? "1" : "0"); writer.WriteAttributeString("maxOccurs", "1"); if (defaultValue != null) { @@ -1189,7 +1191,7 @@ private void AddSchemaType(XmlDictionaryWriter writer, TypeToBuild toBuild, stri writer.WriteAttributeString("type", $"{_xmlNamespaceLookup.LookupPrefix(xsTypename.Namespace)}:{xsTypename.Name}"); } - if (isAttribute && typeInfo.IsValueType && !isOptionalAttribute) + if (isAttribute && typeInfo.IsValueType && !isOptional) { writer.WriteAttributeString("use", "required"); }