From 4072dadab304b53cb028d8eccff138e696e1970b Mon Sep 17 00:00:00 2001 From: Dominik Baran Date: Fri, 31 May 2024 16:53:54 +0200 Subject: [PATCH 1/3] feat(meta): add support for soap:header --- .../Wsdl/Services/IServiceWithSoapHeaders.cs | 35 +++++++++++ src/SoapCore.Tests/Wsdl/WsdlTests.cs | 26 ++++++++ src/SoapCore/Meta/MetaBodyWriter.cs | 59 +++++++++++++++++-- .../ServiceModel/OperationDescription.cs | 4 ++ src/SoapCore/ServiceModel/SoapHeader.cs | 8 +++ 5 files changed, 128 insertions(+), 4 deletions(-) create mode 100644 src/SoapCore.Tests/Wsdl/Services/IServiceWithSoapHeaders.cs create mode 100644 src/SoapCore/ServiceModel/SoapHeader.cs diff --git a/src/SoapCore.Tests/Wsdl/Services/IServiceWithSoapHeaders.cs b/src/SoapCore.Tests/Wsdl/Services/IServiceWithSoapHeaders.cs new file mode 100644 index 00000000..c842f796 --- /dev/null +++ b/src/SoapCore.Tests/Wsdl/Services/IServiceWithSoapHeaders.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.ServiceModel; +using System.Text; +using System.Threading.Tasks; +using System.Xml; +using System.Xml.Serialization; +using SoapCore.ServiceModel; +#pragma warning disable SA1401 // Fields should be private + +namespace SoapCore.Tests.Wsdl.Services; +[ServiceContract] +public interface IServiceWithSoapHeaders +{ + [OperationContract] + [AuthenticationContextSoapHeader] + public void Method(); +} + +public class ServiceWithSoapHeaders : IServiceWithSoapHeaders +{ + public void Method() + { + } +} + +public sealed class AuthenticationContextSoapHeader : SoapHeaderAttribute +{ + [XmlAnyAttribute] + public XmlAttribute[] XAttributes; + + public string OperatorCode { get; set; } + public string Password { get; set; } +} diff --git a/src/SoapCore.Tests/Wsdl/WsdlTests.cs b/src/SoapCore.Tests/Wsdl/WsdlTests.cs index 697a61c0..3b489dac 100644 --- a/src/SoapCore.Tests/Wsdl/WsdlTests.cs +++ b/src/SoapCore.Tests/Wsdl/WsdlTests.cs @@ -806,6 +806,32 @@ public async Task CheckXmlAttributeSerialization(SoapSerializer soapSerializer) Assert.IsNull(optionalIntAttribute.Attribute("use")); } + [DataTestMethod] + [DataRow(SoapSerializer.XmlSerializer)] + public async Task CheckSoapHeaderTypes(SoapSerializer soapSerializer) + { + var wsdl = await GetWsdlFromMetaBodyWriter(soapSerializer); + Trace.TraceInformation(wsdl); + + var root = XElement.Parse(wsdl); + var nm = Namespaces.CreateDefaultXmlNamespaceManager(false); + + var headerComplexType = root.XPathSelectElements("//xsd:complexType[@name='AuthenticationContextSoapHeader']", nm); + Assert.IsNotNull(headerComplexType); + + var headerComplexTypePassword = root.XPathSelectElements("//xsd:complexType[@name='AuthenticationContextSoapHeader']/xsd:sequence/xsd:element[@name='OperatorCode' and @type='xsd:string' and not(@nillable) and @minOccurs=0 and @maxOccurs=1]", nm); + Assert.IsNotNull(headerComplexTypePassword); + + var headerComplexTypeOperatorCode = root.XPathSelectElements("//xsd:complexType[@name='AuthenticationContextSoapHeader']/xsd:sequence/xsd:element[@name='Password' and @type='xsd:string' and not(@nillable) and @minOccurs=0 and @maxOccurs=1]", nm); + Assert.IsNotNull(headerComplexTypeOperatorCode); + + var anyAttribute = root.XPathSelectElement("//xsd:complexType[@name='AuthenticationContextSoapHeader']/xsd:anyAttribute", nm); + Assert.IsNotNull(anyAttribute); + + var headerElementOnOperation = root.XPathSelectElement("//wsdl:operation[@name='Method']/wsdl:input/soap:header[@message='tns:MethodAuthenticationContextSoapHeader' and @part='AuthenticationContextSoapHeader' and @use='literal']", nm); + Assert.IsNotNull(headerElementOnOperation); + } + [DataTestMethod] [DataRow(SoapSerializer.XmlSerializer)] [DataRow(SoapSerializer.DataContractSerializer)] diff --git a/src/SoapCore/Meta/MetaBodyWriter.cs b/src/SoapCore/Meta/MetaBodyWriter.cs index 6a480e17..1e7398be 100644 --- a/src/SoapCore/Meta/MetaBodyWriter.cs +++ b/src/SoapCore/Meta/MetaBodyWriter.cs @@ -299,6 +299,8 @@ private void AddTypes(XmlDictionaryWriter writer) writer.WriteAttributeString("elementFormDefault", "qualified"); writer.WriteAttributeString("targetNamespace", TargetNameSpace); + HashSet headerTypesWritten = new(); + foreach (var operation in _service.Operations) { bool hasWrittenOutParameters = false; @@ -442,6 +444,22 @@ private void AddTypes(XmlDictionaryWriter writer) writer.WriteAttributeString("type", "tns:" + faultTypeToBuild.TypeName); writer.WriteEndElement(); // element } + + if (operation.HeaderType != null && !headerTypesWritten.Contains(operation.HeaderType)) + { + var headerTypeToBuild = new TypeToBuild(operation.HeaderType); + + //enqueue the complex type itself + _complexTypeToBuild.Enqueue(headerTypeToBuild); + + //write element pendant for the header type + writer.WriteStartElement("element", Namespaces.XMLNS_XSD); + writer.WriteAttributeString("name", headerTypeToBuild.TypeName); + writer.WriteAttributeString("type", "tns:" + headerTypeToBuild.TypeName); + writer.WriteEndElement(); // element + + headerTypesWritten.Add(operation.HeaderType); + } } while (_complexTypeToBuild.Count > 0) @@ -595,6 +613,18 @@ private void AddMessage(XmlDictionaryWriter writer) writer.WriteEndElement(); // wsdl:message } + if (operation.HeaderType is not null) + { + var name = operation.HeaderType.Name; + writer.WriteStartElement("wsdl", "message", Namespaces.WSDL_NS); + writer.WriteAttributeString("name", $"{operation.Name}{name}"); + writer.WriteStartElement("wsdl", "part", Namespaces.WSDL_NS); + writer.WriteAttributeString("name", name); + writer.WriteAttributeString("element", "tns:" + name); + writer.WriteEndElement(); // wsdl:part + writer.WriteEndElement(); // wsdl:message + } + AddMessageFaults(writer, operation); } } @@ -688,8 +718,19 @@ private void AddBinding(XmlDictionaryWriter writer) writer.WriteStartElement(soap, "body", soapNamespace); writer.WriteAttributeString("use", "literal"); writer.WriteEndElement(); // soap:body + + if (operation.HeaderType != null) + { + writer.WriteStartElement(soap, "header", soapNamespace); + writer.WriteAttributeString("message", $"tns:{operation.Name}{operation.HeaderType.Name}"); + writer.WriteAttributeString("part", operation.HeaderType.Name); + writer.WriteAttributeString("use", "literal"); + writer.WriteEndElement(); + } + writer.WriteEndElement(); // wsdl:input + if (!operation.IsOneWay) { writer.WriteStartElement("wsdl", "output", Namespaces.WSDL_NS); @@ -752,7 +793,9 @@ private bool HasBaseType(Type type) var baseType = type.GetTypeInfo().BaseType; - return !isArrayType && !type.IsEnum && !type.IsPrimitive && !type.IsValueType && baseType != null && !baseType.Name.Equals("Object"); + return !isArrayType && !type.IsEnum && !type.IsPrimitive && !type.IsValueType && baseType != null + && !baseType.Name.Equals("Object") + && type.BaseType?.BaseType?.FullName?.Equals("System.Attribute") != true; } private void AddSchemaComplexType(XmlDictionaryWriter writer, TypeToBuild toBuild) @@ -803,9 +846,10 @@ private void AddSchemaComplexType(XmlDictionaryWriter writer, TypeToBuild toBuil if (!isWrappedBodyType) { var propertyOrFieldMembers = toBuildBodyType.GetPropertyOrFieldMembers() - .Where(mi => !mi.IsIgnored() && mi.DeclaringType == toBuildType).ToList(); + .Where(mi => !mi.IsIgnored() && mi.DeclaringType == toBuildType) + .ToList(); - var elements = propertyOrFieldMembers.Where(t => !t.IsAttribute()).ToList(); + var elements = propertyOrFieldMembers.Where(t => !t.IsAttribute() && t.GetCustomAttribute() == null).ToList(); if (elements.Any()) { writer.WriteStartElement("sequence", Namespaces.XMLNS_XSD); @@ -817,11 +861,18 @@ private void AddSchemaComplexType(XmlDictionaryWriter writer, TypeToBuild toBuil writer.WriteEndElement(); // sequence } - var attributes = propertyOrFieldMembers.Where(t => t.IsAttribute()); + var attributes = propertyOrFieldMembers.Where(t => t.IsAttribute() && t.GetCustomAttribute() == null); foreach (var attribute in attributes) { AddSchemaTypePropertyOrField(writer, attribute, toBuild); } + + var anyAttribute = propertyOrFieldMembers.FirstOrDefault(t => t.GetCustomAttribute() != null); + if (anyAttribute != null) + { + writer.WriteStartElement("anyAttribute", Namespaces.XMLNS_XSD); + writer.WriteEndElement(); + } } else { diff --git a/src/SoapCore/ServiceModel/OperationDescription.cs b/src/SoapCore/ServiceModel/OperationDescription.cs index 5af21dd8..7e228d84 100644 --- a/src/SoapCore/ServiceModel/OperationDescription.cs +++ b/src/SoapCore/ServiceModel/OperationDescription.cs @@ -82,6 +82,9 @@ public OperationDescription(ContractDescription contract, MethodInfo operationMe .ToArray(); ServiceKnownTypes = operationMethod.GetCustomAttributes(inherit: false); + + var soapHeader = operationMethod.GetCustomAttributes(inherit: false); + HeaderType = soapHeader.FirstOrDefault()?.GetType(); } public ContractDescription Contract { get; private set; } @@ -103,6 +106,7 @@ public OperationDescription(ContractDescription contract, MethodInfo operationMe public IEnumerable ServiceKnownTypes { get; private set; } public IEnumerable ReturnChoices { get; private set; } public bool ReturnsChoice => ReturnChoices != null; + public Type HeaderType { get; set; } public IEnumerable GetServiceKnownTypesHierarchy() { diff --git a/src/SoapCore/ServiceModel/SoapHeader.cs b/src/SoapCore/ServiceModel/SoapHeader.cs new file mode 100644 index 00000000..98b1e249 --- /dev/null +++ b/src/SoapCore/ServiceModel/SoapHeader.cs @@ -0,0 +1,8 @@ +using System; + +namespace SoapCore.ServiceModel; + +[AttributeUsage(AttributeTargets.Method)] +public class SoapHeaderAttribute : Attribute +{ +} From ac579a36a49920f95c5360904ae6f28be0e8f63e Mon Sep 17 00:00:00 2001 From: Sergey Navozenko Date: Tue, 9 Jul 2024 20:08:55 +0300 Subject: [PATCH 2/3] Do not import target namespace --- .../Wsdl/Services/ISystemImportService.cs | 20 ++++++++ src/SoapCore.Tests/Wsdl/WsdlTests.cs | 50 +++++++++++++++++++ src/SoapCore/Meta/MetaWCFBodyWriter.cs | 25 ++++++---- 3 files changed, 86 insertions(+), 9 deletions(-) create mode 100644 src/SoapCore.Tests/Wsdl/Services/ISystemImportService.cs diff --git a/src/SoapCore.Tests/Wsdl/Services/ISystemImportService.cs b/src/SoapCore.Tests/Wsdl/Services/ISystemImportService.cs new file mode 100644 index 00000000..b1473fe3 --- /dev/null +++ b/src/SoapCore.Tests/Wsdl/Services/ISystemImportService.cs @@ -0,0 +1,20 @@ +using System; +using System.ServiceModel; + +namespace SoapCore.Tests.Wsdl.Services +{ + [ServiceContract] + public interface ISystemImportService + { + [OperationContract] + ComplexType GetValue(); + } + + public class SystemImportService : ISystemImportService + { + public ComplexType GetValue() + { + throw new NotImplementedException(); + } + } +} diff --git a/src/SoapCore.Tests/Wsdl/WsdlTests.cs b/src/SoapCore.Tests/Wsdl/WsdlTests.cs index d77921be..403e70b2 100644 --- a/src/SoapCore.Tests/Wsdl/WsdlTests.cs +++ b/src/SoapCore.Tests/Wsdl/WsdlTests.cs @@ -344,6 +344,56 @@ public void CheckSystemTypes() Assert.AreEqual(element.Attributes["type"]?.Value, "xs:anyURI"); } + [TestMethod] + public void CheckSystemAndArraysImport() + { + StartService(typeof(SystemImportService)); + var wsdl = GetWsdl(); + StopServer(); + + var root = new XmlDocument(); + root.LoadXml(wsdl); + + var nsmgr = new XmlNamespaceManager(root.NameTable); + nsmgr.AddNamespace("wsdl", "http://schemas.xmlsoap.org/wsdl/"); + nsmgr.AddNamespace("xs", "http://www.w3.org/2001/XMLSchema"); + + var customNamespace = "http://schemas.datacontract.org/2004/07/SoapCore.Tests.Wsdl.Services"; + var systemNamespace = "http://schemas.datacontract.org/2004/07/System"; + var arraysNamespace = "http://schemas.microsoft.com/2003/10/Serialization/Arrays"; + + // Schema with custom target namespace + var schemaPath = $"/wsdl:definitions/wsdl:types/xs:schema[@targetNamespace='{customNamespace}']"; + var schemaElement = root.SelectSingleNode(schemaPath, nsmgr); + var systemImportElement = schemaElement.SelectSingleNode($"xs:import[@namespace='{systemNamespace}']", nsmgr); + var arraysImportElement = schemaElement.SelectSingleNode($"xs:import[@namespace='{arraysNamespace}']", nsmgr); + + Assert.IsNotNull(schemaElement); + Assert.IsNotNull(systemImportElement); + Assert.IsNotNull(arraysImportElement); + + // Schema with system target namespace + schemaPath = + $"/wsdl:definitions/wsdl:types" + + $"/xs:schema[@targetNamespace='{systemNamespace}']" + + $"/xs:complexType[@name='ArrayOfByte']" + + $"/.."; + + schemaElement = root.SelectSingleNode(schemaPath, nsmgr); + systemImportElement = schemaElement.SelectSingleNode($"xs:import[@namespace='{systemNamespace}']", nsmgr); + arraysImportElement = schemaElement.SelectSingleNode($"xs:import[@namespace='{arraysNamespace}']", nsmgr); + + Assert.IsNull(systemImportElement); + Assert.IsNotNull(arraysImportElement); + + // Schema with arrays target namespace + schemaPath = $"/wsdl:definitions/wsdl:types/xs:schema[@targetNamespace='{arraysNamespace}']"; + schemaElement = root.SelectSingleNode(schemaPath, nsmgr); + arraysImportElement = schemaElement.SelectSingleNode($"xs:import[@namespace='{arraysNamespace}']", nsmgr); + + Assert.IsNull(arraysImportElement); + } + [TestMethod] public void CheckStreamDeclaration() { diff --git a/src/SoapCore/Meta/MetaWCFBodyWriter.cs b/src/SoapCore/Meta/MetaWCFBodyWriter.cs index d34e5264..2a0ed52f 100644 --- a/src/SoapCore/Meta/MetaWCFBodyWriter.cs +++ b/src/SoapCore/Meta/MetaWCFBodyWriter.cs @@ -661,23 +661,30 @@ private void AddComplexTypes(XmlDictionaryWriter writer) foreach (var types in groupedByNamespace.Distinct()) { + var targetNamespace = GetModelNamespace(types.Key); writer.WriteStartElement("xs", "schema", Namespaces.XMLNS_XSD); writer.WriteAttributeString("elementFormDefault", "qualified"); - writer.WriteAttributeString("targetNamespace", GetModelNamespace(types.Key)); + writer.WriteAttributeString("targetNamespace", targetNamespace); writer.WriteXmlnsAttribute("xs", Namespaces.XMLNS_XSD); - writer.WriteXmlnsAttribute("tns", GetModelNamespace(types.Key)); + writer.WriteXmlnsAttribute("tns", targetNamespace); writer.WriteXmlnsAttribute("ser", Namespaces.SERIALIZATION_NS); _namespaceCounter = 1; - _schemaNamespace = GetModelNamespace(types.Key); + _schemaNamespace = targetNamespace; - writer.WriteStartElement("xs", "import", Namespaces.XMLNS_XSD); - writer.WriteAttributeString("namespace", Namespaces.SYSTEM_NS); - writer.WriteEndElement(); + if (targetNamespace != Namespaces.SYSTEM_NS) + { + writer.WriteStartElement("xs", "import", Namespaces.XMLNS_XSD); + writer.WriteAttributeString("namespace", Namespaces.SYSTEM_NS); + writer.WriteEndElement(); + } - writer.WriteStartElement("xs", "import", Namespaces.XMLNS_XSD); - writer.WriteAttributeString("namespace", Namespaces.ARRAYS_NS); - writer.WriteEndElement(); + if (targetNamespace != Namespaces.ARRAYS_NS) + { + writer.WriteStartElement("xs", "import", Namespaces.XMLNS_XSD); + writer.WriteAttributeString("namespace", Namespaces.ARRAYS_NS); + writer.WriteEndElement(); + } foreach (var type in types.Value.Distinct(new TypesComparer(GetTypeName))) { From ffac338e64611546620b442e407f6c94609b1f0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20M=C3=BCller?= Date: Fri, 16 Aug 2024 15:42:32 +0200 Subject: [PATCH 3/3] Fix WSDL for list of anonymous type --- .../Wsdl/Services/ComplexTypeAnonymous.cs | 25 ++++++++++ .../Services/IComplexAnonymousListService.cs | 21 +++++++++ src/SoapCore.Tests/Wsdl/WsdlTests.cs | 47 +++++++++++++++++++ src/SoapCore/Meta/MetaBodyWriter.cs | 5 ++ 4 files changed, 98 insertions(+) create mode 100644 src/SoapCore.Tests/Wsdl/Services/ComplexTypeAnonymous.cs create mode 100644 src/SoapCore.Tests/Wsdl/Services/IComplexAnonymousListService.cs diff --git a/src/SoapCore.Tests/Wsdl/Services/ComplexTypeAnonymous.cs b/src/SoapCore.Tests/Wsdl/Services/ComplexTypeAnonymous.cs new file mode 100644 index 00000000..536c41e5 --- /dev/null +++ b/src/SoapCore.Tests/Wsdl/Services/ComplexTypeAnonymous.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Xml.Serialization; + +namespace SoapCore.Tests.Wsdl.Services +{ + [XmlType(AnonymousType = true)] + public class ComplexTypeAnonymous + { + public int IntProperty { get; set; } + [XmlElement(ElementName = "stringprop")] + public string StringProperty { get; set; } + [XmlElement(ElementName = "mybytes")] + public byte[] ByteArrayProperty { get; set; } + + public Guid MyGuid { get; set; } + + public List StringList { get; set; } + + public List IntList { get; set; } + } +} diff --git a/src/SoapCore.Tests/Wsdl/Services/IComplexAnonymousListService.cs b/src/SoapCore.Tests/Wsdl/Services/IComplexAnonymousListService.cs new file mode 100644 index 00000000..3190016a --- /dev/null +++ b/src/SoapCore.Tests/Wsdl/Services/IComplexAnonymousListService.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.ServiceModel; +using System.Text; +using System.Threading.Tasks; + +namespace SoapCore.Tests.Wsdl.Services +{ + [ServiceContract] + public interface IComplexAnonymousListService + { + [OperationContract] + List Test(); + } + + public class ComplexAnonymousListService : IComplexAnonymousListService + { + public List Test() => throw new NotImplementedException(); + } +} diff --git a/src/SoapCore.Tests/Wsdl/WsdlTests.cs b/src/SoapCore.Tests/Wsdl/WsdlTests.cs index d77921be..04e66aaa 100644 --- a/src/SoapCore.Tests/Wsdl/WsdlTests.cs +++ b/src/SoapCore.Tests/Wsdl/WsdlTests.cs @@ -1239,6 +1239,52 @@ public void CheckComplexBaseTypeServiceWsdl() Assert.IsNotNull(listDerivedType); } + [DataTestMethod] + [DataRow(SoapSerializer.XmlSerializer)] + public async Task CheckComplexAnonymousTypeListWsdl(SoapSerializer soapSerializer) + { + var wsdl = await GetWsdlFromMetaBodyWriter(soapSerializer); + Trace.TraceInformation(wsdl); + Assert.IsNotNull(wsdl); + + var root = XElement.Parse(wsdl); + + // Check complexType exists for xmlserializer meta + var testResultElement = GetElements(root, _xmlSchema + "element").SingleOrDefault(a => a.Attribute("type") != null && a.Attribute("name")?.Value.Equals("TestResult") == true); + Assert.IsNotNull(testResultElement); + + // Now check if we can match the array type up with it's declaration + var split = testResultElement.Attribute("type").Value.Split(':'); + var typeNamespace = testResultElement.GetNamespaceOfPrefix(split[0]); + + var matchingSchema = GetElements(root, _xmlSchema + "schema").Where(schema => schema.Attribute("targetNamespace")?.Value.Equals(typeNamespace.NamespaceName) == true); + Assert.IsTrue(matchingSchema.Count() > 0); + + var matched = false; + XElement matchingComplexType = null; + foreach (var schema in matchingSchema) + { + matchingComplexType = GetElements(schema, _xmlSchema + "complexType").SingleOrDefault(a => a.Attribute("name")?.Value.Equals(split[1]) == true); + if (matchingComplexType != null) + { + matched = true; + } + } + + Assert.IsTrue(matched); + + // The complex type is an array with a single element, which is an anonymous complex type + var arrayElement = matchingComplexType.Element(_xmlSchema + "sequence")?.Element(_xmlSchema + "element"); + Assert.IsNotNull(arrayElement); + + // The element needs a name and a complex type + var nameAttribute = arrayElement.Attribute("name"); + Assert.IsFalse(string.IsNullOrEmpty(nameAttribute.Value)); + + var arrayElementType = arrayElement.Element(_xmlSchema + "complexType"); + Assert.IsNotNull(arrayElementType); + } + [TestCleanup] public void StopServer() { @@ -1281,6 +1327,7 @@ private async Task GetWsdlFromMetaBodyWriter(SoapSerializer serialize var service = new ServiceDescription(typeof(T), false); var baseUrl = "http://tempuri.org/"; var xmlNamespaceManager = Namespaces.CreateDefaultXmlNamespaceManager(useMicrosoftGuid); + xmlNamespaceManager.AddNamespace("tns", service.GeneralContract.Namespace); var defaultBindingName = !string.IsNullOrWhiteSpace(bindingName) ? bindingName : "BasicHttpBinding"; var bodyWriter = serializer == SoapSerializer.DataContractSerializer ? new MetaWCFBodyWriter(service, baseUrl, defaultBindingName, false, new[] { new SoapBindingInfo(MessageVersion.None, bindingName, portName) }, new DefaultWsdlOperationNameGenerator()) as BodyWriter diff --git a/src/SoapCore/Meta/MetaBodyWriter.cs b/src/SoapCore/Meta/MetaBodyWriter.cs index 8e627186..ecc71a3d 100644 --- a/src/SoapCore/Meta/MetaBodyWriter.cs +++ b/src/SoapCore/Meta/MetaBodyWriter.cs @@ -1240,6 +1240,11 @@ private void AddSchemaType(XmlDictionaryWriter writer, TypeToBuild toBuild, stri } else if (toBuild.IsAnonumous) { + if (string.IsNullOrEmpty(name)) + { + name = typeName; + } + writer.WriteAttributeString("name", name); WriteQualification(writer, isUnqualified); AddSchemaComplexType(writer, newTypeToBuild);