In the last post, I showed how to add custom suffix in namespaces when sending SOAP XML request. In this post, I’m going to show how to add custom suffix in namespaces in XML response in a web service that uses SoapCore. At the time of writing, the SoapCore version is 1.0.0.3-alpha.
This post will be based on the web service from this post. Currently it returns this response.
<s:Envelope xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Body>
<ActionAResponse xmlns="http://sample.namespace.com/service/2.19/">
<Status xmlns="http://sample.namespace.com/schema/2.19/">
<MessageStatusCode>200</MessageStatusCode>
<MessageStatusReason>OK</MessageStatusReason>
</Status>
</ActionAResponse>
</s:Body>
</s:Envelope>
What we want to achieve is the response to look like this.
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Body>
<ns1:ActionAResponse xmlns:ns1="http://sample.namespace.com/service/2.19/" xmlns:ns2="http://sample.namespace.com/schema/2.19/">
<ns2:Status>
<ns2:MessageStatusCode>200</ns2:MessageStatusCode>
<ns2:MessageStatusReason>OK</ns2:MessageStatusReason>
</ns2:Status>
</ns1:ActionAResponse>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
Similarly to the previous post, we’ll need to have a custom message where we would add the custom suffix for the namespaces. Since we’re using SoapCore for creating SOAP web service in .Net Core, we’ll need to implement its custom message.
public class CustomNamespaceMessage : CustomMessage
{
private readonly IEnumerable<string> _customNamespaces = new string[]
{
"ns1:http://some.sample.namespace.com/service/2.19/",
"ns2:http://some.sample.namespace.com/schema/2.19/"
};
private const string EnvelopeNamespace = "http://schemas.xmlsoap.org/soap/envelope/";
public CustomNamespaceMessage() { }
public CustomNamespaceMessage(Message message) : base(message) { }
protected override void OnWriteBodyContents(XmlDictionaryWriter writer)
{
foreach (string ns in _customNamespaces)
{
var tokens = ns.Split(new char[] { ':' }, 2);
writer.WriteAttributeString("xmlns", tokens[0], null, tokens[1]);
}
this.Message.WriteBodyContents(writer);
}
public override MessageHeaders Headers => Message.Headers;
public override MessageProperties Properties => Message.Properties;
public override MessageVersion Version => Message.Version;
protected override void OnWriteStartBody(XmlDictionaryWriter writer)
{
writer.WriteStartElement("Body", EnvelopeNamespace);
}
protected override void OnWriteStartEnvelope(XmlDictionaryWriter writer)
{
writer.WriteStartElement("SOAP-ENV", "Envelope", EnvelopeNamespace);
}
}
Then, we need to tell SoapCore to use our custom message when we register the endpoints.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
// Other configurations go here
app.UseSoapEndpoint<ICallbackService, CustomNamespaceMessage>("/integration-service/v2_17/ActionA", new BasicHttpBinding(), SoapSerializer.XmlSerializer);
}
This is what the response looks like after using the custom message
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Body xmlns:ns1="http://sample.namespace.com/service/2.19/" xmlns:ns2="http://sample.namespace.com/schema/2.19/">
<ns1:ActionAResponse xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<ns2:Status>
<ns2:MessageStatusCode>200</ns2:MessageStatusCode>
<ns2:MessageStatusReason>OK</ns2:MessageStatusReason>
</ns2:Status>
</ns1:ActionAResponse>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
It is very close to what we need. However, the custom namespaces are on the Body element instead of the ActionAResponse. This seems strange OnWriteBodyContents is where we write those custom namespaces. As the name of the method suggests, I’d expect it to add the custom namespaces to the ActionAResponse element.
To fix this, I figure that I need to set the message contract of the response to not be wrapped. This will result in the response XML not containing the ActionAResponse element. Then, in the custom message, I would write this ActionAResponse element manually before setting the custom namespaces. The message contract of the response now looks like this.
/// <remarks/>
[System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "4.8.3928.0")]
[System.SerializableAttribute()]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true, Namespace = "http://sample.namespace.com/schema/2.19/")]
[System.Xml.Serialization.XmlRootAttribute(Namespace = "http://sample.namespace.com/service/2.19/", IsNullable = false)]
[MessageContract(IsWrapped = false)]
public partial class ActionAResponse
{
private messageStatusType statusField;
/// <remarks/>
[MessageBodyMember]
public messageStatusType Status
{
get
{
return this.statusField;
}
set
{
this.statusField = value;
}
}
}
And the custom message now looks like this.
public class CustomNamespaceMessage : CustomMessage
{
private readonly IEnumerable<string> _customNamespaces = new string[]
{
"ns1:http://some.sample.namespace.com/service/2.19/",
"ns2:http://some.sample.namespace.com/schema/2.19/"
};
private const string EnvelopeNamespace = "http://schemas.xmlsoap.org/soap/envelope/";
public CustomNamespaceMessage() { }
public CustomNamespaceMessage(Message message) : base(message) { }
protected override void OnWriteBodyContents(XmlDictionaryWriter writer)
{
writer.WriteStartElement("ns1", "ActionAResponse", "http://sample.namespace.com/service/2.19/");
foreach (string ns in _customNamespaces)
{
var tokens = ns.Split(new char[] { ':' }, 2);
writer.WriteAttributeString("xmlns", tokens[0], null, tokens[1]);
}
this.Message.WriteBodyContents(writer);
}
public override MessageHeaders Headers => Message.Headers;
public override MessageProperties Properties => Message.Properties;
public override MessageVersion Version => Message.Version;
protected override void OnWriteStartBody(XmlDictionaryWriter writer)
{
writer.WriteStartElement("Body", EnvelopeNamespace);
}
protected override void OnWriteStartEnvelope(XmlDictionaryWriter writer)
{
writer.WriteStartElement("SOAP-ENV", "Envelope", EnvelopeNamespace);
}
}
The response now looks like this
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Body>
<ns1:ActionAResponse xmlns:ns1="http://sample.namespace.com/service/2.19/" xmlns:ns2="http://sample.namespace.com/schema/2.19/">
<ns1:Status xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<ns2:MessageStatusCode>200</ns2:MessageStatusCode>
<ns2:MessageStatusReason>OK</ns2:MessageStatusReason>
</ns1:Status>
</ns1:ActionAResponse>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
There is still one more problem with the response. The Status element has the namespace ns1 when it should be ns2. To fix this, simply set the namespace in the MessageBodyMember attribute of the Status property in the message contract.
/// <remarks/>
[System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "4.8.3928.0")]
[System.SerializableAttribute()]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true, Namespace = "http://sample.namespace.com/schema/2.19/")]
[System.Xml.Serialization.XmlRootAttribute(Namespace = "http://sample.namespace.com/service/2.19/", IsNullable = false)]
[MessageContract(IsWrapped = false)]
public partial class ActionAResponse
{
private messageStatusType statusField;
/// <remarks/>
[MessageBodyMember(Namespace = "http://sample.namespace.com/schema/2.19/")]
public messageStatusType Status
{
get
{
return this.statusField;
}
set
{
this.statusField = value;
}
}
}
The XML response now looks like what we want.
Note: in the latest version of SoapCore at the time of writing v1.0.0.3 alpha, there is an option to provide the SoapCoreOptions when registering the SOAP endpoint. The SoapCoreOptions has a property called XmlNamespacePrefixOverrides. I added the custom namespaces as below but it did not work and the XML response looked like the one where we did not use our custom message.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
SoapEndpointExtensions.UseSoapEndpoint<ICallbackService>(app, (opt) =>
{
opt.Path = "/integration-service/v2_17/ActionA";
opt.Binding = new BasicHttpBinding();
opt.OmitXmlDeclaration = true;
opt.SoapSerializer = SoapSerializer.XmlSerializer;
var xmlNamespaceManager = new XmlNamespaceManager(new NameTable());
Namespaces.AddNamespaceIfNotAlreadyPresentAndGetPrefix(xmlNamespaceManager, "ns1", "http://sample.namespace.com/service/2.19/");
Namespaces.AddNamespaceIfNotAlreadyPresentAndGetPrefix(xmlNamespaceManager, "ns2", "http://sample.namespace.com/schema/2.19/");
opt.XmlNamespacePrefixOverrides = xmlNamespaceManager;
});
}