Skip to content

Commit

Permalink
Merge branch 'DigDes:develop' into fix-timestamp-wsmessagefilter
Browse files Browse the repository at this point in the history
  • Loading branch information
andersjonsson authored Sep 24, 2024
2 parents 9101865 + a2bcb47 commit 1ead628
Show file tree
Hide file tree
Showing 9 changed files with 312 additions and 13 deletions.
25 changes: 25 additions & 0 deletions src/SoapCore.Tests/Wsdl/Services/ComplexTypeAnonymous.cs
Original file line number Diff line number Diff line change
@@ -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<string> StringList { get; set; }

public List<int> IntList { get; set; }
}
}
21 changes: 21 additions & 0 deletions src/SoapCore.Tests/Wsdl/Services/IComplexAnonymousListService.cs
Original file line number Diff line number Diff line change
@@ -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<ComplexTypeAnonymous> Test();
}

public class ComplexAnonymousListService : IComplexAnonymousListService
{
public List<ComplexTypeAnonymous> Test() => throw new NotImplementedException();
}
}
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; }
}
20 changes: 20 additions & 0 deletions src/SoapCore.Tests/Wsdl/Services/ISystemImportService.cs
Original file line number Diff line number Diff line change
@@ -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();
}
}
}
123 changes: 123 additions & 0 deletions src/SoapCore.Tests/Wsdl/WsdlTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
{
Expand Down Expand Up @@ -847,6 +897,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 Expand Up @@ -1239,6 +1315,52 @@ public void CheckComplexBaseTypeServiceWsdl()
Assert.IsNotNull(listDerivedType);
}

[DataTestMethod]
[DataRow(SoapSerializer.XmlSerializer)]
public async Task CheckComplexAnonymousTypeListWsdl(SoapSerializer soapSerializer)
{
var wsdl = await GetWsdlFromMetaBodyWriter<ComplexAnonymousListService>(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()
{
Expand Down Expand Up @@ -1281,6 +1403,7 @@ private async Task<string> GetWsdlFromMetaBodyWriter<T>(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
Expand Down
64 changes: 60 additions & 4 deletions src/SoapCore/Meta/MetaBodyWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,8 @@ private void AddTypes(XmlDictionaryWriter writer)
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 @@ -452,6 +454,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)
Expand Down Expand Up @@ -621,6 +639,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);
}
}
Expand Down Expand Up @@ -714,8 +744,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);
Expand Down Expand Up @@ -778,7 +819,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)
Expand Down Expand Up @@ -829,9 +872,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<XmlAnyAttributeAttribute>() == null).ToList();
if (elements.Any())
{
writer.WriteStartElement("sequence", Namespaces.XMLNS_XSD);
Expand All @@ -843,11 +887,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<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 Expand Up @@ -1240,6 +1291,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);
Expand Down
Loading

0 comments on commit 1ead628

Please sign in to comment.