Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(meta-wsdl): add support for soap:header #1063

Merged
merged 1 commit into from
Aug 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions src/SoapCore.Tests/Wsdl/Services/IServiceWithSoapHeaders.cs
Original file line number Diff line number Diff line change
@@ -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; }
}
26 changes: 26 additions & 0 deletions src/SoapCore.Tests/Wsdl/WsdlTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<ServiceWithSoapHeaders>(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)]
Expand Down
59 changes: 55 additions & 4 deletions src/SoapCore/Meta/MetaBodyWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
public class MetaBodyWriter : BodyWriter
{
private const string FaultSuffix = "Fault";
private static int _namespaceCounter = 1;

Check warning on line 23 in src/SoapCore/Meta/MetaBodyWriter.cs

View workflow job for this annotation

GitHub Actions / Analyze

The field 'MetaBodyWriter._namespaceCounter' is assigned but its value is never used

private readonly ServiceDescription _service;
private readonly string _baseUrl;
Expand Down Expand Up @@ -299,6 +299,8 @@
writer.WriteAttributeString("elementFormDefault", "qualified");
writer.WriteAttributeString("targetNamespace", TargetNameSpace);

HashSet<Type> headerTypesWritten = new();

foreach (var operation in _service.Operations)
{
bool hasWrittenOutParameters = false;
Expand Down Expand Up @@ -442,6 +444,22 @@
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)
Expand Down Expand Up @@ -595,6 +613,18 @@
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);
}
}
Expand Down Expand Up @@ -688,8 +718,19 @@
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);
Expand Down Expand Up @@ -752,7 +793,9 @@

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)
Expand Down Expand Up @@ -803,9 +846,10 @@
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<XmlAnyAttributeAttribute>() == null).ToList();
if (elements.Any())
{
writer.WriteStartElement("sequence", Namespaces.XMLNS_XSD);
Expand All @@ -817,11 +861,18 @@
writer.WriteEndElement(); // sequence
}

var attributes = propertyOrFieldMembers.Where(t => t.IsAttribute());
var attributes = propertyOrFieldMembers.Where(t => t.IsAttribute() && t.GetCustomAttribute<XmlAnyAttributeAttribute>() == null);
foreach (var attribute in attributes)
{
AddSchemaTypePropertyOrField(writer, attribute, toBuild);
}

var anyAttribute = propertyOrFieldMembers.FirstOrDefault(t => t.GetCustomAttribute<XmlAnyAttributeAttribute>() != null);
if (anyAttribute != null)
{
writer.WriteStartElement("anyAttribute", Namespaces.XMLNS_XSD);
writer.WriteEndElement();
}
}
else
{
Expand Down
4 changes: 4 additions & 0 deletions src/SoapCore/ServiceModel/OperationDescription.cs
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,9 @@ public OperationDescription(ContractDescription contract, MethodInfo operationMe
.ToArray();

ServiceKnownTypes = operationMethod.GetCustomAttributes<ServiceKnownTypeAttribute>(inherit: false);

var soapHeader = operationMethod.GetCustomAttributes<SoapHeaderAttribute>(inherit: false);
HeaderType = soapHeader.FirstOrDefault()?.GetType();
}

public ContractDescription Contract { get; private set; }
Expand All @@ -103,6 +106,7 @@ public OperationDescription(ContractDescription contract, MethodInfo operationMe
public IEnumerable<ServiceKnownTypeAttribute> ServiceKnownTypes { get; private set; }
public IEnumerable<ReturnChoice> ReturnChoices { get; private set; }
public bool ReturnsChoice => ReturnChoices != null;
public Type HeaderType { get; set; }

public IEnumerable<ServiceKnownTypeAttribute> GetServiceKnownTypesHierarchy()
{
Expand Down
8 changes: 8 additions & 0 deletions src/SoapCore/ServiceModel/SoapHeader.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
using System;

namespace SoapCore.ServiceModel;

[AttributeUsage(AttributeTargets.Method)]
public class SoapHeaderAttribute : Attribute
{
}
Loading