From 4072dadab304b53cb028d8eccff138e696e1970b Mon Sep 17 00:00:00 2001 From: Dominik Baran Date: Fri, 31 May 2024 16:53:54 +0200 Subject: [PATCH] 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 +{ +}