Reputation: 1541
We have a WCF setup with the following contracts:
[ServiceContract(
Namespace = Constants.Namespaces.HL7Namespace,
Name = Constants.Roles.ContentRequiredDocumentManagementSystem)]
// XmlSerializerFormat is needed to expose the HL7 schema fields without the "Field" suffix on each one, eg: idField
[XmlSerializerFormat]
public interface ICDARequest
{
[OperationContract(
// wsdl request action
Action = Constants.Namespaces.HL7Namespace + ":" + Constants.Interactions.RCMR_IN000029UV01 + "." + Constants.VersionType.NormativeCode + Constants.Version.InteractionVersion,
// wsdl operation name
Name = Constants.Interactions.RCMR_IN000029UV01,
// wsdl response action
ReplyAction = Constants.Namespaces.HL7Namespace + ":" + Constants.Interactions.RCMR_IN000030UV01 + "." + Constants.VersionType.NormativeCode + Constants.Version.InteractionVersion)]
SearchMessagesResponse SearchMessages(SearchMessagesRequest RCMR_IN000029UV01);
[MessageContract(
IsWrapped = false]
public class SearchMessagesResponse
{
[MessageBodyMember(
Name = State.Constants.Interactions.RCMR_IN000030UV01,
Namespace = State.Constants.Namespaces.HL7Namespace)]
public RCMR_IN000030UV01 data;
}
}
xsd.exe
. It added:
[System.Xml.Serialization.XmlTypeAttribute(TypeName = "BCCDX.DistributionStatus", Namespace = "urn:bccdx.ca")]
public partial class BCCDXDistributionStatus
{
[System.Xml.Serialization.XmlElementAttribute("receivedTime", Namespace = "urn:bccdx.ca", IsNullable = false)]
public TS receivedTime{...}
}
which is what was desired.
Then in the WCF service we are able to use the new class and members:
var distStatus = new BCCDXDistributionStatus();
distStatus.receivedTime = CreateTS(locStat.MessageDownloadDate);
this then gets serialized and sent out across the wire looking like:
<distributionStatus xmlns="urn:bccdx.ca">
<receivedTime value="201702150956-0800"/>
</distributionStatus>
which is almost correct. The hitch comes with the fact that the XML document has no reference to the "urn:bccdx.ca"
namespace. I was assuming it would be automatically added to the document root element upon serialization, but I was wrong. Here's what that ends up looking like:
<RCMR_IN000030UV01 ITSVersion="XML_1.0" xmlns="urn:hl7-org:v3">
...
</RCMR_IN000030UV01>
when what is really desired is something like:
<RCMR_IN000030UV01 ITSVersion="XML_1.0" xmlns="urn:hl7-org:v3" xmlns:x="urn:bccdx.ca">
...
</RCMR_IN000030UV01>
note the urn:bccdx.ca with prefix
I'm wondering how, if at all we can add more than one namespace, with prefixes to the resulting serialized message XML through the contracts? I've seen hints on the web of overriding the default serializer, but I'd rather not. Surely this has been thought of and dealt with before?
Upvotes: 2
Views: 2208
Reputation: 116731
Firstly, I am going to assume that, somewhere in your service contract, you are specifying use of XmlSerializer
by using [XmlSerializerFormat]
, for instance like so:
[ServiceContract()]
[XmlSerializerFormat]
public interface IService1
{
[OperationContract(
// wsdl request action
Action = Constants.Namespaces.HL7Namespace + ":" + Constants.Interactions.RCMR_IN000029UV01 + "." + Constants.VersionType.NormativeCode + Constants.Version.InteractionVersion,
// wsdl operation name
Name = Constants.Interactions.RCMR_IN000029UV01,
// wsdl response action
ReplyAction = Constants.Namespaces.HL7Namespace + ":" + Constants.Interactions.RCMR_IN000030UV01 + "." + Constants.VersionType.NormativeCode + Constants.Version.InteractionVersion)]
SearchMessagesResponse SearchMessages(/* SearchMessagesRequest RCMR_IN000029UV01*/);
}
While this is not mentioned in your question, if you were not doing so, then the [System.Xml.Serialization.XmlElementAttribute(...)]
attribute declarations in your types would have no effect, as they are ignored by DataContractSerializer
.
Second, I am going to assume that your RCMR_IN000030UV01
type currently looks something like this:
[XmlRoot(ElementName = "RCMR_IN000030UV01", Namespace = "urn:hl7-org:v3")]
public partial class RCMR_IN000030UV01
{
// The initially auto-generated code
[XmlAttribute(AttributeName = "ITSVersion")]
public string ITSVersion { get; set; }
}
public partial class RCMR_IN000030UV01
{
// The added property
[System.Xml.Serialization.XmlElementAttribute("distributionStatus", Namespace = "urn:bccdx.ca", IsNullable = false)]
public BCCDXDistributionStatus distStatus { get; set; }
}
[System.Xml.Serialization.XmlTypeAttribute(TypeName = "BCCDX.DistributionStatus", Namespace = "urn:bccdx.ca")]
public partial class BCCDXDistributionStatus
{
[System.Xml.Serialization.XmlElementAttribute("receivedTime", Namespace = "urn:bccdx.ca", IsNullable = false)]
public TS receivedTime { get; set; }
}
public class TS
{
[XmlAttribute("value")]
public DateTime Value { get; set; }
}
Currently your service is returning XML that looks like this:
<RCMR_IN000030UV01 ITSVersion="1.0"
xmlns="urn:hl7-org:v3"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<distributionStatus
xmlns="urn:bccdx.ca">
<receivedTime value="2017-02-23T00:00:00-05:00"/>
</distributionStatus>
</RCMR_IN000030UV01>
But, you want this:
<RCMR_IN000030UV01 ITSVersion="1.0"
xmlns="urn:hl7-org:v3"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
<!---This should be added ---->
xmlns:x="urn:bccdx.ca">
<!---And distributionStatus should be prefixed with x: ---->
<x:distributionStatus>
<x:receivedTime value="2017-02-23T00:00:00-05:00"/>
</x:distributionStatus>
</RCMR_IN000030UV01>
Firstly I will note that these two XML files are semantically identical. In the first case the namespace "urn:bccdx.ca"
is declared as a default namespace on the lowest element in which it is actually needed. In the second, it is is defined with a prefix at the beginning of the file. Either way, the element <distributionStatus>
and its children all end up in the correct namespace.
So, you could simply accept the XML as correct as-is.
If, for some reason, you must have the namespace appearing at the beginning of the XML with the x:
prefix, you can add an [XmlNamespaceDeclarations]
property to your RCMR_IN000030UV01
to force your namespace to be declared at a higher level:
public partial class RCMR_IN000030UV01
{
[XmlNamespaceDeclarations]
public XmlSerializerNamespaces xmlsn
{
get
{
var ns = new XmlSerializerNamespaces();
ns.Add("x", "urn:bccdx.ca");
return ns;
}
set
{
// Do nothing - fake property.
}
}
}
As explained in the docs, this attribute
Specifies that the target property, parameter, return value, or class member contains prefixes associated with namespaces that are used within an XML document.
Now you service should return the XML with the namespace on the root element as desired.
Upvotes: 3