diff --git a/src/SoapCore.Tests/MessageContract/Issue1161Service.cs b/src/SoapCore.Tests/MessageContract/Issue1161Service.cs
new file mode 100644
index 00000000..e882cb5a
--- /dev/null
+++ b/src/SoapCore.Tests/MessageContract/Issue1161Service.cs
@@ -0,0 +1,9 @@
+using SoapCore.Tests.MessageContract.Models;
+
+namespace SoapCore.Tests.MessageContract
+{
+ public class Issue1161Service : IServiceIssue1161
+ {
+ public Issue1161MessageContract Process(Issue1161MessageContract rq) => rq;
+ }
+}
diff --git a/src/SoapCore.Tests/MessageContract/Issue1161Tests.cs b/src/SoapCore.Tests/MessageContract/Issue1161Tests.cs
new file mode 100644
index 00000000..63fef2f0
--- /dev/null
+++ b/src/SoapCore.Tests/MessageContract/Issue1161Tests.cs
@@ -0,0 +1,97 @@
+using System.Net.Http;
+using System.Text;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore.TestHost;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using SoapCore.Tests.MessageContract.Models;
+
+namespace SoapCore.Tests.MessageContract
+{
+ // Tests for https://github.com/DigDes/SoapCore/issues/1161.
+ // When an endpoint uses SoapSerializer.XmlSerializer, response headers must
+ // also use XmlSerializer. DataContractSerializer would either produce the
+ // wrong XML, or throw InvalidDataContractException if the header type contains
+ // an [XmlAnyAttribute] XmlAttribute[] member with values.
+ [TestClass]
+ public class Issue1161Tests
+ {
+ private const string DefaultNamespacedHeaderRequest = @"
+
+
+
+ SomeSender
+
+
+
+
+ someBody
+
+
+ ";
+
+ private const string PrefixedNamespacedHeaderRequest = @"
+
+
+
+ SomeSender
+
+
+
+
+ someBody
+
+
+ ";
+
+ [TestMethod]
+ public async Task Issue1161_DefaultNamespaceHeader_DoesNotEmitDataContractSerializerNamespaces()
+ {
+ var response = await PostSoapAsync(DefaultNamespacedHeaderRequest);
+
+ // On an XmlSerializer endpoint, response headers must use XmlSerializer.
+ // DataContractSerializer would add unwanted namespaces and an extra
+ // element. Neither should appear in the output.
+ StringAssert.Contains(response, "SomeSender");
+ StringAssert.DoesNotMatch(response, new System.Text.RegularExpressions.Regex("schemas\\.datacontract\\.org/2004/07"));
+ StringAssert.DoesNotMatch(response, new System.Text.RegularExpressions.Regex("Serialization/Arrays"));
+ StringAssert.DoesNotMatch(response, new System.Text.RegularExpressions.Regex("SomeSender");
+ StringAssert.DoesNotMatch(response, new System.Text.RegularExpressions.Regex("InvalidDataContractException"));
+ StringAssert.DoesNotMatch(response, new System.Text.RegularExpressions.Regex("schemas\\.datacontract\\.org/2004/07"));
+ }
+
+ private static async Task PostSoapAsync(string body)
+ {
+ using var host = CreateTestHost();
+ using var content = new StringContent(body, Encoding.UTF8, "text/xml");
+ using var res = await host.CreateRequest("/Service.asmx")
+ .AddHeader("SOAPAction", "\"http://tempuri.org/IServiceIssue1161/Process\"")
+ .And(msg => msg.Content = content)
+ .PostAsync();
+
+ res.EnsureSuccessStatusCode();
+ return await res.Content.ReadAsStringAsync();
+ }
+
+ private static TestServer CreateTestHost()
+ {
+ var webHostBuilder = new WebHostBuilder()
+ .UseStartup()
+ .ConfigureServices(services => services.AddSingleton(new StartupConfiguration(typeof(Issue1161Service))));
+ return new TestServer(webHostBuilder);
+ }
+ }
+}
diff --git a/src/SoapCore.Tests/MessageContract/Models/IServiceIssue1161.cs b/src/SoapCore.Tests/MessageContract/Models/IServiceIssue1161.cs
new file mode 100644
index 00000000..454cdb14
--- /dev/null
+++ b/src/SoapCore.Tests/MessageContract/Models/IServiceIssue1161.cs
@@ -0,0 +1,13 @@
+using System.ServiceModel;
+using System.Xml.Serialization;
+
+namespace SoapCore.Tests.MessageContract.Models
+{
+ [ServiceContract(Namespace = "http://tempuri.org/")]
+ public interface IServiceIssue1161
+ {
+ [OperationContract]
+ [XmlSerializerFormat(SupportFaults = true)]
+ Issue1161MessageContract Process(Issue1161MessageContract rq);
+ }
+}
diff --git a/src/SoapCore.Tests/MessageContract/Models/Issue1161MessageContract.cs b/src/SoapCore.Tests/MessageContract/Models/Issue1161MessageContract.cs
new file mode 100644
index 00000000..ed9cc77a
--- /dev/null
+++ b/src/SoapCore.Tests/MessageContract/Models/Issue1161MessageContract.cs
@@ -0,0 +1,14 @@
+using System.ServiceModel;
+
+namespace SoapCore.Tests.MessageContract.Models
+{
+ [MessageContract]
+ public class Issue1161MessageContract
+ {
+ [MessageHeader(Name = "MessageHeader", Namespace = "http://www.ebxml.org/namespaces/messageHeader")]
+ public Issue1161MessageHeader Header { get; set; }
+
+ [MessageBodyMember]
+ public string BodyMessage { get; set; }
+ }
+}
diff --git a/src/SoapCore.Tests/MessageContract/Models/Issue1161MessageHeader.cs b/src/SoapCore.Tests/MessageContract/Models/Issue1161MessageHeader.cs
new file mode 100644
index 00000000..ebc34c66
--- /dev/null
+++ b/src/SoapCore.Tests/MessageContract/Models/Issue1161MessageHeader.cs
@@ -0,0 +1,18 @@
+using System.Xml;
+using System.Xml.Serialization;
+
+namespace SoapCore.Tests.MessageContract.Models
+{
+ // Test type for issue #1161. Contains an [XmlAnyAttribute] XmlAttribute[] member.
+ // When the endpoint uses SoapSerializer.XmlSerializer, the response must use XmlSerializer too.
+ // DataContractSerializer cannot serialize XmlAttribute[] and throws InvalidDataContractException.
+ [XmlType(AnonymousType = true, Namespace = "http://www.ebxml.org/namespaces/messageHeader")]
+ public class Issue1161MessageHeader
+ {
+ [XmlElement(Order = 0)]
+ public string From { get; set; }
+
+ [XmlAnyAttribute]
+ public XmlAttribute[] SomeOtherAttributes { get; set; }
+ }
+}
diff --git a/src/SoapCore/SoapEndpointMiddleware.cs b/src/SoapCore/SoapEndpointMiddleware.cs
index 69f98e7b..5ebf5702 100644
--- a/src/SoapCore/SoapEndpointMiddleware.cs
+++ b/src/SoapCore/SoapEndpointMiddleware.cs
@@ -623,7 +623,27 @@ private Message CreateResponseMessage(
foreach (var messageHeaderMember in messageHeaderMembers)
{
var messageHeaderAttribute = messageHeaderMember.Attribute;
- responseMessage.Headers.Add(MessageHeader.CreateHeader(messageHeaderAttribute.Name ?? messageHeaderMember.Member.Name, messageHeaderAttribute.Namespace ?? operation.Contract.Namespace, messageHeaderMember.Member.GetPropertyOrFieldValue(responseObject), messageHeaderAttribute.MustUnderstand));
+ var headerName = messageHeaderAttribute.Name ?? messageHeaderMember.Member.Name;
+ var headerNamespace = messageHeaderAttribute.Namespace ?? operation.Contract.Namespace;
+ var headerValue = messageHeaderMember.Member.GetPropertyOrFieldValue(responseObject);
+
+ // When the endpoint is configured to use XmlSerializer, MessageHeader.CreateHeader
+ // (which always wraps with DataContractSerializer) produces wrong-shaped XML and
+ // throws InvalidDataContractException on values containing XmlAttribute[] members
+ // (e.g. [XmlAnyAttribute]). See issue #1161.
+ if (_options.SoapSerializer == SoapSerializer.XmlSerializer)
+ {
+ responseMessage.Headers.Add(new XmlSerializerMessageHeader(
+ headerName,
+ headerNamespace,
+ headerValue,
+ messageHeaderMember.Member.GetPropertyOrFieldType(),
+ messageHeaderAttribute.MustUnderstand));
+ }
+ else
+ {
+ responseMessage.Headers.Add(MessageHeader.CreateHeader(headerName, headerNamespace, headerValue, messageHeaderAttribute.MustUnderstand));
+ }
}
}
diff --git a/src/SoapCore/XmlSerializerMessageHeader.cs b/src/SoapCore/XmlSerializerMessageHeader.cs
new file mode 100644
index 00000000..55447986
--- /dev/null
+++ b/src/SoapCore/XmlSerializerMessageHeader.cs
@@ -0,0 +1,75 @@
+using System;
+using System.ServiceModel.Channels;
+using System.Xml;
+using System.Xml.Serialization;
+
+namespace SoapCore
+{
+ public class XmlSerializerMessageHeader : MessageHeader
+ {
+ private const string Xmlns = "xmlns";
+ private readonly object _value;
+ private readonly Type _valueType;
+ private readonly bool _mustUnderstand;
+
+ public XmlSerializerMessageHeader(string name, string ns, object value, Type valueType, bool mustUnderstand)
+ {
+ Name = name;
+ Namespace = ns;
+ _value = value;
+ _valueType = valueType;
+ _mustUnderstand = mustUnderstand;
+ }
+
+ public override string Name { get; }
+ public override string Namespace { get; }
+ public override bool MustUnderstand => _mustUnderstand;
+
+ protected override void OnWriteHeaderContents(XmlDictionaryWriter writer, MessageVersion messageVersion)
+ {
+ if (_value == null)
+ {
+ return;
+ }
+
+ // XmlSerializer always writes a root element, but the surrounding MessageHeader element
+ // is already opened by OnWriteStartHeader. Serialize into a temporary XmlDocument so
+ // only its inner content can then be copied to the actual header writer.
+ var serializer = CachedXmlSerializer.GetXmlSerializer(_valueType, Name, Namespace);
+ var doc = new XmlDocument();
+ using (var docWriter = doc.CreateNavigator().AppendChild())
+ {
+ var namespaces = new XmlSerializerNamespaces();
+ namespaces.Add(string.Empty, Namespace);
+ serializer.Serialize(docWriter, _value, namespaces);
+ }
+
+ // Copy the root's attributes onto the header element.
+ foreach (XmlAttribute attr in doc.DocumentElement.Attributes)
+ {
+ // xmlns="..." default-namespace declaration - skip if it matches the header's
+ // own namespace (already declared by OnWriteStartHeader); otherwise emit as xmlns.
+ if (attr.Prefix.Length == 0 && attr.LocalName == Xmlns)
+ {
+ if (attr.Value == Namespace)
+ {
+ continue;
+ }
+
+ writer.WriteAttributeString(Xmlns, attr.Value);
+ continue;
+ }
+
+ // Other attributes (including xmlns:prefix="..." declarations XmlSerializer produced)
+ // XmlDictionaryWriter recognizes the xmlns NamespaceURI and emits them as namespace declarations when needed.
+ writer.WriteAttributeString(attr.Prefix, attr.LocalName, attr.NamespaceURI, attr.Value);
+ }
+
+ // Copy the root's children into the header element.
+ foreach (XmlNode child in doc.DocumentElement.ChildNodes)
+ {
+ child.WriteTo(writer);
+ }
+ }
+ }
+}